From: Tim Kientzle Date: Fri, 20 Mar 2009 19:21:45 +0000 (-0400) Subject: Testing: test_pathmatch.c now has 100% test coverage of pathmatch.c X-Git-Tag: v2.7.0~110 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7825194f858182efa7b6e76d7a385aaec6db4541;p=thirdparty%2Flibarchive.git Testing: test_pathmatch.c now has 100% test coverage of pathmatch.c Allow '$' to force anchor at end iff PATHMATCH_NO_ANCHOR_END is in flags. Fix backslash escaping so that patterns such as "\\/./" or "/\\./" match an explicit "/./" but do not match plain "/", even though "/./" does ordinarily match "/". I think this is finally sufficient to match my long-standing desire to be able to extract "./foo/" by just asking for "foo". There's still an open question whether this can be extended to handle Windows-style \ path separators without causing conflicts with the use of \ as a quote character. I don't think it can. SVN-Revision: 824 --- diff --git a/cpio/pathmatch.c b/cpio/pathmatch.c index d33bd3438..40fa83604 100644 --- a/cpio/pathmatch.c +++ b/cpio/pathmatch.c @@ -101,11 +101,10 @@ pm_list(const char *start, const char *end, const char c, int flags) */ static const char * pm_slashskip(const char *s) { - while (*s == '.' || *s == '/') { - if (s[0] != '/' && s[1] != '/') - break; + while ((*s == '/') + || (s[0] == '.' && s[1] == '/') + || (s[0] == '.' && s[1] == '\0')) ++s; - } return (s); } @@ -130,8 +129,6 @@ pm(const char *p, const char *s, int flags) return (1); /* "dir" == "dir/" == "dir/." */ s = pm_slashskip(s); - if (s[0] == '.' && s[1] == '\0') - return (1); } return (*s == '\0'); break; @@ -176,19 +173,6 @@ pm(const char *p, const char *s, int flags) if (*p != *s) return (0); break; - default: - if (*p == *s) - break; - if ((*s == '\0') && (*p == '/')) { - p = pm_slashskip(p); - if (*p == '\0') - return (1); - if (p[0] == '.' && p[1] == '\0') - return (1); - return (0); - } - return (0); - break; case '\\': /* Trailing '\\' matches itself. */ if (p[1] == '\0') { @@ -200,19 +184,34 @@ pm(const char *p, const char *s, int flags) return (0); } break; - } - /* - * TODO: pattern of "\/\.\/" should not match plain "/", - * it should only match explicit "/./". - */ - if (*p == '/') + case '/': + if (*s != '/' && *s != '\0') + return (0); + /* Note: pattern "/\./" won't match "/"; + * pm_slashskip() correctly stops at backslash. */ p = pm_slashskip(p); - else - ++p; - if (*s == '/') s = pm_slashskip(s); - else - ++s; + if (*p == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)) + return (1); + --p; /* Counteract the increment below. */ + --s; + break; + case '$': + /* '$' is special only at end of pattern and only + * if PATHMATCH_NO_ANCHOR_END is specified. */ + if (p[1] == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)){ + /* "dir" == "dir/" == "dir/." */ + return (*pm_slashskip(s) == '\0'); + } + /* Otherwise, '$' is not special. */ + /* FALL THROUGH */ + default: + if (*p != *s) + return (0); + break; + } + ++p; + ++s; } } @@ -236,9 +235,9 @@ pathmatch(const char *p, const char *s, int flags) /* If start is unanchored, try to match start of each path element. */ if (flags & PATHMATCH_NO_ANCHOR_START) { - for ( ; p != NULL; p = strchr(p, '/')) { - if (*p == '/') - p++; + for ( ; s != NULL; s = strchr(s, '/')) { + if (*s == '/') + s++; if (pm(p, s, flags)) return (1); } diff --git a/cpio/pathmatch.h b/cpio/pathmatch.h index 990fa1fa1..fd2c2575c 100644 --- a/cpio/pathmatch.h +++ b/cpio/pathmatch.h @@ -29,9 +29,14 @@ #ifndef PATHMATCH_H #define PATHMATCH_H +/* Don't anchor at beginning unless the pattern starts with "^" */ #define PATHMATCH_NO_ANCHOR_START 1 +/* Don't anchor at end unless the pattern ends with "$" */ #define PATHMATCH_NO_ANCHOR_END 2 +/* Note that "^" and "$" are not special unless you set the corresponding + * flag above. */ + int pathmatch(const char *p, const char *s, int flags); #endif diff --git a/cpio/test/test_pathmatch.c b/cpio/test/test_pathmatch.c index 83528c01f..a596eda1d 100644 --- a/cpio/test/test_pathmatch.c +++ b/cpio/test/test_pathmatch.c @@ -38,10 +38,22 @@ __FBSDID("$FreeBSD$"); * * The specification in SUSv2 is a bit incomplete, I assume the following: * Trailing '-' in [...] is not special. + * + * TODO: Figure out if there's a good way to extend this to handle + * Windows paths that use '\' as a path separator. */ DEFINE_TEST(test_pathmatch) { + assertEqualInt(1, pathmatch("a/b/c", "a/b/c", 0)); + assertEqualInt(0, pathmatch("a/b/", "a/b/c", 0)); + assertEqualInt(0, pathmatch("a/b", "a/b/c", 0)); + assertEqualInt(0, pathmatch("a/b/c", "a/b/", 0)); + assertEqualInt(0, pathmatch("a/b/c", "a/b", 0)); + + /* Empty pattern only matches empty string. */ + assertEqualInt(1, pathmatch("","", 0)); + assertEqualInt(0, pathmatch("","a", 0)); assertEqualInt(1, pathmatch("*","", 0)); assertEqualInt(1, pathmatch("*","a", 0)); assertEqualInt(1, pathmatch("*","abcd", 0)); @@ -68,6 +80,8 @@ DEFINE_TEST(test_pathmatch) assertEqualInt(1, pathmatch("*a*", "defaaaaaaa", 0)); assertEqualInt(0, pathmatch("a*", "defghi", 0)); assertEqualInt(0, pathmatch("*a*", "defghi", 0)); + + /* Character classes */ assertEqualInt(1, pathmatch("abc[def", "abc[def", 0)); assertEqualInt(0, pathmatch("abc[def]", "abc[def", 0)); assertEqualInt(0, pathmatch("abc[def", "abcd", 0)); @@ -84,6 +98,7 @@ DEFINE_TEST(test_pathmatch) assertEqualInt(1, pathmatch("abc[d-f]", "abce", 0)); assertEqualInt(1, pathmatch("abc[d-f]", "abcf", 0)); assertEqualInt(0, pathmatch("abc[d-f]", "abcg", 0)); + assertEqualInt(0, pathmatch("abc[d-fh-k]", "abca", 0)); assertEqualInt(1, pathmatch("abc[d-fh-k]", "abcd", 0)); assertEqualInt(1, pathmatch("abc[d-fh-k]", "abce", 0)); assertEqualInt(1, pathmatch("abc[d-fh-k]", "abcf", 0)); @@ -95,6 +110,14 @@ DEFINE_TEST(test_pathmatch) assertEqualInt(0, pathmatch("abc[d-fh-k]", "abcl", 0)); assertEqualInt(0, pathmatch("abc[d-fh-k]", "abc-", 0)); + /* [] matches nothing, [!] is the same as ? */ + assertEqualInt(0, pathmatch("abc[]efg", "abcdefg", 0)); + assertEqualInt(0, pathmatch("abc[]efg", "abcqefg", 0)); + assertEqualInt(0, pathmatch("abc[]efg", "abcefg", 0)); + assertEqualInt(1, pathmatch("abc[!]efg", "abcdefg", 0)); + assertEqualInt(1, pathmatch("abc[!]efg", "abcqefg", 0)); + assertEqualInt(0, pathmatch("abc[!]efg", "abcefg", 0)); + /* I assume: Trailing '-' is non-special. */ assertEqualInt(0, pathmatch("abc[d-fh-]", "abcl", 0)); assertEqualInt(1, pathmatch("abc[d-fh-]", "abch", 0)); @@ -138,12 +161,23 @@ DEFINE_TEST(test_pathmatch) assertEqualInt(0, pathmatch("abc\\\\[def]", "abc[def]", 0)); assertEqualInt(0, pathmatch("abc\\\\[def]", "abc\\[def]", 0)); assertEqualInt(1, pathmatch("abc\\\\[def]", "abc\\d", 0)); + assertEqualInt(1, pathmatch("abcd\\", "abcd\\", 0)); + assertEqualInt(0, pathmatch("abcd\\", "abcd\\[", 0)); + assertEqualInt(0, pathmatch("abcd\\", "abcde", 0)); + assertEqualInt(0, pathmatch("abcd\\[", "abcd\\", 0)); /* * Because '.' and '/' have special meanings, we can * identify many equivalent paths even if they're expressed - * differently. + * differently. (But quoting a character with '\\' suppresses + * special meanings!) */ + assertEqualInt(0, pathmatch("a/b/", "a/bc", 0)); + assertEqualInt(1, pathmatch("a/./b", "a/b", 0)); + assertEqualInt(0, pathmatch("a\\/./b", "a/b", 0)); + assertEqualInt(0, pathmatch("a/\\./b", "a/b", 0)); + assertEqualInt(0, pathmatch("a/.\\/b", "a/b", 0)); + assertEqualInt(0, pathmatch("a\\/\\.\\/b", "a/b", 0)); assertEqualInt(1, pathmatch("./abc/./def/", "abc/def/", 0)); assertEqualInt(1, pathmatch("abc/def", "./././abc/./def", 0)); assertEqualInt(1, pathmatch("abc/def/././//", "./././abc/./def/", 0)); @@ -162,4 +196,48 @@ DEFINE_TEST(test_pathmatch) assertEqualInt(1, pathmatch("./abc/./def", "abc/def/./", 0)); failure("Trailing '/.' is still the same directory."); assertEqualInt(1, pathmatch("./abc*/./def", "abc/def/.", 0)); + + /* Matches not anchored at beginning. */ + assertEqualInt(0, + pathmatch("bcd", "abcd", PATHMATCH_NO_ANCHOR_START)); + assertEqualInt(1, + pathmatch("abcd", "abcd", PATHMATCH_NO_ANCHOR_START)); + assertEqualInt(0, + pathmatch("^bcd", "abcd", PATHMATCH_NO_ANCHOR_START)); + assertEqualInt(1, + pathmatch("b/c/d", "a/b/c/d", PATHMATCH_NO_ANCHOR_START)); + assertEqualInt(0, + pathmatch("b/c", "a/b/c/d", PATHMATCH_NO_ANCHOR_START)); + assertEqualInt(0, + pathmatch("^b/c", "a/b/c/d", PATHMATCH_NO_ANCHOR_START)); + + /* Matches not anchored at end. */ + assertEqualInt(0, + pathmatch("bcd", "abcd", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("abcd", "abcd", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("abcd", "abcd/", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("abcd", "abcd/.", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(0, + pathmatch("abc", "abcd", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c", "a/b/c/d", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(0, + pathmatch("a/b/c$", "a/b/c/d", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c$", "a/b/c", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c$", "a/b/c/", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c/", "a/b/c/d", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(0, + pathmatch("a/b/c/$", "a/b/c/d", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c/$", "a/b/c/", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(1, + pathmatch("a/b/c/$", "a/b/c", PATHMATCH_NO_ANCHOR_END)); + assertEqualInt(0, + pathmatch("b/c", "a/b/c/d", PATHMATCH_NO_ANCHOR_END)); }