Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel (at) haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.haxx.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  ***************************************************************************/
     22 
     23 #include "curl_setup.h"
     24 #ifndef CURL_DISABLE_FTP
     25 #include <curl/curl.h>
     26 
     27 #include "curl_fnmatch.h"
     28 #include "curl_memory.h"
     29 
     30 /* The last #include file should be: */
     31 #include "memdebug.h"
     32 
     33 #ifndef HAVE_FNMATCH
     34 
     35 /*
     36  * TODO:
     37  *
     38  * Make this function match POSIX. Test 1307 includes a set of test patterns
     39  * that returns different results with a POSIX fnmatch() than with this
     40  * implementation and this is considered a bug where POSIX is the guiding
     41  * light.
     42  */
     43 
     44 #define CURLFNM_CHARSET_LEN (sizeof(char) * 256)
     45 #define CURLFNM_CHSET_SIZE (CURLFNM_CHARSET_LEN + 15)
     46 
     47 #define CURLFNM_NEGATE  CURLFNM_CHARSET_LEN
     48 
     49 #define CURLFNM_ALNUM   (CURLFNM_CHARSET_LEN + 1)
     50 #define CURLFNM_DIGIT   (CURLFNM_CHARSET_LEN + 2)
     51 #define CURLFNM_XDIGIT  (CURLFNM_CHARSET_LEN + 3)
     52 #define CURLFNM_ALPHA   (CURLFNM_CHARSET_LEN + 4)
     53 #define CURLFNM_PRINT   (CURLFNM_CHARSET_LEN + 5)
     54 #define CURLFNM_BLANK   (CURLFNM_CHARSET_LEN + 6)
     55 #define CURLFNM_LOWER   (CURLFNM_CHARSET_LEN + 7)
     56 #define CURLFNM_GRAPH   (CURLFNM_CHARSET_LEN + 8)
     57 #define CURLFNM_SPACE   (CURLFNM_CHARSET_LEN + 9)
     58 #define CURLFNM_UPPER   (CURLFNM_CHARSET_LEN + 10)
     59 
     60 typedef enum {
     61   CURLFNM_SCHS_DEFAULT = 0,
     62   CURLFNM_SCHS_RIGHTBR,
     63   CURLFNM_SCHS_RIGHTBRLEFTBR
     64 } setcharset_state;
     65 
     66 typedef enum {
     67   CURLFNM_PKW_INIT = 0,
     68   CURLFNM_PKW_DDOT
     69 } parsekey_state;
     70 
     71 typedef enum {
     72   CCLASS_OTHER = 0,
     73   CCLASS_DIGIT,
     74   CCLASS_UPPER,
     75   CCLASS_LOWER
     76 } char_class;
     77 
     78 #define SETCHARSET_OK     1
     79 #define SETCHARSET_FAIL   0
     80 
     81 static int parsekeyword(unsigned char **pattern, unsigned char *charset)
     82 {
     83   parsekey_state state = CURLFNM_PKW_INIT;
     84 #define KEYLEN 10
     85   char keyword[KEYLEN] = { 0 };
     86   int found = FALSE;
     87   int i;
     88   unsigned char *p = *pattern;
     89   for(i = 0; !found; i++) {
     90     char c = *p++;
     91     if(i >= KEYLEN)
     92       return SETCHARSET_FAIL;
     93     switch(state) {
     94     case CURLFNM_PKW_INIT:
     95       if(ISLOWER(c))
     96         keyword[i] = c;
     97       else if(c == ':')
     98         state = CURLFNM_PKW_DDOT;
     99       else
    100         return SETCHARSET_FAIL;
    101       break;
    102     case CURLFNM_PKW_DDOT:
    103       if(c == ']')
    104         found = TRUE;
    105       else
    106         return SETCHARSET_FAIL;
    107     }
    108   }
    109 #undef KEYLEN
    110 
    111   *pattern = p; /* move caller's pattern pointer */
    112   if(strcmp(keyword, "digit") == 0)
    113     charset[CURLFNM_DIGIT] = 1;
    114   else if(strcmp(keyword, "alnum") == 0)
    115     charset[CURLFNM_ALNUM] = 1;
    116   else if(strcmp(keyword, "alpha") == 0)
    117     charset[CURLFNM_ALPHA] = 1;
    118   else if(strcmp(keyword, "xdigit") == 0)
    119     charset[CURLFNM_XDIGIT] = 1;
    120   else if(strcmp(keyword, "print") == 0)
    121     charset[CURLFNM_PRINT] = 1;
    122   else if(strcmp(keyword, "graph") == 0)
    123     charset[CURLFNM_GRAPH] = 1;
    124   else if(strcmp(keyword, "space") == 0)
    125     charset[CURLFNM_SPACE] = 1;
    126   else if(strcmp(keyword, "blank") == 0)
    127     charset[CURLFNM_BLANK] = 1;
    128   else if(strcmp(keyword, "upper") == 0)
    129     charset[CURLFNM_UPPER] = 1;
    130   else if(strcmp(keyword, "lower") == 0)
    131     charset[CURLFNM_LOWER] = 1;
    132   else
    133     return SETCHARSET_FAIL;
    134   return SETCHARSET_OK;
    135 }
    136 
    137 /* Return the character class. */
    138 static char_class charclass(unsigned char c)
    139 {
    140   if(ISUPPER(c))
    141     return CCLASS_UPPER;
    142   if(ISLOWER(c))
    143     return CCLASS_LOWER;
    144   if(ISDIGIT(c))
    145     return CCLASS_DIGIT;
    146   return CCLASS_OTHER;
    147 }
    148 
    149 /* Include a character or a range in set. */
    150 static void setcharorrange(unsigned char **pp, unsigned char *charset)
    151 {
    152   unsigned char *p = (*pp)++;
    153   unsigned char c = *p++;
    154 
    155   charset[c] = 1;
    156   if(ISALNUM(c) && *p++ == '-') {
    157     char_class cc = charclass(c);
    158     unsigned char endrange = *p++;
    159 
    160     if(endrange == '\\')
    161       endrange = *p++;
    162     if(endrange >= c && charclass(endrange) == cc) {
    163       while(c++ != endrange)
    164         if(charclass(c) == cc)  /* Chars in class may be not consecutive. */
    165           charset[c] = 1;
    166       *pp = p;
    167     }
    168   }
    169 }
    170 
    171 /* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */
    172 static int setcharset(unsigned char **p, unsigned char *charset)
    173 {
    174   setcharset_state state = CURLFNM_SCHS_DEFAULT;
    175   bool something_found = FALSE;
    176   unsigned char c;
    177 
    178   memset(charset, 0, CURLFNM_CHSET_SIZE);
    179   for(;;) {
    180     c = **p;
    181     if(!c)
    182       return SETCHARSET_FAIL;
    183 
    184     switch(state) {
    185     case CURLFNM_SCHS_DEFAULT:
    186       if(c == ']') {
    187         if(something_found)
    188           return SETCHARSET_OK;
    189         something_found = TRUE;
    190         state = CURLFNM_SCHS_RIGHTBR;
    191         charset[c] = 1;
    192         (*p)++;
    193       }
    194       else if(c == '[') {
    195         unsigned char *pp = *p + 1;
    196 
    197         if(*pp++ == ':' && parsekeyword(&pp, charset))
    198           *p = pp;
    199         else {
    200           charset[c] = 1;
    201           (*p)++;
    202         }
    203         something_found = TRUE;
    204       }
    205       else if(c == '^' || c == '!') {
    206         if(!something_found) {
    207           if(charset[CURLFNM_NEGATE]) {
    208             charset[c] = 1;
    209             something_found = TRUE;
    210           }
    211           else
    212             charset[CURLFNM_NEGATE] = 1; /* negate charset */
    213         }
    214         else
    215           charset[c] = 1;
    216         (*p)++;
    217       }
    218       else if(c == '\\') {
    219         c = *(++(*p));
    220         if(c)
    221           setcharorrange(p, charset);
    222         else
    223           charset['\\'] = 1;
    224         something_found = TRUE;
    225       }
    226       else {
    227         setcharorrange(p, charset);
    228         something_found = TRUE;
    229       }
    230       break;
    231     case CURLFNM_SCHS_RIGHTBR:
    232       if(c == '[') {
    233         state = CURLFNM_SCHS_RIGHTBRLEFTBR;
    234         charset[c] = 1;
    235         (*p)++;
    236       }
    237       else if(c == ']') {
    238         return SETCHARSET_OK;
    239       }
    240       else if(ISPRINT(c)) {
    241         charset[c] = 1;
    242         (*p)++;
    243         state = CURLFNM_SCHS_DEFAULT;
    244       }
    245       else
    246         /* used 'goto fail' instead of 'return SETCHARSET_FAIL' to avoid a
    247          * nonsense warning 'statement not reached' at end of the fnc when
    248          * compiling on Solaris */
    249         goto fail;
    250       break;
    251     case CURLFNM_SCHS_RIGHTBRLEFTBR:
    252       if(c == ']')
    253         return SETCHARSET_OK;
    254       state  = CURLFNM_SCHS_DEFAULT;
    255       charset[c] = 1;
    256       (*p)++;
    257       break;
    258     }
    259   }
    260 fail:
    261   return SETCHARSET_FAIL;
    262 }
    263 
    264 static int loop(const unsigned char *pattern, const unsigned char *string,
    265                 int maxstars)
    266 {
    267   unsigned char *p = (unsigned char *)pattern;
    268   unsigned char *s = (unsigned char *)string;
    269   unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 };
    270 
    271   for(;;) {
    272     unsigned char *pp;
    273 
    274     switch(*p) {
    275     case '*':
    276       if(!maxstars)
    277         return CURL_FNMATCH_NOMATCH;
    278       /* Regroup consecutive stars and question marks. This can be done because
    279          '*?*?*' can be expressed as '??*'. */
    280       for(;;) {
    281         if(*++p == '\0')
    282           return CURL_FNMATCH_MATCH;
    283         if(*p == '?') {
    284           if(!*s++)
    285             return CURL_FNMATCH_NOMATCH;
    286         }
    287         else if(*p != '*')
    288           break;
    289       }
    290       /* Skip string characters until we find a match with pattern suffix. */
    291       for(maxstars--; *s; s++) {
    292         if(loop(p, s, maxstars) == CURL_FNMATCH_MATCH)
    293           return CURL_FNMATCH_MATCH;
    294       }
    295       return CURL_FNMATCH_NOMATCH;
    296     case '?':
    297       if(!*s)
    298         return CURL_FNMATCH_NOMATCH;
    299       s++;
    300       p++;
    301       break;
    302     case '\0':
    303       return *s? CURL_FNMATCH_NOMATCH: CURL_FNMATCH_MATCH;
    304     case '\\':
    305       if(p[1])
    306         p++;
    307       if(*s++ != *p++)
    308         return CURL_FNMATCH_NOMATCH;
    309       break;
    310     case '[':
    311       pp = p + 1; /* Copy in case of syntax error in set. */
    312       if(setcharset(&pp, charset)) {
    313         int found = FALSE;
    314         if(!*s)
    315           return CURL_FNMATCH_NOMATCH;
    316         if(charset[(unsigned int)*s])
    317           found = TRUE;
    318         else if(charset[CURLFNM_ALNUM])
    319           found = ISALNUM(*s);
    320         else if(charset[CURLFNM_ALPHA])
    321           found = ISALPHA(*s);
    322         else if(charset[CURLFNM_DIGIT])
    323           found = ISDIGIT(*s);
    324         else if(charset[CURLFNM_XDIGIT])
    325           found = ISXDIGIT(*s);
    326         else if(charset[CURLFNM_PRINT])
    327           found = ISPRINT(*s);
    328         else if(charset[CURLFNM_SPACE])
    329           found = ISSPACE(*s);
    330         else if(charset[CURLFNM_UPPER])
    331           found = ISUPPER(*s);
    332         else if(charset[CURLFNM_LOWER])
    333           found = ISLOWER(*s);
    334         else if(charset[CURLFNM_BLANK])
    335           found = ISBLANK(*s);
    336         else if(charset[CURLFNM_GRAPH])
    337           found = ISGRAPH(*s);
    338 
    339         if(charset[CURLFNM_NEGATE])
    340           found = !found;
    341 
    342         if(!found)
    343           return CURL_FNMATCH_NOMATCH;
    344         p = pp + 1;
    345         s++;
    346         break;
    347       }
    348       /* Syntax error in set; mismatch! */
    349       return CURL_FNMATCH_NOMATCH;
    350 
    351     default:
    352       if(*p++ != *s++)
    353         return CURL_FNMATCH_NOMATCH;
    354       break;
    355     }
    356   }
    357 }
    358 
    359 /*
    360  * @unittest: 1307
    361  */
    362 int Curl_fnmatch(void *ptr, const char *pattern, const char *string)
    363 {
    364   (void)ptr; /* the argument is specified by the curl_fnmatch_callback
    365                 prototype, but not used by Curl_fnmatch() */
    366   if(!pattern || !string) {
    367     return CURL_FNMATCH_FAIL;
    368   }
    369   return loop((unsigned char *)pattern, (unsigned char *)string, 2);
    370 }
    371 #else
    372 #include <fnmatch.h>
    373 /*
    374  * @unittest: 1307
    375  */
    376 int Curl_fnmatch(void *ptr, const char *pattern, const char *string)
    377 {
    378   int rc;
    379   (void)ptr; /* the argument is specified by the curl_fnmatch_callback
    380                 prototype, but not used by Curl_fnmatch() */
    381   if(!pattern || !string) {
    382     return CURL_FNMATCH_FAIL;
    383   }
    384   rc = fnmatch(pattern, string, 0);
    385   switch(rc) {
    386   case 0:
    387     return CURL_FNMATCH_MATCH;
    388   case FNM_NOMATCH:
    389     return CURL_FNMATCH_NOMATCH;
    390   default:
    391     return CURL_FNMATCH_FAIL;
    392   }
    393   /* not reached */
    394 }
    395 
    396 #endif
    397 
    398 #endif /* if FTP is disabled */
    399