1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2016, 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.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
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 #define CURLFNM_CHARSET_LEN (sizeof(char) * 256)
34 #define CURLFNM_CHSET_SIZE (CURLFNM_CHARSET_LEN + 15)
35
36 #define CURLFNM_NEGATE CURLFNM_CHARSET_LEN
37
38 #define CURLFNM_ALNUM (CURLFNM_CHARSET_LEN + 1)
39 #define CURLFNM_DIGIT (CURLFNM_CHARSET_LEN + 2)
40 #define CURLFNM_XDIGIT (CURLFNM_CHARSET_LEN + 3)
41 #define CURLFNM_ALPHA (CURLFNM_CHARSET_LEN + 4)
42 #define CURLFNM_PRINT (CURLFNM_CHARSET_LEN + 5)
43 #define CURLFNM_BLANK (CURLFNM_CHARSET_LEN + 6)
44 #define CURLFNM_LOWER (CURLFNM_CHARSET_LEN + 7)
45 #define CURLFNM_GRAPH (CURLFNM_CHARSET_LEN + 8)
46 #define CURLFNM_SPACE (CURLFNM_CHARSET_LEN + 9)
47 #define CURLFNM_UPPER (CURLFNM_CHARSET_LEN + 10)
48
49 typedef enum {
50 CURLFNM_LOOP_DEFAULT = 0,
51 CURLFNM_LOOP_BACKSLASH
52 } loop_state;
53
54 typedef enum {
55 CURLFNM_SCHS_DEFAULT = 0,
56 CURLFNM_SCHS_MAYRANGE,
57 CURLFNM_SCHS_MAYRANGE2,
58 CURLFNM_SCHS_RIGHTBR,
59 CURLFNM_SCHS_RIGHTBRLEFTBR
60 } setcharset_state;
61
62 typedef enum {
63 CURLFNM_PKW_INIT = 0,
64 CURLFNM_PKW_DDOT
65 } parsekey_state;
66
67 #define SETCHARSET_OK 1
68 #define SETCHARSET_FAIL 0
69
parsekeyword(unsigned char ** pattern,unsigned char * charset)70 static int parsekeyword(unsigned char **pattern, unsigned char *charset)
71 {
72 parsekey_state state = CURLFNM_PKW_INIT;
73 #define KEYLEN 10
74 char keyword[KEYLEN] = { 0 };
75 int found = FALSE;
76 int i;
77 unsigned char *p = *pattern;
78 for(i = 0; !found; i++) {
79 char c = *p++;
80 if(i >= KEYLEN)
81 return SETCHARSET_FAIL;
82 switch(state) {
83 case CURLFNM_PKW_INIT:
84 if(ISALPHA(c) && ISLOWER(c))
85 keyword[i] = c;
86 else if(c == ':')
87 state = CURLFNM_PKW_DDOT;
88 else
89 return 0;
90 break;
91 case CURLFNM_PKW_DDOT:
92 if(c == ']')
93 found = TRUE;
94 else
95 return SETCHARSET_FAIL;
96 }
97 }
98 #undef KEYLEN
99
100 *pattern = p; /* move caller's pattern pointer */
101 if(strcmp(keyword, "digit") == 0)
102 charset[CURLFNM_DIGIT] = 1;
103 else if(strcmp(keyword, "alnum") == 0)
104 charset[CURLFNM_ALNUM] = 1;
105 else if(strcmp(keyword, "alpha") == 0)
106 charset[CURLFNM_ALPHA] = 1;
107 else if(strcmp(keyword, "xdigit") == 0)
108 charset[CURLFNM_XDIGIT] = 1;
109 else if(strcmp(keyword, "print") == 0)
110 charset[CURLFNM_PRINT] = 1;
111 else if(strcmp(keyword, "graph") == 0)
112 charset[CURLFNM_GRAPH] = 1;
113 else if(strcmp(keyword, "space") == 0)
114 charset[CURLFNM_SPACE] = 1;
115 else if(strcmp(keyword, "blank") == 0)
116 charset[CURLFNM_BLANK] = 1;
117 else if(strcmp(keyword, "upper") == 0)
118 charset[CURLFNM_UPPER] = 1;
119 else if(strcmp(keyword, "lower") == 0)
120 charset[CURLFNM_LOWER] = 1;
121 else
122 return SETCHARSET_FAIL;
123 return SETCHARSET_OK;
124 }
125
126 /* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */
setcharset(unsigned char ** p,unsigned char * charset)127 static int setcharset(unsigned char **p, unsigned char *charset)
128 {
129 setcharset_state state = CURLFNM_SCHS_DEFAULT;
130 unsigned char rangestart = 0;
131 unsigned char lastchar = 0;
132 bool something_found = FALSE;
133 unsigned char c;
134 for(;;) {
135 c = **p;
136 switch(state) {
137 case CURLFNM_SCHS_DEFAULT:
138 if(ISALNUM(c)) { /* ASCII value */
139 rangestart = c;
140 charset[c] = 1;
141 (*p)++;
142 state = CURLFNM_SCHS_MAYRANGE;
143 something_found = TRUE;
144 }
145 else if(c == ']') {
146 if(something_found)
147 return SETCHARSET_OK;
148 else
149 something_found = TRUE;
150 state = CURLFNM_SCHS_RIGHTBR;
151 charset[c] = 1;
152 (*p)++;
153 }
154 else if(c == '[') {
155 char c2 = *((*p)+1);
156 if(c2 == ':') { /* there has to be a keyword */
157 (*p) += 2;
158 if(parsekeyword(p, charset)) {
159 state = CURLFNM_SCHS_DEFAULT;
160 }
161 else
162 return SETCHARSET_FAIL;
163 }
164 else {
165 charset[c] = 1;
166 (*p)++;
167 }
168 something_found = TRUE;
169 }
170 else if(c == '?' || c == '*') {
171 something_found = TRUE;
172 charset[c] = 1;
173 (*p)++;
174 }
175 else if(c == '^' || c == '!') {
176 if(!something_found) {
177 if(charset[CURLFNM_NEGATE]) {
178 charset[c] = 1;
179 something_found = TRUE;
180 }
181 else
182 charset[CURLFNM_NEGATE] = 1; /* negate charset */
183 }
184 else
185 charset[c] = 1;
186 (*p)++;
187 }
188 else if(c == '\\') {
189 c = *(++(*p));
190 if(ISPRINT((c))) {
191 something_found = TRUE;
192 state = CURLFNM_SCHS_MAYRANGE;
193 charset[c] = 1;
194 rangestart = c;
195 (*p)++;
196 }
197 else
198 return SETCHARSET_FAIL;
199 }
200 else if(c == '\0') {
201 return SETCHARSET_FAIL;
202 }
203 else {
204 charset[c] = 1;
205 (*p)++;
206 something_found = TRUE;
207 }
208 break;
209 case CURLFNM_SCHS_MAYRANGE:
210 if(c == '-') {
211 charset[c] = 1;
212 (*p)++;
213 lastchar = '-';
214 state = CURLFNM_SCHS_MAYRANGE2;
215 }
216 else if(c == '[') {
217 state = CURLFNM_SCHS_DEFAULT;
218 }
219 else if(ISALNUM(c)) {
220 charset[c] = 1;
221 (*p)++;
222 }
223 else if(c == '\\') {
224 c = *(++(*p));
225 if(ISPRINT(c)) {
226 charset[c] = 1;
227 (*p)++;
228 }
229 else
230 return SETCHARSET_FAIL;
231 }
232 else if(c == ']') {
233 return SETCHARSET_OK;
234 }
235 else
236 return SETCHARSET_FAIL;
237 break;
238 case CURLFNM_SCHS_MAYRANGE2:
239 if(c == '\\') {
240 c = *(++(*p));
241 if(!ISPRINT(c))
242 return SETCHARSET_FAIL;
243 }
244 if(c == ']') {
245 return SETCHARSET_OK;
246 }
247 else if(c == '\\') {
248 c = *(++(*p));
249 if(ISPRINT(c)) {
250 charset[c] = 1;
251 state = CURLFNM_SCHS_DEFAULT;
252 (*p)++;
253 }
254 else
255 return SETCHARSET_FAIL;
256 }
257 if(c >= rangestart) {
258 if((ISLOWER(c) && ISLOWER(rangestart)) ||
259 (ISDIGIT(c) && ISDIGIT(rangestart)) ||
260 (ISUPPER(c) && ISUPPER(rangestart))) {
261 charset[lastchar] = 0;
262 rangestart++;
263 while(rangestart++ <= c)
264 charset[rangestart-1] = 1;
265 (*p)++;
266 state = CURLFNM_SCHS_DEFAULT;
267 }
268 else
269 return SETCHARSET_FAIL;
270 }
271 break;
272 case CURLFNM_SCHS_RIGHTBR:
273 if(c == '[') {
274 state = CURLFNM_SCHS_RIGHTBRLEFTBR;
275 charset[c] = 1;
276 (*p)++;
277 }
278 else if(c == ']') {
279 return SETCHARSET_OK;
280 }
281 else if(c == '\0') {
282 return SETCHARSET_FAIL;
283 }
284 else if(ISPRINT(c)) {
285 charset[c] = 1;
286 (*p)++;
287 state = CURLFNM_SCHS_DEFAULT;
288 }
289 else
290 /* used 'goto fail' instead of 'return SETCHARSET_FAIL' to avoid a
291 * nonsense warning 'statement not reached' at end of the fnc when
292 * compiling on Solaris */
293 goto fail;
294 break;
295 case CURLFNM_SCHS_RIGHTBRLEFTBR:
296 if(c == ']') {
297 return SETCHARSET_OK;
298 }
299 else {
300 state = CURLFNM_SCHS_DEFAULT;
301 charset[c] = 1;
302 (*p)++;
303 }
304 break;
305 }
306 }
307 fail:
308 return SETCHARSET_FAIL;
309 }
310
loop(const unsigned char * pattern,const unsigned char * string)311 static int loop(const unsigned char *pattern, const unsigned char *string)
312 {
313 loop_state state = CURLFNM_LOOP_DEFAULT;
314 unsigned char *p = (unsigned char *)pattern;
315 unsigned char *s = (unsigned char *)string;
316 unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 };
317 int rc = 0;
318
319 for(;;) {
320 switch(state) {
321 case CURLFNM_LOOP_DEFAULT:
322 if(*p == '*') {
323 while(*(p+1) == '*') /* eliminate multiple stars */
324 p++;
325 if(*s == '\0' && *(p+1) == '\0')
326 return CURL_FNMATCH_MATCH;
327 rc = loop(p + 1, s); /* *.txt matches .txt <=> .txt matches .txt */
328 if(rc == CURL_FNMATCH_MATCH)
329 return CURL_FNMATCH_MATCH;
330 if(*s) /* let the star eat up one character */
331 s++;
332 else
333 return CURL_FNMATCH_NOMATCH;
334 }
335 else if(*p == '?') {
336 if(ISPRINT(*s)) {
337 s++;
338 p++;
339 }
340 else if(*s == '\0')
341 return CURL_FNMATCH_NOMATCH;
342 else
343 return CURL_FNMATCH_FAIL; /* cannot deal with other character */
344 }
345 else if(*p == '\0') {
346 if(*s == '\0')
347 return CURL_FNMATCH_MATCH;
348 else
349 return CURL_FNMATCH_NOMATCH;
350 }
351 else if(*p == '\\') {
352 state = CURLFNM_LOOP_BACKSLASH;
353 p++;
354 }
355 else if(*p == '[') {
356 unsigned char *pp = p+1; /* cannot handle with pointer to register */
357 if(setcharset(&pp, charset)) {
358 int found = FALSE;
359 if(charset[(unsigned int)*s])
360 found = TRUE;
361 else if(charset[CURLFNM_ALNUM])
362 found = ISALNUM(*s);
363 else if(charset[CURLFNM_ALPHA])
364 found = ISALPHA(*s);
365 else if(charset[CURLFNM_DIGIT])
366 found = ISDIGIT(*s);
367 else if(charset[CURLFNM_XDIGIT])
368 found = ISXDIGIT(*s);
369 else if(charset[CURLFNM_PRINT])
370 found = ISPRINT(*s);
371 else if(charset[CURLFNM_SPACE])
372 found = ISSPACE(*s);
373 else if(charset[CURLFNM_UPPER])
374 found = ISUPPER(*s);
375 else if(charset[CURLFNM_LOWER])
376 found = ISLOWER(*s);
377 else if(charset[CURLFNM_BLANK])
378 found = ISBLANK(*s);
379 else if(charset[CURLFNM_GRAPH])
380 found = ISGRAPH(*s);
381
382 if(charset[CURLFNM_NEGATE])
383 found = !found;
384
385 if(found) {
386 p = pp+1;
387 s++;
388 memset(charset, 0, CURLFNM_CHSET_SIZE);
389 }
390 else
391 return CURL_FNMATCH_NOMATCH;
392 }
393 else
394 return CURL_FNMATCH_FAIL;
395 }
396 else {
397 if(*p++ != *s++)
398 return CURL_FNMATCH_NOMATCH;
399 }
400 break;
401 case CURLFNM_LOOP_BACKSLASH:
402 if(ISPRINT(*p)) {
403 if(*p++ == *s++)
404 state = CURLFNM_LOOP_DEFAULT;
405 else
406 return CURL_FNMATCH_NOMATCH;
407 }
408 else
409 return CURL_FNMATCH_FAIL;
410 break;
411 }
412 }
413 }
414
415 /*
416 * @unittest: 1307
417 */
Curl_fnmatch(void * ptr,const char * pattern,const char * string)418 int Curl_fnmatch(void *ptr, const char *pattern, const char *string)
419 {
420 (void)ptr; /* the argument is specified by the curl_fnmatch_callback
421 prototype, but not used by Curl_fnmatch() */
422 if(!pattern || !string) {
423 return CURL_FNMATCH_FAIL;
424 }
425 return loop((unsigned char *)pattern, (unsigned char *)string);
426 }
427