Coverage Report

Created: 2025-06-13 06:43

/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
   | https://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_dir_int.h"
24
#include "php_scandir.h"
25
#include "basic_functions.h"
26
#include "dir_arginfo.h"
27
28
#ifdef 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
typedef struct {
39
  zend_resource *default_dir;
40
} php_dir_globals;
41
42
#ifdef ZTS
43
#define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v)
44
int dir_globals_id;
45
#else
46
300k
#define DIRG(v) (dir_globals.v)
47
php_dir_globals dir_globals;
48
#endif
49
50
static zend_class_entry *dir_class_entry_ptr;
51
static zend_object_handlers dir_class_object_handlers;
52
53
#define Z_DIRECTORY_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0)
54
0
#define Z_DIRECTORY_HANDLE_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 1)
55
56
static zend_function *dir_class_get_constructor(zend_object *object)
57
5
{
58
5
  zend_throw_error(NULL, "Cannot directly construct Directory, use dir() instead");
59
5
  return NULL;
60
5
}
61
62
static void php_set_default_dir(zend_resource *res)
63
112
{
64
112
  if (DIRG(default_dir)) {
65
56
    zend_list_delete(DIRG(default_dir));
66
56
  }
67
68
112
  if (res) {
69
56
    GC_ADDREF(res);
70
56
  }
71
72
112
  DIRG(default_dir) = res;
73
112
}
74
75
PHP_RINIT_FUNCTION(dir)
76
300k
{
77
300k
  DIRG(default_dir) = NULL;
78
300k
  return SUCCESS;
79
300k
}
80
81
PHP_MINIT_FUNCTION(dir)
82
16
{
83
16
  dirsep_str[0] = DEFAULT_SLASH;
84
16
  dirsep_str[1] = '\0';
85
86
16
  pathsep_str[0] = ZEND_PATHS_SEPARATOR;
87
16
  pathsep_str[1] = '\0';
88
89
16
  register_dir_symbols(module_number);
90
91
16
  dir_class_entry_ptr = register_class_Directory();
92
16
  dir_class_entry_ptr->default_object_handlers = &dir_class_object_handlers;
93
94
16
  memcpy(&dir_class_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
95
16
  dir_class_object_handlers.get_constructor = dir_class_get_constructor;
96
16
  dir_class_object_handlers.clone_obj = NULL;
97
16
  dir_class_object_handlers.compare = zend_objects_not_comparable;
98
99
#ifdef ZTS
100
  ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
101
#endif
102
103
16
  return SUCCESS;
104
16
}
105
/* }}} */
106
107
/* {{{ internal functions */
108
static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
109
60
{
110
60
  char *dirname;
111
60
  size_t dir_len;
112
60
  zval *zcontext = NULL;
113
60
  php_stream_context *context = NULL;
114
60
  php_stream *dirp;
115
116
180
  ZEND_PARSE_PARAMETERS_START(1, 2)
117
240
    Z_PARAM_PATH(dirname, dir_len)
118
60
    Z_PARAM_OPTIONAL
119
120
    Z_PARAM_RESOURCE_OR_NULL(zcontext)
120
60
  ZEND_PARSE_PARAMETERS_END();
121
122
60
  context = php_stream_context_from_zval(zcontext, 0);
123
124
60
  dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
125
126
60
  if (dirp == NULL) {
127
4
    RETURN_FALSE;
128
4
  }
129
130
56
  dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
131
132
56
  php_set_default_dir(dirp->res);
133
134
56
  if (createobject) {
135
0
    object_init_ex(return_value, dir_class_entry_ptr);
136
0
    ZVAL_STRINGL(Z_DIRECTORY_PATH_P(return_value), dirname, dir_len);
137
0
    ZVAL_RES(Z_DIRECTORY_HANDLE_P(return_value), dirp->res);
138
0
    php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
139
56
  } else {
140
56
    php_stream_to_zval(dirp, return_value);
141
56
  }
142
56
}
143
/* }}} */
144
145
/* {{{ Open a directory and return a dir_handle */
146
PHP_FUNCTION(opendir)
147
60
{
148
60
  _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
149
60
}
150
/* }}} */
151
152
/* {{{ Directory class with properties, handle and class and methods read, rewind and close */
153
PHP_FUNCTION(dir)
154
0
{
155
0
  _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
156
0
}
157
/* }}} */
158
159
160
static php_stream* php_dir_get_directory_stream_from_user_arg(php_stream *dir_stream)
161
56
{
162
56
  if (dir_stream == NULL) {
163
0
    if (UNEXPECTED(DIRG(default_dir) == NULL)) {
164
0
      zend_type_error("No resource supplied");
165
0
      return NULL;
166
0
    }
167
0
    zend_resource *res = DIRG(default_dir);
168
0
    ZEND_ASSERT(res->type == php_file_le_stream());
169
0
    dir_stream = (php_stream*) res->ptr;
170
0
  }
171
172
56
  if (UNEXPECTED((dir_stream->flags & PHP_STREAM_FLAG_IS_DIR)) == 0) {
173
0
    zend_argument_type_error(1, "must be a valid Directory resource");
174
0
    return NULL;
175
0
  }
176
56
  return dir_stream;
177
56
}
178
179
static php_stream* php_dir_get_directory_stream_from_this(zval *this_z)
180
0
{
181
0
  zval *handle_zv = Z_DIRECTORY_HANDLE_P(this_z);
182
0
  if (UNEXPECTED(Z_TYPE_P(handle_zv) != IS_RESOURCE)) {
183
0
    zend_throw_error(NULL, "Internal directory stream has been altered");
184
0
    return NULL;
185
0
  }
186
0
  zend_resource *res = Z_RES_P(handle_zv);
187
  /* Assume the close() method was called
188
   * (instead of the hacky case where a different resource would have been set via the ArrayObject "hack") */
189
0
  if (UNEXPECTED(res->type != php_file_le_stream())) {
190
    /* TypeError is used for BC, TODO: Use base Error in PHP 9 */
191
0
    zend_type_error("Directory::%s(): cannot use Directory resource after it has been closed", get_active_function_name());
192
0
    return NULL;
193
0
  }
194
0
  php_stream *dir_stream = (php_stream*) res->ptr;
195
0
  if (UNEXPECTED((dir_stream->flags & PHP_STREAM_FLAG_IS_DIR)) == 0) {
196
0
    zend_throw_error(NULL, "Internal directory stream has been altered");
197
0
    return NULL;
198
0
  }
199
0
  return dir_stream;
200
0
}
201
202
/* {{{ Close directory connection identified by the dir_handle */
203
PHP_FUNCTION(closedir)
204
56
{
205
56
  php_stream *dirp = NULL;
206
207
168
  ZEND_PARSE_PARAMETERS_START(0, 1)
208
168
    Z_PARAM_OPTIONAL
209
224
    PHP_Z_PARAM_STREAM_OR_NULL(dirp)
210
56
  ZEND_PARSE_PARAMETERS_END();
211
212
56
  dirp = php_dir_get_directory_stream_from_user_arg(dirp);
213
56
  if (UNEXPECTED(dirp == NULL)) {
214
0
    RETURN_THROWS();
215
0
  }
216
56
  zend_resource *res = dirp->res;
217
56
  zend_list_close(res);
218
219
56
  if (res == DIRG(default_dir)) {
220
56
    php_set_default_dir(NULL);
221
56
  }
222
56
}
223
/* }}} */
224
225
PHP_METHOD(Directory, close)
226
0
{
227
0
  ZEND_PARSE_PARAMETERS_NONE();
228
229
0
  php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS);
230
0
  if (UNEXPECTED(dirp == NULL)) {
231
0
    RETURN_THROWS();
232
0
  }
233
234
0
  zend_resource *res = dirp->res;
235
0
  zend_list_close(res);
236
237
0
  if (res == DIRG(default_dir)) {
238
0
    php_set_default_dir(NULL);
239
0
  }
240
0
}
241
242
/* {{{ Rewind dir_handle back to the start */
243
PHP_FUNCTION(rewinddir)
244
0
{
245
0
  php_stream *dirp = NULL;
246
247
0
  ZEND_PARSE_PARAMETERS_START(0, 1)
248
0
    Z_PARAM_OPTIONAL
249
0
    PHP_Z_PARAM_STREAM_OR_NULL(dirp)
250
0
  ZEND_PARSE_PARAMETERS_END();
251
252
0
  dirp = php_dir_get_directory_stream_from_user_arg(dirp);
253
0
  if (UNEXPECTED(dirp == NULL)) {
254
0
    RETURN_THROWS();
255
0
  }
256
257
0
  php_stream_rewinddir(dirp);
258
0
}
259
/* }}} */
260
261
PHP_METHOD(Directory, rewind)
262
0
{
263
0
  ZEND_PARSE_PARAMETERS_NONE();
264
265
0
  php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS);
266
0
  if (UNEXPECTED(dirp == NULL)) {
267
0
    RETURN_THROWS();
268
0
  }
269
270
0
  php_stream_rewinddir(dirp);
271
0
}
272
273
/* {{{ Read directory entry from dir_handle */
274
PHP_FUNCTION(readdir)
275
0
{
276
0
  php_stream *dirp = NULL;
277
278
0
  ZEND_PARSE_PARAMETERS_START(0, 1)
279
0
    Z_PARAM_OPTIONAL
280
0
    PHP_Z_PARAM_STREAM_OR_NULL(dirp)
281
0
  ZEND_PARSE_PARAMETERS_END();
282
283
0
  dirp = php_dir_get_directory_stream_from_user_arg(dirp);
284
0
  if (UNEXPECTED(dirp == NULL)) {
285
0
    RETURN_THROWS();
286
0
  }
287
288
0
  php_stream_dirent entry;
289
0
  if (php_stream_readdir(dirp, &entry)) {
290
0
    RETURN_STRING(entry.d_name);
291
0
  }
292
0
  RETURN_FALSE;
293
0
}
294
/* }}} */
295
296
PHP_METHOD(Directory, read)
297
0
{
298
0
  ZEND_PARSE_PARAMETERS_NONE();
299
300
0
  php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS);
301
0
  if (UNEXPECTED(dirp == NULL)) {
302
0
    RETURN_THROWS();
303
0
  }
304
305
0
  php_stream_dirent entry;
306
0
  if (php_stream_readdir(dirp, &entry)) {
307
0
    RETURN_STRING(entry.d_name);
308
0
  }
309
0
  RETURN_FALSE;
310
0
}
311
312
#if defined(HAVE_CHROOT) && !defined(ZTS) && defined(ENABLE_CHROOT_FUNC)
313
/* {{{ Change root directory */
314
PHP_FUNCTION(chroot)
315
{
316
  char *str;
317
  int ret;
318
  size_t str_len;
319
320
  ZEND_PARSE_PARAMETERS_START(1, 1)
321
    Z_PARAM_PATH(str, str_len)
322
  ZEND_PARSE_PARAMETERS_END();
323
324
  ret = chroot(str);
325
  if (ret != 0) {
326
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
327
    RETURN_FALSE;
328
  }
329
330
  php_clear_stat_cache(1, NULL, 0);
331
332
  ret = chdir("/");
333
334
  if (ret != 0) {
335
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
336
    RETURN_FALSE;
337
  }
338
339
  RETURN_TRUE;
340
}
341
/* }}} */
342
#endif
343
344
/* {{{ Change the current directory */
345
PHP_FUNCTION(chdir)
346
0
{
347
0
  char *str;
348
0
  int ret;
349
0
  size_t str_len;
350
351
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
352
0
    Z_PARAM_PATH(str, str_len)
353
0
  ZEND_PARSE_PARAMETERS_END();
354
355
0
  if (php_check_open_basedir(str)) {
356
0
    RETURN_FALSE;
357
0
  }
358
0
  ret = VCWD_CHDIR(str);
359
360
0
  if (ret != 0) {
361
0
    php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
362
0
    RETURN_FALSE;
363
0
  }
364
365
0
  if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentStatFile)), ZSTR_LEN(BG(CurrentStatFile)))) {
366
0
    zend_string_release(BG(CurrentStatFile));
367
0
    BG(CurrentStatFile) = NULL;
368
0
  }
369
0
  if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentLStatFile)), ZSTR_LEN(BG(CurrentLStatFile)))) {
370
0
    zend_string_release(BG(CurrentLStatFile));
371
0
    BG(CurrentLStatFile) = NULL;
372
0
  }
373
374
0
  RETURN_TRUE;
375
0
}
376
/* }}} */
377
378
/* {{{ Gets the current directory */
379
PHP_FUNCTION(getcwd)
380
5
{
381
5
  char path[MAXPATHLEN];
382
5
  char *ret=NULL;
383
384
5
  ZEND_PARSE_PARAMETERS_NONE();
385
386
5
#ifdef HAVE_GETCWD
387
5
  ret = VCWD_GETCWD(path, MAXPATHLEN);
388
#elif defined(HAVE_GETWD)
389
  ret = VCWD_GETWD(path);
390
#endif
391
392
5
  if (ret) {
393
5
    RETURN_STRING(path);
394
5
  } else {
395
0
    RETURN_FALSE;
396
0
  }
397
5
}
398
/* }}} */
399
400
/* {{{ Find pathnames matching a pattern */
401
PHP_FUNCTION(glob)
402
0
{
403
0
  size_t cwd_skip = 0;
404
#ifdef ZTS
405
  char cwd[MAXPATHLEN];
406
  char work_pattern[MAXPATHLEN];
407
  char *result;
408
#endif
409
0
  char *pattern = NULL;
410
0
  size_t pattern_len;
411
0
  zend_long flags = 0;
412
0
  php_glob_t globbuf;
413
0
  size_t n;
414
0
  int ret;
415
0
  bool basedir_limit = 0;
416
0
  zval tmp;
417
418
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
419
0
    Z_PARAM_PATH(pattern, pattern_len)
420
0
    Z_PARAM_OPTIONAL
421
0
    Z_PARAM_LONG(flags)
422
0
  ZEND_PARSE_PARAMETERS_END();
423
424
0
  if (pattern_len >= MAXPATHLEN) {
425
0
    php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
426
0
    RETURN_FALSE;
427
0
  }
428
429
0
  if ((PHP_GLOB_AVAILABLE_FLAGS & flags) != flags) {
430
0
    php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
431
0
    RETURN_FALSE;
432
0
  }
433
434
#ifdef ZTS
435
  if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
436
    result = VCWD_GETCWD(cwd, MAXPATHLEN);
437
    if (!result) {
438
      cwd[0] = '\0';
439
    }
440
#ifdef PHP_WIN32
441
    if (IS_SLASH(*pattern)) {
442
      cwd[2] = '\0';
443
    }
444
#endif
445
    cwd_skip = strlen(cwd)+1;
446
447
    snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
448
    pattern = work_pattern;
449
  }
450
#endif
451
452
453
0
  memset(&globbuf, 0, sizeof(globbuf));
454
0
  globbuf.gl_offs = 0;
455
0
  if (0 != (ret = php_glob(pattern, flags & PHP_GLOB_FLAGMASK, NULL, &globbuf))) {
456
0
#ifdef PHP_GLOB_NOMATCH
457
0
    if (PHP_GLOB_NOMATCH == ret) {
458
      /* Some glob implementation simply return no data if no matches
459
         were found, others return the PHP_GLOB_NOMATCH error code.
460
         We don't want to treat PHP_GLOB_NOMATCH as an error condition
461
         so that PHP glob() behaves the same on both types of
462
         implementations and so that 'foreach (glob() as ...'
463
         can be used for simple glob() calls without further error
464
         checking.
465
      */
466
0
      goto no_results;
467
0
    }
468
0
#endif
469
0
    RETURN_FALSE;
470
0
  }
471
472
  /* now catch the FreeBSD style of "no matches" */
473
0
  if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
474
0
#ifdef PHP_GLOB_NOMATCH
475
0
no_results:
476
0
#endif
477
0
    array_init(return_value);
478
0
    return;
479
0
  }
480
481
0
  array_init(return_value);
482
0
  for (n = 0; n < (size_t)globbuf.gl_pathc; n++) {
483
0
    if (PG(open_basedir) && *PG(open_basedir)) {
484
0
      if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) {
485
0
        basedir_limit = 1;
486
0
        continue;
487
0
      }
488
0
    }
489
    /* we need to do this every time since PHP_GLOB_ONLYDIR does not guarantee that
490
     * all directories will be filtered. GNU libc documentation states the
491
     * following:
492
     * If the information about the type of the file is easily available
493
     * non-directories will be rejected but no extra work will be done to
494
     * determine the information for each file. I.e., the caller must still be
495
     * able to filter directories out.
496
     */
497
0
    if (flags & PHP_GLOB_ONLYDIR) {
498
0
      zend_stat_t s = {0};
499
500
0
      if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
501
0
        continue;
502
0
      }
503
504
0
      if (S_IFDIR != (s.st_mode & S_IFMT)) {
505
0
        continue;
506
0
      }
507
0
    }
508
0
    ZVAL_STRING(&tmp, globbuf.gl_pathv[n]+cwd_skip);
509
0
    zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
510
0
  }
511
512
0
  php_globfree(&globbuf);
513
514
0
  if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
515
0
    zend_array_destroy(Z_ARR_P(return_value));
516
0
    RETURN_FALSE;
517
0
  }
518
0
}
519
/* }}} */
520
521
/* {{{ List files & directories inside the specified path */
522
PHP_FUNCTION(scandir)
523
0
{
524
0
  char *dirn;
525
0
  size_t dirn_len;
526
0
  zend_long flags = PHP_SCANDIR_SORT_ASCENDING;
527
0
  zend_string **namelist;
528
0
  int n, i;
529
0
  zval *zcontext = NULL;
530
0
  php_stream_context *context = NULL;
531
532
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
533
0
    Z_PARAM_PATH(dirn, dirn_len)
534
0
    Z_PARAM_OPTIONAL
535
0
    Z_PARAM_LONG(flags)
536
0
    Z_PARAM_RESOURCE_OR_NULL(zcontext)
537
0
  ZEND_PARSE_PARAMETERS_END();
538
539
0
  if (dirn_len < 1) {
540
0
    zend_argument_must_not_be_empty_error(1);
541
0
    RETURN_THROWS();
542
0
  }
543
544
0
  if (zcontext) {
545
0
    context = php_stream_context_from_zval(zcontext, 0);
546
0
  }
547
548
0
  if (flags == PHP_SCANDIR_SORT_ASCENDING) {
549
0
    n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
550
0
  } else if (flags == PHP_SCANDIR_SORT_NONE) {
551
0
    n = php_stream_scandir(dirn, &namelist, context, NULL);
552
0
  } else {
553
0
    n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
554
0
  }
555
0
  if (n < 0) {
556
0
    php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno));
557
0
    RETURN_FALSE;
558
0
  }
559
560
0
  array_init(return_value);
561
562
0
  for (i = 0; i < n; i++) {
563
0
    add_next_index_str(return_value, namelist[i]);
564
0
  }
565
566
0
  if (n) {
567
0
    efree(namelist);
568
0
  }
569
0
}
570
/* }}} */