Coverage Report

Created: 2025-12-05 06:38

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