const char *data;
const char *mask;
bool result;
-} tests[] = {
+} tests_common[] = {
{ "foo", "*", TRUE },
{ "foo", "*foo*", TRUE },
{ "foo", "foo", TRUE },
{ "", "?", FALSE }
};
+static const struct {
+ const char *data;
+ const char *mask;
+ bool result;
+} tests_unescaped[] = {
+ { "f*o", "f*o", TRUE },
+ { "f*o", "f\\*o", FALSE },
+};
+
+static const struct {
+ const char *data;
+ const char *mask;
+ bool result;
+} tests_escaped[] = {
+ { "f*o", "f*o", TRUE },
+ { "f*o", "f\\*o", TRUE },
+ { "f?o", "f\\*o", FALSE },
+ { "f?o", "f\\?o", TRUE },
+ { "f*o", "f\\*", FALSE },
+ { "f*", "f\\", FALSE },
+ { "f\\*o", "f\\*o", FALSE },
+ { "f\\*o", "f\\\\*", TRUE },
+ { "f\\*o", "f\\\\*o", TRUE },
+ { "f\\*o", "f\\\\\\*o", TRUE },
+ { "f\\*o", "f\\?o", FALSE },
+ { "f\\*o", "f\\\\?o", TRUE },
+ { "f\\*o", "f\\\\\\?o", FALSE },
+ { "f\\", "f\\", TRUE },
+ { "\\\\?", "?\\\\?", TRUE },
+};
+
void test_wildcard_match(void)
{
unsigned int i;
test_begin("wildcard_match()");
- for (i = 0; i < N_ELEMENTS(tests); i++) {
- test_assert_idx(wildcard_match(tests[i].data, tests[i].mask) == tests[i].result, i);
+ for (i = 0; i < N_ELEMENTS(tests_common); i++) {
+ test_assert_idx(wildcard_match(tests_common[i].data,
+ tests_common[i].mask) ==
+ tests_common[i].result, i);
+ test_assert_idx(wildcard_match_escaped(tests_common[i].data,
+ tests_common[i].mask) ==
+ tests_common[i].result, i);
+ }
+
+ for (i = 0; i < N_ELEMENTS(tests_unescaped); i++) {
+ test_assert_idx(wildcard_match(tests_unescaped[i].data,
+ tests_unescaped[i].mask) ==
+ tests_unescaped[i].result, i);
}
+
+ for (i = 0; i < N_ELEMENTS(tests_escaped); i++) {
+ test_assert_idx(wildcard_match_escaped(tests_escaped[i].data,
+ tests_escaped[i].mask) ==
+ tests_escaped[i].result, i);
+ }
+ test_assert(!wildcard_is_literal("\\*foo\\?bar\\?"));
+ test_assert(wildcard_is_escaped_literal("\\*foo\\?bar\\?"));
+ test_assert(!wildcard_is_escaped_literal("*foo"));
+ test_end();
+
+ test_begin("wildcard_str_escape()");
+ const char *foo = "foo";
+ test_assert(wildcard_str_escape(foo) == foo);
+ test_assert_strcmp(wildcard_str_escape("foo?"), "foo\\?");
+ test_assert_strcmp(wildcard_str_escape("foo*"), "foo\\*");
+ test_assert_strcmp(wildcard_str_escape("foo\\"), "foo\\\\");
+ test_assert_strcmp(wildcard_str_escape("foo\\"), "foo\\\\");
+ test_assert_strcmp(wildcard_str_escape("\\f*o?o'\"x"), "\\\\f\\*o\\?o\\'\\\"x");
test_end();
}
*/
#include "lib.h"
+#include "str.h"
#include "wildcard-match.h"
#include <ctype.h>
#define WILDS '*' /* matches 0 or more characters (including spaces) */
#define WILDQ '?' /* matches exactly one character */
+#define WILDE '\\' /* escapes one wildcard */
#define NOMATCH 0
#define MATCH (match+sofar)
-static int wildcard_match_int(const char *data, const char *mask, bool icase)
+static bool is_escaped(const char *p, const char *start)
+{
+ bool is_escaped = FALSE;
+ while (p > start && p[-1] == WILDE) {
+ is_escaped = !is_escaped;
+ p--;
+ }
+ return is_escaped;
+}
+
+static int
+wildcard_match_int(const char *data, const char *mask, bool icase, bool escaped)
{
const char *ma = mask, *na = data, *lsm = NULL, *lsn = NULL;
int match = 1;
}
switch (*mask) {
+ case WILDE:
+ if (escaped && is_escaped(mask, ma)) {
+ if (*mask != *data)
+ goto nomatch;
+ mask -= 2;
+ data--;
+ sofar++;
+ continue;
+ }
+ break;
case WILDS: /* Matches anything */
+ if (escaped && is_escaped(mask, ma)) {
+ if (*mask != *data)
+ goto nomatch;
+ mask -= 2;
+ data--;
+ sofar++;
+ continue;
+ }
do
- mask--; /* Zap redundant wilds */
- while ((mask >= ma) && (*mask == WILDS));
+ mask--; /* Zap redundant wilds */
+ while ((mask >= ma) && (*mask == WILDS) &&
+ (!escaped || !is_escaped(mask, ma)));
lsm = mask;
lsn = data;
match += sofar;
sofar = 0; /* Update fallback pos */
if (mask < ma)
- return MATCH;
+ return MATCH;
continue; /* Next char, please */
case WILDQ:
+ if (escaped && is_escaped(mask, ma)) {
+ if (*mask != *data)
+ goto nomatch;
+ mask -= 2;
+ data--;
+ sofar++;
+ continue;
+ }
mask--;
data--;
continue; /* '?' always matches */
sofar++; /* Tally the match */
continue; /* Next char, please */
}
+nomatch:
if (lsm != NULL) { /* To to fallback on '*' */
data = --lsn;
mask = lsm;
}
return NOMATCH; /* No fallback=No match */
}
- while ((mask >= ma) && (*mask == WILDS))
+ while ((mask >= ma) && (*mask == WILDS) &&
+ (!escaped || !is_escaped(mask, ma)))
mask--; /* Zap leftover %s & *s */
return (mask >= ma) ? NOMATCH : MATCH; /* Start of both = match */
}
bool wildcard_match(const char *data, const char *mask)
{
- return wildcard_match_int(data, mask, FALSE) != 0;
+ return wildcard_match_int(data, mask, FALSE, FALSE) != 0;
}
bool wildcard_match_icase(const char *data, const char *mask)
{
- return wildcard_match_int(data, mask, TRUE) != 0;
+ return wildcard_match_int(data, mask, TRUE, FALSE) != 0;
+}
+
+bool wildcard_match_escaped(const char *data, const char *mask)
+{
+ return wildcard_match_int(data, mask, FALSE, TRUE) != 0;
+}
+
+bool wildcard_match_escaped_icase(const char *data, const char *mask)
+{
+ return wildcard_match_int(data, mask, TRUE, TRUE) != 0;
+}
+
+bool wildcard_is_escaped_literal(const char *mask)
+{
+ const char *p = mask;
+
+ while ((p = strpbrk(p, "*?\\")) != NULL) {
+ if (*p != '\\')
+ return FALSE;
+ if (p[1] == '\0')
+ break;
+ p += 2;
+ }
+ return TRUE;
+}
+
+const char *wildcard_str_escape(const char *str)
+{
+ const char *p = strpbrk(str, "*?\\\"'");
+ if (p == NULL)
+ return str;
+
+ string_t *esc = t_str_new((p - str) + strlen(p) + 8);
+ do {
+ str_append_data(esc, str, p - str);
+ str_append_c(esc, '\\');
+ str_append_c(esc, *p);
+
+ str = p + 1;
+ p = strpbrk(str, "*?\\\"'");
+ } while (p != NULL);
+ str_append(esc, str);
+ return str_c(esc);
}
return strpbrk(mask, "*?") == NULL;
}
+bool wildcard_match_escaped(const char *data, const char *mask);
+bool wildcard_match_escaped_icase(const char *data, const char *mask);
+/* Returns TRUE if mask does *not* contain any '*' or '?' wildcards, except
+ preceded by '\' escape character. */
+bool wildcard_is_escaped_literal(const char *mask);
+
+/* Same as str_escape(), but also escape '*' and '?' characters. */
+const char *wildcard_str_escape(const char *str);
+
#endif