Home | History | Annotate | Download | only in gen
      1 /*	$OpenBSD: fnmatch.c,v 1.17 2013/11/24 23:51:29 deraadt Exp $	*/
      2 
      3 /* Copyright (c) 2011, VMware, Inc.
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *     * Redistributions of source code must retain the above copyright
      9  *       notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above copyright
     11  *       notice, this list of conditions and the following disclaimer in the
     12  *       documentation and/or other materials provided with the distribution.
     13  *     * Neither the name of the VMware, Inc. nor the names of its contributors
     14  *       may be used to endorse or promote products derived from this software
     15  *       without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     20  * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE FOR
     21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /*
     30  * Copyright (c) 2008 Todd C. Miller <millert (at) openbsd.org>
     31  *
     32  * Permission to use, copy, modify, and distribute this software for any
     33  * purpose with or without fee is hereby granted, provided that the above
     34  * copyright notice and this permission notice appear in all copies.
     35  *
     36  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     37  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     38  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     39  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     40  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     41  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     42  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     43  */
     44 
     45 /* Authored by William A. Rowe Jr. <wrowe; apache.org, vmware.com>, April 2011
     46  *
     47  * Derived from The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008
     48  * as described in;
     49  *   http://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
     50  *
     51  * Filename pattern matches defined in section 2.13, "Pattern Matching Notation"
     52  * from chapter 2. "Shell Command Language"
     53  *   http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
     54  * where; 1. A bracket expression starting with an unquoted <circumflex> '^'
     55  * character CONTINUES to specify a non-matching list; 2. an explicit <period> '.'
     56  * in a bracket expression matching list, e.g. "[.abc]" does NOT match a leading
     57  * <period> in a filename; 3. a <left-square-bracket> '[' which does not introduce
     58  * a valid bracket expression is treated as an ordinary character; 4. a differing
     59  * number of consecutive slashes within pattern and string will NOT match;
     60  * 5. a trailing '\' in FNM_ESCAPE mode is treated as an ordinary '\' character.
     61  *
     62  * Bracket expansion defined in section 9.3.5, "RE Bracket Expression",
     63  * from chapter 9, "Regular Expressions"
     64  *   http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
     65  * with no support for collating symbols, equivalence class expressions or
     66  * character class expressions.  A partial range expression with a leading
     67  * hyphen following a valid range expression will match only the ordinary
     68  * <hyphen> and the ending character (e.g. "[a-m-z]" will match characters
     69  * 'a' through 'm', a <hyphen> '-', or a 'z').
     70  *
     71  * Supports BSD extensions FNM_LEADING_DIR to match pattern to the end of one
     72  * path segment of string, and FNM_CASEFOLD to ignore alpha case.
     73  *
     74  * NOTE: Only POSIX/C single byte locales are correctly supported at this time.
     75  * Notably, non-POSIX locales with FNM_CASEFOLD produce undefined results,
     76  * particularly in ranges of mixed case (e.g. "[A-z]") or spanning alpha and
     77  * nonalpha characters within a range.
     78  *
     79  * XXX comments below indicate porting required for multi-byte character sets
     80  * and non-POSIX locale collation orders; requires mbr* APIs to track shift
     81  * state of pattern and string (rewinding pattern and string repeatedly).
     82  *
     83  * Certain parts of the code assume 0x00-0x3F are unique with any MBCS (e.g.
     84  * UTF-8, SHIFT-JIS, etc).  Any implementation allowing '\' as an alternate
     85  * path delimiter must be aware that 0x5C is NOT unique within SHIFT-JIS.
     86  */
     87 
     88 #include <fnmatch.h>
     89 #include <string.h>
     90 #include <ctype.h>
     91 #include <limits.h>
     92 
     93 #include "charclass.h"
     94 
     95 #define	RANGE_MATCH	1
     96 #define	RANGE_NOMATCH	0
     97 #define	RANGE_ERROR	(-1)
     98 
     99 static int
    100 classmatch(const char *pattern, char test, int foldcase, const char **ep)
    101 {
    102 	struct cclass *cc;
    103 	const char *colon;
    104 	size_t len;
    105 	int rval = RANGE_NOMATCH;
    106 	const char * const mismatch = pattern;
    107 
    108 	if (*pattern != '[' || pattern[1] != ':') {
    109 		*ep = mismatch;
    110 		return(RANGE_ERROR);
    111 	}
    112 
    113 	pattern += 2;
    114 
    115 	if ((colon = strchr(pattern, ':')) == NULL || colon[1] != ']') {
    116 		*ep = mismatch;
    117 		return(RANGE_ERROR);
    118 	}
    119 	*ep = colon + 2;
    120 	len = (size_t)(colon - pattern);
    121 
    122 	if (foldcase && strncmp(pattern, "upper:]", 7) == 0)
    123 		pattern = "lower:]";
    124 	for (cc = cclasses; cc->name != NULL; cc++) {
    125 		if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
    126 			if (cc->isctype((unsigned char)test))
    127 				rval = RANGE_MATCH;
    128 			break;
    129 		}
    130 	}
    131 	if (cc->name == NULL) {
    132 		/* invalid character class, treat as normal text */
    133 		*ep = mismatch;
    134 		rval = RANGE_ERROR;
    135 	}
    136 	return(rval);
    137 }
    138 
    139 /* Most MBCS/collation/case issues handled here.  Wildcard '*' is not handled.
    140  * EOS '\0' and the FNM_PATHNAME '/' delimiters are not advanced over,
    141  * however the "\/" sequence is advanced to '/'.
    142  *
    143  * Both pattern and string are **char to support pointer increment of arbitrary
    144  * multibyte characters for the given locale, in a later iteration of this code
    145  */
    146 static int fnmatch_ch(const char **pattern, const char **string, int flags)
    147 {
    148     const char * const mismatch = *pattern;
    149     const int nocase = !!(flags & FNM_CASEFOLD);
    150     const int escape = !(flags & FNM_NOESCAPE);
    151     const int slash = !!(flags & FNM_PATHNAME);
    152     int result = FNM_NOMATCH;
    153     const char *startch;
    154     int negate;
    155 
    156     if (**pattern == '[')
    157     {
    158         ++*pattern;
    159 
    160         /* Handle negation, either leading ! or ^ operators (never both) */
    161         negate = ((**pattern == '!') || (**pattern == '^'));
    162         if (negate)
    163             ++*pattern;
    164 
    165         /* ']' is an ordinary character at the start of the range pattern */
    166         if (**pattern == ']')
    167             goto leadingclosebrace;
    168 
    169         while (**pattern)
    170         {
    171             if (**pattern == ']') {
    172                 ++*pattern;
    173                 /* XXX: Fix for MBCS character width */
    174                 ++*string;
    175                 return (result ^ negate);
    176             }
    177 
    178             if (escape && (**pattern == '\\')) {
    179                 ++*pattern;
    180 
    181                 /* Patterns must be terminated with ']', not EOS */
    182                 if (!**pattern)
    183                     break;
    184             }
    185 
    186             /* Patterns must be terminated with ']' not '/' */
    187             if (slash && (**pattern == '/'))
    188                 break;
    189 
    190             /* Match character classes. */
    191             if (classmatch(*pattern, **string, nocase, pattern)
    192                 == RANGE_MATCH) {
    193                 result = 0;
    194                 continue;
    195             }
    196 
    197 leadingclosebrace:
    198             /* Look at only well-formed range patterns;
    199              * "x-]" is not allowed unless escaped ("x-\]")
    200              * XXX: Fix for locale/MBCS character width
    201              */
    202             if (((*pattern)[1] == '-') && ((*pattern)[2] != ']'))
    203             {
    204                 startch = *pattern;
    205                 *pattern += (escape && ((*pattern)[2] == '\\')) ? 3 : 2;
    206 
    207                 /* NOT a properly balanced [expr] pattern, EOS terminated
    208                  * or ranges containing a slash in FNM_PATHNAME mode pattern
    209                  * fall out to to the rewind and test '[' literal code path
    210                  */
    211                 if (!**pattern || (slash && (**pattern == '/')))
    212                     break;
    213 
    214                 /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
    215                 if ((**string >= *startch) && (**string <= **pattern))
    216                     result = 0;
    217                 else if (nocase && (isupper((unsigned char)**string) ||
    218 			    isupper((unsigned char)*startch) ||
    219                             isupper((unsigned char)**pattern))
    220                             && (tolower((unsigned char)**string) >=
    221 			        tolower((unsigned char)*startch))
    222                             && (tolower((unsigned char)**string) <=
    223 				tolower((unsigned char)**pattern)))
    224                     result = 0;
    225 
    226                 ++*pattern;
    227                 continue;
    228             }
    229 
    230             /* XXX: handle locale/MBCS comparison, advance by MBCS char width */
    231             if ((**string == **pattern))
    232                 result = 0;
    233             else if (nocase && (isupper((unsigned char)**string) ||
    234 			    isupper((unsigned char)**pattern))
    235                             && (tolower((unsigned char)**string) ==
    236 				tolower((unsigned char)**pattern)))
    237                 result = 0;
    238 
    239             ++*pattern;
    240         }
    241 
    242         /* NOT a properly balanced [expr] pattern; Rewind
    243          * and reset result to test '[' literal
    244          */
    245         *pattern = mismatch;
    246         result = FNM_NOMATCH;
    247     }
    248     else if (**pattern == '?') {
    249         /* Optimize '?' match before unescaping **pattern */
    250         if (!**string || (slash && (**string == '/')))
    251             return FNM_NOMATCH;
    252         result = 0;
    253         goto fnmatch_ch_success;
    254     }
    255     else if (escape && (**pattern == '\\') && (*pattern)[1]) {
    256         ++*pattern;
    257     }
    258 
    259     /* XXX: handle locale/MBCS comparison, advance by the MBCS char width */
    260     if (**string == **pattern)
    261         result = 0;
    262     else if (nocase && (isupper((unsigned char)**string) ||
    263 		    isupper((unsigned char)**pattern))
    264                     && (tolower((unsigned char)**string) ==
    265 			tolower((unsigned char)**pattern)))
    266         result = 0;
    267 
    268     /* Refuse to advance over trailing slash or nulls
    269      */
    270     if (!**string || !**pattern || (slash && ((**string == '/') || (**pattern == '/'))))
    271         return result;
    272 
    273 fnmatch_ch_success:
    274     ++*pattern;
    275     ++*string;
    276     return result;
    277 }
    278 
    279 
    280 int fnmatch(const char *pattern, const char *string, int flags)
    281 {
    282     static const char dummystring[2] = {' ', 0};
    283     const int escape = !(flags & FNM_NOESCAPE);
    284     const int slash = !!(flags & FNM_PATHNAME);
    285     const int leading_dir = !!(flags & FNM_LEADING_DIR);
    286     const char *strendseg;
    287     const char *dummyptr;
    288     const char *matchptr;
    289     int wild;
    290     /* For '*' wild processing only; surpress 'used before initialization'
    291      * warnings with dummy initialization values;
    292      */
    293     const char *strstartseg = NULL;
    294     const char *mismatch = NULL;
    295     int matchlen = 0;
    296 
    297     if (strnlen(pattern, PATH_MAX) == PATH_MAX ||
    298         strnlen(string, PATH_MAX) == PATH_MAX)
    299             return (FNM_NOMATCH);
    300 
    301     if (*pattern == '*')
    302         goto firstsegment;
    303 
    304     while (*pattern && *string)
    305     {
    306         /* Pre-decode "\/" which has no special significance, and
    307          * match balanced slashes, starting a new segment pattern
    308          */
    309         if (slash && escape && (*pattern == '\\') && (pattern[1] == '/'))
    310             ++pattern;
    311         if (slash && (*pattern == '/') && (*string == '/')) {
    312             ++pattern;
    313             ++string;
    314         }
    315 
    316 firstsegment:
    317         /* At the beginning of each segment, validate leading period behavior.
    318          */
    319         if ((flags & FNM_PERIOD) && (*string == '.'))
    320         {
    321             if (*pattern == '.')
    322                 ++pattern;
    323             else if (escape && (*pattern == '\\') && (pattern[1] == '.'))
    324                 pattern += 2;
    325             else
    326                 return FNM_NOMATCH;
    327             ++string;
    328         }
    329 
    330         /* Determine the end of string segment
    331          *
    332          * Presumes '/' character is unique, not composite in any MBCS encoding
    333          */
    334         if (slash) {
    335             strendseg = strchr(string, '/');
    336             if (!strendseg)
    337                 strendseg = strchr(string, '\0');
    338         }
    339         else {
    340             strendseg = strchr(string, '\0');
    341         }
    342 
    343         /* Allow pattern '*' to be consumed even with no remaining string to match
    344          */
    345         while (*pattern)
    346         {
    347             if ((string > strendseg)
    348                 || ((string == strendseg) && (*pattern != '*')))
    349                 break;
    350 
    351             if (slash && ((*pattern == '/')
    352                            || (escape && (*pattern == '\\')
    353                                       && (pattern[1] == '/'))))
    354                 break;
    355 
    356             /* Reduce groups of '*' and '?' to n '?' matches
    357              * followed by one '*' test for simplicity
    358              */
    359             for (wild = 0; ((*pattern == '*') || (*pattern == '?')); ++pattern)
    360             {
    361                 if (*pattern == '*') {
    362                     wild = 1;
    363                 }
    364                 else if (string < strendseg) {  /* && (*pattern == '?') */
    365                     /* XXX: Advance 1 char for MBCS locale */
    366                     ++string;
    367                 }
    368                 else {  /* (string >= strendseg) && (*pattern == '?') */
    369                     return FNM_NOMATCH;
    370                 }
    371             }
    372 
    373             if (wild)
    374             {
    375                 strstartseg = string;
    376                 mismatch = pattern;
    377 
    378                 /* Count fixed (non '*') char matches remaining in pattern
    379                  * excluding '/' (or "\/") and '*'
    380                  */
    381                 for (matchptr = pattern, matchlen = 0; 1; ++matchlen)
    382                 {
    383                     if ((*matchptr == '\0')
    384                         || (slash && ((*matchptr == '/')
    385                                       || (escape && (*matchptr == '\\')
    386                                                  && (matchptr[1] == '/')))))
    387                     {
    388                         /* Compare precisely this many trailing string chars,
    389                          * the resulting match needs no wildcard loop
    390                          */
    391                         /* XXX: Adjust for MBCS */
    392                         if (string + matchlen > strendseg)
    393                             return FNM_NOMATCH;
    394 
    395                         string = strendseg - matchlen;
    396                         wild = 0;
    397                         break;
    398                     }
    399 
    400                     if (*matchptr == '*')
    401                     {
    402                         /* Ensure at least this many trailing string chars remain
    403                          * for the first comparison
    404                          */
    405                         /* XXX: Adjust for MBCS */
    406                         if (string + matchlen > strendseg)
    407                             return FNM_NOMATCH;
    408 
    409                         /* Begin first wild comparison at the current position */
    410                         break;
    411                     }
    412 
    413                     /* Skip forward in pattern by a single character match
    414                      * Use a dummy fnmatch_ch() test to count one "[range]" escape
    415                      */
    416                     /* XXX: Adjust for MBCS */
    417                     if (escape && (*matchptr == '\\') && matchptr[1]) {
    418                         matchptr += 2;
    419                     }
    420                     else if (*matchptr == '[') {
    421                         dummyptr = dummystring;
    422                         fnmatch_ch(&matchptr, &dummyptr, flags);
    423                     }
    424                     else {
    425                         ++matchptr;
    426                     }
    427                 }
    428             }
    429 
    430             /* Incrementally match string against the pattern
    431              */
    432             while (*pattern && (string < strendseg))
    433             {
    434                 /* Success; begin a new wild pattern search
    435                  */
    436                 if (*pattern == '*')
    437                     break;
    438 
    439                 if (slash && ((*string == '/')
    440                               || (*pattern == '/')
    441                               || (escape && (*pattern == '\\')
    442                                          && (pattern[1] == '/'))))
    443                     break;
    444 
    445                 /* Compare ch's (the pattern is advanced over "\/" to the '/',
    446                  * but slashes will mismatch, and are not consumed)
    447                  */
    448                 if (!fnmatch_ch(&pattern, &string, flags))
    449                     continue;
    450 
    451                 /* Failed to match, loop against next char offset of string segment
    452                  * until not enough string chars remain to match the fixed pattern
    453                  */
    454                 if (wild) {
    455                     /* XXX: Advance 1 char for MBCS locale */
    456                     string = ++strstartseg;
    457                     if (string + matchlen > strendseg)
    458                         return FNM_NOMATCH;
    459 
    460                     pattern = mismatch;
    461                     continue;
    462                 }
    463                 else
    464                     return FNM_NOMATCH;
    465             }
    466         }
    467 
    468         if (*string && !((slash || leading_dir) && (*string == '/')))
    469             return FNM_NOMATCH;
    470 
    471         if (*pattern && !(slash && ((*pattern == '/')
    472                                     || (escape && (*pattern == '\\')
    473                                                && (pattern[1] == '/')))))
    474             return FNM_NOMATCH;
    475 
    476         if (leading_dir && !*pattern && *string == '/')
    477             return 0;
    478     }
    479 
    480     /* Where both pattern and string are at EOS, declare success
    481      */
    482     if (!*string && !*pattern)
    483         return 0;
    484 
    485     /* pattern didn't match to the end of string */
    486     return FNM_NOMATCH;
    487 }
    488