From bb53cf53e7c683c667a9e47324360d2414fcf0bc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 3 Jun 2021 23:55:30 -0400 Subject: [PATCH 1/2] fnmatch: Support case-folding This will be used in GtkFileFilter in the future. Update all callers. --- gtk/fnmatch.c | 51 ++++++++++++++++++++++++--------------------- gtk/gtkfilefilter.c | 2 +- gtk/gtkprivate.h | 3 ++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/gtk/fnmatch.c b/gtk/fnmatch.c index fada2b7229..ba661afbbe 100644 --- a/gtk/fnmatch.c +++ b/gtk/fnmatch.c @@ -36,14 +36,14 @@ #include static gunichar -get_char (const char **str) +get_char (const char **str, + gboolean casefold) { gunichar c = g_utf8_get_char (*str); *str = g_utf8_next_char (*str); -#ifdef G_PLATFORM_WIN32 - c = g_unichar_tolower (c); -#endif + if (casefold) + c = g_unichar_tolower (c); return c; } @@ -56,13 +56,14 @@ get_char (const char **str) static gunichar get_unescaped_char (const char **str, - gboolean *was_escaped) + gboolean *was_escaped, + gboolean casefold) { - gunichar c = get_char (str); + gunichar c = get_char (str, casefold); *was_escaped = DO_ESCAPE && c == '\\'; if (*was_escaped) - c = get_char (str); + c = get_char (str, casefold); return c; } @@ -74,7 +75,8 @@ static gboolean gtk_fnmatch_intern (const char *pattern, const char *string, gboolean component_start, - gboolean no_leading_period) + gboolean no_leading_period, + gboolean casefold) { const char *p = pattern, *n = string; @@ -82,8 +84,8 @@ gtk_fnmatch_intern (const char *pattern, { const char *last_n = n; - gunichar c = get_char (&p); - gunichar nc = get_char (&n); + gunichar c = get_char (&p, casefold); + gunichar nc = get_char (&n, casefold); switch (c) { @@ -97,7 +99,7 @@ gtk_fnmatch_intern (const char *pattern, break; case '\\': if (DO_ESCAPE) - c = get_char (&p); + c = get_char (&p, casefold); if (nc != c) return FALSE; break; @@ -108,9 +110,9 @@ gtk_fnmatch_intern (const char *pattern, { const char *last_p; - for (last_p = p, c = get_char (&p); + for (last_p = p, c = get_char (&p, casefold); c == '?' || c == '*'; - last_p = p, c = get_char (&p)) + last_p = p, c = get_char (&p, casefold)) { if (c == '?') { @@ -120,7 +122,7 @@ gtk_fnmatch_intern (const char *pattern, return FALSE; else { - last_n = n; nc = get_char (&n); + last_n = n; nc = get_char (&n, casefold); } } } @@ -138,17 +140,17 @@ gtk_fnmatch_intern (const char *pattern, } if (DO_ESCAPE && c == '\\') - c = get_char (&p); + c = get_char (&p, casefold); for (p = last_p; nc != '\0';) { if ((c == '[' || nc == c) && - gtk_fnmatch_intern (p, last_n, component_start, no_leading_period)) + gtk_fnmatch_intern (p, last_n, component_start, no_leading_period, casefold)) return TRUE; component_start = (nc == G_DIR_SEPARATOR); last_n = n; - nc = get_char (&n); + nc = get_char (&n, casefold); } return FALSE; @@ -170,7 +172,7 @@ gtk_fnmatch_intern (const char *pattern, if (not) ++p; - c = get_unescaped_char (&p, &was_escaped); + c = get_unescaped_char (&p, &was_escaped, casefold); for (;;) { register gunichar cstart = c, cend = c; @@ -178,15 +180,15 @@ gtk_fnmatch_intern (const char *pattern, /* [ (unterminated) loses. */ return FALSE; - c = get_unescaped_char (&p, &was_escaped); + c = get_unescaped_char (&p, &was_escaped, casefold); if (!was_escaped && c == '-' && *p != ']') { - cend = get_unescaped_char (&p, &was_escaped); + cend = get_unescaped_char (&p, &was_escaped, casefold); if (cend == '\0') return FALSE; - c = get_char (&p); + c = get_char (&p, casefold); } if (nc >= cstart && nc <= cend) @@ -208,7 +210,7 @@ gtk_fnmatch_intern (const char *pattern, /* [... (unterminated) loses. */ return FALSE; - c = get_unescaped_char (&p, &was_escaped); + c = get_unescaped_char (&p, &was_escaped, casefold); } if (not) return FALSE; @@ -246,9 +248,10 @@ gtk_fnmatch_intern (const char *pattern, gboolean _gtk_fnmatch (const char *pattern, const char *string, - gboolean no_leading_period) + gboolean no_leading_period, + gboolean casefold) { - return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period); + return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period, casefold); } #undef FNMATCH_TEST_CASES diff --git a/gtk/gtkfilefilter.c b/gtk/gtkfilefilter.c index ed933fd030..99a82d6845 100644 --- a/gtk/gtkfilefilter.c +++ b/gtk/gtkfilefilter.c @@ -804,7 +804,7 @@ gtk_file_filter_match (GtkFilter *filter, display_name = g_file_info_get_display_name (info); if (display_name) { - if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE)) + if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE, FALSE)) return TRUE; } } diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h index ff3e188eef..d24c272065 100644 --- a/gtk/gtkprivate.h +++ b/gtk/gtkprivate.h @@ -61,7 +61,8 @@ const char * _gtk_get_data_prefix (void); gboolean _gtk_fnmatch (const char *pattern, const char *string, - gboolean no_leading_period); + gboolean no_leading_period, + gboolean casefold); char * _gtk_get_lc_ctype (void); From 31407d0a4cc86b2507bc50684439b14e97b35e5e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 3 Jun 2021 23:33:33 -0400 Subject: [PATCH 2/2] Move fnmatch testcases to the testsuite We have a well-working way to test internal apis now, lets use it for these tests. --- gtk/fnmatch.c | 106 ----------------------------- testsuite/gtk/fnmatch.c | 140 ++++++++++++++++++++++++++++++++++++++ testsuite/gtk/meson.build | 1 + 3 files changed, 141 insertions(+), 106 deletions(-) create mode 100644 testsuite/gtk/fnmatch.c diff --git a/gtk/fnmatch.c b/gtk/fnmatch.c index ba661afbbe..0ad1cd8992 100644 --- a/gtk/fnmatch.c +++ b/gtk/fnmatch.c @@ -253,109 +253,3 @@ _gtk_fnmatch (const char *pattern, { return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period, casefold); } - -#undef FNMATCH_TEST_CASES -#ifdef FNMATCH_TEST_CASES - -#define TEST(pat, str, no_leading_period, result) \ - g_assert (_gtk_fnmatch ((pat), (str), (no_leading_period)) == result) - -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 /* FNMATCH_TEST_CASES */ diff --git a/testsuite/gtk/fnmatch.c b/testsuite/gtk/fnmatch.c new file mode 100644 index 0000000000..a614c1f99b --- /dev/null +++ b/testsuite/gtk/fnmatch.c @@ -0,0 +1,140 @@ +#include +#include "gtk/gtkprivate.h" + +#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) +#define DO_ESCAPE 0 +#else +#define DO_ESCAPE 1 +#endif + +typedef struct { + const char *pat; + const char *str; + gboolean no_leading_period; + gboolean ci; + gboolean result; +} TestCase; + +static TestCase tests[] = { + { "[a-]", "-", TRUE, FALSE, TRUE }, + + { "a", "a", TRUE, FALSE, TRUE }, + { "a", "b", TRUE, FALSE, FALSE }, + + /* Test what ? matches */ + { "?", "a", TRUE, FALSE, TRUE }, + { "?", ".", TRUE, FALSE, FALSE }, + { "a?", "a.", TRUE, FALSE, TRUE }, + { "a/?", "a/b", TRUE, FALSE, TRUE }, + { "a/?", "a/.", TRUE, FALSE, FALSE }, + { "?", "/", TRUE, FALSE, FALSE }, + + /* Test what * matches */ + { "*", "a", TRUE, FALSE, TRUE }, + { "*", ".", TRUE, FALSE, FALSE }, + { "a*", "a.", TRUE, FALSE, TRUE }, + { "a/*", "a/b", TRUE, FALSE, TRUE }, + { "a/*", "a/.", TRUE, FALSE, FALSE }, + { "*", "/", TRUE, FALSE, FALSE }, + + /* Range tests */ + { "[ab]", "a", TRUE, FALSE, TRUE }, + { "[ab]", "c", TRUE, FALSE, FALSE }, + { "[^ab]", "a", TRUE, FALSE, FALSE }, + { "[!ab]", "a", TRUE, FALSE, FALSE }, + { "[^ab]", "c", TRUE, FALSE, TRUE }, + { "[!ab]", "c", TRUE, FALSE, TRUE }, + { "[a-c]", "b", TRUE, FALSE, TRUE }, + { "[a-c]", "d", TRUE, FALSE, FALSE }, + { "[a-]", "-", TRUE, FALSE, TRUE }, + { "[]]", "]", TRUE, FALSE, TRUE }, + { "[^]]", "a", TRUE, FALSE, TRUE }, + { "[!]]", "a", TRUE, FALSE, TRUE }, + + /* Various unclosed ranges */ + { "[ab", "a", TRUE, FALSE, FALSE }, + { "[a-", "a", TRUE, FALSE, FALSE }, + { "[ab", "c", TRUE, FALSE, FALSE }, + { "[a-", "c", TRUE, FALSE, FALSE }, + { "[^]", "a", TRUE, FALSE, FALSE }, + + /* Ranges and special no-wildcard matches */ + { "[.]", ".", TRUE, FALSE, FALSE }, + { "a[.]", "a.", TRUE, FALSE, TRUE }, + { "a/[.]", "a/.", TRUE, FALSE, FALSE }, + { "[/]", "/", TRUE, FALSE, FALSE }, + { "[^/]", "a", TRUE, FALSE, TRUE }, + + /* Basic tests of * (and combinations of * and ?) */ + { "a*b", "ab", TRUE, FALSE, TRUE }, + { "a*b", "axb", TRUE, FALSE, TRUE }, + { "a*b", "axxb", TRUE, FALSE, TRUE }, + { "a**b", "ab", TRUE, FALSE, TRUE }, + { "a**b", "axb", TRUE, FALSE, TRUE }, + { "a**b", "axxb", TRUE, FALSE, TRUE }, + { "a*?*b", "ab", TRUE, FALSE, FALSE }, + { "a*?*b", "axb", TRUE, FALSE, TRUE }, + { "a*?*b", "axxb", TRUE, FALSE, TRUE }, + + /* Test of *[range] */ + { "a*[cd]", "ac", TRUE, FALSE, TRUE }, + { "a*[cd]", "axc", TRUE, FALSE, TRUE }, + { "a*[cd]", "axx", TRUE, FALSE, FALSE }, + + { "a/[.]", "a/.", TRUE, FALSE, FALSE }, + { "a*[.]", "a/.", TRUE, FALSE, FALSE }, + + + /* Test of UTF-8 */ + + { "ä", "ä", TRUE, FALSE, TRUE }, + { "?", "ä", TRUE, FALSE, TRUE }, + { "*ö", "äö", TRUE, FALSE, TRUE }, + { "*ö", "ääö", TRUE, FALSE, TRUE }, + { "[ä]", "ä", TRUE, FALSE, TRUE }, + { "[ä-ö]", "é", TRUE, FALSE, TRUE }, + { "[ä-ö]", "a", TRUE, FALSE, FALSE }, + + /* ci patterns */ + { "*.txt", "a.TXT", TRUE, TRUE, TRUE }, + { "*.txt", "a.TxT", TRUE, TRUE, TRUE }, + { "*.txt", "a.txT", TRUE, TRUE, TRUE }, + { "*ö", "äÖ", TRUE, TRUE, TRUE }, + +#ifdef DO_ESCAPE + /* Tests of escaping */ + { "\\\\", "\\", TRUE, FALSE, TRUE }, + { "\\?", "?", TRUE, FALSE, TRUE }, + { "\\?", "a", TRUE, FALSE, FALSE }, + { "\\*", "*", TRUE, FALSE, TRUE }, + { "\\*", "a", TRUE, FALSE, FALSE }, + { "\\[a-b]", "[a-b]", TRUE, FALSE, TRUE }, + { "[\\\\]", "\\", TRUE, FALSE, TRUE }, + { "[\\^a]", "a", TRUE, FALSE, TRUE }, + { "[a\\-c]", "b", TRUE, FALSE, FALSE }, + { "[a\\-c]", "-", TRUE, FALSE, TRUE }, + { "[a\\]", "a", TRUE, FALSE, FALSE }, +#endif /* DO_ESCAPE */ +}; + +static void +test_fnmatch (gconstpointer data) +{ + const TestCase *test = data; + + g_assert_true (_gtk_fnmatch (test->pat, test->str, test->no_leading_period, test->ci) == test->result); +} + +int +main (int argc, char *argv[]) +{ + (g_test_init) (&argc, &argv, NULL); + + for (int i = 0; i < G_N_ELEMENTS (tests); i++) + { + char *path = g_strdup_printf ("/fnmatch/test%d", i); + g_test_add_data_func (path, &tests[i], test_fnmatch); + } + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 9fe45d339b..a0b53250ed 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -118,6 +118,7 @@ internal_tests = [ { 'name': 'rbtree' }, { 'name': 'timsort' }, { 'name': 'texthistory' }, + { 'name': 'fnmatch' }, ] # Tests that are expected to fail