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