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