Coverage Report

Created: 2026-09-14 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmlibarchive/libarchive/archive_pathmatch.c
Line
Count
Source
1
/*-
2
 * Copyright (c) 2003-2007 Tim Kientzle
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer
10
 *    in this position and unchanged.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#include "archive_platform.h"
28
29
#ifdef HAVE_STRING_H
30
#include <string.h>
31
#endif
32
#ifdef HAVE_WCHAR_H
33
#include <wchar.h>
34
#endif
35
36
#include "archive_pathmatch.h"
37
38
0
#define MAX_RECURSION 100
39
40
/*
41
 * Check whether a character 'c' is matched by a list specification [...]:
42
 *    * Leading '!' or '^' negates the class.
43
 *    * <char>-<char> is a range of characters
44
 *    * \<char> removes any special meaning for <char>
45
 *
46
 * Some interesting boundary cases:
47
 *   a-d-e is one range (a-d) followed by two single characters - and e.
48
 *   \a-\d is same as a-d
49
 *   a\-d is three single characters: a, d, -
50
 *   Trailing - is not special (so [a-] is two characters a and -).
51
 *   Initial - is not special ([a-] is same as [-a] is same as [\\-a])
52
 *   This function never sees a trailing \.
53
 *   [] always fails
54
 *   [!] always succeeds
55
 */
56
static int
57
pm_list(const char *start, const char *end, const char c, int flags)
58
0
{
59
0
  const char *p = start;
60
0
  char rangeStart = '\0', nextRangeStart;
61
0
  int match = 1, nomatch = 0;
62
63
  /* This will be used soon... */
64
0
  (void)flags; /* UNUSED */
65
66
  /* If this is a negated class, return success for nomatch. */
67
0
  if ((*p == '!' || *p == '^') && p < end) {
68
0
    match = 0;
69
0
    nomatch = 1;
70
0
    ++p;
71
0
  }
72
73
0
  while (p < end) {
74
0
    nextRangeStart = '\0';
75
0
    switch (*p) {
76
0
    case '-':
77
      /* Trailing or initial '-' is not special. */
78
0
      if ((rangeStart == '\0') || (p == end - 1)) {
79
0
        if (*p == c)
80
0
          return (match);
81
0
      } else {
82
0
        char rangeEnd = *++p;
83
0
        if (rangeEnd == '\\')
84
0
          rangeEnd = *++p;
85
0
        if ((rangeStart <= c) && (c <= rangeEnd))
86
0
          return (match);
87
0
      }
88
0
      break;
89
0
    case '\\':
90
0
      ++p;
91
      /* Fall through */
92
0
    default:
93
0
      if (*p == c)
94
0
        return (match);
95
0
      nextRangeStart = *p; /* Possible start of range. */
96
0
    }
97
0
    rangeStart = nextRangeStart;
98
0
    ++p;
99
0
  }
100
0
  return (nomatch);
101
0
}
102
103
static int
104
pm_list_w(const wchar_t *start, const wchar_t *end, const wchar_t c, int flags)
105
0
{
106
0
  const wchar_t *p = start;
107
0
  wchar_t rangeStart = L'\0', nextRangeStart;
108
0
  int match = 1, nomatch = 0;
109
110
  /* This will be used soon... */
111
0
  (void)flags; /* UNUSED */
112
113
  /* If this is a negated class, return success for nomatch. */
114
0
  if ((*p == L'!' || *p == L'^') && p < end) {
115
0
    match = 0;
116
0
    nomatch = 1;
117
0
    ++p;
118
0
  }
119
120
0
  while (p < end) {
121
0
    nextRangeStart = L'\0';
122
0
    switch (*p) {
123
0
    case L'-':
124
      /* Trailing or initial '-' is not special. */
125
0
      if ((rangeStart == L'\0') || (p == end - 1)) {
126
0
        if (*p == c)
127
0
          return (match);
128
0
      } else {
129
0
        wchar_t rangeEnd = *++p;
130
0
        if (rangeEnd == L'\\')
131
0
          rangeEnd = *++p;
132
0
        if ((rangeStart <= c) && (c <= rangeEnd))
133
0
          return (match);
134
0
      }
135
0
      break;
136
0
    case L'\\':
137
0
      ++p;
138
      /* Fall through */
139
0
    default:
140
0
      if (*p == c)
141
0
        return (match);
142
0
      nextRangeStart = *p; /* Possible start of range. */
143
0
    }
144
0
    rangeStart = nextRangeStart;
145
0
    ++p;
146
0
  }
147
0
  return (nomatch);
148
0
}
149
150
/*
151
 * If s is pointing to "./", ".//", "./././" or the like, skip it.
152
 */
153
static const char *
154
0
pm_slashskip(const char *s) {
155
0
  while ((*s == '/')
156
0
      || (s[0] == '.' && s[1] == '/')
157
0
      || (s[0] == '.' && s[1] == '\0'))
158
0
    ++s;
159
0
  return (s);
160
0
}
161
162
static const wchar_t *
163
0
pm_slashskip_w(const wchar_t *s) {
164
0
  while ((*s == L'/')
165
0
      || (s[0] == L'.' && s[1] == L'/')
166
0
      || (s[0] == L'.' && s[1] == L'\0'))
167
0
    ++s;
168
0
  return (s);
169
0
}
170
171
static int
172
pm(const char *p, const char *s, int flags, int depth)
173
0
{
174
0
  const char *end;
175
0
  int r;
176
177
0
  if (depth > MAX_RECURSION)
178
0
    return (-1);
179
180
  /*
181
   * Ignore leading './', './/', '././', etc.
182
   */
183
0
  if (s[0] == '.' && s[1] == '/')
184
0
    s = pm_slashskip(s + 1);
185
0
  if (p[0] == '.' && p[1] == '/')
186
0
    p = pm_slashskip(p + 1);
187
188
0
  for (;;) {
189
0
    switch (*p) {
190
0
    case '\0':
191
0
      if (s[0] == '/') {
192
0
        if (flags & PATHMATCH_NO_ANCHOR_END)
193
0
          return (1);
194
        /* "dir" == "dir/" == "dir/." */
195
0
        s = pm_slashskip(s);
196
0
      }
197
0
      return (*s == '\0');
198
0
    case '?':
199
      /* ? always succeeds, unless we hit end of 's' */
200
0
      if (*s == '\0')
201
0
        return (0);
202
0
      break;
203
0
    case '*':
204
      /* "*" == "**" == "***" ... */
205
0
      while (*p == '*')
206
0
        ++p;
207
      /* Trailing '*' always succeeds. */
208
0
      if (*p == '\0')
209
0
        return (1);
210
0
      while (*s) {
211
0
        r = pm(p, s, flags, depth + 1);
212
0
        if (r)
213
0
          return (r);
214
0
        ++s;
215
0
      }
216
0
      return (0);
217
0
    case '[':
218
      /*
219
       * Find the end of the [...] character class,
220
       * ignoring \] that might occur within the class.
221
       */
222
0
      end = p + 1;
223
0
      while (*end != '\0' && *end != ']') {
224
0
        if (*end == '\\' && end[1] != '\0')
225
0
          ++end;
226
0
        ++end;
227
0
      }
228
0
      if (*end == ']') {
229
        /* We found [...], try to match it. */
230
0
        if (*s == '\0' || !pm_list(p + 1, end, *s, flags))
231
0
          return (0);
232
0
        p = end; /* Jump to trailing ']' char. */
233
0
        break;
234
0
      } else
235
        /* No final ']', so just match '['. */
236
0
        if (*p != *s)
237
0
          return (0);
238
0
      break;
239
0
    case '\\':
240
      /* Trailing '\\' matches itself. */
241
0
      if (p[1] == '\0') {
242
0
        if (*s != '\\')
243
0
          return (0);
244
0
      } else {
245
0
        ++p;
246
0
        if (*p != *s)
247
0
          return (0);
248
0
      }
249
0
      break;
250
0
    case '/':
251
0
      if (*s != '/' && *s != '\0')
252
0
        return (0);
253
      /* Note: pattern "/\./" won't match "/";
254
       * pm_slashskip() correctly stops at backslash. */
255
0
      p = pm_slashskip(p);
256
0
      s = pm_slashskip(s);
257
0
      if (*p == '\0' && (flags & PATHMATCH_NO_ANCHOR_END))
258
0
        return (1);
259
0
      --p; /* Counteract the increment below. */
260
0
      --s;
261
0
      break;
262
0
    case '$':
263
      /* '$' is special only at end of pattern and only
264
       * if PATHMATCH_NO_ANCHOR_END is specified. */
265
0
      if (p[1] == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
266
        /* "dir" == "dir/" == "dir/." */
267
0
        return (*pm_slashskip(s) == '\0');
268
0
      }
269
      /* Otherwise, '$' is not special. */
270
      /* FALL THROUGH */
271
0
    default:
272
0
      if (*p != *s)
273
0
        return (0);
274
0
      break;
275
0
    }
276
0
    ++p;
277
0
    ++s;
278
0
  }
279
0
}
280
281
static int
282
pm_w(const wchar_t *p, const wchar_t *s, int flags, int depth)
283
0
{
284
0
  const wchar_t *end;
285
0
  int r;
286
287
0
  if (depth > MAX_RECURSION)
288
0
    return (-1);
289
290
  /*
291
   * Ignore leading './', './/', '././', etc.
292
   */
293
0
  if (s[0] == L'.' && s[1] == L'/')
294
0
    s = pm_slashskip_w(s + 1);
295
0
  if (p[0] == L'.' && p[1] == L'/')
296
0
    p = pm_slashskip_w(p + 1);
297
298
0
  for (;;) {
299
0
    switch (*p) {
300
0
    case L'\0':
301
0
      if (s[0] == L'/') {
302
0
        if (flags & PATHMATCH_NO_ANCHOR_END)
303
0
          return (1);
304
        /* "dir" == "dir/" == "dir/." */
305
0
        s = pm_slashskip_w(s);
306
0
      }
307
0
      return (*s == L'\0');
308
0
    case L'?':
309
      /* ? always succeeds, unless we hit end of 's' */
310
0
      if (*s == L'\0')
311
0
        return (0);
312
0
      break;
313
0
    case L'*':
314
      /* "*" == "**" == "***" ... */
315
0
      while (*p == L'*')
316
0
        ++p;
317
      /* Trailing '*' always succeeds. */
318
0
      if (*p == L'\0')
319
0
        return (1);
320
0
      while (*s) {
321
0
        r = pm_w(p, s, flags, depth + 1);
322
0
        if (r)
323
0
          return (r);
324
0
        ++s;
325
0
      }
326
0
      return (0);
327
0
    case L'[':
328
      /*
329
       * Find the end of the [...] character class,
330
       * ignoring \] that might occur within the class.
331
       */
332
0
      end = p + 1;
333
0
      while (*end != L'\0' && *end != L']') {
334
0
        if (*end == L'\\' && end[1] != L'\0')
335
0
          ++end;
336
0
        ++end;
337
0
      }
338
0
      if (*end == L']') {
339
        /* We found [...], try to match it. */
340
0
        if (*s == L'\0' || !pm_list_w(p + 1, end, *s, flags))
341
0
          return (0);
342
0
        p = end; /* Jump to trailing ']' char. */
343
0
        break;
344
0
      } else
345
        /* No final ']', so just match '['. */
346
0
        if (*p != *s)
347
0
          return (0);
348
0
      break;
349
0
    case L'\\':
350
      /* Trailing '\\' matches itself. */
351
0
      if (p[1] == L'\0') {
352
0
        if (*s != L'\\')
353
0
          return (0);
354
0
      } else {
355
0
        ++p;
356
0
        if (*p != *s)
357
0
          return (0);
358
0
      }
359
0
      break;
360
0
    case L'/':
361
0
      if (*s != L'/' && *s != L'\0')
362
0
        return (0);
363
      /* Note: pattern "/\./" won't match "/";
364
       * pm_slashskip() correctly stops at backslash. */
365
0
      p = pm_slashskip_w(p);
366
0
      s = pm_slashskip_w(s);
367
0
      if (*p == L'\0' && (flags & PATHMATCH_NO_ANCHOR_END))
368
0
        return (1);
369
0
      --p; /* Counteract the increment below. */
370
0
      --s;
371
0
      break;
372
0
    case L'$':
373
      /* '$' is special only at end of pattern and only
374
       * if PATHMATCH_NO_ANCHOR_END is specified. */
375
0
      if (p[1] == L'\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
376
        /* "dir" == "dir/" == "dir/." */
377
0
        return (*pm_slashskip_w(s) == L'\0');
378
0
      }
379
      /* Otherwise, '$' is not special. */
380
      /* FALL THROUGH */
381
0
    default:
382
0
      if (*p != *s)
383
0
        return (0);
384
0
      break;
385
0
    }
386
0
    ++p;
387
0
    ++s;
388
0
  }
389
0
}
390
391
/* Main entry point. */
392
int
393
__archive_pathmatch(const char *p, const char *s, int flags)
394
0
{
395
  /* Empty pattern only matches the empty string. */
396
0
  if (p == NULL || *p == '\0')
397
0
    return (s == NULL || *s == '\0');
398
0
  else if (s == NULL)
399
0
    return (0);
400
401
  /* Leading '^' anchors the start of the pattern. */
402
0
  if ((flags & PATHMATCH_NO_ANCHOR_START) && *p == '^') {
403
0
    ++p;
404
0
    flags &= ~PATHMATCH_NO_ANCHOR_START;
405
0
  }
406
407
0
  if (*p == '/' && *s != '/')
408
0
    return (0);
409
410
  /* Certain patterns anchor implicitly. */
411
0
  if (*p == '*' || *p == '/') {
412
0
    while (*p == '/')
413
0
      ++p;
414
0
    while (*s == '/')
415
0
      ++s;
416
0
    return (pm(p, s, flags, 0));
417
0
  }
418
419
  /* If start is unanchored, try to match start of each path element. */
420
0
  if (flags & PATHMATCH_NO_ANCHOR_START) {
421
0
    for ( ; s != NULL; s = strchr(s, '/')) {
422
0
      int r;
423
424
0
      if (*s == '/')
425
0
        s++;
426
0
      r = pm(p, s, flags, 0);
427
0
      if (r)
428
0
        return (r);
429
0
    }
430
0
    return (0);
431
0
  }
432
433
  /* Default: Match from beginning. */
434
0
  return (pm(p, s, flags, 0));
435
0
}
436
437
int
438
__archive_pathmatch_w(const wchar_t *p, const wchar_t *s, int flags)
439
0
{
440
  /* Empty pattern only matches the empty string. */
441
0
  if (p == NULL || *p == L'\0')
442
0
    return (s == NULL || *s == L'\0');
443
0
  else if (s == NULL)
444
0
    return (0);
445
446
  /* Leading '^' anchors the start of the pattern. */
447
0
  if ((flags & PATHMATCH_NO_ANCHOR_START) && *p == L'^') {
448
0
    ++p;
449
0
    flags &= ~PATHMATCH_NO_ANCHOR_START;
450
0
  }
451
452
0
  if (*p == L'/' && *s != L'/')
453
0
    return (0);
454
455
  /* Certain patterns anchor implicitly. */
456
0
  if (*p == L'*' || *p == L'/') {
457
0
    while (*p == L'/')
458
0
      ++p;
459
0
    while (*s == L'/')
460
0
      ++s;
461
0
    return (pm_w(p, s, flags, 0));
462
0
  }
463
464
  /* If start is unanchored, try to match start of each path element. */
465
0
  if (flags & PATHMATCH_NO_ANCHOR_START) {
466
0
    for ( ; s != NULL; s = wcschr(s, L'/')) {
467
0
      int r;
468
469
0
      if (*s == L'/')
470
0
        s++;
471
0
      r = pm_w(p, s, flags, 0);
472
0
      if (r)
473
0
        return (r);
474
0
    }
475
0
    return (0);
476
0
  }
477
478
  /* Default: Match from beginning. */
479
0
  return (pm_w(p, s, flags, 0));
480
0
}