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