Coverage Report

Created: 2022-10-14 11:23

/src/php-src/ext/standard/dir.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | http://www.php.net/license/3_01.txt                                  |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Author: Thies C. Arntzen <thies@thieso.net>                          |
14
   +----------------------------------------------------------------------+
15
 */
16
17
/* {{{ includes/startup/misc */
18
19
#include "php.h"
20
#include "fopen_wrappers.h"
21
#include "file.h"
22
#include "php_dir.h"
23
#include "php_string.h"
24
#include "php_scandir.h"
25
#include "basic_functions.h"
26
#include "dir_arginfo.h"
27
28
#if HAVE_UNISTD_H
29
#include <unistd.h>
30
#endif
31
32
#include <errno.h>
33
34
#ifdef PHP_WIN32
35
#include "win32/readdir.h"
36
#endif
37
38
39
#ifdef HAVE_GLOB
40
#ifndef PHP_WIN32
41
#include <glob.h>
42
#else
43
#include "win32/glob.h"
44
#endif
45
#endif
46
47
typedef struct {
48
  zend_resource *default_dir;
49
} php_dir_globals;
50
51
#ifdef ZTS
52
#define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v)
53
int dir_globals_id;
54
#else
55
390k
#define DIRG(v) (dir_globals.v)
56
php_dir_globals dir_globals;
57
#endif
58
59
static zend_class_entry *dir_class_entry_ptr;
60
61
#define FETCH_DIRP() \
62
0
  ZEND_PARSE_PARAMETERS_START(0, 1) \
63
0
    Z_PARAM_OPTIONAL \
64
0
    Z_PARAM_RESOURCE(id) \
65
0
  ZEND_PARSE_PARAMETERS_END(); \
66
0
  if (ZEND_NUM_ARGS() == 0) { \
67
0
    myself = getThis(); \
68
0
    if (myself) { \
69
0
      if ((tmp = zend_hash_str_find(Z_OBJPROP_P(myself), "handle", sizeof("handle")-1)) == NULL) { \
70
0
        zend_throw_error(NULL, "Unable to find my handle property"); \
71
0
        RETURN_THROWS(); \
72
0
      } \
73
0
      if ((dirp = (php_stream *)zend_fetch_resource_ex(tmp, "Directory", php_file_le_stream())) == NULL) { \
74
0
        RETURN_THROWS(); \
75
0
      } \
76
0
    } else { \
77
0
      if (!DIRG(default_dir)) { \
78
0
        zend_type_error("No resource supplied"); \
79
0
        RETURN_THROWS(); \
80
0
      } else if ((dirp = (php_stream *)zend_fetch_resource(DIRG(default_dir), "Directory", php_file_le_stream())) == NULL) { \
81
0
        RETURN_THROWS(); \
82
0
      } \
83
0
    } \
84
0
  } else { \
85
0
    if ((dirp = (php_stream *)zend_fetch_resource(Z_RES_P(id), "Directory", php_file_le_stream())) == NULL) { \
86
0
      RETURN_THROWS(); \
87
0
    } \
88
0
  }
89
90
91
static void php_set_default_dir(zend_resource *res)
92
0
{
93
0
  if (DIRG(default_dir)) {
94
0
    zend_list_delete(DIRG(default_dir));
95
0
  }
96
97
0
  if (res) {
98
0
    GC_ADDREF(res);
99
0
  }
100
101
0
  DIRG(default_dir) = res;
102
0
}
103
104
PHP_RINIT_FUNCTION(dir)
105
390k
{
106
390k
  DIRG(default_dir) = NULL;
107
390k
  return SUCCESS;
108
390k
}
109
110
PHP_MINIT_FUNCTION(dir)
111
3.47k
{
112
3.47k
  static char dirsep_str[2], pathsep_str[2];
113
3.47k
  zend_class_entry dir_class_entry;
114
115
3.47k
  INIT_CLASS_ENTRY(dir_class_entry, "Directory", class_Directory_methods);
116
3.47k
  dir_class_entry_ptr = zend_register_internal_class(&dir_class_entry);
117
118
#ifdef ZTS
119
  ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
120
#endif
121
122
3.47k
  dirsep_str[0] = DEFAULT_SLASH;
123
3.47k
  dirsep_str[1] = '\0';
124
3.47k
  REGISTER_STRING_CONSTANT("DIRECTORY_SEPARATOR", dirsep_str, CONST_CS|CONST_PERSISTENT);
125
126
3.47k
  pathsep_str[0] = ZEND_PATHS_SEPARATOR;
127
3.47k
  pathsep_str[1] = '\0';
128
3.47k
  REGISTER_STRING_CONSTANT("PATH_SEPARATOR", pathsep_str, CONST_CS|CONST_PERSISTENT);
129
130
3.47k
  REGISTER_LONG_CONSTANT("SCANDIR_SORT_ASCENDING",  PHP_SCANDIR_SORT_ASCENDING,  CONST_CS | CONST_PERSISTENT);
131
3.47k
  REGISTER_LONG_CONSTANT("SCANDIR_SORT_DESCENDING", PHP_SCANDIR_SORT_DESCENDING, CONST_CS | CONST_PERSISTENT);
132
3.47k
  REGISTER_LONG_CONSTANT("SCANDIR_SORT_NONE",       PHP_SCANDIR_SORT_NONE,       CONST_CS | CONST_PERSISTENT);
133
134
3.47k
#ifdef HAVE_GLOB
135
136
3.47k
#ifdef GLOB_BRACE
137
3.47k
  REGISTER_LONG_CONSTANT("GLOB_BRACE", GLOB_BRACE, CONST_CS | CONST_PERSISTENT);
138
#else
139
# define GLOB_BRACE 0
140
#endif
141
142
3.47k
#ifdef GLOB_MARK
143
3.47k
  REGISTER_LONG_CONSTANT("GLOB_MARK", GLOB_MARK, CONST_CS | CONST_PERSISTENT);
144
#else
145
# define GLOB_MARK 0
146
#endif
147
148
3.47k
#ifdef GLOB_NOSORT
149
3.47k
  REGISTER_LONG_CONSTANT("GLOB_NOSORT", GLOB_NOSORT, CONST_CS | CONST_PERSISTENT);
150
#else
151
# define GLOB_NOSORT 0
152
#endif
153
154
3.47k
#ifdef GLOB_NOCHECK
155
3.47k
  REGISTER_LONG_CONSTANT("GLOB_NOCHECK", GLOB_NOCHECK, CONST_CS | CONST_PERSISTENT);
156
#else
157
# define GLOB_NOCHECK 0
158
#endif
159
160
3.47k
#ifdef GLOB_NOESCAPE
161
3.47k
  REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", GLOB_NOESCAPE, CONST_CS | CONST_PERSISTENT);
162
#else
163
# define GLOB_NOESCAPE 0
164
#endif
165
166
3.47k
#ifdef GLOB_ERR
167
3.47k
  REGISTER_LONG_CONSTANT("GLOB_ERR", GLOB_ERR, CONST_CS | CONST_PERSISTENT);
168
#else
169
# define GLOB_ERR 0
170
#endif
171
172
#ifndef GLOB_ONLYDIR
173
# define GLOB_ONLYDIR (1<<30)
174
# define GLOB_EMULATE_ONLYDIR
175
# define GLOB_FLAGMASK (~GLOB_ONLYDIR)
176
#else
177
0
# define GLOB_FLAGMASK (~0)
178
3.47k
#endif
179
180
/* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */
181
0
#define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR)
182
183
3.47k
  REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", GLOB_ONLYDIR, CONST_CS | CONST_PERSISTENT);
184
3.47k
  REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", GLOB_AVAILABLE_FLAGS, CONST_CS | CONST_PERSISTENT);
185
186
3.47k
#endif /* HAVE_GLOB */
187
188
3.47k
  return SUCCESS;
189
3.47k
}
190
/* }}} */
191
192
/* {{{ internal functions */
193
static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
194
0
{
195
0
  char *dirname;
196
0
  size_t dir_len;
197
0
  zval *zcontext = NULL;
198
0
  php_stream_context *context = NULL;
199
0
  php_stream *dirp;
200
201
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
202
0
    Z_PARAM_PATH(dirname, dir_len)
203
0
    Z_PARAM_OPTIONAL
204
0
    Z_PARAM_RESOURCE(zcontext)
205
0
  ZEND_PARSE_PARAMETERS_END();
206
207
0
  context = php_stream_context_from_zval(zcontext, 0);
208
209
0
  dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
210
211
0
  if (dirp == NULL) {
212
0
    RETURN_FALSE;
213
0
  }
214
215
0
  dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
216
217
0
  php_set_default_dir(dirp->res);
218
219
0
  if (createobject) {
220
0
    object_init_ex(return_value, dir_class_entry_ptr);
221
0
    add_property_stringl(return_value, "path", dirname, dir_len);
222
0
    add_property_resource(return_value, "handle", dirp->res);
223
0
    php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
224
0
  } else {
225
0
    php_stream_to_zval(dirp, return_value);
226
0
  }
227
0
}
228
/* }}} */
229
230
/* {{{ proto resource|false opendir(string path[, resource context])
231
   Open a directory and return a dir_handle */
232
PHP_FUNCTION(opendir)
233
0
{
234
0
  _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
235
0
}
236
/* }}} */
237
238
/* {{{ proto object|false dir(string directory[, resource context])
239
   Directory class with properties, handle and class and methods read, rewind and close */
240
PHP_FUNCTION(getdir)
241
0
{
242
0
  _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
243
0
}
244
/* }}} */
245
246
/* {{{ proto bool closedir([resource dir_handle])
247
   Close directory connection identified by the dir_handle */
248
PHP_FUNCTION(closedir)
249
0
{
250
0
  zval *id = NULL, *tmp, *myself;
251
0
  php_stream *dirp;
252
0
  zend_resource *res;
253
254
0
  FETCH_DIRP();
255
256
0
  if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
257
0
    zend_argument_type_error(1, "must be a valid Directory resource");
258
0
    RETURN_THROWS();
259
0
  }
260
261
0
  res = dirp->res;
262
0
  zend_list_close(dirp->res);
263
264
0
  if (res == DIRG(default_dir)) {
265
0
    php_set_default_dir(NULL);
266
0
  }
267
0
}
268
/* }}} */
269
270
#if defined(HAVE_CHROOT) && !defined(ZTS) && ENABLE_CHROOT_FUNC
271
/* {{{ proto bool chroot(string directory)
272
   Change root directory */
273
PHP_FUNCTION(chroot)
274
{
275
  char *str;
276
  int ret;
277
  size_t str_len;
278
279
  ZEND_PARSE_PARAMETERS_START(1, 1)
280
    Z_PARAM_PATH(str, str_len)
281
  ZEND_PARSE_PARAMETERS_END();
282
283
  ret = chroot(str);
284
  if (ret != 0) {
285
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
286
    RETURN_FALSE;
287
  }
288
289
  php_clear_stat_cache(1, NULL, 0);
290
291
  ret = chdir("/");
292
293
  if (ret != 0) {
294
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
295
    RETURN_FALSE;
296
  }
297
298
  RETURN_TRUE;
299
}
300
/* }}} */
301
#endif
302
303
/* {{{ proto bool chdir(string directory)
304
   Change the current directory */
305
PHP_FUNCTION(chdir)
306
0
{
307
0
  char *str;
308
0
  int ret;
309
0
  size_t str_len;
310
311
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
312
0
    Z_PARAM_PATH(str, str_len)
313
0
  ZEND_PARSE_PARAMETERS_END();
314
315
0
  if (php_check_open_basedir(str)) {
316
0
    RETURN_FALSE;
317
0
  }
318
0
  ret = VCWD_CHDIR(str);
319
320
0
  if (ret != 0) {
321
0
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
322
0
    RETURN_FALSE;
323
0
  }
324
325
0
  if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentStatFile), strlen(BG(CurrentStatFile)))) {
326
0
    efree(BG(CurrentStatFile));
327
0
    BG(CurrentStatFile) = NULL;
328
0
  }
329
0
  if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentLStatFile), strlen(BG(CurrentLStatFile)))) {
330
0
    efree(BG(CurrentLStatFile));
331
0
    BG(CurrentLStatFile) = NULL;
332
0
  }
333
334
0
  RETURN_TRUE;
335
0
}
336
/* }}} */
337
338
/* {{{ proto mixed getcwd(void)
339
   Gets the current directory */
340
PHP_FUNCTION(getcwd)
341
0
{
342
0
  char path[MAXPATHLEN];
343
0
  char *ret=NULL;
344
345
0
  ZEND_PARSE_PARAMETERS_NONE();
346
347
0
#if HAVE_GETCWD
348
0
  ret = VCWD_GETCWD(path, MAXPATHLEN);
349
#elif HAVE_GETWD
350
  ret = VCWD_GETWD(path);
351
#endif
352
353
0
  if (ret) {
354
0
    RETURN_STRING(path);
355
0
  } else {
356
0
    RETURN_FALSE;
357
0
  }
358
0
}
359
/* }}} */
360
361
/* {{{ proto void rewinddir([resource dir_handle])
362
   Rewind dir_handle back to the start */
363
PHP_FUNCTION(rewinddir)
364
0
{
365
0
  zval *id = NULL, *tmp, *myself;
366
0
  php_stream *dirp;
367
368
0
  FETCH_DIRP();
369
370
0
  if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
371
0
    zend_argument_type_error(1, "must be a valid Directory resource");
372
0
    RETURN_THROWS();
373
0
  }
374
375
0
  php_stream_rewinddir(dirp);
376
0
}
377
/* }}} */
378
379
/* {{{ proto string|false readdir([resource dir_handle])
380
   Read directory entry from dir_handle */
381
PHP_FUNCTION(readdir)
382
0
{
383
0
  zval *id = NULL, *tmp, *myself;
384
0
  php_stream *dirp;
385
0
  php_stream_dirent entry;
386
387
0
  FETCH_DIRP();
388
389
0
  if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
390
0
    zend_argument_type_error(1, "must be a valid Directory resource");
391
0
    RETURN_THROWS();
392
0
  }
393
394
0
  if (php_stream_readdir(dirp, &entry)) {
395
0
    RETURN_STRINGL(entry.d_name, strlen(entry.d_name));
396
0
  }
397
0
  RETURN_FALSE;
398
0
}
399
/* }}} */
400
401
#ifdef HAVE_GLOB
402
/* {{{ proto array|false glob(string pattern [, int flags])
403
   Find pathnames matching a pattern */
404
PHP_FUNCTION(glob)
405
0
{
406
0
  size_t cwd_skip = 0;
407
#ifdef ZTS
408
  char cwd[MAXPATHLEN];
409
  char work_pattern[MAXPATHLEN];
410
  char *result;
411
#endif
412
0
  char *pattern = NULL;
413
0
  size_t pattern_len;
414
0
  zend_long flags = 0;
415
0
  glob_t globbuf;
416
0
  size_t n;
417
0
  int ret;
418
0
  zend_bool basedir_limit = 0;
419
420
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
421
0
    Z_PARAM_PATH(pattern, pattern_len)
422
0
    Z_PARAM_OPTIONAL
423
0
    Z_PARAM_LONG(flags)
424
0
  ZEND_PARSE_PARAMETERS_END();
425
426
0
  if (pattern_len >= MAXPATHLEN) {
427
0
    php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
428
0
    RETURN_FALSE;
429
0
  }
430
431
0
  if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
432
0
    php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
433
0
    RETURN_FALSE;
434
0
  }
435
436
#ifdef ZTS
437
  if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
438
    result = VCWD_GETCWD(cwd, MAXPATHLEN);
439
    if (!result) {
440
      cwd[0] = '\0';
441
    }
442
#ifdef PHP_WIN32
443
    if (IS_SLASH(*pattern)) {
444
      cwd[2] = '\0';
445
    }
446
#endif
447
    cwd_skip = strlen(cwd)+1;
448
449
    snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
450
    pattern = work_pattern;
451
  }
452
#endif
453
454
455
0
  memset(&globbuf, 0, sizeof(glob_t));
456
0
  globbuf.gl_offs = 0;
457
0
  if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
458
0
#ifdef GLOB_NOMATCH
459
0
    if (GLOB_NOMATCH == ret) {
460
      /* Some glob implementation simply return no data if no matches
461
         were found, others return the GLOB_NOMATCH error code.
462
         We don't want to treat GLOB_NOMATCH as an error condition
463
         so that PHP glob() behaves the same on both types of
464
         implementations and so that 'foreach (glob() as ...'
465
         can be used for simple glob() calls without further error
466
         checking.
467
      */
468
0
      goto no_results;
469
0
    }
470
0
#endif
471
0
    RETURN_FALSE;
472
0
  }
473
474
  /* now catch the FreeBSD style of "no matches" */
475
0
  if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
476
0
#ifdef GLOB_NOMATCH
477
0
no_results:
478
0
#endif
479
0
#ifndef PHP_WIN32
480
    /* Paths containing '*', '?' and some other chars are
481
    illegal on Windows but legit on other platforms. For
482
    this reason the direct basedir check against the glob
483
    query is senseless on windows. For instance while *.txt
484
    is a pretty valid filename on EXT3, it's invalid on NTFS. */
485
0
    if (PG(open_basedir) && *PG(open_basedir)) {
486
0
      if (php_check_open_basedir_ex(pattern, 0)) {
487
0
        RETURN_FALSE;
488
0
      }
489
0
    }
490
0
#endif
491
0
    array_init(return_value);
492
0
    return;
493
0
  }
494
495
0
  array_init(return_value);
496
0
  for (n = 0; n < (size_t)globbuf.gl_pathc; n++) {
497
0
    if (PG(open_basedir) && *PG(open_basedir)) {
498
0
      if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) {
499
0
        basedir_limit = 1;
500
0
        continue;
501
0
      }
502
0
    }
503
    /* we need to do this every time since GLOB_ONLYDIR does not guarantee that
504
     * all directories will be filtered. GNU libc documentation states the
505
     * following:
506
     * If the information about the type of the file is easily available
507
     * non-directories will be rejected but no extra work will be done to
508
     * determine the information for each file. I.e., the caller must still be
509
     * able to filter directories out.
510
     */
511
0
    if (flags & GLOB_ONLYDIR) {
512
0
      zend_stat_t s;
513
514
0
      if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
515
0
        continue;
516
0
      }
517
518
0
      if (S_IFDIR != (s.st_mode & S_IFMT)) {
519
0
        continue;
520
0
      }
521
0
    }
522
0
    add_next_index_string(return_value, globbuf.gl_pathv[n]+cwd_skip);
523
0
  }
524
525
0
  globfree(&globbuf);
526
527
0
  if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
528
0
    zend_array_destroy(Z_ARR_P(return_value));
529
0
    RETURN_FALSE;
530
0
  }
531
0
}
532
/* }}} */
533
#endif
534
535
/* {{{ proto array|false scandir(string dir [, int sorting_order [, resource context]])
536
   List files & directories inside the specified path */
537
PHP_FUNCTION(scandir)
538
0
{
539
0
  char *dirn;
540
0
  size_t dirn_len;
541
0
  zend_long flags = 0;
542
0
  zend_string **namelist;
543
0
  int n, i;
544
0
  zval *zcontext = NULL;
545
0
  php_stream_context *context = NULL;
546
547
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
548
0
    Z_PARAM_PATH(dirn, dirn_len)
549
0
    Z_PARAM_OPTIONAL
550
0
    Z_PARAM_LONG(flags)
551
0
    Z_PARAM_RESOURCE(zcontext)
552
0
  ZEND_PARSE_PARAMETERS_END();
553
554
0
  if (dirn_len < 1) {
555
0
    zend_argument_value_error(1, "cannot be empty");
556
0
    RETURN_THROWS();
557
0
  }
558
559
0
  if (zcontext) {
560
0
    context = php_stream_context_from_zval(zcontext, 0);
561
0
  }
562
563
0
  if (flags == PHP_SCANDIR_SORT_ASCENDING) {
564
0
    n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
565
0
  } else if (flags == PHP_SCANDIR_SORT_NONE) {
566
0
    n = php_stream_scandir(dirn, &namelist, context, NULL);
567
0
  } else {
568
0
    n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
569
0
  }
570
0
  if (n < 0) {
571
0
    php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno));
572
0
    RETURN_FALSE;
573
0
  }
574
575
0
  array_init(return_value);
576
577
0
  for (i = 0; i < n; i++) {
578
0
    add_next_index_str(return_value, namelist[i]);
579
0
  }
580
581
0
  if (n) {
582
0
    efree(namelist);
583
0
  }
584
0
}
585
/* }}} */