Imported Upstream version 3.3.0
[debian/amanda] / common-src / match.c
index dba15c802c28add576135af2f4a85476a2ccfd57..362e3451c4f113d9502786594d4816a88286d8d8 100644 (file)
  */
 
 #include "amanda.h"
+#include "match.h"
 #include <regex.h>
 
 static int match_word(const char *glob, const char *word, const char separator);
+static char *tar_to_regex(const char *glob);
+
+/*
+ * REGEX MATCHING FUNCTIONS
+ */
+
+/*
+ * Define a specific type to hold error messages in case regex compile/matching
+ * fails
+ */
+
+typedef char regex_errbuf[STR_SIZE];
+
+/*
+ * Validate one regular expression. If the regex is invalid, copy the error
+ * message into the supplied regex_errbuf pointer. Also, we want to know whether
+ * flags should include REG_NEWLINE (See regcomp(3) for details). Since this is
+ * the more frequent case, add REG_NEWLINE to the default flags, and remove it
+ * only if match_newline is set to FALSE.
+ */
+
+static gboolean do_validate_regex(const char *str, regex_t *regex,
+       regex_errbuf *errbuf, gboolean match_newline)
+{
+       int flags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
+       int result;
+
+       if (!match_newline)
+               CLR(flags, REG_NEWLINE);
+
+       result = regcomp(regex, str, flags);
+
+       if (!result)
+               return TRUE;
+
+       regerror(result, regex, *errbuf, SIZEOF(*errbuf));
+       return FALSE;
+}
+
+/*
+ * See if a string matches a regular expression. Return one of MATCH_* defined
+ * below. If, for some reason, regexec() returns something other than not 0 or
+ * REG_NOMATCH, return MATCH_ERROR and print the error message in the supplied
+ * regex_errbuf.
+ */
+
+#define MATCH_OK (1)
+#define MATCH_NONE (0)
+#define MATCH_ERROR (-1)
+
+static int try_match(regex_t *regex, const char *str,
+    regex_errbuf *errbuf)
+{
+    int result = regexec(regex, str, 0, 0, 0);
+
+    switch(result) {
+        case 0:
+            return MATCH_OK;
+        case REG_NOMATCH:
+            return MATCH_NONE;
+        /* Fall through: something went really wrong */
+    }
+
+    regerror(result, regex, *errbuf, SIZEOF(*errbuf));
+    return MATCH_ERROR;
+}
 
 char *
 validate_regexp(
     const char *       regex)
 {
     regex_t regc;
-    int result;
-    static char errmsg[STR_SIZE];
+    static regex_errbuf errmsg;
+    gboolean valid;
 
-    if ((result = regcomp(&regc, regex,
-                         REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
-      regerror(result, &regc, errmsg, SIZEOF(errmsg));
-      return errmsg;
-    }
+    valid = do_validate_regex(regex, &regc, &errmsg, TRUE);
 
     regfree(&regc);
-
-    return NULL;
+    return (valid) ? NULL : errmsg;
 }
 
 char *
 clean_regex(
-    const char *       regex)
+    const char *       str,
+    gboolean           anchor)
 {
     char *result;
     int j;
     size_t i;
-    result = alloc(2*strlen(regex)+1);
+    result = alloc(2*strlen(str)+3);
 
-    for(i=0,j=0;i<strlen(regex);i++) {
-       if(!isalnum((int)regex[i]))
+    j = 0;
+    if (anchor)
+       result[j++] = '^';
+    for(i=0;i<strlen(str);i++) {
+       if(!isalnum((int)str[i]))
            result[j++]='\\';
-       result[j++]=regex[i];
+       result[j++]=str[i];
     }
+    if (anchor)
+       result[j++] = '$';
     result[j] = '\0';
     return result;
 }
 
-int
-match(
-    const char *       regex,
-    const char *       str)
+/*
+ * Check whether a given character should be escaped (that is, prepended with a
+ * backslash), EXCEPT for one character.
+ */
+
+static gboolean should_be_escaped_except(char c, char not_this_one)
+{
+    if (c == not_this_one)
+        return FALSE;
+
+    switch (c) {
+        case '\\':
+        case '^':
+        case '$':
+        case '?':
+        case '*':
+        case '[':
+        case ']':
+        case '.':
+        case '/':
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*
+ * Take a disk/host expression and turn it into a full-blown amglob (with
+ * start and end anchors) following rules in amanda-match(7). The not_this_one
+ * argument represents a character which is NOT meant to be special in this
+ * case: '/' for disks and '.' for hosts.
+ */
+
+static char *full_amglob_from_expression(const char *str, char not_this_one)
+{
+    const char *src;
+    char *result, *dst;
+
+    result = alloc(2 * strlen(str) + 3);
+    dst = result;
+
+    *(dst++) = '^';
+
+    for (src = str; *src; src++) {
+        if (should_be_escaped_except(*src, not_this_one))
+            *(dst++) = '\\';
+        *(dst++) = *src;
+    }
+
+    *(dst++) = '$';
+    *dst = '\0';
+    return result;
+}
+
+char *
+make_exact_host_expression(
+    const char *       host)
+{
+    return full_amglob_from_expression(host, '.');
+}
+
+char *
+make_exact_disk_expression(
+    const char *       disk)
+{
+    return full_amglob_from_expression(disk, '/');
+}
+
+int do_match(const char *regex, const char *str, gboolean match_newline)
 {
     regex_t regc;
     int result;
-    char errmsg[STR_SIZE];
+    regex_errbuf errmsg;
+    gboolean ok;
 
-    if((result = regcomp(&regc, regex,
-                        REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("regex \"%s\": %s", regex, errmsg);
-       /*NOTREACHED*/
-    }
+    ok = do_validate_regex(regex, &regc, &errmsg, match_newline);
 
-    if((result = regexec(&regc, str, 0, 0, 0)) != 0
-       && result != REG_NOMATCH) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("regex \"%s\": %s", regex, errmsg);
-       /*NOTREACHED*/
-    }
+    if (!ok)
+        error(_("regex \"%s\": %s"), regex, errmsg);
+        /*NOTREACHED*/
+
+    result = try_match(&regc, str, &errmsg);
+
+    if (result == MATCH_ERROR)
+        error(_("regex \"%s\": %s"), regex, errmsg);
+        /*NOTREACHED*/
 
     regfree(&regc);
 
-    return result == 0;
+    return result;
 }
 
 char *
 validate_glob(
     const char *       glob)
 {
-    char *regex;
+    char *regex, *ret = NULL;
     regex_t regc;
-    int result;
-    static char errmsg[STR_SIZE];
+    static regex_errbuf errmsg;
 
     regex = glob_to_regex(glob);
-    if ((result = regcomp(&regc, regex,
-                         REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
-      regerror(result, &regc, errmsg, SIZEOF(errmsg));
-      amfree(regex);
-      return errmsg;
-    }
+
+    if (!do_validate_regex(regex, &regc, &errmsg, TRUE))
+        ret = errmsg;
 
     regfree(&regc);
     amfree(regex);
-
-    return NULL;
+    return ret;
 }
 
 int
@@ -130,105 +257,223 @@ match_glob(
     char *regex;
     regex_t regc;
     int result;
-    char errmsg[STR_SIZE];
+    regex_errbuf errmsg;
+    gboolean ok;
 
     regex = glob_to_regex(glob);
-    if((result = regcomp(&regc, regex,
-                        REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
-       /*NOTREACHED*/
-    }
+    ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
 
-    if((result = regexec(&regc, str, 0, 0, 0)) != 0
-       && result != REG_NOMATCH) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
-       /*NOTREACHED*/
-    }
+    if (!ok)
+        error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
+        /*NOTREACHED*/
+
+    result = try_match(&regc, str, &errmsg);
+
+    if (result == MATCH_ERROR)
+        error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
+        /*NOTREACHED*/
 
     regfree(&regc);
     amfree(regex);
 
-    return result == 0;
+    return result;
 }
 
-char *
-glob_to_regex(
-    const char *       glob)
+/*
+ * Macro to tell whether a character is a regex metacharacter. Note that '*'
+ * and '?' are NOT included: they are themselves special in globs.
+ */
+
+#define IS_REGEX_META(c) ( \
+    (c) == '.' || (c) == '(' || (c) == ')' || (c) == '{' || (c) == '}' || \
+    (c) == '+' || (c) == '^' || (c) == '$' || (c) == '|' \
+)
+
+/*
+ * EXPANDING A MATCH TO A REGEX (as per amanda-match(7))
+ *
+ * The function at the code of this operation is amglob_to_regex(). It
+ * takes three arguments: the string to convert, a substitution table and a
+ * worst-case expansion.
+ *
+ * The substitution table, defined right below, is used to replace particular
+ * string positions and/or characters. Its fields are:
+ * - begin: what the beginnin of the string should be replaced with;
+ * - end: what the end of the string should be replaced with;
+ * - question_mark: what the question mark ('?') should be replaced with;
+ * - star: what the star ('*') should be replaced with;
+ * - double_star: what two consecutive stars should be replaced with.
+ *
+ * Note that apart from double_star, ALL OTHER FIELDS MUST NOT BE NULL
+ */
+
+struct subst_table {
+    const char *begin;
+    const char *end;
+    const char *question_mark;
+    const char *star;
+    const char *double_star;
+};
+
+static char *amglob_to_regex(const char *str, struct subst_table *table,
+    size_t worst_case)
 {
-    char *regex;
-    char *r;
-    size_t len;
-    int ch;
-    int last_ch;
+    const char *src;
+    char *result, *dst;
+    char c;
 
     /*
-     * Allocate an area to convert into.  The worst case is a five to
-     * one expansion.
+     * There are two particular cases when building a regex out of a glob:
+     * character classes (anything inside [...] or [!...] and quotes (anything
+     * preceded by a backslash). We start with none being true.
      */
-    len = strlen(glob);
-    regex = alloc(1 + len * 5 + 1 + 1);
+
+    gboolean in_character_class = FALSE, in_quote = FALSE;
 
     /*
-     * Do the conversion:
-     *
-     *  ?      -> [^/]
-     *  *      -> [^/]*
-     *  [!...] -> [^...]
-     *
-     * The following are given a leading backslash to protect them
-     * unless they already have a backslash:
-     *
-     *   ( ) { } + . ^ $ |
-     *
-     * Put a leading ^ and trailing $ around the result.  If the last
-     * non-escaped character is \ leave the $ off to cause a syntax
-     * error when the regex is compiled.
+     * Allocate enough space for our string. At worst, the allocated space is
+     * the length of the following:
+     * - beginning of regex;
+     * - size of original string multiplied by worst-case expansion;
+     * - end of regex;
+     * - final 0.
      */
 
-    r = regex;
-    *r++ = '^';
-    last_ch = '\0';
-    for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
-       if (last_ch == '\\') {
-           *r++ = (char)ch;
-           ch = '\0';                  /* so last_ch != '\\' next time */
-       } else if (last_ch == '[' && ch == '!') {
-           *r++ = '^';
-       } else if (ch == '\\') {
-           *r++ = (char)ch;
-       } else if (ch == '*' || ch == '?') {
-           *r++ = '[';
-           *r++ = '^';
-           *r++ = '/';
-           *r++ = ']';
-           if (ch == '*') {
-               *r++ = '*';
-           }
-       } else if (ch == '('
-                  || ch == ')'
-                  || ch == '{'
-                  || ch == '}'
-                  || ch == '+'
-                  || ch == '.'
-                  || ch == '^'
-                  || ch == '$'
-                  || ch == '|') {
-           *r++ = '\\';
-           *r++ = (char)ch;
-       } else {
-           *r++ = (char)ch;
-       }
-    }
-    if (last_ch != '\\') {
-       *r++ = '$';
+    result = alloc(strlen(table->begin) + strlen(str) * worst_case
+        + strlen(table->end) + 1);
+
+    /*
+     * Start by copying the beginning of the regex...
+     */
+
+    dst = g_stpcpy(result, table->begin);
+
+    /*
+     * ... Now to the meat of it.
+     */
+
+    for (src = str; *src; src++) {
+        c = *src;
+
+        /*
+         * First, check that we're in a character class: each and every
+         * character can be copied as is. We only need to be careful is the
+         * character is a closing bracket: it will end the character class IF
+         * AND ONLY IF it is not preceded by a backslash.
+         */
+
+        if (in_character_class) {
+            in_character_class = ((c != ']') || (*(src - 1) == '\\'));
+            goto straight_copy;
+        }
+
+        /*
+         * Are we in a quote? If yes, it is really simple: copy the current
+         * character, close the quote, the end.
+         */
+
+        if (in_quote) {
+            in_quote = FALSE;
+            goto straight_copy;
+        }
+
+        /*
+         * The only thing left to handle now is the "normal" case: we are not in
+         * a character class nor in a quote.
+         */
+
+        if (c == '\\') {
+            /*
+             * Backslash: append it, and open a new quote.
+             */
+            in_quote = TRUE;
+            goto straight_copy;
+        } else if (c == '[') {
+            /*
+             * Opening bracket: the beginning of a character class.
+             *
+             * Look ahead the next character: if it's an exclamation mark, then
+             * this is a complemented character class; append a caret to make
+             * the result string regex-friendly, and forward one character in
+             * advance.
+             */
+            *dst++ = c;
+            in_character_class = TRUE;
+            if (*(src + 1) == '!') {
+                *dst++ = '^';
+                src++;
+            }
+        } else if (IS_REGEX_META(c)) {
+            /*
+             * Regex metacharacter (except for ? and *, see below): append a
+             * backslash, and then the character itself.
+             */
+            *dst++ = '\\';
+            goto straight_copy;
+        } else if (c == '?')
+            /*
+             * Question mark: take the subsitution string out of our subst_table
+             * and append it to the string.
+             */
+            dst = g_stpcpy(dst, table->question_mark);
+        else if (c == '*') {
+            /*
+             * Star: append the subsitution string found in our subst_table.
+             * However, look forward the next character: if it's yet another
+             * star, then see if there is a substitution string for the double
+             * star and append this one instead.
+             *
+             * FIXME: this means that two consecutive stars in a glob string
+             * where there is no substition for double_star can lead to
+             * exponential regex execution time: consider [^/]*[^/]*.
+             */
+            const char *p = table->star;
+            if (*(src + 1) == '*' && table->double_star) {
+                src++;
+                p = table->double_star;
+            }
+            dst = g_stpcpy(dst, p);
+        } else {
+            /*
+             * Any other character: append each time.
+             */
+straight_copy:
+            *dst++ = c;
+        }
     }
-    *r = '\0';
 
-    return regex;
+    /*
+     * Done, now append the end, ONLY if we are not in a quote - a lone
+     * backslash at the end of a glob is illegal, just leave it as it, it will
+     * make the regex compile fail.
+     */
+
+    if (!in_quote)
+        dst = g_stpcpy(dst, table->end);
+    /*
+     * Finalize, return.
+     */
+
+    *dst = '\0';
+    return result;
 }
 
+static struct subst_table glob_subst_stable = {
+    "^", /* begin */
+    "$", /* end */
+    "[^/]", /* question_mark */
+    "[^/]*", /* star */
+    NULL /* double_star */
+};
+
+static size_t glob_worst_case = 5; /* star */
+
+char *
+glob_to_regex(
+    const char *       glob)
+{
+    return amglob_to_regex(glob, &glob_subst_stable, glob_worst_case);
+}
 
 int
 match_tar(
@@ -238,105 +483,110 @@ match_tar(
     char *regex;
     regex_t regc;
     int result;
-    char errmsg[STR_SIZE];
+    regex_errbuf errmsg;
+    gboolean ok;
 
     regex = tar_to_regex(glob);
-    if((result = regcomp(&regc, regex,
-                        REG_EXTENDED|REG_NOSUB|REG_NEWLINE)) != 0) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
-       /*NOTREACHED*/
-    }
+    ok = do_validate_regex(regex, &regc, &errmsg, TRUE);
 
-    if((result = regexec(&regc, str, 0, 0, 0)) != 0
-       && result != REG_NOMATCH) {
-        regerror(result, &regc, errmsg, SIZEOF(errmsg));
-       error("glob \"%s\" -> regex \"%s\": %s", glob, regex, errmsg);
-       /*NOTREACHED*/
-    }
+    if (!ok)
+        error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
+        /*NOTREACHED*/
+
+    result = try_match(&regc, str, &errmsg);
+
+    if (result == MATCH_ERROR)
+        error(_("glob \"%s\" -> regex \"%s\": %s"), glob, regex, errmsg);
+        /*NOTREACHED*/
 
     regfree(&regc);
     amfree(regex);
 
-    return result == 0;
+    return result;
 }
 
-char *
+static struct subst_table tar_subst_stable = {
+    "(^|/)", /* begin */
+    "($|/)", /* end */
+    "[^/]", /* question_mark */
+    ".*", /* star */
+    NULL /* double_star */
+};
+
+static size_t tar_worst_case = 5; /* begin or end */
+
+static char *
 tar_to_regex(
     const char *       glob)
 {
-    char *regex;
-    char *r;
-    size_t len;
-    int ch;
-    int last_ch;
+    return amglob_to_regex(glob, &tar_subst_stable, tar_worst_case);
+}
 
-    /*
-     * Allocate an area to convert into.  The worst case is a five to
-     * one expansion.
-     */
-    len = strlen(glob);
-    regex = alloc(1 + len * 5 + 1 + 1);
+/*
+ * Two utility functions used by match_disk() below: they are used to convert a
+ * disk and glob from Windows expressed paths (backslashes) into Unix paths
+ * (slashes).
+ *
+ * Note: the resulting string is dynamically allocated, it is up to the caller
+ * to free it.
+ *
+ * Note 2: UNC in convert_unc_to_unix stands for Uniform Naming Convention.
+ */
 
-    /*
-     * Do the conversion:
-     *
-     *  ?      -> [^/]
-     *  *      -> .*
-     *  [!...] -> [^...]
-     *
-     * The following are given a leading backslash to protect them
-     * unless they already have a backslash:
-     *
-     *   ( ) { } + . ^ $ |
-     *
-     * Put a leading ^ and trailing $ around the result.  If the last
-     * non-escaped character is \ leave the $ off to cause a syntax
-     * error when the regex is compiled.
-     */
+static char *convert_unc_to_unix(const char *unc)
+{
+    const char *src;
+    char *result, *dst;
+    result = alloc(strlen(unc) + 1);
+    dst = result;
 
-    r = regex;
-    *r++ = '^';
-    last_ch = '\0';
-    for (ch = *glob++; ch != '\0'; last_ch = ch, ch = *glob++) {
-       if (last_ch == '\\') {
-           *r++ = (char)ch;
-           ch = '\0';                  /* so last_ch != '\\' next time */
-       } else if (last_ch == '[' && ch == '!') {
-           *r++ = '^';
-       } else if (ch == '\\') {
-           *r++ = (char)ch;
-       } else if (ch == '*') {
-           *r++ = '.';
-           *r++ = '*';
-       } else if (ch == '?') {
-           *r++ = '[';
-           *r++ = '^';
-           *r++ = '/';
-           *r++ = ']';
-       } else if (ch == '('
-                  || ch == ')'
-                  || ch == '{'
-                  || ch == '}'
-                  || ch == '+'
-                  || ch == '.'
-                  || ch == '^'
-                  || ch == '$'
-                  || ch == '|') {
-           *r++ = '\\';
-           *r++ = (char)ch;
-       } else {
-           *r++ = (char)ch;
-       }
-    }
-    if (last_ch != '\\') {
-       *r++ = '$';
-    }
-    *r = '\0';
+    for (src = unc; *src; src++)
+        *(dst++) = (*src == '\\') ? '/' : *src;
+
+    *dst = '\0';
+    return result;
+}
 
-    return regex;
+static char *convert_winglob_to_unix(const char *glob)
+{
+    const char *src;
+    char *result, *dst;
+    result = alloc(strlen(glob) + 1);
+    dst = result;
+
+    for (src = glob; *src; src++) {
+        if (*src == '\\' && *(src + 1) == '\\') {
+            *(dst++) = '/';
+            src++;
+            continue;
+        }
+        *(dst++) = *src;
+    }
+    *dst = '\0';
+    return result;
 }
 
+/*
+ * Check whether a glob passed as an argument to match_word() only looks for the
+ * separator
+ */
+
+static gboolean glob_is_separator_only(const char *glob, char sep) {
+    size_t len = strlen(glob);
+    const char len2_1[] = { '^', sep , 0 }, len2_2[] = { sep, '$', 0 },
+        len3[] = { '^', sep, '$', 0 };
+
+    switch (len) {
+        case 1:
+            return (*glob == sep);
+        case 2:
+            return !(strcmp(glob, len2_1) && strcmp(glob, len2_2));
+        case 3:
+            return !strcmp(glob, len3);
+        default:
+            return FALSE;
+    }
+}
 
 static int
 match_word(
@@ -345,150 +595,143 @@ match_word(
     const char         separator)
 {
     char *regex;
-    char *r;
+    char *dst;
     size_t  len;
-    int  ch;
-    int  last_ch;
-    int  next_ch;
     size_t  lenword;
     char *nword;
     char *nglob;
-    char *g; 
-    const char *w;
-    int  i;
+    const char *src;
+    int ret;
 
     lenword = strlen(word);
     nword = (char *)alloc(lenword + 3);
 
-    r = nword;
-    w = word;
-    if(lenword == 1 && *w == separator) {
-       *r++ = separator;
-       *r++ = separator;
+    dst = nword;
+    src = word;
+    if(lenword == 1 && *src == separator) {
+       *dst++ = separator;
+       *dst++ = separator;
     }
     else {
-       if(*w != separator)
-           *r++ = separator;
-       while(*w != '\0')
-           *r++ = *w++;
-       if(*(r-1) != separator)
-           *r++ = separator;    
+       if(*src != separator)
+           *dst++ = separator;
+       while(*src != '\0')
+           *dst++ = *src++;
+       if(*(dst-1) != separator)
+           *dst++ = separator;
     }
-    *r = '\0';
+    *dst = '\0';
 
-    /*
-     * Allocate an area to convert into.  The worst case is a six to
-     * one expansion.
-     */
     len = strlen(glob);
-    regex = (char *)alloc(1 + len * 6 + 1 + 1 + 2 + 2);
-    r = regex;
     nglob = stralloc(glob);
-    g = nglob;
-
-    if((len == 1 && nglob[0] == separator) ||
-       (len == 2 && nglob[0] == '^' && nglob[1] == separator) ||
-       (len == 2 && nglob[0] == separator && nglob[1] == '$') ||
-       (len == 3 && nglob[0] == '^' && nglob[1] == separator &&
-        nglob[2] == '$')) {
-       *r++ = '^';
-       *r++ = '\\';
-       *r++ = separator;
-       *r++ = '\\';
-       *r++ = separator;
-       *r++ = '$';
-    }
-    else {
-       /*
-        * Do the conversion:
-        *
-        *  ?      -> [^\separator]
-        *  *      -> [^\separator]*
-        *  [!...] -> [^...]
-        *  **     -> .*
-        *
-        * The following are given a leading backslash to protect them
-        * unless they already have a backslash:
-        *
-        *   ( ) { } + . ^ $ |
-        *
-        * If the last
-        * non-escaped character is \ leave it to cause a syntax
-        * error when the regex is compiled.
-        */
-
-       if(*g == '^') {
-           *r++ = '^';
-           *r++ = '\\';        /* escape the separator */
-           *r++ = separator;
-           g++;
-           if(*g == separator) g++;
-       }
-       else if(*g != separator) {
-           *r++ = '\\';        /* add a leading \separator */
-           *r++ = separator;
-       }
-       last_ch = '\0';
-       for (ch = *g++; ch != '\0'; last_ch = ch, ch = *g++) {
-           next_ch = *g;
-           if (last_ch == '\\') {
-               *r++ = (char)ch;
-               ch = '\0';              /* so last_ch != '\\' next time */
-           } else if (last_ch == '[' && ch == '!') {
-               *r++ = '^';
-           } else if (ch == '\\') {
-               *r++ = (char)ch;
-           } else if (ch == '*' || ch == '?') {
-               if(ch == '*' && next_ch == '*') {
-                   *r++ = '.';
-                   g++;
-               }
-               else {
-                   *r++ = '[';
-                   *r++ = '^';
-                   *r++ = '\\';
-                   *r++ = separator;
-                   *r++ = ']';
-               }
-               if (ch == '*') {
-                   *r++ = '*';
-               }
-           } else if (ch == '$' && next_ch == '\0') {
-               if(last_ch != separator) {
-                   *r++ = '\\';
-                   *r++ = separator;
-               }
-               *r++ = (char)ch;
-           } else if (   ch == '('
-                      || ch == ')'
-                      || ch == '{'
-                      || ch == '}'
-                      || ch == '+'
-                      || ch == '.'
-                      || ch == '^'
-                      || ch == '$'
-                      || ch == '|') {
-               *r++ = '\\';
-               *r++ = (char)ch;
-           } else {
-               *r++ = (char)ch;
-           }
-       }
-       if(last_ch != '\\') {
-           if(last_ch != separator && last_ch != '$') {
-               *r++ = '\\';
-               *r++ = separator;               /* add a trailing \separator */
-           }
-       }
+
+    if(glob_is_separator_only(nglob, separator)) {
+        regex = alloc(7); /* Length of what is written below plus '\0' */
+        dst = regex;
+       *dst++ = '^';
+       *dst++ = '\\';
+       *dst++ = separator;
+       *dst++ = '\\';
+       *dst++ = separator;
+       *dst++ = '$';
+        *dst = '\0';
+    } else {
+        /*
+         * Unlike what happens for tar and disk expressions, here the
+         * substitution table needs to be dynamically allocated. When we enter
+         * here, we know what the expansions will be for the question mark, the
+         * star and the double star, and also the worst case expansion. We
+         * calculate the begin and end expansions below.
+         */
+
+#define MATCHWORD_STAR_EXPANSION(c) (const char []) { \
+    '[', '^', (c), ']', '*', 0 \
+}
+#define MATCHWORD_QUESTIONMARK_EXPANSION(c) (const char []) { \
+    '[', '^', (c), ']', 0 \
+}
+#define MATCHWORD_DOUBLESTAR_EXPANSION ".*"
+
+        struct subst_table table;
+        size_t worst_case = 5;
+        const char *begin, *end;
+        char *p, *g = nglob;
+
+        /*
+         * Calculate the beginning of the regex:
+         * - by default, it is an unanchored separator;
+         * - if the glob begins with a caret, make that an anchored separator,
+         *   and increment g appropriately;
+         * - if it begins with a separator, make it the empty string.
+         */
+
+        p = nglob;
+
+#define REGEX_BEGIN_FULL(c) (const char[]) { '^', '\\', (c), 0 }
+#define REGEX_BEGIN_NOANCHOR(c) (const char[]) { '\\', (c), 0 }
+#define REGEX_BEGIN_ANCHORONLY "^" /* Unused, but defined for consistency */
+#define REGEX_BEGIN_EMPTY ""
+
+        begin = REGEX_BEGIN_NOANCHOR(separator);
+
+        if (*p == '^') {
+            begin = REGEX_BEGIN_FULL(separator);
+            p++, g++;
+            if (*p == separator)
+                g++;
+        } else if (*p == separator)
+            begin = REGEX_BEGIN_EMPTY;
+
+        /*
+         * Calculate the end of the regex:
+         * - an unanchored separator by default;
+         * - if the last character is a backslash or the separator itself, it
+         *   should be the empty string;
+         * - if it is a dollar sign, overwrite it with 0 and look at the
+         *   character before it: if it is the separator, only anchor at the
+         *   end, otherwise, add a separator before the anchor.
+         */
+
+        p = &(nglob[strlen(nglob) - 1]);
+
+#define REGEX_END_FULL(c) (const char[]) { '\\', (c), '$', 0 }
+#define REGEX_END_NOANCHOR(c) REGEX_BEGIN_NOANCHOR(c)
+#define REGEX_END_ANCHORONLY "$"
+#define REGEX_END_EMPTY REGEX_BEGIN_EMPTY
+
+        end = REGEX_END_NOANCHOR(separator);
+
+        if (*p == '\\' || *p == separator)
+            end = REGEX_END_EMPTY;
+        else if (*p == '$') {
+            char prev = *(p - 1);
+            *p = '\0';
+            if (prev == separator)
+                end = REGEX_END_ANCHORONLY;
+            else
+                end = REGEX_END_FULL(separator);
+        }
+
+        /*
+         * Fill in our substitution table and generate the regex
+         */
+
+        table.begin = begin;
+        table.end = end;
+        table.question_mark = MATCHWORD_QUESTIONMARK_EXPANSION(separator);
+        table.star = MATCHWORD_STAR_EXPANSION(separator);
+        table.double_star = MATCHWORD_DOUBLESTAR_EXPANSION;
+
+        regex = amglob_to_regex(g, &table, worst_case);
     }
-    *r = '\0';
 
-    i = match(regex,nword);
+    ret = do_match(regex, nword, TRUE);
 
     amfree(nword);
     amfree(nglob);
     amfree(regex);
-    return i;
+
+    return ret;
 }
 
 
@@ -498,27 +741,17 @@ match_host(
     const char *       host)
 {
     char *lglob, *lhost;
-    char *c;
-    const char *d;
-    int i;
+    int ret;
 
     
-    lglob = (char *)alloc(strlen(glob)+1);
-    c = lglob, d=glob;
-    while( *d != '\0')
-       *c++ = (char)tolower(*d++);
-    *c = *d;
-
-    lhost = (char *)alloc(strlen(host)+1);
-    c = lhost, d=host;
-    while( *d != '\0')
-       *c++ = (char)tolower(*d++);
-    *c = *d;
-
-    i = match_word(lglob, lhost, (int)'.');
+    lglob = g_ascii_strdown(glob, -1);
+    lhost = g_ascii_strdown(host, -1);
+
+    ret = match_word(lglob, lhost, '.');
+
     amfree(lglob);
     amfree(lhost);
-    return i;
+    return ret;
 }
 
 
@@ -527,7 +760,46 @@ match_disk(
     const char *       glob,
     const char *       disk)
 {
-    return match_word(glob, disk, '/');
+    char *glob2 = NULL, *disk2 = NULL;
+    const char *g = glob, *d = disk;
+    int result;
+
+    /*
+     * Check whether our disk potentially refers to a Windows share (the first
+     * two characters are '\' and there is no / in the word at all): if yes,
+     * convert all double backslashes to slashes in the glob, and simple
+     * backslashes into slashes in the disk, and pass these new strings as
+     * arguments instead of the originals.
+     */
+    gboolean windows_share = !(strncmp(disk, "\\\\", 2) || strchr(disk, '/'));
+
+    if (windows_share) {
+        glob2 = convert_winglob_to_unix(glob);
+        disk2 = convert_unc_to_unix(disk);
+        g = (const char *) glob2;
+        d = (const char *) disk2;
+    }
+
+    result = match_word(g, d, '/');
+
+    /*
+     * We can amfree(NULL), so this is "safe"
+     */
+    amfree(glob2);
+    amfree(disk2);
+
+    return result;
+}
+
+static int
+alldigits(
+    const char *str)
+{
+    while (*str) {
+       if (!isdigit((int)*(str++)))
+           return 0;
+    }
+    return 1;
 }
 
 int
@@ -543,45 +815,56 @@ match_datestamp(
     int match_exact;
 
     if(strlen(dateexp) >= 100 || strlen(dateexp) < 1) {
-       error("Illegal datestamp expression %s",dateexp);
-       /*NOTREACHED*/
+       goto illegal;
     }
    
+    /* strip and ignore an initial "^" */
     if(dateexp[0] == '^') {
-       strncpy(mydateexp, dateexp+1, strlen(dateexp)-1); 
-       mydateexp[strlen(dateexp)-1] = '\0';
+       strncpy(mydateexp, dateexp+1, sizeof(mydateexp)-1);
+       mydateexp[sizeof(mydateexp)-1] = '\0';
     }
     else {
-       strncpy(mydateexp, dateexp, strlen(dateexp));
-       mydateexp[strlen(dateexp)] = '\0';
+       strncpy(mydateexp, dateexp, sizeof(mydateexp)-1);
+       mydateexp[sizeof(mydateexp)-1] = '\0';
     }
 
-    if(mydateexp[strlen(mydateexp)] == '$') {
+    if(mydateexp[strlen(mydateexp)-1] == '$') {
        match_exact = 1;
-       mydateexp[strlen(mydateexp)] = '\0';
+       mydateexp[strlen(mydateexp)-1] = '\0';  /* strip the trailing $ */
     }
     else
        match_exact = 0;
 
+    /* a single dash represents a date range */
     if((dash = strchr(mydateexp,'-'))) {
-       if(match_exact == 1) {
-           error("Illegal datestamp expression %s",dateexp);
-           /*NOTREACHED*/
+       if(match_exact == 1 || strchr(dash+1, '-')) {
+           goto illegal;
        }
-       len = (size_t)(dash - mydateexp);
-       len_suffix = strlen(dash) - 1;
-       len_prefix = len - len_suffix;
+
+       /* format: XXXYYYY-ZZZZ, indicating dates XXXYYYY to XXXZZZZ */
+
+       len = (size_t)(dash - mydateexp);   /* length of XXXYYYY */
+       len_suffix = strlen(dash) - 1;  /* length of ZZZZ */
+       if (len_suffix > len) goto illegal;
+       len_prefix = len - len_suffix; /* length of XXX */
 
        dash++;
+
        strncpy(firstdate, mydateexp, len);
        firstdate[len] = '\0';
        strncpy(lastdate, mydateexp, len_prefix);
        strncpy(&(lastdate[len_prefix]), dash, len_suffix);
        lastdate[len] = '\0';
+       if (!alldigits(firstdate) || !alldigits(lastdate))
+           goto illegal;
+       if (strncmp(firstdate, lastdate, strlen(firstdate)) > 0)
+           goto illegal;
        return ((strncmp(datestamp, firstdate, strlen(firstdate)) >= 0) &&
                (strncmp(datestamp, lastdate , strlen(lastdate))  <= 0));
     }
     else {
+       if (!alldigits(mydateexp))
+           goto illegal;
        if(match_exact == 1) {
            return (strcmp(datestamp, mydateexp) == 0);
        }
@@ -589,6 +872,9 @@ match_datestamp(
            return (strncmp(datestamp, mydateexp, strlen(mydateexp)) == 0);
        }
     }
+illegal:
+       error(_("Illegal datestamp expression %s"),dateexp);
+       /*NOTREACHED*/
 }
 
 
@@ -598,14 +884,12 @@ match_level(
     const char *       level)
 {
     char *dash;
-    size_t len, len_suffix;
-    size_t len_prefix;
-    char lowend[100], highend[100];
+    long int low, hi, level_i;
     char mylevelexp[100];
     int match_exact;
 
     if(strlen(levelexp) >= 100 || strlen(levelexp) < 1) {
-       error("Illegal level expression %s",levelexp);
+       error(_("Illegal level expression %s"),levelexp);
        /*NOTREACHED*/
     }
    
@@ -618,32 +902,33 @@ match_level(
        mylevelexp[strlen(levelexp)] = '\0';
     }
 
-    if(mylevelexp[strlen(mylevelexp)] == '$') {
+    if(mylevelexp[strlen(mylevelexp)-1] == '$') {
        match_exact = 1;
-       mylevelexp[strlen(mylevelexp)] = '\0';
+       mylevelexp[strlen(mylevelexp)-1] = '\0';
     }
     else
        match_exact = 0;
 
     if((dash = strchr(mylevelexp,'-'))) {
        if(match_exact == 1) {
-           error("Illegal level expression %s",levelexp);
-           /*NOTREACHED*/
+            goto illegal;
        }
-       len = (size_t)(dash - mylevelexp);
-       len_suffix = strlen(dash) - 1;
-       len_prefix = len - len_suffix;
 
-       dash++;
-       strncpy(lowend, mylevelexp, len);
-       lowend[len] = '\0';
-       strncpy(highend, mylevelexp, len_prefix);
-       strncpy(&(highend[len_prefix]), dash, len_suffix);
-       highend[len] = '\0';
-       return ((strncmp(level, lowend, strlen(lowend)) >= 0) &&
-               (strncmp(level, highend , strlen(highend))  <= 0));
+        *dash = '\0';
+        if (!alldigits(mylevelexp) || !alldigits(dash+1)) goto illegal;
+
+        errno = 0;
+        low = strtol(mylevelexp, (char **) NULL, 10);
+        if (errno) goto illegal;
+        hi = strtol(dash+1, (char **) NULL, 10);
+        if (errno) goto illegal;
+        level_i = strtol(level, (char **) NULL, 10);
+        if (errno) goto illegal;
+
+       return ((level_i >= low) && (level_i <= hi));
     }
     else {
+       if (!alldigits(mylevelexp)) goto illegal;
        if(match_exact == 1) {
            return (strcmp(level, mylevelexp) == 0);
        }
@@ -651,4 +936,7 @@ match_level(
            return (strncmp(level, mylevelexp, strlen(mylevelexp)) == 0);
        }
     }
+illegal:
+    error(_("Illegal level expression %s"),levelexp);
+    /*NOTREACHED*/
 }