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