/*

  sshglob.c

  Author: Sami Lehtinen <sjl@ssh.com>

  Copyright (C) 1999-2000 SSH Communications Security Corp, Helsinki, Finland
  All rights reserved.

  Globbing. Primarily for use with scp and sftp, but can be used
  elsewhere.
  
 */

#include "sshincludes.h"
#include "sshglob.h"

#define SSH_DEBUG_MODULE "SshGlob"

#ifdef __SUNPRO_C
#pragma error_messages (off,E_STATEMENT_NOT_REACHED)
#endif /* __SUNPRO_C */

Boolean _ssh_glob_match_pattern(const char *glob_string,
                                const char *string, int echar)
{
  /* _ph for placeholder. */
  const char *glob_string_ph, *string_ph;
  Boolean escaped = FALSE;

  SSH_PRECOND(glob_string);
  SSH_PRECOND(string);

  SSH_DEBUG(5, ("Matching \"%s\" with \"%s\".", glob_string, string));
  
  glob_string_ph = glob_string;
  string_ph = string;
  
  for (;;)
    {
      escaped = FALSE;
      
      /* If at end of glob_string, accept if also at end of string. */
      if (*glob_string_ph == '\0')
        return (*string_ph == '\0');

      if (*glob_string_ph == echar)
        {
          escaped = TRUE;
          glob_string_ph++;
        }

      /* Process '*'. */
      if (*glob_string_ph == '*' && !escaped)
        {
          /* Skip the asterisk. */
          glob_string_ph++;
          
          if (*glob_string_ph == echar)
            {
              escaped = TRUE;                  
              glob_string_ph++;
            }

          /* If at end of glob_string_ph, accept immediately. */
          if (*glob_string_ph == '\0')
            return TRUE;

          /* If next character in glob_string_ph is known, optimize. */
          if (*glob_string_ph != '?' && *glob_string_ph != '*' && !escaped)
            {
              /* Look instances of the next character in glob_string_ph, and try
                 to match starting from those. */
              for (; *string_ph != '\0'; string_ph++)
                if (*string_ph == *glob_string_ph &&
                    _ssh_glob_match_pattern(glob_string_ph + 1,
                                            string_ph + 1, echar))
                  return TRUE;
              /* Failed. */
              return FALSE;
            }

          /* Move ahead one character at a time and try to match at each
             position. */
          /* If the current character is escaped, the escape char must
             go with the string as we're called again.*/
          if (escaped)
            glob_string_ph--;
          
          for (; *string_ph != '\0'; string_ph++)
            if (_ssh_glob_match_pattern(glob_string_ph, string_ph, echar))
              return TRUE;
          /* Failed. */
          return FALSE;
        }

      /* There must be at least one more character in the string_ph.  If we are
         at the end, fail. */
      if (*string_ph == '\0')
        return FALSE;

      /* Check if the next character of the string_ph is acceptable. */
      if ((*glob_string_ph != '?' || (*glob_string_ph == '?' && escaped)) &&
          *glob_string_ph != *string_ph)
        return FALSE;
      
      /* Move to the next character, both in string_ph and in
         glob_string_ph. */
      string_ph++;
      glob_string_ph++;
    }
  
  SSH_NOTREACHED;
}

/* This function checks for wildcards (currently only '*?') in a
   string. Parameter str must be a valid null-terminated string. If
   wildcards are found, returns TRUE. Otherwise, returns
   FALSE. Wildcards (and every other character too) can be escaped
   with 'echar' (usually backslash). Doesn't modify the original
   string. */
Boolean _ssh_glob_check_for_wildcards(const char *str, int echar)
{
  int i = 0;
  
  SSH_PRECOND(str);  
  
  for (i = 0; i < strlen(SSH_WILDCARDS) ; i++)
    {
      if (_ssh_glob_next_unescaped_char(str, SSH_WILDCARDS[i], echar))
        return TRUE;
    }
  
  return FALSE;
}

/* Check if a character in a string is escaped. 'ch' is a pointer to
   the character being checked, and it must belong to
   'string'. Returns TRUE if quoted, FALSE otherwise. Escape character
   is 'echar'.

   e.g. if a string is '\\\\\*' (5*'\'), then the '*' is escaped
   (returns TRUE), assuming '\' is the escape character. */
Boolean _ssh_glob_isescaped(const char *ch, const char *string, int echar)
{
  char * placeholder;
  Boolean escaped = FALSE;

  SSH_PRECOND(ch);
  SSH_PRECOND(string);
  
  placeholder = (char *)ch;
  
  while (placeholder > string)
    {
      placeholder--;

      if (*placeholder == echar)
        escaped = ~escaped;
      else
        break;
    }

  return escaped;
}

/* Find next character 'ch' (which is not escaped with 'echar') from
   string 'string'. */
char *_ssh_glob_next_unescaped_char(const char *string, int ch, int echar)
{
  char *placeholder;

  if (string == NULL)
    return NULL;
  
  placeholder = strchr(string, ch);

  while (placeholder)
    {
      if (_ssh_glob_isescaped(placeholder, string, echar))
        {
          placeholder++;
          if (!placeholder)
            return NULL;
          placeholder = strchr(placeholder, ch);
        }
      else
        {
          break;
        }
    }

  return placeholder;
}

/* Find next wildchar (which is not escaped with 'echar') from
   string 'string'. */
char *_ssh_glob_next_unescaped_wildchar(const char *string, int echar)
{
  char *ph;
  char *first = NULL, *current = NULL;
  
  ph = SSH_WILDCARDS;
  
  while (*ph != '\0')
    {
      if ((current = _ssh_glob_next_unescaped_char(string, *ph, echar))
           != NULL)
        {
          if (!first)
            {
              first = current;
            }
          else
            {
              if (first > current)
                first = current;
            }
        }
      /* Advance. */
      ph++;
    }
  return first;
}

/* Strip string of escape characters, typically '\'. Return a newly
   allocated string. */
char *_ssh_glob_strip_escapes(const char *string, int echar)
{
  char *stripped;
  int len, i, str_ind;
  
  if (string == NULL)
    return NULL;

  len = strlen(string);
  
  stripped = ssh_xcalloc(len + 1, sizeof(char));

  for(i = 0, str_ind = 0; i < len; i++, str_ind++)
    {
      if (string[i] == echar)
        i++;

      stripped[str_ind] = string[i];
    }

  return stripped;
}

