Coverage Report

Created: 2025-07-23 06:58

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