X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Ffnmatch.c;h=f76a1c62d396ad33b35cb6e9e2ffdab0e008ebc6;hb=9d0febc9a64a5bfb0fcfc3a88de4757f6c1ff090;hp=06576cb56124cc733262223b7eaaa68e74fdbccd;hpb=6ec06edbbfeed89767a720694ca7e7a091e18e66;p=~andy%2Fgtk diff --git a/gtk/fnmatch.c b/gtk/fnmatch.c index 06576cb56..f76a1c62d 100644 --- a/gtk/fnmatch.c +++ b/gtk/fnmatch.c @@ -11,9 +11,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -23,205 +21,342 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ -#ifdef HAVE_CONFIG_H +/* + * Stripped down, converted to UTF-8 and test cases added + * + * Owen Taylor, 13 December 2002; + */ + #include "config.h" -#endif +#include -#include +#include -/* Added for GTK. We need to make sure that all constants are defined - * to properly compile this file */ +/* We need to make sure that all constants are defined + * to properly compile this file + */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "fnmatch.h" -/* We need glib.h for G_DIR_SEPARATOR and G_OS_WIN32 */ -#include +static gunichar +get_char (const char **str) +{ + gunichar c = g_utf8_get_char (*str); + *str = g_utf8_next_char (*str); +#ifdef G_PLATFORM_WIN32 + c = g_unichar_tolower (c); +#endif -/* Comment out all this code if we are using the GNU C Library, and are not - actually compiling the library itself. This code is part of the GNU C - Library, but also included in many other GNU distributions. Compiling - and linking in this code is a waste when using the GNU C library - (especially if it is a shared library). Rather than having every GNU - program understand `configure --with-gnu-libc' and omit the object files, - it is simpler to just do this in the source for each such file. */ + return c; +} -#if defined (_LIBC) || !defined (__GNU_LIBRARY__) +#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) +#define DO_ESCAPE 0 +#else +#define DO_ESCAPE 1 +#endif -#if !defined(__GNU_LIBRARY__) && !defined(STDC_HEADERS) -extern int errno; -#endif +static gunichar +get_unescaped_char (const char **str, + gboolean *was_escaped) +{ + gunichar c = get_char (str); + + *was_escaped = DO_ESCAPE && c == '\\'; + if (*was_escaped) + c = get_char (str); + + return c; +} /* Match STRING against the filename pattern PATTERN, returning zero if it matches, nonzero if not. */ -int -fnmatch (pattern, string, flags) - const char *pattern; - const char *string; - int flags; -{ - register const char *p = pattern, *n = string; - register char c; - -/* Note that this evalutes C many times. */ -#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) -#define FOLD(c) ((flags & FNM_CASEFOLD) && isupper ((unsigned char )(c)) ? tolower ((unsigned char)(c)) : (c)) -#else -#define FOLD(c) (tolower ((unsigned char)(c))) -#endif - while ((c = *p++) != '\0') +static gboolean +gtk_fnmatch_intern (const char *pattern, + const char *string, + gboolean component_start, + gboolean no_leading_period) +{ + const char *p = pattern, *n = string; + + while (*p) { - c = FOLD (c); - + const char *last_n = n; + + gunichar c = get_char (&p); + gunichar nc = get_char (&n); + switch (c) { - case '?': - if (*n == '\0') - return FNM_NOMATCH; - else if ((flags & FNM_FILE_NAME) && *n == G_DIR_SEPARATOR) - return FNM_NOMATCH; - else if ((flags & FNM_PERIOD) && *n == '.' && - (n == string || ((flags & FNM_FILE_NAME) && n[-1] == G_DIR_SEPARATOR))) - return FNM_NOMATCH; + case '?': + if (nc == '\0') + return FALSE; + else if (nc == G_DIR_SEPARATOR) + return FALSE; + else if (nc == '.' && component_start && no_leading_period) + return FALSE; break; -#ifndef G_OS_WIN32 case '\\': - if (!(flags & FNM_NOESCAPE)) - { - c = *p++; - c = FOLD (c); - } - if (FOLD (*n) != c) - return FNM_NOMATCH; + if (DO_ESCAPE) + c = get_char (&p); + if (nc != c) + return FALSE; break; -#endif case '*': - if ((flags & FNM_PERIOD) && *n == '.' && - (n == string || ((flags & FNM_FILE_NAME) && n[-1] == G_DIR_SEPARATOR))) - return FNM_NOMATCH; + if (nc == '.' && component_start && no_leading_period) + return FALSE; + + { + const char *last_p = p; + + for (last_p = p, c = get_char (&p); + c == '?' || c == '*'; + last_p = p, c = get_char (&p)) + { + if (c == '?') + { + if (nc == '\0') + return FALSE; + else if (nc == G_DIR_SEPARATOR) + return FALSE; + else + { + last_n = n; nc = get_char (&n); + } + } + } - for (c = *p++; c == '?' || c == '*'; c = *p++, ++n) - if (((flags & FNM_FILE_NAME) && *n == G_DIR_SEPARATOR) || - (c == '?' && *n == '\0')) - return FNM_NOMATCH; + /* If the pattern ends with wildcards, we have a + * guaranteed match unless there is a dir separator + * in the remainder of the string. + */ + if (c == '\0') + { + if (strchr (last_n, G_DIR_SEPARATOR) != NULL) + return FALSE; + else + return TRUE; + } - if (c == '\0') - return 0; + if (DO_ESCAPE && c == '\\') + c = get_char (&p); - { -#ifndef G_OS_WIN32 - char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; -#else - char c1 = c; -#endif - c1 = FOLD (c1); - for (--p; *n != '\0'; ++n) - if ((c == '[' || FOLD (*n) == c1) && - fnmatch (p, n, flags & ~FNM_PERIOD) == 0) - return 0; - return FNM_NOMATCH; + for (p = last_p; nc != '\0';) + { + if ((c == '[' || nc == c) && + gtk_fnmatch_intern (p, last_n, component_start, no_leading_period)) + return TRUE; + + component_start = (nc == G_DIR_SEPARATOR); + last_n = n; + nc = get_char (&n); + } + + return FALSE; } case '[': { /* Nonzero if the sense of the character class is inverted. */ - register int not; + gboolean not; + gboolean was_escaped; - if (*n == '\0') - return FNM_NOMATCH; + if (nc == '\0' || nc == G_DIR_SEPARATOR) + return FALSE; - if ((flags & FNM_PERIOD) && *n == '.' && - (n == string || ((flags & FNM_FILE_NAME) && n[-1] == G_DIR_SEPARATOR))) - return FNM_NOMATCH; + if (nc == '.' && component_start && no_leading_period) + return FALSE; not = (*p == '!' || *p == '^'); if (not) ++p; - c = *p++; + c = get_unescaped_char (&p, &was_escaped); for (;;) { - register char cstart = c, cend = c; -#ifndef G_OS_WIN32 - if (!(flags & FNM_NOESCAPE) && c == '\\') - cstart = cend = *p++; -#endif - cstart = cend = FOLD (cstart); - + register gunichar cstart = c, cend = c; if (c == '\0') /* [ (unterminated) loses. */ - return FNM_NOMATCH; + return FALSE; - c = *p++; - c = FOLD (c); - - if ((flags & FNM_FILE_NAME) && c == G_DIR_SEPARATOR) - /* [/] can never match. */ - return FNM_NOMATCH; - - if (c == '-' && *p != ']') + c = get_unescaped_char (&p, &was_escaped); + + if (!was_escaped && c == '-' && *p != ']') { - cend = *p++; -#ifndef G_OS_WIN32 - if (!(flags & FNM_NOESCAPE) && cend == '\\') - cend = *p++; -#endif + cend = get_unescaped_char (&p, &was_escaped); if (cend == '\0') - return FNM_NOMATCH; - cend = FOLD (cend); + return FALSE; - c = *p++; + c = get_char (&p); } - if (FOLD (*n) >= cstart && FOLD (*n) <= cend) + if (nc >= cstart && nc <= cend) goto matched; - if (c == ']') + if (!was_escaped && c == ']') break; } if (!not) - return FNM_NOMATCH; + return FALSE; break; matched:; /* Skip the rest of the [...] that already matched. */ - while (c != ']') + /* XXX 1003.2d11 is unclear if was_escaped is right. */ + while (was_escaped || c != ']') { if (c == '\0') /* [... (unterminated) loses. */ - return FNM_NOMATCH; + return FALSE; - c = *p++; -#ifndef G_OS_WIN32 - if (!(flags & FNM_NOESCAPE) && c == '\\') - /* XXX 1003.2d11 is unclear if this is right. */ - ++p; -#endif + c = get_unescaped_char (&p, &was_escaped); } if (not) - return FNM_NOMATCH; + return FALSE; } break; default: - if (c != FOLD (*n)) - return FNM_NOMATCH; + if (c != nc) + return FALSE; } - ++n; + component_start = (nc == G_DIR_SEPARATOR); } if (*n == '\0') - return 0; + return TRUE; + + return FALSE; +} + +/* Match STRING against the filename pattern PATTERN, returning zero if + * it matches, nonzero if not. + * + * GTK+ used to use a old version of GNU fnmatch() that was buggy + * in various ways and didn't handle UTF-8. The following is + * converted to UTF-8. To simplify the process of making it + * correct, this is special-cased to the combinations of flags + * that gtkfilesel.c uses. + * + * FNM_FILE_NAME - always set + * FNM_LEADING_DIR - never set + * FNM_NOESCAPE - set only on windows + * FNM_CASEFOLD - set only on windows + */ +gboolean +_gtk_fnmatch (const char *pattern, + const char *string, + gboolean no_leading_period) +{ + return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period); +} + +#undef FNMATCH_TEST_CASES +#ifdef FNMATCH_TEST_CASES - if ((flags & FNM_LEADING_DIR) && *n == G_DIR_SEPARATOR) - /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ - return 0; +#define TEST(pat, str, no_leading_period, result) \ + g_assert (_gtk_fnmatch ((pat), (str), (no_leading_period)) == result) - return FNM_NOMATCH; +int main (int argc, char **argv) +{ + TEST ("[a-]", "-", TRUE, TRUE); + + TEST ("a", "a", TRUE, TRUE); + TEST ("a", "b", TRUE, FALSE); + + /* Test what ? matches */ + TEST ("?", "a", TRUE, TRUE); + TEST ("?", ".", TRUE, FALSE); + TEST ("a?", "a.", TRUE, TRUE); + TEST ("a/?", "a/b", TRUE, TRUE); + TEST ("a/?", "a/.", TRUE, FALSE); + TEST ("?", "/", TRUE, FALSE); + + /* Test what * matches */ + TEST ("*", "a", TRUE, TRUE); + TEST ("*", ".", TRUE, FALSE); + TEST ("a*", "a.", TRUE, TRUE); + TEST ("a/*", "a/b", TRUE, TRUE); + TEST ("a/*", "a/.", TRUE, FALSE); + TEST ("*", "/", TRUE, FALSE); + + /* Range tests */ + TEST ("[ab]", "a", TRUE, TRUE); + TEST ("[ab]", "c", TRUE, FALSE); + TEST ("[^ab]", "a", TRUE, FALSE); + TEST ("[!ab]", "a", TRUE, FALSE); + TEST ("[^ab]", "c", TRUE, TRUE); + TEST ("[!ab]", "c", TRUE, TRUE); + TEST ("[a-c]", "b", TRUE, TRUE); + TEST ("[a-c]", "d", TRUE, FALSE); + TEST ("[a-]", "-", TRUE, TRUE); + TEST ("[]]", "]", TRUE, TRUE); + TEST ("[^]]", "a", TRUE, TRUE); + TEST ("[!]]", "a", TRUE, TRUE); + + /* Various unclosed ranges */ + TEST ("[ab", "a", TRUE, FALSE); + TEST ("[a-", "a", TRUE, FALSE); + TEST ("[ab", "c", TRUE, FALSE); + TEST ("[a-", "c", TRUE, FALSE); + TEST ("[^]", "a", TRUE, FALSE); + + /* Ranges and special no-wildcard matches */ + TEST ("[.]", ".", TRUE, FALSE); + TEST ("a[.]", "a.", TRUE, TRUE); + TEST ("a/[.]", "a/.", TRUE, FALSE); + TEST ("[/]", "/", TRUE, FALSE); + TEST ("[^/]", "a", TRUE, TRUE); + + /* Basic tests of * (and combinations of * and ?) */ + TEST ("a*b", "ab", TRUE, TRUE); + TEST ("a*b", "axb", TRUE, TRUE); + TEST ("a*b", "axxb", TRUE, TRUE); + TEST ("a**b", "ab", TRUE, TRUE); + TEST ("a**b", "axb", TRUE, TRUE); + TEST ("a**b", "axxb", TRUE, TRUE); + TEST ("a*?*b", "ab", TRUE, FALSE); + TEST ("a*?*b", "axb", TRUE, TRUE); + TEST ("a*?*b", "axxb", TRUE, TRUE); + + /* Test of *[range] */ + TEST ("a*[cd]", "ac", TRUE, TRUE); + TEST ("a*[cd]", "axc", TRUE, TRUE); + TEST ("a*[cd]", "axx", TRUE, FALSE); + + TEST ("a/[.]", "a/.", TRUE, FALSE); + TEST ("a*[.]", "a/.", TRUE, FALSE); + + /* Test of UTF-8 */ + + TEST ("ä", "ä", TRUE, TRUE); /* TEST ("ä", "ä", TRUE); */ + TEST ("?", "ä", TRUE, TRUE); /* TEST ("?", "ä", TRUE); */ + TEST ("*ö", "äö", TRUE, TRUE); /* TEST ("*ö", "äö", TRUE); */ + TEST ("*ö", "ääö", TRUE, TRUE); /* TEST ("*ö", "ääö", TRUE); */ + TEST ("[ä]", "ä", TRUE, TRUE); /* TEST ("[ä]", "ä", TRUE); */ + TEST ("[ä-ö]", "é", TRUE, TRUE); /* TEST ("[ä-ö]", "é", TRUE); */ + TEST ("[ä-ö]", "a", TRUE, FALSE); /* TEST ("[ä-ö]", "a", FALSE); */ + +#ifdef DO_ESCAPE + /* Tests of escaping */ + TEST ("\\\\", "\\", TRUE, TRUE); + TEST ("\\?", "?", TRUE, TRUE); + TEST ("\\?", "a", TRUE, FALSE); + TEST ("\\*", "*", TRUE, TRUE); + TEST ("\\*", "a", TRUE, FALSE); + TEST ("\\[a-b]", "[a-b]", TRUE, TRUE); + TEST ("[\\\\]", "\\", TRUE, TRUE); + TEST ("[\\^a]", "a", TRUE, TRUE); + TEST ("[a\\-c]", "b", TRUE, FALSE); + TEST ("[a\\-c]", "-", TRUE, TRUE); + TEST ("[a\\]", "a", TRUE, FALSE); +#endif /* DO_ESCAPE */ + + return 0; } -#endif /* _LIBC or not __GNU_LIBRARY__. */ +#endif /* FNMATCH_TEST_CASES */