The patch below adds character class matching to fnmatch.
This code was originally written for OpenBSD by Todd C. Miller.

It also adds a sanity check on the input size which was suggested
by Miod Vallat. fnmatch(3) is used to match path names so patterns
or strings longer than PATH_MAX don't need to be processed.

I've committed APR's fnmatch implementation to OpenBSD, with these
changes merged in, see
http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/gen/fnmatch.c
Thanks again Bill for providing this under a BSD licence.

It is likely that we'll apply some intending changes on the OpenBSD side.
Would these also be welcome at APR so we can stay in sync more easily?

[[[
Add character class matching to fnmatch().
Don't try to match patterns or strings longer than APR_PATH_MAX.
Both changes were obtained from OpenBSD.
Submitted by: Stefan Sperling <[email protected]>
]]]

Index: strings/charclass.h
===================================================================
--- strings/charclass.h (revision 0)
+++ strings/charclass.h (working copy)
@@ -0,0 +1,29 @@
+/*
+ * Public domain, 2008, Todd C. Miller <[email protected]>
+ *
+ * $OpenBSD: charclass.h,v 1.1 2008/10/01 23:04:13 millert Exp $
+ */
+
+/*
+ * POSIX character class support for fnmatch() and glob().
+ */
+static struct cclass {
+       const char *name;
+       int (*isctype)(int);
+} cclasses[] = {
+       { "alnum",      isalnum },
+       { "alpha",      isalpha },
+       { "blank",      isblank },
+       { "cntrl",      iscntrl },
+       { "digit",      isdigit },
+       { "graph",      isgraph },
+       { "lower",      islower },
+       { "print",      isprint },
+       { "punct",      ispunct },
+       { "space",      isspace },
+       { "upper",      isupper },
+       { "xdigit",     isxdigit },
+       { NULL,         NULL }
+};
+
+#define NCCLASSES      (sizeof(cclasses) / sizeof(cclasses[0]) - 1)
Index: strings/apr_fnmatch.c
===================================================================
--- strings/apr_fnmatch.c       (revision 1210859)
+++ strings/apr_fnmatch.c       (working copy)
@@ -14,6 +14,21 @@
  * limitations under the License.
  */ 
 
+/*
+ * Copyright (c) 2008 Todd C. Miller <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
 
 /* Derived from The Open Group Base Specifications Issue 7, IEEE Std 
1003.1-2008
  * as described in;
@@ -63,7 +78,52 @@
 # include <ctype.h>
 #endif
 
+#include "charclass.h"
 
+#define        RANGE_MATCH     1
+#define        RANGE_NOMATCH   0
+#define        RANGE_ERROR     (-1)
+
+static int
+classmatch(const char *pattern, char test, int foldcase, const char **ep)
+{
+       struct cclass *cc;
+       const char *colon;
+       size_t len;
+       int rval = RANGE_NOMATCH;
+       const char * const mismatch = pattern;
+
+       if (*pattern != '[' || pattern[1] != ':') {
+               *ep = mismatch;
+               return(RANGE_ERROR);
+       }
+
+       pattern += 2;
+
+       if ((colon = strchr(pattern, ':')) == NULL || colon[1] != ']') {
+               *ep = mismatch;
+               return(RANGE_ERROR);
+       }
+       *ep = colon + 2;
+       len = (size_t)(colon - pattern);
+
+       if (foldcase && strncmp(pattern, "upper:]", 7) == 0)
+               pattern = "lower:]";
+       for (cc = cclasses; cc->name != NULL; cc++) {
+               if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
+                       if (cc->isctype((unsigned char)test))
+                               rval = RANGE_MATCH;
+                       break;
+               }
+       }
+       if (cc->name == NULL) {
+               /* invalid character class, treat as normal text */
+               *ep = mismatch;
+               rval = RANGE_ERROR;
+       }
+       return(rval);
+}
+
 /* Most MBCS/collation/case issues handled here.  Wildcard '*' is not handled.
  * EOS '\0' and the FNM_PATHNAME '/' delimiters are not advanced over, 
  * however the "\/" sequence is advanced to '/'.
@@ -115,6 +175,13 @@ static APR_INLINE int fnmatch_ch(const char **patt
             if (slash && (**pattern == '/'))
                 break;
 
+            /* Match character classes. */
+            if (classmatch(*pattern, **string, nocase, pattern)
+                == RANGE_MATCH) {
+                result = 0;
+                continue;
+            }
+
 leadingclosebrace:
             /* Look at only well-formed range patterns; 
              * "x-]" is not allowed unless escaped ("x-\]")
@@ -207,6 +274,10 @@ APR_DECLARE(int) apr_fnmatch(const char *pattern,
     const char *mismatch = NULL;
     int matchlen = 0;
 
+    if (strnlen(pattern, APR_PATH_MAX) == APR_PATH_MAX ||
+        strnlen(string, APR_PATH_MAX) == APR_PATH_MAX)
+            return (APR_FNM_NOMATCH);
+
     if (*pattern == '*')
         goto firstsegment;
 

Reply via email to