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