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