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;
