xref: /PHP-7.2/ext/standard/dir.c (revision 7a7ec01a)
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 /* $Id$ */
20 
21 /* {{{ includes/startup/misc */
22 
23 #include "php.h"
24 #include "fopen_wrappers.h"
25 #include "file.h"
26 #include "php_dir.h"
27 #include "php_string.h"
28 #include "php_scandir.h"
29 #include "basic_functions.h"
30 
31 #ifdef HAVE_DIRENT_H
32 #include <dirent.h>
33 #endif
34 
35 #if HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 
39 #include <errno.h>
40 
41 #ifdef PHP_WIN32
42 #include "win32/readdir.h"
43 #endif
44 
45 
46 #ifdef HAVE_GLOB
47 #ifndef PHP_WIN32
48 #include <glob.h>
49 #else
50 #include "win32/glob.h"
51 #endif
52 #endif
53 
54 typedef struct {
55 	zend_resource *default_dir;
56 } php_dir_globals;
57 
58 #ifdef ZTS
59 #define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v)
60 int dir_globals_id;
61 #else
62 #define DIRG(v) (dir_globals.v)
63 php_dir_globals dir_globals;
64 #endif
65 
66 static zend_class_entry *dir_class_entry_ptr;
67 
68 #define FETCH_DIRP() \
69 	ZEND_PARSE_PARAMETERS_START(0, 1) \
70 		Z_PARAM_OPTIONAL \
71 		Z_PARAM_RESOURCE(id) \
72 	ZEND_PARSE_PARAMETERS_END(); \
73 	if (ZEND_NUM_ARGS() == 0) { \
74 		myself = getThis(); \
75 		if (myself) { \
76 			if ((tmp = zend_hash_str_find(Z_OBJPROP_P(myself), "handle", sizeof("handle")-1)) == NULL) { \
77 				php_error_docref(NULL, E_WARNING, "Unable to find my handle property"); \
78 				RETURN_FALSE; \
79 			} \
80 			if ((dirp = (php_stream *)zend_fetch_resource_ex(tmp, "Directory", php_file_le_stream())) == NULL) { \
81 				RETURN_FALSE; \
82 			} \
83 		} else { \
84 			if (!DIRG(default_dir) || \
85 				(dirp = (php_stream *)zend_fetch_resource(DIRG(default_dir), "Directory", php_file_le_stream())) == NULL) { \
86 				RETURN_FALSE; \
87 			} \
88 		} \
89 	} else { \
90 		if ((dirp = (php_stream *)zend_fetch_resource(Z_RES_P(id), "Directory", php_file_le_stream())) == NULL) { \
91 			RETURN_FALSE; \
92 		} \
93 	}
94 
95 /* {{{ arginfo */
96 ZEND_BEGIN_ARG_INFO_EX(arginfo_dir, 0, 0, 0)
97 	ZEND_ARG_INFO(0, dir_handle)
98 ZEND_END_ARG_INFO()
99 /* }}} */
100 
101 static const zend_function_entry php_dir_class_functions[] = {
102 	PHP_FALIAS(close,	closedir,		arginfo_dir)
103 	PHP_FALIAS(rewind,	rewinddir,		arginfo_dir)
104 	PHP_NAMED_FE(read,  php_if_readdir, arginfo_dir)
105 	PHP_FE_END
106 };
107 
108 
php_set_default_dir(zend_resource * res)109 static void php_set_default_dir(zend_resource *res)
110 {
111 	if (DIRG(default_dir)) {
112 		zend_list_delete(DIRG(default_dir));
113 	}
114 
115 	if (res) {
116 		GC_REFCOUNT(res)++;
117 	}
118 
119 	DIRG(default_dir) = res;
120 }
121 
PHP_RINIT_FUNCTION(dir)122 PHP_RINIT_FUNCTION(dir)
123 {
124 	DIRG(default_dir) = NULL;
125 	return SUCCESS;
126 }
127 
PHP_MINIT_FUNCTION(dir)128 PHP_MINIT_FUNCTION(dir)
129 {
130 	static char dirsep_str[2], pathsep_str[2];
131 	zend_class_entry dir_class_entry;
132 
133 	INIT_CLASS_ENTRY(dir_class_entry, "Directory", php_dir_class_functions);
134 	dir_class_entry_ptr = zend_register_internal_class(&dir_class_entry);
135 
136 #ifdef ZTS
137 	ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
138 #endif
139 
140 	dirsep_str[0] = DEFAULT_SLASH;
141 	dirsep_str[1] = '\0';
142 	REGISTER_STRING_CONSTANT("DIRECTORY_SEPARATOR", dirsep_str, CONST_CS|CONST_PERSISTENT);
143 
144 	pathsep_str[0] = ZEND_PATHS_SEPARATOR;
145 	pathsep_str[1] = '\0';
146 	REGISTER_STRING_CONSTANT("PATH_SEPARATOR", pathsep_str, CONST_CS|CONST_PERSISTENT);
147 
148 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_ASCENDING",  PHP_SCANDIR_SORT_ASCENDING,  CONST_CS | CONST_PERSISTENT);
149 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_DESCENDING", PHP_SCANDIR_SORT_DESCENDING, CONST_CS | CONST_PERSISTENT);
150 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_NONE",       PHP_SCANDIR_SORT_NONE,       CONST_CS | CONST_PERSISTENT);
151 
152 #ifdef HAVE_GLOB
153 
154 #ifdef GLOB_BRACE
155 	REGISTER_LONG_CONSTANT("GLOB_BRACE", GLOB_BRACE, CONST_CS | CONST_PERSISTENT);
156 #else
157 # define GLOB_BRACE 0
158 #endif
159 
160 #ifdef GLOB_MARK
161 	REGISTER_LONG_CONSTANT("GLOB_MARK", GLOB_MARK, CONST_CS | CONST_PERSISTENT);
162 #else
163 # define GLOB_MARK 0
164 #endif
165 
166 #ifdef GLOB_NOSORT
167 	REGISTER_LONG_CONSTANT("GLOB_NOSORT", GLOB_NOSORT, CONST_CS | CONST_PERSISTENT);
168 #else
169 # define GLOB_NOSORT 0
170 #endif
171 
172 #ifdef GLOB_NOCHECK
173 	REGISTER_LONG_CONSTANT("GLOB_NOCHECK", GLOB_NOCHECK, CONST_CS | CONST_PERSISTENT);
174 #else
175 # define GLOB_NOCHECK 0
176 #endif
177 
178 #ifdef GLOB_NOESCAPE
179 	REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", GLOB_NOESCAPE, CONST_CS | CONST_PERSISTENT);
180 #else
181 # define GLOB_NOESCAPE 0
182 #endif
183 
184 #ifdef GLOB_ERR
185 	REGISTER_LONG_CONSTANT("GLOB_ERR", GLOB_ERR, CONST_CS | CONST_PERSISTENT);
186 #else
187 # define GLOB_ERR 0
188 #endif
189 
190 #ifndef GLOB_ONLYDIR
191 # define GLOB_ONLYDIR (1<<30)
192 # define GLOB_EMULATE_ONLYDIR
193 # define GLOB_FLAGMASK (~GLOB_ONLYDIR)
194 #else
195 # define GLOB_FLAGMASK (~0)
196 #endif
197 
198 /* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */
199 #define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR)
200 
201 	REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", GLOB_ONLYDIR, CONST_CS | CONST_PERSISTENT);
202 	REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", GLOB_AVAILABLE_FLAGS, CONST_CS | CONST_PERSISTENT);
203 
204 #endif /* HAVE_GLOB */
205 
206 	return SUCCESS;
207 }
208 /* }}} */
209 
210 /* {{{ internal functions */
_php_do_opendir(INTERNAL_FUNCTION_PARAMETERS,int createobject)211 static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
212 {
213 	char *dirname;
214 	size_t dir_len;
215 	zval *zcontext = NULL;
216 	php_stream_context *context = NULL;
217 	php_stream *dirp;
218 
219 	ZEND_PARSE_PARAMETERS_START(1, 2)
220 		Z_PARAM_PATH(dirname, dir_len)
221 		Z_PARAM_OPTIONAL
222 		Z_PARAM_RESOURCE(zcontext)
223 	ZEND_PARSE_PARAMETERS_END();
224 
225 	context = php_stream_context_from_zval(zcontext, 0);
226 
227 	dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
228 
229 	if (dirp == NULL) {
230 		RETURN_FALSE;
231 	}
232 
233 	dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
234 
235 	php_set_default_dir(dirp->res);
236 
237 	if (createobject) {
238 		object_init_ex(return_value, dir_class_entry_ptr);
239 		add_property_stringl(return_value, "path", dirname, dir_len);
240 		add_property_resource(return_value, "handle", dirp->res);
241 		php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
242 	} else {
243 		php_stream_to_zval(dirp, return_value);
244 	}
245 }
246 /* }}} */
247 
248 /* {{{ proto mixed opendir(string path[, resource context])
249    Open a directory and return a dir_handle */
PHP_FUNCTION(opendir)250 PHP_FUNCTION(opendir)
251 {
252 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
253 }
254 /* }}} */
255 
256 /* {{{ proto object dir(string directory[, resource context])
257    Directory class with properties, handle and class and methods read, rewind and close */
PHP_FUNCTION(getdir)258 PHP_FUNCTION(getdir)
259 {
260 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
261 }
262 /* }}} */
263 
264 /* {{{ proto void closedir([resource dir_handle])
265    Close directory connection identified by the dir_handle */
PHP_FUNCTION(closedir)266 PHP_FUNCTION(closedir)
267 {
268 	zval *id = NULL, *tmp, *myself;
269 	php_stream *dirp;
270 	zend_resource *res;
271 
272 	FETCH_DIRP();
273 
274 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
275 		php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
276 		RETURN_FALSE;
277 	}
278 
279 	res = dirp->res;
280 	zend_list_close(dirp->res);
281 
282 	if (res == DIRG(default_dir)) {
283 		php_set_default_dir(NULL);
284 	}
285 }
286 /* }}} */
287 
288 #if defined(HAVE_CHROOT) && !defined(ZTS) && ENABLE_CHROOT_FUNC
289 /* {{{ proto bool chroot(string directory)
290    Change root directory */
PHP_FUNCTION(chroot)291 PHP_FUNCTION(chroot)
292 {
293 	char *str;
294 	int ret;
295 	size_t str_len;
296 
297 	ZEND_PARSE_PARAMETERS_START(1, 1)
298 		Z_PARAM_PATH(str, str_len)
299 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
300 
301 	ret = chroot(str);
302 	if (ret != 0) {
303 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
304 		RETURN_FALSE;
305 	}
306 
307 	php_clear_stat_cache(1, NULL, 0);
308 
309 	ret = chdir("/");
310 
311 	if (ret != 0) {
312 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
313 		RETURN_FALSE;
314 	}
315 
316 	RETURN_TRUE;
317 }
318 /* }}} */
319 #endif
320 
321 /* {{{ proto bool chdir(string directory)
322    Change the current directory */
PHP_FUNCTION(chdir)323 PHP_FUNCTION(chdir)
324 {
325 	char *str;
326 	int ret;
327 	size_t str_len;
328 
329 	ZEND_PARSE_PARAMETERS_START(1, 1)
330 		Z_PARAM_PATH(str, str_len)
331 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
332 
333 	if (php_check_open_basedir(str)) {
334 		RETURN_FALSE;
335 	}
336 	ret = VCWD_CHDIR(str);
337 
338 	if (ret != 0) {
339 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
340 		RETURN_FALSE;
341 	}
342 
343 	if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentStatFile), strlen(BG(CurrentStatFile)))) {
344 		efree(BG(CurrentStatFile));
345 		BG(CurrentStatFile) = NULL;
346 	}
347 	if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentLStatFile), strlen(BG(CurrentLStatFile)))) {
348 		efree(BG(CurrentLStatFile));
349 		BG(CurrentLStatFile) = NULL;
350 	}
351 
352 	RETURN_TRUE;
353 }
354 /* }}} */
355 
356 /* {{{ proto mixed getcwd(void)
357    Gets the current directory */
PHP_FUNCTION(getcwd)358 PHP_FUNCTION(getcwd)
359 {
360 	char path[MAXPATHLEN];
361 	char *ret=NULL;
362 
363 	if (zend_parse_parameters_none() == FAILURE) {
364 		return;
365 	}
366 
367 #if HAVE_GETCWD
368 	ret = VCWD_GETCWD(path, MAXPATHLEN);
369 #elif HAVE_GETWD
370 	ret = VCWD_GETWD(path);
371 #endif
372 
373 	if (ret) {
374 		RETURN_STRING(path);
375 	} else {
376 		RETURN_FALSE;
377 	}
378 }
379 /* }}} */
380 
381 /* {{{ proto void rewinddir([resource dir_handle])
382    Rewind dir_handle back to the start */
PHP_FUNCTION(rewinddir)383 PHP_FUNCTION(rewinddir)
384 {
385 	zval *id = NULL, *tmp, *myself;
386 	php_stream *dirp;
387 
388 	FETCH_DIRP();
389 
390 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
391 		php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
392 		RETURN_FALSE;
393 	}
394 
395 	php_stream_rewinddir(dirp);
396 }
397 /* }}} */
398 
399 /* {{{ proto string readdir([resource dir_handle])
400    Read directory entry from dir_handle */
PHP_NAMED_FUNCTION(php_if_readdir)401 PHP_NAMED_FUNCTION(php_if_readdir)
402 {
403 	zval *id = NULL, *tmp, *myself;
404 	php_stream *dirp;
405 	php_stream_dirent entry;
406 
407 	FETCH_DIRP();
408 
409 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
410 		php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
411 		RETURN_FALSE;
412 	}
413 
414 	if (php_stream_readdir(dirp, &entry)) {
415 		RETURN_STRINGL(entry.d_name, strlen(entry.d_name));
416 	}
417 	RETURN_FALSE;
418 }
419 /* }}} */
420 
421 #ifdef HAVE_GLOB
422 /* {{{ proto array glob(string pattern [, int flags])
423    Find pathnames matching a pattern */
PHP_FUNCTION(glob)424 PHP_FUNCTION(glob)
425 {
426 	size_t cwd_skip = 0;
427 #ifdef ZTS
428 	char cwd[MAXPATHLEN];
429 	char work_pattern[MAXPATHLEN];
430 	char *result;
431 #endif
432 	char *pattern = NULL;
433 	size_t pattern_len;
434 	zend_long flags = 0;
435 	glob_t globbuf;
436 	size_t n;
437 	int ret;
438 	zend_bool basedir_limit = 0;
439 
440 	ZEND_PARSE_PARAMETERS_START(1, 2)
441 		Z_PARAM_PATH(pattern, pattern_len)
442 		Z_PARAM_OPTIONAL
443 		Z_PARAM_LONG(flags)
444 	ZEND_PARSE_PARAMETERS_END();
445 
446 	if (pattern_len >= MAXPATHLEN) {
447 		php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
448 		RETURN_FALSE;
449 	}
450 
451 	if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
452 		php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
453 		RETURN_FALSE;
454 	}
455 
456 #ifdef ZTS
457 	if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
458 		result = VCWD_GETCWD(cwd, MAXPATHLEN);
459 		if (!result) {
460 			cwd[0] = '\0';
461 		}
462 #ifdef PHP_WIN32
463 		if (IS_SLASH(*pattern)) {
464 			cwd[2] = '\0';
465 		}
466 #endif
467 		cwd_skip = strlen(cwd)+1;
468 
469 		snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
470 		pattern = work_pattern;
471 	}
472 #endif
473 
474 
475 	memset(&globbuf, 0, sizeof(glob_t));
476 	globbuf.gl_offs = 0;
477 	if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
478 #ifdef GLOB_NOMATCH
479 		if (GLOB_NOMATCH == ret) {
480 			/* Some glob implementation simply return no data if no matches
481 			   were found, others return the GLOB_NOMATCH error code.
482 			   We don't want to treat GLOB_NOMATCH as an error condition
483 			   so that PHP glob() behaves the same on both types of
484 			   implementations and so that 'foreach (glob() as ...'
485 			   can be used for simple glob() calls without further error
486 			   checking.
487 			*/
488 			goto no_results;
489 		}
490 #endif
491 		RETURN_FALSE;
492 	}
493 
494 	/* now catch the FreeBSD style of "no matches" */
495 	if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
496 no_results:
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 < 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 		zval_dtor(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