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