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