xref: /php-src/ext/standard/dir.c (revision 5bb03158)
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_dir_int.h"
24 #include "php_string.h"
25 #include "php_scandir.h"
26 #include "basic_functions.h"
27 #include "dir_arginfo.h"
28 
29 #ifdef 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 typedef struct {
40 	zend_resource *default_dir;
41 } php_dir_globals;
42 
43 #ifdef ZTS
44 #define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v)
45 int dir_globals_id;
46 #else
47 #define DIRG(v) (dir_globals.v)
48 php_dir_globals dir_globals;
49 #endif
50 
51 static zend_class_entry *dir_class_entry_ptr;
52 
53 #define Z_DIRECTORY_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0)
54 #define Z_DIRECTORY_HANDLE_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 1)
55 
56 #define FETCH_DIRP() \
57 	myself = getThis(); \
58 	if (!myself) { \
59 		ZEND_PARSE_PARAMETERS_START(0, 1) \
60 			Z_PARAM_OPTIONAL \
61 			Z_PARAM_RESOURCE_OR_NULL(id) \
62 		ZEND_PARSE_PARAMETERS_END(); \
63 		if (id) { \
64 			if ((dirp = (php_stream *)zend_fetch_resource(Z_RES_P(id), "Directory", php_file_le_stream())) == NULL) { \
65 				RETURN_THROWS(); \
66 			} \
67 		} else { \
68 			if (!DIRG(default_dir)) { \
69 				zend_type_error("No resource supplied"); \
70 				RETURN_THROWS(); \
71 			} \
72 			if ((dirp = (php_stream *)zend_fetch_resource(DIRG(default_dir), "Directory", php_file_le_stream())) == NULL) { \
73 				RETURN_THROWS(); \
74 			} \
75 		} \
76 	} else { \
77 		ZEND_PARSE_PARAMETERS_NONE(); \
78 		zval *handle_zv = Z_DIRECTORY_HANDLE_P(myself); \
79 		if (Z_TYPE_P(handle_zv) != IS_RESOURCE) { \
80 			zend_throw_error(NULL, "Unable to find my handle property"); \
81 			RETURN_THROWS(); \
82 		} \
83 		if ((dirp = (php_stream *)zend_fetch_resource_ex(handle_zv, "Directory", php_file_le_stream())) == NULL) { \
84 			RETURN_THROWS(); \
85 		} \
86 	}
87 
88 
php_set_default_dir(zend_resource * res)89 static void php_set_default_dir(zend_resource *res)
90 {
91 	if (DIRG(default_dir)) {
92 		zend_list_delete(DIRG(default_dir));
93 	}
94 
95 	if (res) {
96 		GC_ADDREF(res);
97 	}
98 
99 	DIRG(default_dir) = res;
100 }
101 
PHP_RINIT_FUNCTION(dir)102 PHP_RINIT_FUNCTION(dir)
103 {
104 	DIRG(default_dir) = NULL;
105 	return SUCCESS;
106 }
107 
PHP_MINIT_FUNCTION(dir)108 PHP_MINIT_FUNCTION(dir)
109 {
110 	dirsep_str[0] = DEFAULT_SLASH;
111 	dirsep_str[1] = '\0';
112 
113 	pathsep_str[0] = ZEND_PATHS_SEPARATOR;
114 	pathsep_str[1] = '\0';
115 
116 	register_dir_symbols(module_number);
117 
118 	dir_class_entry_ptr = register_class_Directory();
119 
120 #ifdef ZTS
121 	ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
122 #endif
123 
124 	return SUCCESS;
125 }
126 /* }}} */
127 
128 /* {{{ internal functions */
_php_do_opendir(INTERNAL_FUNCTION_PARAMETERS,int createobject)129 static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
130 {
131 	char *dirname;
132 	size_t dir_len;
133 	zval *zcontext = NULL;
134 	php_stream_context *context = NULL;
135 	php_stream *dirp;
136 
137 	ZEND_PARSE_PARAMETERS_START(1, 2)
138 		Z_PARAM_PATH(dirname, dir_len)
139 		Z_PARAM_OPTIONAL
140 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
141 	ZEND_PARSE_PARAMETERS_END();
142 
143 	context = php_stream_context_from_zval(zcontext, 0);
144 
145 	dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
146 
147 	if (dirp == NULL) {
148 		RETURN_FALSE;
149 	}
150 
151 	dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
152 
153 	php_set_default_dir(dirp->res);
154 
155 	if (createobject) {
156 		object_init_ex(return_value, dir_class_entry_ptr);
157 		ZVAL_STRINGL(Z_DIRECTORY_PATH_P(return_value), dirname, dir_len);
158 		ZVAL_RES(Z_DIRECTORY_HANDLE_P(return_value), dirp->res);
159 		php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
160 	} else {
161 		php_stream_to_zval(dirp, return_value);
162 	}
163 }
164 /* }}} */
165 
166 /* {{{ Open a directory and return a dir_handle */
PHP_FUNCTION(opendir)167 PHP_FUNCTION(opendir)
168 {
169 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
170 }
171 /* }}} */
172 
173 /* {{{ Directory class with properties, handle and class and methods read, rewind and close */
PHP_FUNCTION(dir)174 PHP_FUNCTION(dir)
175 {
176 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
177 }
178 /* }}} */
179 
180 /* {{{ Close directory connection identified by the dir_handle */
PHP_FUNCTION(closedir)181 PHP_FUNCTION(closedir)
182 {
183 	zval *id = NULL, *myself;
184 	php_stream *dirp;
185 	zend_resource *res;
186 
187 	FETCH_DIRP();
188 
189 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
190 		zend_argument_type_error(1, "must be a valid Directory resource");
191 		RETURN_THROWS();
192 	}
193 
194 	res = dirp->res;
195 	zend_list_close(dirp->res);
196 
197 	if (res == DIRG(default_dir)) {
198 		php_set_default_dir(NULL);
199 	}
200 }
201 /* }}} */
202 
203 #if defined(HAVE_CHROOT) && !defined(ZTS) && defined(ENABLE_CHROOT_FUNC)
204 /* {{{ Change root directory */
PHP_FUNCTION(chroot)205 PHP_FUNCTION(chroot)
206 {
207 	char *str;
208 	int ret;
209 	size_t str_len;
210 
211 	ZEND_PARSE_PARAMETERS_START(1, 1)
212 		Z_PARAM_PATH(str, str_len)
213 	ZEND_PARSE_PARAMETERS_END();
214 
215 	ret = chroot(str);
216 	if (ret != 0) {
217 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
218 		RETURN_FALSE;
219 	}
220 
221 	php_clear_stat_cache(1, NULL, 0);
222 
223 	ret = chdir("/");
224 
225 	if (ret != 0) {
226 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
227 		RETURN_FALSE;
228 	}
229 
230 	RETURN_TRUE;
231 }
232 /* }}} */
233 #endif
234 
235 /* {{{ Change the current directory */
PHP_FUNCTION(chdir)236 PHP_FUNCTION(chdir)
237 {
238 	char *str;
239 	int ret;
240 	size_t str_len;
241 
242 	ZEND_PARSE_PARAMETERS_START(1, 1)
243 		Z_PARAM_PATH(str, str_len)
244 	ZEND_PARSE_PARAMETERS_END();
245 
246 	if (php_check_open_basedir(str)) {
247 		RETURN_FALSE;
248 	}
249 	ret = VCWD_CHDIR(str);
250 
251 	if (ret != 0) {
252 		php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
253 		RETURN_FALSE;
254 	}
255 
256 	if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentStatFile)), ZSTR_LEN(BG(CurrentStatFile)))) {
257 		zend_string_release(BG(CurrentStatFile));
258 		BG(CurrentStatFile) = NULL;
259 	}
260 	if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentLStatFile)), ZSTR_LEN(BG(CurrentLStatFile)))) {
261 		zend_string_release(BG(CurrentLStatFile));
262 		BG(CurrentLStatFile) = NULL;
263 	}
264 
265 	RETURN_TRUE;
266 }
267 /* }}} */
268 
269 /* {{{ Gets the current directory */
PHP_FUNCTION(getcwd)270 PHP_FUNCTION(getcwd)
271 {
272 	char path[MAXPATHLEN];
273 	char *ret=NULL;
274 
275 	ZEND_PARSE_PARAMETERS_NONE();
276 
277 #ifdef HAVE_GETCWD
278 	ret = VCWD_GETCWD(path, MAXPATHLEN);
279 #elif defined(HAVE_GETWD)
280 	ret = VCWD_GETWD(path);
281 #endif
282 
283 	if (ret) {
284 		RETURN_STRING(path);
285 	} else {
286 		RETURN_FALSE;
287 	}
288 }
289 /* }}} */
290 
291 /* {{{ Rewind dir_handle back to the start */
PHP_FUNCTION(rewinddir)292 PHP_FUNCTION(rewinddir)
293 {
294 	zval *id = NULL, *myself;
295 	php_stream *dirp;
296 
297 	FETCH_DIRP();
298 
299 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
300 		zend_argument_type_error(1, "must be a valid Directory resource");
301 		RETURN_THROWS();
302 	}
303 
304 	php_stream_rewinddir(dirp);
305 }
306 /* }}} */
307 
308 /* {{{ Read directory entry from dir_handle */
PHP_FUNCTION(readdir)309 PHP_FUNCTION(readdir)
310 {
311 	zval *id = NULL, *myself;
312 	php_stream *dirp;
313 	php_stream_dirent entry;
314 
315 	FETCH_DIRP();
316 
317 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
318 		zend_argument_type_error(1, "must be a valid Directory resource");
319 		RETURN_THROWS();
320 	}
321 
322 	if (php_stream_readdir(dirp, &entry)) {
323 		RETURN_STRINGL(entry.d_name, strlen(entry.d_name));
324 	}
325 	RETURN_FALSE;
326 }
327 /* }}} */
328 
329 #ifdef HAVE_GLOB
330 /* {{{ Find pathnames matching a pattern */
PHP_FUNCTION(glob)331 PHP_FUNCTION(glob)
332 {
333 	size_t cwd_skip = 0;
334 #ifdef ZTS
335 	char cwd[MAXPATHLEN];
336 	char work_pattern[MAXPATHLEN];
337 	char *result;
338 #endif
339 	char *pattern = NULL;
340 	size_t pattern_len;
341 	zend_long flags = 0;
342 	glob_t globbuf;
343 	size_t n;
344 	int ret;
345 	bool basedir_limit = 0;
346 	zval tmp;
347 
348 	ZEND_PARSE_PARAMETERS_START(1, 2)
349 		Z_PARAM_PATH(pattern, pattern_len)
350 		Z_PARAM_OPTIONAL
351 		Z_PARAM_LONG(flags)
352 	ZEND_PARSE_PARAMETERS_END();
353 
354 	if (pattern_len >= MAXPATHLEN) {
355 		php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
356 		RETURN_FALSE;
357 	}
358 
359 	if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
360 		php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
361 		RETURN_FALSE;
362 	}
363 
364 #ifdef ZTS
365 	if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
366 		result = VCWD_GETCWD(cwd, MAXPATHLEN);
367 		if (!result) {
368 			cwd[0] = '\0';
369 		}
370 #ifdef PHP_WIN32
371 		if (IS_SLASH(*pattern)) {
372 			cwd[2] = '\0';
373 		}
374 #endif
375 		cwd_skip = strlen(cwd)+1;
376 
377 		snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
378 		pattern = work_pattern;
379 	}
380 #endif
381 
382 
383 	memset(&globbuf, 0, sizeof(glob_t));
384 	globbuf.gl_offs = 0;
385 	if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
386 #ifdef GLOB_NOMATCH
387 		if (GLOB_NOMATCH == ret) {
388 			/* Some glob implementation simply return no data if no matches
389 			   were found, others return the GLOB_NOMATCH error code.
390 			   We don't want to treat GLOB_NOMATCH as an error condition
391 			   so that PHP glob() behaves the same on both types of
392 			   implementations and so that 'foreach (glob() as ...'
393 			   can be used for simple glob() calls without further error
394 			   checking.
395 			*/
396 			goto no_results;
397 		}
398 #endif
399 		RETURN_FALSE;
400 	}
401 
402 	/* now catch the FreeBSD style of "no matches" */
403 	if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
404 #ifdef GLOB_NOMATCH
405 no_results:
406 #endif
407 		array_init(return_value);
408 		return;
409 	}
410 
411 	array_init(return_value);
412 	for (n = 0; n < (size_t)globbuf.gl_pathc; n++) {
413 		if (PG(open_basedir) && *PG(open_basedir)) {
414 			if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) {
415 				basedir_limit = 1;
416 				continue;
417 			}
418 		}
419 		/* we need to do this every time since GLOB_ONLYDIR does not guarantee that
420 		 * all directories will be filtered. GNU libc documentation states the
421 		 * following:
422 		 * If the information about the type of the file is easily available
423 		 * non-directories will be rejected but no extra work will be done to
424 		 * determine the information for each file. I.e., the caller must still be
425 		 * able to filter directories out.
426 		 */
427 		if (flags & GLOB_ONLYDIR) {
428 			zend_stat_t s = {0};
429 
430 			if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
431 				continue;
432 			}
433 
434 			if (S_IFDIR != (s.st_mode & S_IFMT)) {
435 				continue;
436 			}
437 		}
438 		ZVAL_STRING(&tmp, globbuf.gl_pathv[n]+cwd_skip);
439 		zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
440 	}
441 
442 	globfree(&globbuf);
443 
444 	if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
445 		zend_array_destroy(Z_ARR_P(return_value));
446 		RETURN_FALSE;
447 	}
448 }
449 /* }}} */
450 #endif
451 
452 /* {{{ List files & directories inside the specified path */
PHP_FUNCTION(scandir)453 PHP_FUNCTION(scandir)
454 {
455 	char *dirn;
456 	size_t dirn_len;
457 	zend_long flags = PHP_SCANDIR_SORT_ASCENDING;
458 	zend_string **namelist;
459 	int n, i;
460 	zval *zcontext = NULL;
461 	php_stream_context *context = NULL;
462 
463 	ZEND_PARSE_PARAMETERS_START(1, 3)
464 		Z_PARAM_PATH(dirn, dirn_len)
465 		Z_PARAM_OPTIONAL
466 		Z_PARAM_LONG(flags)
467 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
468 	ZEND_PARSE_PARAMETERS_END();
469 
470 	if (dirn_len < 1) {
471 		zend_argument_value_error(1, "cannot be empty");
472 		RETURN_THROWS();
473 	}
474 
475 	if (zcontext) {
476 		context = php_stream_context_from_zval(zcontext, 0);
477 	}
478 
479 	if (flags == PHP_SCANDIR_SORT_ASCENDING) {
480 		n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
481 	} else if (flags == PHP_SCANDIR_SORT_NONE) {
482 		n = php_stream_scandir(dirn, &namelist, context, NULL);
483 	} else {
484 		n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
485 	}
486 	if (n < 0) {
487 		php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno));
488 		RETURN_FALSE;
489 	}
490 
491 	array_init(return_value);
492 
493 	for (i = 0; i < n; i++) {
494 		add_next_index_str(return_value, namelist[i]);
495 	}
496 
497 	if (n) {
498 		efree(namelist);
499 	}
500 }
501 /* }}} */
502