allow escaped path-separator slashes in glob
authorRich Felker <dalias@aerifal.cx>
Sat, 13 Oct 2018 04:55:48 +0000 (00:55 -0400)
committerRich Felker <dalias@aerifal.cx>
Sat, 13 Oct 2018 05:28:07 +0000 (01:28 -0400)
previously (before and after rewrite), spurious escaping of path
separators as \/ was not treated the same as /, but rather got split
as an unpaired \ at the end of the fnmatch pattern and an unescaped /,
resulting in a mismatch/error.

for the case of \/ as part of the maximal literal prefix, remove the
explicit rejection of it and move the handling of / below escape
processing.

for the case of \/ after a proper glob pattern, it's hard to parse the
pattern, so don't. instead cheat and count repetitions of \ prior to
the already-found / character. if there are an odd number, the last is
escaping the /, so back up the split position by one. now the
char clobbered by null termination is variable, so save it and restore
as needed.

src/regex/glob.c

index 751b69668a2c4232b03bc9fde927cd341f2c27ce..aa1c6a4482ee4424ace4b8a97ba3bb7ec23f9a1f 100644 (file)
@@ -53,22 +53,23 @@ static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*
                        break;
                } else if (pat[i] == '[') {
                        in_bracket = 1;
-               } else if (pat[i] == '/') {
-                       if (overflow) return 0;
-                       in_bracket = 0;
-                       pat += i+1;
-                       i = -1;
-                       pos += j+1;
-                       j = -1;
                } else if (pat[i] == '\\' && !(flags & GLOB_NOESCAPE)) {
                        /* Backslashes inside a bracket are (at least by
                         * our interpretation) non-special, so if next
                         * char is ']' we have a complete expression. */
                        if (in_bracket && pat[i+1]==']') break;
                        /* Unpaired final backslash never matches. */
-                       if (!pat[i+1] || pat[i+1]=='/') return 0;
+                       if (!pat[i+1]) return 0;
                        i++;
                }
+               if (pat[i] == '/') {
+                       if (overflow) return 0;
+                       in_bracket = 0;
+                       pat += i+1;
+                       i = -1;
+                       pos += j+1;
+                       j = -1;
+               }
                /* Only store a character if it fits in the buffer, but if
                 * a potential bracket expression is open, the overflow
                 * must be remembered and handled later only if the bracket
@@ -103,7 +104,17 @@ static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*
                        return GLOB_NOSPACE;
                return 0;
        }
-       char *p2 = strchr(pat, '/');
+       char *p2 = strchr(pat, '/'), saved_sep = '/';
+       /* Check if the '/' was escaped and, if so, remove the escape char
+        * so that it will not be unpaired when passed to fnmatch. */
+       if (p2 && !(flags & GLOB_NOESCAPE)) {
+               char *p;
+               for (p=p2; p>pat && p[-1]=='\\'; p--);
+               if ((p2-p)%2) {
+                       p2--;
+                       saved_sep = '\\';
+               }
+       }
        DIR *dir = opendir(pos ? buf : ".");
        if (!dir) {
                if (errfunc(buf, errno) || (flags & GLOB_ERR))
@@ -136,7 +147,7 @@ static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*
                        continue;
 
                memcpy(buf+pos, de->d_name, l+1);
-               if (p2) *p2 = '/';
+               if (p2) *p2 = saved_sep;
                int r = do_glob(buf, pos+l, de->d_type, p2 ? p2 : "", flags, errfunc, tail);
                if (r) {
                        closedir(dir);
@@ -144,7 +155,7 @@ static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*
                }
        }
        int readerr = errno;
-       if (p2) *p2 = '/';
+       if (p2) *p2 = saved_sep;
        closedir(dir);
        if (readerr && (errfunc(buf, errno) || (flags & GLOB_ERR)))
                return GLOB_ABORTED;