Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/php_glob.c
Line
Count
Source
1
/*
2
 * Copyright (c) 1989, 1993
3
 *  The Regents of the University of California.  All rights reserved.
4
 *
5
 * This code is derived from software contributed to Berkeley by
6
 * Guido van Rossum.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 * 3. Neither the name of the University nor the names of its contributors
17
 *    may be used to endorse or promote products derived from this software
18
 *    without specific prior written permission.
19
 *
20
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30
 * SUCH DAMAGE.
31
 */
32
33
/*
34
 * glob(3) -- a superset of the one defined in POSIX 1003.2.
35
 *
36
 * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
37
 *
38
 * Optional extra services, controlled by flags not defined by POSIX:
39
 *
40
 * PHP_GLOB_QUOTE:
41
 *  Escaping convention: \ inhibits any special meaning the following
42
 *  character might have (except \ at end of string is retained).
43
 * PHP_GLOB_MAGCHAR:
44
 *  Set in gl_flags if pattern contained a globbing character.
45
 * PHP_GLOB_NOMAGIC:
46
 *  Same as PHP_GLOB_NOCHECK, but it will only append pattern if it did
47
 *  not contain any magic characters.  [Used in csh style globbing]
48
 * PHP_GLOB_ALTDIRFUNC:
49
 *  Use alternately specified directory access functions.
50
 * PHP_GLOB_TILDE:
51
 *  expand ~user/foo to the /home/dir/of/user/foo
52
 * PHP_GLOB_BRACE:
53
 *  expand {1,2}{a,b} to 1a 1b 2a 2b
54
 * gl_matchc:
55
 *  Number of matches in the current invocation of glob.
56
 */
57
58
#include "php_glob.h"
59
60
#if !(defined(HAVE_GLOB) && defined(PHP_SYSTEM_GLOB))
61
62
#ifdef PHP_WIN32
63
#if _MSC_VER < 1800
64
# define _POSIX_
65
# include <limits.h>
66
# undef _POSIX_
67
#else
68
/* Visual Studio 2013 removed all the _POSIX_ defines, but we depend on some */
69
# ifndef ARG_MAX
70
#  define ARG_MAX 14500
71
# endif
72
#endif
73
# ifndef PATH_MAX
74
#  define PATH_MAX MAXPATHLEN
75
# endif
76
/* Windows defines SIZE_MAX but not SSIZE_MAX */
77
# ifndef SSIZE_MAX
78
#  ifdef _WIN64
79
#   define SSIZE_MAX _I64_MAX
80
#  else
81
#   define SSIZE_MAX INT_MAX
82
#  endif
83
# endif
84
#endif
85
86
#ifndef _PW_BUF_LEN
87
/* XXX: Should be sysconf(_SC_GETPW_R_SIZE_MAX), but then VLA */
88
#define _PW_BUF_LEN 4096
89
#endif
90
91
#include "php.h"
92
#include <sys/stat.h>
93
94
#include <ctype.h>
95
#ifndef PHP_WIN32
96
#include <sys/param.h>
97
#include <dirent.h>
98
#include <pwd.h>
99
#include <unistd.h>
100
#endif
101
#include <errno.h>
102
#include <limits.h>
103
#include <stdint.h>
104
#include <stdio.h>
105
#include <stdlib.h>
106
#include <string.h>
107
108
#include "charclass.h"
109
110
0
#define CIRCUMFLEX  '^'
111
#define DOLLAR    '$'
112
0
#define DOT   '.'
113
0
#define EOS   '\0'
114
0
#define LBRACKET  '['
115
0
#define NOT   '!'
116
0
#define QUESTION  '?'
117
0
#define QUOTE   '\\'
118
0
#define RANGE   '-'
119
0
#define RBRACKET  ']'
120
0
#define SEP   DEFAULT_SLASH
121
0
#define STAR    '*'
122
0
#define TILDE   '~'
123
#define UNDERSCORE  '_'
124
0
#define LBRACE    '{'
125
0
#define RBRACE    '}'
126
0
#define SLASH   '/'
127
0
#define COMMA   ','
128
129
#ifndef DEBUG
130
131
0
#define M_QUOTE   0x8000
132
0
#define M_PROTECT 0x4000
133
0
#define M_MASK    0xffff
134
0
#define M_ASCII   0x00ff
135
136
typedef u_short Char;
137
138
#else
139
140
#define M_QUOTE   0x80
141
#define M_PROTECT 0x40
142
#define M_MASK    0xff
143
#define M_ASCII   0x7f
144
145
typedef char Char;
146
147
#endif
148
149
150
0
#define CHAR(c)   ((Char)((c)&M_ASCII))
151
0
#define META(c)   ((Char)((c)|M_QUOTE))
152
0
#define M_ALL   META('*')
153
0
#define M_END   META(']')
154
0
#define M_NOT   META('!')
155
0
#define M_ONE   META('?')
156
0
#define M_RNG   META('-')
157
0
#define M_SET   META('[')
158
0
#define M_CLASS   META(':')
159
0
#define ismeta(c) (((c)&M_QUOTE) != 0)
160
161
0
#define PHP_GLOB_LIMIT_MALLOC 65536
162
0
#define PHP_GLOB_LIMIT_STAT   2048
163
0
#define PHP_GLOB_LIMIT_READDIR  16384
164
165
struct glob_lim {
166
  size_t  glim_malloc;
167
  size_t  glim_stat;
168
  size_t  glim_readdir;
169
};
170
171
struct glob_path_stat {
172
  char    *gps_path;
173
  zend_stat_t *gps_stat;
174
};
175
176
#ifndef HAVE_REALLOCARRAY
177
/*
178
 * XXX: This is temporary to avoid having reallocarray be imported and part of
179
 * PHP's public API. Since it's only needed here and on Windows, we can just
180
 * put it here for now. Convert this file to ZendMM and remove this function
181
 * when that's complete.
182
 */
183
184
/*  $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */
185
/*
186
 * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
187
 *
188
 * Permission to use, copy, modify, and distribute this software for any
189
 * purpose with or without fee is hereby granted, provided that the above
190
 * copyright notice and this permission notice appear in all copies.
191
 *
192
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
193
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
194
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
195
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
196
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
197
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
198
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
199
 */
200
201
/*
202
 * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
203
 * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
204
 */
205
#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
206
207
static void *
208
reallocarray(void *optr, size_t nmemb, size_t size)
209
{
210
  if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
211
      nmemb > 0 && SIZE_MAX / nmemb < size) {
212
    errno = ENOMEM;
213
    return NULL;
214
  }
215
  return realloc(optr, size * nmemb);
216
}
217
#endif
218
219
static int   compare(const void *, const void *);
220
static int   compare_gps(const void *, const void *);
221
static int   g_Ctoc(const Char *, char *, size_t);
222
static int   g_lstat(Char *, zend_stat_t *, php_glob_t *);
223
static DIR  *g_opendir(Char *, php_glob_t *);
224
static Char *g_strchr(const Char *, int);
225
static int   g_strncmp(const Char *, const char *, size_t);
226
static int   g_stat(Char *, zend_stat_t *, php_glob_t *);
227
static int   glob0(const Char *, php_glob_t *, struct glob_lim *);
228
static int   glob1(Char *, Char *, php_glob_t *, struct glob_lim *);
229
static int   glob2(Char *, Char *, Char *, Char *, Char *, Char *,
230
        php_glob_t *, struct glob_lim *);
231
static int   glob3(Char *, Char *, Char *, Char *, Char *,
232
        Char *, Char *, php_glob_t *, struct glob_lim *);
233
static int   globextend(const Char *, php_glob_t *, struct glob_lim *,
234
        zend_stat_t *);
235
static const Char *globtilde(const Char *, Char *, size_t, php_glob_t *);
236
static int   globexp1(const Char *, php_glob_t *, struct glob_lim *);
237
static int   globexp2(const Char *, const Char *, php_glob_t *,
238
        struct glob_lim *);
239
static int   match(Char *, Char *, Char *);
240
#ifdef DEBUG
241
static void  qprintf(const char *, Char *);
242
#endif
243
244
PHPAPI int php_glob(const char *pattern, int flags, int (*errfunc)(const char *, int), php_glob_t *pglob)
245
0
{
246
0
  const uint8_t *patnext;
247
0
  int c;
248
0
  Char *bufnext, *bufend, patbuf[PATH_MAX];
249
0
  struct glob_lim limit = { 0, 0, 0 };
250
251
#ifdef PHP_WIN32
252
  /* Force skipping escape sequences on windows
253
   * due to the ambiguity with path backslashes
254
   */
255
  flags |= PHP_GLOB_NOESCAPE;
256
#endif
257
258
0
  patnext = (uint8_t *) pattern;
259
0
  if (!(flags & PHP_GLOB_APPEND)) {
260
0
    pglob->gl_pathc = 0;
261
0
    pglob->gl_pathv = NULL;
262
0
    pglob->gl_statv = NULL;
263
0
    if (!(flags & PHP_GLOB_DOOFFS))
264
0
      pglob->gl_offs = 0;
265
0
  }
266
0
  pglob->gl_flags = flags & ~PHP_GLOB_MAGCHAR;
267
0
  pglob->gl_errfunc = errfunc;
268
0
  pglob->gl_matchc = 0;
269
270
0
  if (zend_strnlen(pattern, PATH_MAX) == PATH_MAX)
271
0
    return(PHP_GLOB_NOMATCH);
272
273
0
  if (pglob->gl_offs >= SSIZE_MAX || pglob->gl_pathc >= SSIZE_MAX ||
274
0
    pglob->gl_pathc >= SSIZE_MAX - pglob->gl_offs - 1)
275
0
    return PHP_GLOB_NOSPACE;
276
277
0
  bufnext = patbuf;
278
0
  bufend = bufnext + PATH_MAX - 1;
279
0
  if (flags & PHP_GLOB_NOESCAPE)
280
0
    while (bufnext < bufend && (c = *patnext++) != EOS)
281
0
      *bufnext++ = c;
282
0
  else {
283
    /* Protect the quoted characters. */
284
0
    while (bufnext < bufend && (c = *patnext++) != EOS)
285
0
      if (c == QUOTE) {
286
0
        if ((c = *patnext++) == EOS) {
287
0
          c = QUOTE;
288
0
          --patnext;
289
0
        }
290
0
        *bufnext++ = c | M_PROTECT;
291
0
      } else
292
0
        *bufnext++ = c;
293
0
  }
294
0
  *bufnext = EOS;
295
296
0
  if (flags & PHP_GLOB_BRACE)
297
0
    return globexp1(patbuf, pglob, &limit);
298
0
  else
299
0
    return glob0(patbuf, pglob, &limit);
300
0
}
301
302
/*
303
 * Expand recursively a glob {} pattern. When there is no more expansion
304
 * invoke the standard globbing routine to glob the rest of the magic
305
 * characters
306
 */
307
static int globexp1(const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp)
308
0
{
309
0
  const Char* ptr = pattern;
310
311
  /* Protect a single {}, for find(1), like csh */
312
0
  if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
313
0
    return glob0(pattern, pglob, limitp);
314
315
0
  if ((ptr = (const Char *) g_strchr(ptr, LBRACE)) != NULL)
316
0
    return globexp2(ptr, pattern, pglob, limitp);
317
318
0
  return glob0(pattern, pglob, limitp);
319
0
}
320
321
322
/*
323
 * Recursive brace globbing helper. Tries to expand a single brace.
324
 * If it succeeds then it invokes globexp1 with the new pattern.
325
 * If it fails then it tries to glob the rest of the pattern and returns.
326
 */
327
static int globexp2(const Char *ptr, const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp)
328
0
{
329
0
  int     i, rv;
330
0
  Char   *lm, *ls;
331
0
  const Char *pe, *pm, *pl;
332
0
  Char    patbuf[PATH_MAX];
333
334
  /* copy part up to the brace */
335
0
  for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
336
0
    ;
337
0
  *lm = EOS;
338
0
  ls = lm;
339
340
  /* Find the balanced brace */
341
0
  for (i = 0, pe = ++ptr; *pe; pe++)
342
0
    if (*pe == LBRACKET) {
343
      /* Ignore everything between [] */
344
0
      for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
345
0
        ;
346
0
      if (*pe == EOS) {
347
        /*
348
         * We could not find a matching RBRACKET.
349
         * Ignore and just look for RBRACE
350
         */
351
0
        pe = pm;
352
0
      }
353
0
    } else if (*pe == LBRACE)
354
0
      i++;
355
0
    else if (*pe == RBRACE) {
356
0
      if (i == 0)
357
0
        break;
358
0
      i--;
359
0
    }
360
361
  /* Non matching braces; just glob the pattern */
362
0
  if (i != 0 || *pe == EOS)
363
0
    return glob0(patbuf, pglob, limitp);
364
365
0
  for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
366
0
    const Char *pb;
367
368
0
    switch (*pm) {
369
0
    case LBRACKET:
370
      /* Ignore everything between [] */
371
0
      for (pb = pm++; *pm != RBRACKET && *pm != EOS; pm++)
372
0
        ;
373
0
      if (*pm == EOS) {
374
        /*
375
         * We could not find a matching RBRACKET.
376
         * Ignore and just look for RBRACE
377
         */
378
0
        pm = pb;
379
0
      }
380
0
      break;
381
382
0
    case LBRACE:
383
0
      i++;
384
0
      break;
385
386
0
    case RBRACE:
387
0
      if (i) {
388
0
        i--;
389
0
        break;
390
0
      }
391
      /* FALLTHROUGH */
392
0
    case COMMA:
393
0
      if (i && *pm == COMMA)
394
0
        break;
395
0
      else {
396
        /* Append the current string */
397
0
        for (lm = ls; (pl < pm); *lm++ = *pl++)
398
0
          ;
399
400
        /*
401
         * Append the rest of the pattern after the
402
         * closing brace
403
         */
404
0
        for (pl = pe + 1; (*lm++ = *pl++) != EOS; )
405
0
          ;
406
407
        /* Expand the current pattern */
408
#ifdef DEBUG
409
        qprintf("globexp2:", patbuf);
410
#endif
411
0
        rv = globexp1(patbuf, pglob, limitp);
412
0
        if (rv && rv != PHP_GLOB_NOMATCH)
413
0
          return rv;
414
415
        /* move after the comma, to the next string */
416
0
        pl = pm + 1;
417
0
      }
418
0
      break;
419
420
0
    default:
421
0
      break;
422
0
    }
423
0
  }
424
0
  return 0;
425
0
}
426
427
428
429
/*
430
 * expand tilde from the passwd file.
431
 */
432
static const Char *globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, php_glob_t *pglob)
433
0
{
434
0
#ifndef PHP_WIN32
435
0
  struct passwd pwstore, *pwd = NULL;
436
0
  char pwbuf[_PW_BUF_LEN];
437
0
#endif
438
0
  char *h;
439
0
  const Char *p;
440
0
  Char *b, *eb;
441
442
0
  if (*pattern != TILDE || !(pglob->gl_flags & PHP_GLOB_TILDE))
443
0
    return pattern;
444
445
  /* Copy up to the end of the string or / */
446
0
  eb = &patbuf[patbuf_len - 1];
447
0
  for (p = pattern + 1, h = (char *) patbuf;
448
0
    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
449
0
    ;
450
451
0
  *h = EOS;
452
453
#if 0
454
  if (h == (char *)eb)
455
    return what;
456
#endif
457
458
0
  if (((char *) patbuf)[0] == EOS) {
459
0
#ifndef PHP_WIN32
460
    /*
461
     * handle a plain ~ or ~/ by expanding $HOME
462
     * first and then trying the password file
463
     */
464
#ifdef HAVE_ISSETUGID
465
    if (issetugid() != 0 || (h = getenv("HOME")) == NULL) {
466
#else
467
0
    if ((h = getenv("HOME")) == NULL) {
468
0
#endif
469
0
      getpwuid_r(getuid(), &pwstore, pwbuf, sizeof(pwbuf),
470
0
        &pwd);
471
0
      if (pwd == NULL)
472
0
        return pattern;
473
0
      else
474
0
        h = pwd->pw_dir;
475
0
    }
476
#else
477
    return pattern;
478
#endif
479
0
  } else {
480
    /*
481
     * Expand a ~user
482
     */
483
0
#ifndef PHP_WIN32
484
0
    getpwnam_r((char *)patbuf, &pwstore, pwbuf, sizeof(pwbuf),
485
0
      &pwd);
486
0
    if (pwd == NULL)
487
0
      return pattern;
488
0
    else
489
0
      h = pwd->pw_dir;
490
#else
491
    return pattern;
492
#endif
493
0
  }
494
495
  /* Copy the home directory */
496
0
  for (b = patbuf; b < eb && *h; *b++ = *h++)
497
0
    ;
498
499
  /* Append the rest of the pattern */
500
0
  while (b < eb && (*b++ = *p++) != EOS)
501
0
    ;
502
0
  *b = EOS;
503
504
0
  return patbuf;
505
0
}
506
507
static int g_strncmp(const Char *s1, const char *s2, size_t n)
508
0
{
509
0
  int rv = 0;
510
511
0
  while (n--) {
512
0
    rv = *(Char *)s1 - *(const unsigned char *)s2++;
513
0
    if (rv)
514
0
      break;
515
0
    if (*s1++ == '\0')
516
0
      break;
517
0
  }
518
0
  return rv;
519
0
}
520
521
static int g_charclass(const Char **patternp, Char **bufnextp)
522
0
{
523
0
  const Char *pattern = *patternp + 1;
524
0
  Char *bufnext = *bufnextp;
525
0
  const Char *colon;
526
0
  const struct cclass *cc;
527
0
  size_t len;
528
529
0
  if ((colon = g_strchr(pattern, ':')) == NULL || colon[1] != ']')
530
0
    return 1; /* not a character class */
531
532
0
  len = (size_t)(colon - pattern);
533
0
  for (cc = cclasses; cc->name != NULL; cc++) {
534
0
    if (!g_strncmp(pattern, cc->name, len) && cc->name[len] == '\0')
535
0
      break;
536
0
  }
537
0
  if (cc->name == NULL)
538
0
    return -1; /* invalid character class */
539
0
  *bufnext++ = M_CLASS;
540
0
  *bufnext++ = (Char)(cc - &cclasses[0]);
541
0
  *bufnextp = bufnext;
542
0
  *patternp += len + 3;
543
544
0
  return 0;
545
0
}
546
547
/*
548
 * The main glob() routine: compiles the pattern (optionally processing
549
 * quotes), calls glob1() to do the real pattern matching, and finally
550
 * sorts the list (unless unsorted operation is requested).  Returns 0
551
 * if things went well, nonzero if errors occurred.  It is not an error
552
 * to find no matches.
553
 */
554
static int glob0(const Char *pattern, php_glob_t *pglob, struct glob_lim *limitp)
555
0
{
556
0
  const Char *qpatnext;
557
0
  int c, err;
558
0
  size_t oldpathc;
559
0
  Char *bufnext, patbuf[PATH_MAX];
560
561
0
  qpatnext = globtilde(pattern, patbuf, PATH_MAX, pglob);
562
0
  oldpathc = pglob->gl_pathc;
563
0
  bufnext = patbuf;
564
565
  /* We don't need to check for buffer overflow any more. */
566
0
  while ((c = *qpatnext++) != EOS) {
567
0
    switch (c) {
568
0
    case LBRACKET:
569
0
      c = *qpatnext;
570
      /*
571
       * Not in OpenBSD's glob(), keep it when syncing: like fnmatch(),
572
       * treat a leading '^' as '!'.
573
       */
574
0
      if (c == CIRCUMFLEX)
575
0
        c = NOT;
576
0
      if (c == NOT)
577
0
        ++qpatnext;
578
0
      if (*qpatnext == EOS ||
579
0
        g_strchr(qpatnext+1, RBRACKET) == NULL) {
580
0
        *bufnext++ = LBRACKET;
581
0
        if (c == NOT)
582
0
          --qpatnext;
583
0
        break;
584
0
      }
585
0
      *bufnext++ = M_SET;
586
0
      if (c == NOT)
587
0
        *bufnext++ = M_NOT;
588
0
      c = *qpatnext++;
589
0
      do {
590
0
        if (c == LBRACKET && *qpatnext == ':') {
591
0
          do {
592
0
            err = g_charclass(&qpatnext,
593
0
              &bufnext);
594
0
            if (err)
595
0
              break;
596
0
            c = *qpatnext++;
597
0
          } while (c == LBRACKET && *qpatnext == ':');
598
0
          if (err == -1 &&
599
0
            !(pglob->gl_flags & PHP_GLOB_NOCHECK))
600
0
            return PHP_GLOB_NOMATCH;
601
0
          if (c == RBRACKET)
602
0
            break;
603
0
        }
604
0
        *bufnext++ = CHAR(c);
605
0
        if (*qpatnext == RANGE &&
606
0
          (c = qpatnext[1]) != RBRACKET) {
607
0
          *bufnext++ = M_RNG;
608
0
          *bufnext++ = CHAR(c);
609
0
          qpatnext += 2;
610
0
        }
611
0
      } while ((c = *qpatnext++) != RBRACKET);
612
0
      pglob->gl_flags |= PHP_GLOB_MAGCHAR;
613
0
      *bufnext++ = M_END;
614
0
      break;
615
0
    case QUESTION:
616
0
      pglob->gl_flags |= PHP_GLOB_MAGCHAR;
617
0
      *bufnext++ = M_ONE;
618
0
      break;
619
0
    case STAR:
620
0
      pglob->gl_flags |= PHP_GLOB_MAGCHAR;
621
      /* collapse adjacent stars to one,
622
       * to avoid exponential behavior
623
       */
624
0
      if (bufnext == patbuf || bufnext[-1] != M_ALL)
625
0
        *bufnext++ = M_ALL;
626
0
      break;
627
0
    default:
628
0
      *bufnext++ = CHAR(c);
629
0
      break;
630
0
    }
631
0
  }
632
0
  *bufnext = EOS;
633
#ifdef DEBUG
634
  qprintf("glob0:", patbuf);
635
#endif
636
637
0
  if ((err = glob1(patbuf, patbuf+PATH_MAX-1, pglob, limitp)) != 0)
638
0
    return(err);
639
640
  /*
641
   * If there was no match we are going to append the pattern
642
   * if PHP_GLOB_NOCHECK was specified or if PHP_GLOB_NOMAGIC was specified
643
   * and the pattern did not contain any magic characters
644
   * PHP_GLOB_NOMAGIC is there just for compatibility with csh.
645
   */
646
0
  if (pglob->gl_pathc == oldpathc) {
647
0
    if ((pglob->gl_flags & PHP_GLOB_NOCHECK) ||
648
0
      ((pglob->gl_flags & PHP_GLOB_NOMAGIC) &&
649
0
      !(pglob->gl_flags & PHP_GLOB_MAGCHAR)))
650
0
      return(globextend(pattern, pglob, limitp, NULL));
651
0
    else
652
0
      return(PHP_GLOB_NOMATCH);
653
0
  }
654
0
  if (!(pglob->gl_flags & PHP_GLOB_NOSORT)) {
655
0
    if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT)) {
656
      /* Keep the paths and stat info synced during sort */
657
0
      struct glob_path_stat *path_stat;
658
0
      size_t i;
659
0
      size_t n = pglob->gl_pathc - oldpathc;
660
0
      size_t o = pglob->gl_offs + oldpathc;
661
662
0
      if ((path_stat = calloc(n, sizeof(*path_stat))) == NULL)
663
0
        return PHP_GLOB_NOSPACE;
664
0
      for (i = 0; i < n; i++) {
665
0
        path_stat[i].gps_path = pglob->gl_pathv[o + i];
666
0
        path_stat[i].gps_stat = pglob->gl_statv[o + i];
667
0
      }
668
0
      qsort(path_stat, n, sizeof(*path_stat), compare_gps);
669
0
      for (i = 0; i < n; i++) {
670
0
        pglob->gl_pathv[o + i] = path_stat[i].gps_path;
671
0
        pglob->gl_statv[o + i] = path_stat[i].gps_stat;
672
0
      }
673
0
      free(path_stat);
674
0
    } else {
675
0
      qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
676
0
        pglob->gl_pathc - oldpathc, sizeof(char *),
677
0
        compare);
678
0
    }
679
0
  }
680
0
  return(0);
681
0
}
682
683
static int compare(const void *p, const void *q)
684
0
{
685
0
  return(strcmp(*(char **)p, *(char **)q));
686
0
}
687
688
static int compare_gps(const void *_p, const void *_q)
689
0
{
690
0
  const struct glob_path_stat *p = (const struct glob_path_stat *)_p;
691
0
  const struct glob_path_stat *q = (const struct glob_path_stat *)_q;
692
693
0
  return(strcmp(p->gps_path, q->gps_path));
694
0
}
695
696
static int glob1(Char *pattern, Char *pattern_last, php_glob_t *pglob, struct glob_lim *limitp)
697
0
{
698
0
  Char pathbuf[PATH_MAX];
699
700
  /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
701
0
  if (*pattern == EOS)
702
0
    return(0);
703
0
  return(glob2(pathbuf, pathbuf+PATH_MAX-1,
704
0
    pathbuf, pathbuf+PATH_MAX-1,
705
0
    pattern, pattern_last, pglob, limitp));
706
0
}
707
708
/*
709
 * The functions glob2 and glob3 are mutually recursive; there is one level
710
 * of recursion for each segment in the pattern that contains one or more
711
 * meta characters.
712
 */
713
static int glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *pattern_last, php_glob_t *pglob, struct glob_lim *limitp)
714
0
{
715
0
  zend_stat_t sb;
716
0
  Char *p, *q;
717
0
  int anymeta;
718
719
  /*
720
   * Loop over pattern segments until end of pattern or until
721
   * segment with meta character found.
722
   */
723
0
  for (anymeta = 0;;) {
724
0
    if (*pattern == EOS) {   /* End of pattern? */
725
0
      *pathend = EOS;
726
727
0
      if ((pglob->gl_flags & PHP_GLOB_LIMIT) &&
728
0
        limitp->glim_stat++ >= PHP_GLOB_LIMIT_STAT) {
729
0
        errno = 0;
730
0
        *pathend++ = SEP;
731
0
        *pathend = EOS;
732
0
        return(PHP_GLOB_NOSPACE);
733
0
      }
734
0
      if (g_lstat(pathbuf, &sb, pglob))
735
0
        return(0);
736
737
0
      if (((pglob->gl_flags & PHP_GLOB_MARK) &&
738
0
        pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) ||
739
0
        (S_ISLNK(sb.st_mode) &&
740
0
        (g_stat(pathbuf, &sb, pglob) == 0) &&
741
0
        S_ISDIR(sb.st_mode)))) {
742
0
        if (pathend+1 > pathend_last)
743
0
          return (1);
744
0
        *pathend++ = SEP;
745
0
        *pathend = EOS;
746
0
      }
747
0
      ++pglob->gl_matchc;
748
0
      return(globextend(pathbuf, pglob, limitp, &sb));
749
0
    }
750
751
    /* Find end of next segment, copy tentatively to pathend. */
752
0
    q = pathend;
753
0
    p = pattern;
754
0
    while (*p != EOS && !IS_SLASH(*p)) {
755
0
      if (ismeta(*p))
756
0
        anymeta = 1;
757
0
      if (q+1 > pathend_last)
758
0
        return (1);
759
0
      *q++ = *p++;
760
0
    }
761
762
0
    if (!anymeta) {   /* No expansion, do next segment. */
763
0
      pathend = q;
764
0
      pattern = p;
765
0
      while (IS_SLASH(*pattern)) {
766
0
        if (pathend+1 > pathend_last)
767
0
          return (1);
768
0
        *pathend++ = *pattern++;
769
0
      }
770
0
    } else
771
      /* Need expansion, recurse. */
772
0
      return(glob3(pathbuf, pathbuf_last, pathend,
773
0
        pathend_last, pattern, p, pattern_last,
774
0
        pglob, limitp));
775
0
  }
776
  /* NOTREACHED */
777
0
}
778
779
static int glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last, Char *pattern, Char *restpattern, Char *restpattern_last, php_glob_t *pglob, struct glob_lim *limitp)
780
0
{
781
0
  struct dirent *dp;
782
0
  DIR *dirp;
783
0
  int err;
784
0
  char buf[PATH_MAX];
785
786
  /*
787
   * The readdirfunc declaration can't be prototyped, because it is
788
   * assigned, below, to two functions which are prototyped in glob.h
789
   * and dirent.h as taking pointers to differently typed opaque
790
   * structures.
791
   */
792
0
  struct dirent *(*readdirfunc)(void *);
793
794
0
  if (pathend > pathend_last)
795
0
    return (1);
796
0
  *pathend = EOS;
797
0
  errno = 0;
798
799
0
  if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
800
    /* TODO: don't call for ENOENT or ENOTDIR? */
801
0
    if (pglob->gl_errfunc) {
802
0
      if (g_Ctoc(pathbuf, buf, sizeof(buf)))
803
0
        return(PHP_GLOB_ABORTED);
804
0
      if (pglob->gl_errfunc(buf, errno) ||
805
0
        pglob->gl_flags & PHP_GLOB_ERR)
806
0
        return(PHP_GLOB_ABORTED);
807
0
    }
808
0
    return(0);
809
0
  }
810
811
0
  err = 0;
812
813
  /* Search directory for matching names. */
814
0
  if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC)
815
0
    readdirfunc = pglob->gl_readdir;
816
0
  else
817
0
    readdirfunc = (struct dirent *(*)(void *))readdir;
818
0
  while ((dp = (*readdirfunc)(dirp))) {
819
0
    uint8_t *sc;
820
0
    Char *dc;
821
822
0
    if ((pglob->gl_flags & PHP_GLOB_LIMIT) &&
823
0
      limitp->glim_readdir++ >= PHP_GLOB_LIMIT_READDIR) {
824
0
      errno = 0;
825
0
      *pathend++ = SEP;
826
0
      *pathend = EOS;
827
0
      err = PHP_GLOB_NOSPACE;
828
0
      break;
829
0
    }
830
831
    /* Initial DOT must be matched literally. */
832
0
    if (dp->d_name[0] == DOT && *pattern != DOT)
833
0
      continue;
834
0
    dc = pathend;
835
0
    sc = (uint8_t *) dp->d_name;
836
0
    while (dc < pathend_last && (*dc++ = *sc++) != EOS)
837
0
      ;
838
0
    if (dc >= pathend_last) {
839
0
      *dc = EOS;
840
0
      err = 1;
841
0
      break;
842
0
    }
843
844
0
    if (!match(pathend, pattern, restpattern)) {
845
0
      *pathend = EOS;
846
0
      continue;
847
0
    }
848
0
    err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
849
0
      restpattern, restpattern_last, pglob, limitp);
850
0
    if (err)
851
0
      break;
852
0
  }
853
854
0
  if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC)
855
0
    (*pglob->gl_closedir)(dirp);
856
0
  else
857
0
    closedir(dirp);
858
0
  return(err);
859
0
}
860
861
862
/*
863
 * Extend the gl_pathv member of a php_glob_t structure to accommodate a new item,
864
 * add the new item, and update gl_pathc.
865
 *
866
 * This assumes the BSD realloc, which only copies the block when its size
867
 * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
868
 * behavior.
869
 *
870
 * Return 0 if new item added, error code if memory couldn't be allocated.
871
 *
872
 * Invariant of the php_glob_t structure:
873
 *  Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
874
 *  gl_pathv points to (gl_offs + gl_pathc + 1) items.
875
 */
876
static int globextend(const Char *path, php_glob_t *pglob, struct glob_lim *limitp, zend_stat_t *sb)
877
0
{
878
0
  char **pathv;
879
0
  size_t i, newn, len;
880
0
  char *copy = NULL;
881
0
  const Char *p;
882
0
  zend_stat_t **statv;
883
884
0
  newn = 2 + pglob->gl_pathc + pglob->gl_offs;
885
0
  if (pglob->gl_offs >= SSIZE_MAX ||
886
0
    pglob->gl_pathc >= SSIZE_MAX ||
887
0
    newn >= SSIZE_MAX ||
888
0
    SIZE_MAX / sizeof(*pathv) <= newn ||
889
0
    SIZE_MAX / sizeof(*statv) <= newn) {
890
0
 nospace:
891
0
    for (i = pglob->gl_offs; i < newn - 2; i++) {
892
0
      if (pglob->gl_pathv && pglob->gl_pathv[i])
893
0
        free(pglob->gl_pathv[i]);
894
0
      if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT) != 0 &&
895
0
        pglob->gl_pathv && pglob->gl_pathv[i])
896
0
        free(pglob->gl_statv[i]);
897
0
    }
898
0
    free(pglob->gl_pathv);
899
0
    pglob->gl_pathv = NULL;
900
0
    free(pglob->gl_statv);
901
0
    pglob->gl_statv = NULL;
902
0
    return(PHP_GLOB_NOSPACE);
903
0
  }
904
905
0
  pathv = reallocarray(pglob->gl_pathv, newn, sizeof(*pathv));
906
0
  if (pathv == NULL)
907
0
    goto nospace;
908
0
  if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
909
    /* first time around -- clear initial gl_offs items */
910
0
    pathv += pglob->gl_offs;
911
0
    for (i = pglob->gl_offs; i > 0; i--)
912
0
      *--pathv = NULL;
913
0
  }
914
0
  pglob->gl_pathv = pathv;
915
916
0
  if ((pglob->gl_flags & PHP_GLOB_KEEPSTAT) != 0) {
917
0
    statv = reallocarray(pglob->gl_statv, newn, sizeof(*statv));
918
0
    if (statv == NULL)
919
0
      goto nospace;
920
0
    if (pglob->gl_statv == NULL && pglob->gl_offs > 0) {
921
      /* first time around -- clear initial gl_offs items */
922
0
      statv += pglob->gl_offs;
923
0
      for (i = pglob->gl_offs; i > 0; i--)
924
0
        *--statv = NULL;
925
0
    }
926
0
    pglob->gl_statv = statv;
927
0
    if (sb == NULL)
928
0
      statv[pglob->gl_offs + pglob->gl_pathc] = NULL;
929
0
    else {
930
0
      limitp->glim_malloc += sizeof(**statv);
931
0
      if ((pglob->gl_flags & PHP_GLOB_LIMIT) &&
932
0
        limitp->glim_malloc >= PHP_GLOB_LIMIT_MALLOC) {
933
0
        errno = 0;
934
0
        return(PHP_GLOB_NOSPACE);
935
0
      }
936
0
      if ((statv[pglob->gl_offs + pglob->gl_pathc] =
937
0
        malloc(sizeof(**statv))) == NULL)
938
0
        goto copy_error;
939
0
      memcpy(statv[pglob->gl_offs + pglob->gl_pathc], sb,
940
0
        sizeof(*sb));
941
0
    }
942
0
    statv[pglob->gl_offs + pglob->gl_pathc + 1] = NULL;
943
0
  }
944
945
0
  for (p = path; *p++;)
946
0
    ;
947
0
  len = (size_t)(p - path);
948
0
  limitp->glim_malloc += len;
949
0
  if ((copy = malloc(len)) != NULL) {
950
0
    if (g_Ctoc(path, copy, len)) {
951
0
      free(copy);
952
0
      return(PHP_GLOB_NOSPACE);
953
0
    }
954
0
    pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
955
0
  }
956
0
  pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
957
958
0
  if ((pglob->gl_flags & PHP_GLOB_LIMIT) &&
959
0
    (newn * sizeof(*pathv)) + limitp->glim_malloc >
960
0
    PHP_GLOB_LIMIT_MALLOC) {
961
0
    errno = 0;
962
0
    return(PHP_GLOB_NOSPACE);
963
0
  }
964
0
 copy_error:
965
0
  return(copy == NULL ? PHP_GLOB_NOSPACE : 0);
966
0
}
967
968
969
/*
970
 * pattern matching function for filenames.  Each occurrence of the *
971
 * pattern causes an iteration.
972
 *
973
 * Note, this function differs from the original as per the discussion
974
 * here: https://research.swtch.com/glob
975
 *
976
 * Basically we removed the recursion and made it use the algorithm
977
 * from Russ Cox to not go quadratic on cases like a file called
978
 * ("a" x 100) . "x" matched against a pattern like "a*a*a*a*a*a*a*y".
979
 */
980
static int match(Char *name, Char *pat, Char *patend)
981
0
{
982
0
  int ok, negate_range;
983
0
  Char c, k;
984
0
  Char *nextp = NULL;
985
0
  Char *nextn = NULL;
986
987
0
loop:
988
0
  while (pat < patend) {
989
0
    c = *pat++;
990
0
    switch (c & M_MASK) {
991
0
    case M_ALL:
992
0
      while (pat < patend && (*pat & M_MASK) == M_ALL)
993
0
        pat++; /* eat consecutive '*' */
994
0
      if (pat == patend)
995
0
        return(1);
996
0
      if (*name == EOS)
997
0
        return(0);
998
0
      nextn = name + 1;
999
0
      nextp = pat - 1;
1000
0
      break;
1001
0
    case M_ONE:
1002
0
      if (*name++ == EOS)
1003
0
        goto fail;
1004
0
      break;
1005
0
    case M_SET:
1006
0
      ok = 0;
1007
0
      if ((k = *name++) == EOS)
1008
0
        goto fail;
1009
0
      if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
1010
0
        ++pat;
1011
0
      while (((c = *pat++) & M_MASK) != M_END) {
1012
0
        if ((c & M_MASK) == M_CLASS) {
1013
0
          Char idx = *pat & M_MASK;
1014
0
          if (idx < NCCLASSES &&
1015
0
            cclasses[idx].isctype(k))
1016
0
            ok = 1;
1017
0
          ++pat;
1018
0
        }
1019
0
        if ((*pat & M_MASK) == M_RNG) {
1020
0
          if (c <= k && k <= pat[1])
1021
0
            ok = 1;
1022
0
          pat += 2;
1023
0
        } else if (c == k)
1024
0
          ok = 1;
1025
0
      }
1026
0
      if (ok == negate_range)
1027
0
        goto fail;
1028
0
      break;
1029
0
    default:
1030
0
      if (*name++ != c)
1031
0
        goto fail;
1032
0
      break;
1033
0
    }
1034
0
  }
1035
0
  if (*name == EOS)
1036
0
    return(1);
1037
1038
0
fail:
1039
0
  if (nextn) {
1040
0
    pat = nextp;
1041
0
    name = nextn;
1042
0
    goto loop;
1043
0
  }
1044
0
  return(0);
1045
0
}
1046
1047
/* Free allocated data belonging to a php_glob_t structure. */
1048
PHPAPI void php_globfree(php_glob_t *pglob)
1049
0
{
1050
0
  size_t i;
1051
0
  char **pp;
1052
1053
0
  if (pglob->gl_pathv != NULL) {
1054
0
    pp = pglob->gl_pathv + pglob->gl_offs;
1055
0
    for (i = pglob->gl_pathc; i--; ++pp)
1056
0
      free(*pp);
1057
0
    free(pglob->gl_pathv);
1058
0
    pglob->gl_pathv = NULL;
1059
0
  }
1060
0
  if (pglob->gl_statv != NULL) {
1061
0
    for (i = 0; i < pglob->gl_pathc; i++) {
1062
0
      free(pglob->gl_statv[i]);
1063
0
    }
1064
0
    free(pglob->gl_statv);
1065
0
    pglob->gl_statv = NULL;
1066
0
  }
1067
0
}
1068
1069
static DIR *g_opendir(Char *str, php_glob_t *pglob)
1070
0
{
1071
0
  char buf[PATH_MAX];
1072
1073
0
  if (!*str)
1074
0
    strlcpy(buf, ".", sizeof buf);
1075
0
  else {
1076
0
    if (g_Ctoc(str, buf, sizeof(buf)))
1077
0
      return(NULL);
1078
0
  }
1079
1080
0
  if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC)
1081
0
    return((*pglob->gl_opendir)(buf));
1082
1083
0
  return(opendir(buf));
1084
0
}
1085
1086
static int g_lstat(Char *fn, zend_stat_t *sb, php_glob_t *pglob)
1087
0
{
1088
0
  char buf[PATH_MAX];
1089
1090
0
  if (g_Ctoc(fn, buf, sizeof(buf)))
1091
0
    return(-1);
1092
0
  if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC)
1093
0
    return((*pglob->gl_lstat)(buf, sb));
1094
0
  return(php_sys_lstat(buf, sb));
1095
0
}
1096
1097
static int g_stat(Char *fn, zend_stat_t *sb, php_glob_t *pglob)
1098
0
{
1099
0
  char buf[PATH_MAX];
1100
1101
0
  if (g_Ctoc(fn, buf, sizeof(buf)))
1102
0
    return(-1);
1103
0
  if (pglob->gl_flags & PHP_GLOB_ALTDIRFUNC)
1104
0
    return((*pglob->gl_stat)(buf, sb));
1105
0
  return(php_sys_stat(buf, sb));
1106
0
}
1107
1108
static Char *g_strchr(const Char *str, int ch)
1109
0
{
1110
0
  do {
1111
0
    if (*str == ch)
1112
0
      return ((Char *)str);
1113
0
  } while (*str++);
1114
0
  return (NULL);
1115
0
}
1116
1117
static int g_Ctoc(const Char *str, char *buf, size_t len)
1118
0
{
1119
1120
0
  while (len--) {
1121
0
    if ((*buf++ = *str++) == EOS)
1122
0
      return (0);
1123
0
  }
1124
0
  return (1);
1125
0
}
1126
1127
#ifdef DEBUG
1128
static void qprintf(const char *str, Char *s)
1129
{
1130
  Char *p;
1131
1132
  (void)printf("%s:\n", str);
1133
  for (p = s; *p; p++)
1134
    (void)printf("%c", CHAR(*p));
1135
  (void)printf("\n");
1136
  for (p = s; *p; p++)
1137
    (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
1138
  (void)printf("\n");
1139
  for (p = s; *p; p++)
1140
    (void)printf("%c", ismeta(*p) ? '_' : ' ');
1141
  (void)printf("\n");
1142
}
1143
#endif
1144
1145
#endif /* defined(HAVE_GLOB) */