]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Testing: test_pathmatch.c now has 100% test coverage of pathmatch.c
authorTim Kientzle <kientzle@gmail.com>
Fri, 20 Mar 2009 19:21:45 +0000 (15:21 -0400)
committerTim Kientzle <kientzle@gmail.com>
Fri, 20 Mar 2009 19:21:45 +0000 (15:21 -0400)
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

cpio/pathmatch.c
cpio/pathmatch.h
cpio/test/test_pathmatch.c

index d33bd3438985f95400a867c8e8c28c6e9769fc63..40fa836041b0427906a17292c9fb79a1f8536954 100644 (file)
@@ -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);
                }
index 990fa1fa1e04b02eda5fb4acc2292040312e53e7..fd2c2575cc0a308a6554044a6b7085cadde1a0f8 100644 (file)
 #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
index 83528c01ffb35edfbf5d0b6869a07a94b42bbd4f..a596eda1daec77afd147a678e3629ede2d37cdd2 100644 (file)
@@ -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.  <sigh>
  */
 
 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));
 }