• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2019, 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 #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 
parsekeyword(unsigned char ** pattern,unsigned char * charset)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. */
charclass(unsigned char c)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. */
setcharorrange(unsigned char ** pp,unsigned char * charset)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) */
setcharset(unsigned char ** p,unsigned char * charset)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 
loop(const unsigned char * pattern,const unsigned char * string,int maxstars)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  */
Curl_fnmatch(void * ptr,const char * pattern,const char * string)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  */
Curl_fnmatch(void * ptr,const char * pattern,const char * string)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