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