--- /dev/null
+
+#include <usual/fnmatch.h>
+
+#include <usual/string.h>
+#include "test_common.h"
+
+/*
+ * POSIX syntax.
+ */
+
+static void test_fnmatch_posix(void *p)
+{
+ /* literal */
+ int_check(0, fnmatch("", "", 0));
+ int_check(0, fnmatch("a", "a", 0));
+ int_check(0, fnmatch("abc", "abc", 0));
+ int_check(1, fnmatch("", "b", 0));
+ int_check(1, fnmatch("a", "", 0));
+ int_check(1, fnmatch("a", "b", 0));
+
+ /* single wildcard */
+ int_check(0, fnmatch("a?", "ax", 0));
+ int_check(0, fnmatch("??", "ax", 0));
+ int_check(1, fnmatch("?", "ax", 0));
+ int_check(1, fnmatch("???", "ax", 0));
+
+ /* wildcard */
+ int_check(0, fnmatch("a*", "ax", 0));
+ int_check(0, fnmatch("*", "", 0));
+ int_check(0, fnmatch("*", "qwe", 0));
+ int_check(0, fnmatch("ab*ab", "abxxab", 0));
+ int_check(1, fnmatch("ab*ab", "abxxabc", 0));
+
+ /* wildcard+ */
+ int_check(0, fnmatch("ab*ab*", "abxxabc", 0));
+ int_check(0, fnmatch("ab*ab*c", "abxxabc", 0));
+ int_check(0, fnmatch("ab*ab*c", "abxxabxc", 0));
+ int_check(0, fnmatch("??*??", "abxxab", 0));
+ int_check(0, fnmatch("*??", "abxxab", 0));
+ int_check(0, fnmatch("??*", "abxxab", 0));
+ int_check(0, fnmatch("a**c", "abc", 0));
+
+ /* classes */
+ int_check(0, fnmatch("[abc]", "b", 0));
+ int_check(1, fnmatch("[abc]", "x", 0));
+ int_check(0, fnmatch("[a-c]", "b", 0));
+ int_check(1, fnmatch("[a-c]", "x", 0));
+ int_check(0, fnmatch("[b-b]", "b", 0));
+ int_check(1, fnmatch("[!abc]", "b", 0));
+ int_check(1, fnmatch("[!a-c]", "b", 0));
+ int_check(0, fnmatch("[!a-c]", "x", 0));
+ int_check(0, fnmatch("[*?[][*?[][*?[]", "*?[", 0));
+ int_check(0, fnmatch("[[:alpha:]][![:alpha:]]", "a9", 0));
+ int_check(0, fnmatch("[[:alnum:]][![:alnum:]]", "9-", 0));
+ int_check(0, fnmatch("[[:blank:]][![:blank:]]", " -", 0));
+ int_check(0, fnmatch("[[:cntrl:]][![:cntrl:]]", "\tx", 0));
+ int_check(0, fnmatch("[[:digit:]][![:digit:]]", "9a", 0));
+ int_check(0, fnmatch("[[:graph:]][![:graph:]]", "a\t", 0));
+ int_check(0, fnmatch("[[:lower:]][![:lower:]]", "aA", 0));
+ int_check(0, fnmatch("[[:print:]][![:print:]]", "a\t", 0));
+ int_check(0, fnmatch("[[:punct:]][![:punct:]]", ".x", 0));
+ int_check(0, fnmatch("[[:space:]][![:space:]]", " x", 0));
+ int_check(0, fnmatch("[[:upper:]][![:upper:]]", "Ff", 0));
+ int_check(0, fnmatch("[[:xdigit:]][![:xdigit:]]", "Fx", 0));
+ int_check(0, fnmatch("[", "[", 0));
+ int_check(0, fnmatch("[f", "[f", 0));
+
+ /* escaping */
+ int_check(1, fnmatch("\\a\\?", "ax", 0));
+ int_check(0, fnmatch("\\a\\?", "a?", 0));
+ int_check(1, fnmatch("\\a\\*", "ax", 0));
+ int_check(0, fnmatch("\\a\\*", "a*", 0));
+ int_check(0, fnmatch("\\[a]", "[a]", 0));
+ int_check(0, fnmatch("\\\\", "\\", 0));
+ int_check(0, fnmatch("\\$\\'\\\"\\<\\>", "$'\"<>", 0));
+ int_check(1, fnmatch("a\\", "a", 0));
+ int_check(1, fnmatch("a\\", "a\\", 0));
+ int_check(0, fnmatch("a\\", "a\\", FNM_NOESCAPE));
+ int_check(0, fnmatch("\\[a]", "\\a", FNM_NOESCAPE));
+ int_check(0, fnmatch("\\*b", "\\aab", FNM_NOESCAPE));
+
+ /* FNM_PATHNAME */
+ int_check(0, fnmatch("ab*c", "ab/c", 0));
+ int_check(1, fnmatch("ab*c", "ab/c", FNM_PATHNAME));
+ int_check(1, fnmatch("ab?c", "ab/c", FNM_PATHNAME));
+ int_check(1, fnmatch("ab[/]c", "ab/c", FNM_PATHNAME));
+ int_check(0, fnmatch("/*/", "//", FNM_PATHNAME));
+ int_check(1, fnmatch("a[b/c]d", "a/d", FNM_PATHNAME));
+ int_check(0, fnmatch("abd", "abd", FNM_PATHNAME));
+ int_check(1, fnmatch("a[b/c]d", "a[b/c]d", FNM_PATHNAME));
+
+ /* FNM_PERIOD */
+ int_check(0, fnmatch(".foo", ".foo", 0));
+ int_check(0, fnmatch("?foo", ".foo", 0));
+ int_check(0, fnmatch("[.]foo", ".foo", 0));
+ int_check(0, fnmatch("[!abc]foo", ".foo", 0));
+ int_check(0, fnmatch("*foo", ".foo", 0));
+ int_check(0, fnmatch(".foo", ".foo", FNM_PERIOD));
+ int_check(1, fnmatch("*foo", ".foo", FNM_PERIOD));
+ int_check(1, fnmatch("?foo", ".foo", FNM_PERIOD));
+ int_check(0, fnmatch("*/?foo", "sub/.foo", FNM_PERIOD));
+ int_check(1, fnmatch("*.foo", ".foo", FNM_PERIOD));
+ int_check(1, fnmatch("[.]foo", ".foo", FNM_PERIOD));
+
+ /* FNM_PATHNAME | FNM_PERIOD */
+ int_check(1, fnmatch("*/?foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+ int_check(1, fnmatch("*/[.]foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+ int_check(1, fnmatch("*/*.c", "sub/.foo.c", FNM_PERIOD|FNM_PATHNAME));
+ int_check(1, fnmatch("*/*", "sub/.foo.c", FNM_PERIOD|FNM_PATHNAME));
+ int_check(0, fnmatch("*/*.c", "sub/foo..c", FNM_PERIOD|FNM_PATHNAME));
+ int_check(1, fnmatch("*/*.foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+
+ /* escapes in brackets ~ posix */
+ int_check(0, fnmatch("[A\\]]", "\\]", FNM_NOESCAPE));
+ int_check(0, fnmatch("[a\\-x]", "_", FNM_NOESCAPE));
+end:;
+}
+
+/*
+ * GNU syntax.
+ */
+
+static void test_fnmatch_gnu(void *p)
+{
+ /* FNM_CASEFOLD */
+ int_check(1, fnmatch("aaAA", "AaAa", 0));
+ int_check(1, fnmatch("[b][b][B][B][a-c][A-C][a-c][A-C]", "bBbBbbBB", 0));
+ int_check(0, fnmatch("aaAA", "AaAa", FNM_CASEFOLD));
+ int_check(0, fnmatch("[b][b][B][B][a-c][A-C][a-c][A-C]", "bBbBbbBB", FNM_CASEFOLD));
+
+ /* FNM_LEADING_DIR */
+ int_check(0, fnmatch("a", "a", FNM_LEADING_DIR|FNM_PATHNAME));
+ int_check(0, fnmatch("a", "a/b", FNM_LEADING_DIR|FNM_PATHNAME));
+ int_check(0, fnmatch("a/b", "a/b/c/d", FNM_LEADING_DIR|FNM_PATHNAME));
+ int_check(0, fnmatch("a/*/*", "a/b/c/d", FNM_LEADING_DIR|FNM_PATHNAME));
+ int_check(0, fnmatch("*", "/a", FNM_LEADING_DIR|FNM_PATHNAME));
+ /* seems wrong to allow it */
+ int_check(0, fnmatch("a", "a/b", FNM_LEADING_DIR));
+
+ /* escapes in brackets ~ gnu */
+ int_check(0, fnmatch("[A\\]][A\\]]", "]A", 0));
+ int_check(1, fnmatch("[a\\-x]", "_", 0));
+ int_check(0, fnmatch("[\\!x]", "!", 0));
+ int_check(1, fnmatch("[\\!x]", "\\", 0));
+ int_check(0, fnmatch("[\\[:alnum:]", ":", 0));
+end:;
+}
+
+/*
+ * DoS possibilities.
+ */
+
+static void test_fnmatch_weird(void *p)
+{
+ char pat[4096];
+ char str[4096];
+ int i;
+
+ memset(pat, 0, sizeof(pat));
+ memset(str, 0, sizeof(str));
+
+ memset(pat, '*', 1500);
+ memset(str, 'a', 1500);
+ int_check(0, fnmatch(pat, str, 0));
+
+ pat[10] = 'a';
+ pat[1200] = 'b';
+ int_check(0, fnmatch(pat, "ab", 0));
+
+ for (i = 0; i < 1200; i++) {
+ char c = 'a' + (i%26);
+ pat[i*2] = c;
+ pat[i*2+1] = '*';
+ str[i*2] = c;
+ str[i*2+1] = c;
+ }
+ pat[i*2] = 0;
+ str[i*2] = 0;
+ int_check(0, fnmatch(pat, str, 0));
+
+ for (i = 0; i < 2000; i++) {
+ pat[i*2] = '*';
+ pat[i*2+1] = '?';
+ str[i*2] = 'a';
+ str[i*2+1] = 'b';
+ }
+ str[i*2] = 0;
+ pat[i*2] = 0;
+ int_check(0, fnmatch(pat, str, 0));
+ pat[i*2] = 'a';
+ pat[i*2 + 1] = 0;
+ int_check(1, fnmatch(pat, str, 0));
+ pat[i*2] = 'b';
+ int_check(0, fnmatch(pat, str, 0));
+ pat[i*2] = '*';
+ pat[3] = 'x';
+ str[2000] = 'x';
+ int_check(0, fnmatch(pat, str, 0));
+
+ memset(pat, '?', sizeof(pat));
+ memset(str, 'x', sizeof(str));
+ str[4000] = 0;
+ pat[2000] = 0;
+ pat[0] = '*';
+ int_check(0, fnmatch(pat, str, 0));
+end:;
+}
+
+struct testcase_t fnmatch_tests[] = {
+ { "posix", test_fnmatch_posix },
+ { "gnu", test_fnmatch_gnu },
+ { "weird", test_fnmatch_weird },
+ END_OF_TESTCASES
+};
+
--- /dev/null
+/*
+ * fnmatch.c
+ *
+ * Copyright (c) 2012 Marko Kreen
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Differences from POSIX:
+ * - ^ can be used in place of !
+ * - \ is escape in bracket expression, unless FNM_NOESCAPE is given.
+ * - FNM_CASEFOLD
+ * - FNM_LEADING_DIR
+ */
+
+#include <usual/fnmatch.h>
+#include <usual/wchar.h>
+
+#include <string.h>
+
+#ifdef NEED_USUAL_FNMATCH
+
+/* compare chars with case folding */
+static inline bool cmp_fold(wchar_t c1, wchar_t c2, int flags)
+{
+ if (c1 == c2)
+ return true;
+ if (flags & FNM_CASEFOLD) {
+ if (iswupper(c1) && iswlower(c2))
+ return c1 == (wchar_t)towupper(c2);
+ else if (iswlower(c1) && iswupper(c2))
+ return c1 == (wchar_t)towlower(c2);
+ }
+ return false;
+}
+
+/* compare char to range with case folding */
+static inline bool range_fold(wchar_t c, wchar_t r1, wchar_t r2, int flags)
+{
+ if (c >= r1 && c <= r2)
+ return true;
+ if (flags & FNM_CASEFOLD) {
+ /* convert only if it makes sense */
+ if (iswupper(c) && iswlower(r1) && iswlower(r2)) {
+ c = towlower(c);
+ if (c >= r1 && c <= r2)
+ return true;
+ } else if (iswlower(c) && iswupper(r1) && iswupper(r2)) {
+ c = towupper(c);
+ if (c >= r1 && c <= r2)
+ return true;
+ }
+ }
+ return false;
+}
+
+/* match bracket expression */
+static const wchar_t *match_class(const wchar_t *pat, wchar_t c, int flags)
+{
+ const wchar_t *p = pat;
+ const wchar_t *start;
+ bool neg = false;
+ bool match = false;
+ bool fallback_ok = true;
+ const wchar_t *n1, *n2;
+ wctype_t wct;
+
+ /* negation */
+ if (*p == '!' || *p == '^') {
+ neg = true;
+ p++;
+ }
+ start = p;
+loop:
+ /* named class, equivalence class or collating symbol */
+ if (p[0] == '[' && (p[1] == ':' || p[1] == '.' || p[1] == '=')) {
+ n1 = p + 2;
+ n2 = wcschr(n1, p[1]);
+ if (!n2 || n2[1] != ']')
+ goto parse_fail;
+ if (p[1] != ':')
+ return NULL;
+ p = n2 + 2;
+ wct = wctype_wcsn(n1, n2-n1);
+ if (wct == (wctype_t)0)
+ return NULL;
+ if (iswctype(c, wct))
+ match = true;
+ fallback_ok = false;
+ /* skip rest */
+ goto loop;
+ }
+parse_fail:
+
+ /* unexpected pattern end */
+ if (p[0] == '\0') {
+ /* only open bracket exists, take it as literal */
+ if (fallback_ok && c == '[')
+ return pat - 1;
+ return NULL;
+ }
+
+ /* closing bracket */
+ if (p[0] == ']' && p != start)
+ return (match ^ neg) ? p : NULL;
+
+ /* escape next char */
+ if (p[0] == '\\' && !(flags & FNM_NOESCAPE)) {
+ if (p[1] == '\0')
+ return NULL;
+ p++;
+ }
+
+ /* its either simple range or char */
+ if (p[1] == '-' && p[2] != ']' && p[2] != '\0') {
+ wchar_t r1 = p[0];
+ wchar_t r2 = p[2];
+ if (r2 == '\\' && !(flags & FNM_NOESCAPE)) {
+ p++;
+ r2 = p[2];
+ if (r2 == '\0')
+ return NULL;
+ }
+ if (range_fold(c, r1, r2, flags))
+ match = true;
+ p += 3;
+ } else {
+ if (cmp_fold(c, p[0], flags))
+ match = true;
+ p++;
+ }
+ goto loop;
+}
+
+/*
+ * FNM_PATHNAME disallows wildcard match for '/',
+ * FNM_PERIOD disallows wildcard match for leading '.',
+ * check for string end also.
+ */
+static bool disallow_wildcard(const wchar_t *s, const wchar_t *str, int flags)
+{
+ if (*s == '\0')
+ return true;
+ if (*s == '/')
+ return (flags & FNM_PATHNAME);
+ if (*s == '.' && (flags & FNM_PERIOD)) {
+ if (s == str)
+ return true;
+ if (s[-1] == '/' && (flags & FNM_PATHNAME))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Non-recursive fnmatch(), based on globmatch() by <linux@horizon.com>
+ */
+static int wfnmatch(const wchar_t *pat, const wchar_t *str, int flags)
+{
+ const wchar_t *p = pat;
+ const wchar_t *s = str;
+ const wchar_t *retry_p = NULL;
+ const wchar_t *skip_s = NULL;
+loop:
+ switch (*p) {
+ case '*':
+ /* match any number of chars from this position on */
+ retry_p = p + 1;
+ skip_s = s;
+ /* dot after '*' must not match leading dot */
+ if (p[1] == '.' && disallow_wildcard(s, str, flags))
+ return FNM_NOMATCH;
+ break;
+ case '?':
+ /* match any char */
+ if (disallow_wildcard(s, str, flags))
+ goto nomatch_retry;
+ s++;
+ break;
+ case '[':
+ /* match character class */
+ if (disallow_wildcard(s, str, flags))
+ goto nomatch_retry;
+ p = match_class(p + 1, *s, flags);
+ if (p == NULL)
+ goto nomatch_retry;
+ s++;
+ break;
+ case '\\':
+ /* escape next char */
+ if (!(flags & FNM_NOESCAPE)) {
+ p++;
+ if (*p == '\0')
+ return FNM_NOMATCH;
+ }
+ default:
+ /* match single char */
+ if (*s == '/' && *p == '\0' && (flags & FNM_LEADING_DIR))
+ return 0;
+ if (!cmp_fold(*p, *s, flags))
+ goto nomatch_retry;
+ if (*s == '\0')
+ return 0;
+ s++;
+ }
+ p++;
+ goto loop;
+
+nomatch_retry:
+ /* eat chars with '*', if possible */
+ if (retry_p == NULL || *s == '\0')
+ return FNM_NOMATCH;
+ s = skip_s++;
+ p = retry_p;
+ if (*s == '\0')
+ return (*p == '\0') ? 0 : FNM_NOMATCH;
+ if (disallow_wildcard(s, str, flags))
+ return FNM_NOMATCH;
+ s++;
+ goto loop;
+}
+
+/*
+ * Convert locale-specific encoding to wchar_t string
+ */
+int fnmatch(const char *pat, const char *str, int flags)
+{
+ const wchar_t *wpat, *wstr;
+ wchar_t pbuf[128];
+ wchar_t sbuf[128];
+ int plen = strlen(pat);
+ int slen = strlen(str);
+ int res;
+
+ /* convert encoding */
+ wpat = mbstr_decode(pat, plen, NULL, pbuf, sizeof(pbuf) / sizeof(wchar_t), false);
+ if (!wpat)
+ return (errno == EILSEQ) ? FNM_NOMATCH : -1;
+ wstr = mbstr_decode(str, slen, NULL, sbuf, sizeof(sbuf) / sizeof(wchar_t), true);
+ if (!wstr)
+ return -1;
+
+ /* run actual fnmatch */
+ res = wfnmatch(wpat, wstr, flags);
+
+ /* free buffers */
+ if (wstr != sbuf)
+ free(wstr);
+ if (wpat != pbuf)
+ free(wpat);
+
+ return res;
+}
+
+#endif
+