xref: /third_party/curl/lib/curl_fnmatch.c (revision 13498266)
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