/* * 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 #include #include #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 */ 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