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