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