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