curl_fnmatch.c (10408B)
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 53 typedef enum { 54 CURLFNM_SCHS_DEFAULT = 0, 55 CURLFNM_SCHS_RIGHTBR, 56 CURLFNM_SCHS_RIGHTBRLEFTBR 57 } setcharset_state; 58 59 typedef enum { 60 CURLFNM_PKW_INIT = 0, 61 CURLFNM_PKW_DDOT 62 } parsekey_state; 63 64 typedef 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 74 static int parsekeyword(const 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 const unsigned char *p = *pattern; 81 bool found = FALSE; 82 for(i = 0; !found; i++) { 83 char c = (char)*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. */ 131 static 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. */ 143 static void setcharorrange(const unsigned char **pp, unsigned char *charset) 144 { 145 const 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) */ 165 static int setcharset(const 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 const 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 } 253 fail: 254 return SETCHARSET_FAIL; 255 } 256 257 static int loop(const unsigned char *pattern, const unsigned char *string, 258 int maxstars) 259 { 260 const unsigned char *p = (const unsigned char *)pattern; 261 const unsigned char *s = (const unsigned char *)string; 262 unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; 263 264 for(;;) { 265 const 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 bool 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 = ISBLANK(*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 */ 355 int 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((const unsigned char *)pattern, 363 (const unsigned char *)string, 2); 364 } 365 #else 366 #include <fnmatch.h> 367 /* 368 * @unittest: 1307 369 */ 370 int Curl_fnmatch(void *ptr, const char *pattern, const char *string) 371 { 372 (void)ptr; /* the argument is specified by the curl_fnmatch_callback 373 prototype, but not used by Curl_fnmatch() */ 374 if(!pattern || !string) { 375 return CURL_FNMATCH_FAIL; 376 } 377 378 switch(fnmatch(pattern, string, 0)) { 379 case 0: 380 return CURL_FNMATCH_MATCH; 381 case FNM_NOMATCH: 382 return CURL_FNMATCH_NOMATCH; 383 default: 384 return CURL_FNMATCH_FAIL; 385 } 386 /* not reached */ 387 } 388 389 #endif 390 391 #endif /* if FTP is disabled */