xref: /PHP-5.5/ext/standard/dir.c (revision be9b2a95)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2015 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 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_ASCENDING",  PHP_SCANDIR_SORT_ASCENDING,  CONST_CS | CONST_PERSISTENT);
152 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_DESCENDING", PHP_SCANDIR_SORT_DESCENDING, CONST_CS | CONST_PERSISTENT);
153 	REGISTER_LONG_CONSTANT("SCANDIR_SORT_NONE",       PHP_SCANDIR_SORT_NONE,       CONST_CS | CONST_PERSISTENT);
154 
155 #ifdef HAVE_GLOB
156 
157 #ifdef GLOB_BRACE
158 	REGISTER_LONG_CONSTANT("GLOB_BRACE", GLOB_BRACE, CONST_CS | CONST_PERSISTENT);
159 #else
160 # define GLOB_BRACE 0
161 #endif
162 
163 #ifdef GLOB_MARK
164 	REGISTER_LONG_CONSTANT("GLOB_MARK", GLOB_MARK, CONST_CS | CONST_PERSISTENT);
165 #else
166 # define GLOB_MARK 0
167 #endif
168 
169 #ifdef GLOB_NOSORT
170 	REGISTER_LONG_CONSTANT("GLOB_NOSORT", GLOB_NOSORT, CONST_CS | CONST_PERSISTENT);
171 #else
172 # define GLOB_NOSORT 0
173 #endif
174 
175 #ifdef GLOB_NOCHECK
176 	REGISTER_LONG_CONSTANT("GLOB_NOCHECK", GLOB_NOCHECK, CONST_CS | CONST_PERSISTENT);
177 #else
178 # define GLOB_NOCHECK 0
179 #endif
180 
181 #ifdef GLOB_NOESCAPE
182 	REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", GLOB_NOESCAPE, CONST_CS | CONST_PERSISTENT);
183 #else
184 # define GLOB_NOESCAPE 0
185 #endif
186 
187 #ifdef GLOB_ERR
188 	REGISTER_LONG_CONSTANT("GLOB_ERR", GLOB_ERR, CONST_CS | CONST_PERSISTENT);
189 #else
190 # define GLOB_ERR 0
191 #endif
192 
193 #ifndef GLOB_ONLYDIR
194 # define GLOB_ONLYDIR (1<<30)
195 # define GLOB_EMULATE_ONLYDIR
196 # define GLOB_FLAGMASK (~GLOB_ONLYDIR)
197 #else
198 # define GLOB_FLAGMASK (~0)
199 #endif
200 
201 /* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */
202 #define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR)
203 
204 	REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", GLOB_ONLYDIR, CONST_CS | CONST_PERSISTENT);
205 	REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", GLOB_AVAILABLE_FLAGS, CONST_CS | CONST_PERSISTENT);
206 
207 #endif /* HAVE_GLOB */
208 
209 	return SUCCESS;
210 }
211 /* }}} */
212 
213 /* {{{ internal functions */
_php_do_opendir(INTERNAL_FUNCTION_PARAMETERS,int createobject)214 static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
215 {
216 	char *dirname;
217 	int dir_len;
218 	zval *zcontext = NULL;
219 	php_stream_context *context = NULL;
220 	php_stream *dirp;
221 
222 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p|r", &dirname, &dir_len, &zcontext) == FAILURE) {
223 		RETURN_NULL();
224 	}
225 
226 	context = php_stream_context_from_zval(zcontext, 0);
227 
228 	dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
229 
230 	if (dirp == NULL) {
231 		RETURN_FALSE;
232 	}
233 
234 	dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
235 
236 	php_set_default_dir(dirp->rsrc_id TSRMLS_CC);
237 
238 	if (createobject) {
239 		object_init_ex(return_value, dir_class_entry_ptr);
240 		add_property_stringl(return_value, "path", dirname, dir_len, 1);
241 		add_property_resource(return_value, "handle", dirp->rsrc_id);
242 		php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
243 	} else {
244 		php_stream_to_zval(dirp, return_value);
245 	}
246 }
247 /* }}} */
248 
249 /* {{{ proto mixed opendir(string path[, resource context])
250    Open a directory and return a dir_handle */
PHP_FUNCTION(opendir)251 PHP_FUNCTION(opendir)
252 {
253 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
254 }
255 /* }}} */
256 
257 /* {{{ proto object dir(string directory[, resource context])
258    Directory class with properties, handle and class and methods read, rewind and close */
PHP_FUNCTION(getdir)259 PHP_FUNCTION(getdir)
260 {
261 	_php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
262 }
263 /* }}} */
264 
265 /* {{{ proto void closedir([resource dir_handle])
266    Close directory connection identified by the dir_handle */
PHP_FUNCTION(closedir)267 PHP_FUNCTION(closedir)
268 {
269 	zval *id = NULL, **tmp, *myself;
270 	php_stream *dirp;
271 	int rsrc_id;
272 
273 	FETCH_DIRP();
274 
275 	if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
276 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "%d is not a valid Directory resource", dirp->rsrc_id);
277 		RETURN_FALSE;
278 	}
279 
280 	rsrc_id = dirp->rsrc_id;
281 	zend_list_delete(dirp->rsrc_id);
282 
283 	if (rsrc_id == DIRG(default_dir)) {
284 		php_set_default_dir(-1 TSRMLS_CC);
285 	}
286 }
287 /* }}} */
288 
289 #if defined(HAVE_CHROOT) && !defined(ZTS) && ENABLE_CHROOT_FUNC
290 /* {{{ proto bool chroot(string directory)
291    Change root directory */
PHP_FUNCTION(chroot)292 PHP_FUNCTION(chroot)
293 {
294 	char *str;
295 	int ret, str_len;
296 
297 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &str, &str_len) == FAILURE) {
298 		RETURN_FALSE;
299 	}
300 
301 	ret = chroot(str);
302 	if (ret != 0) {
303 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s (errno %d)", strerror(errno), errno);
304 		RETURN_FALSE;
305 	}
306 
307 	php_clear_stat_cache(1, NULL, 0 TSRMLS_CC);
308 
309 	ret = chdir("/");
310 
311 	if (ret != 0) {
312 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s (errno %d)", strerror(errno), errno);
313 		RETURN_FALSE;
314 	}
315 
316 	RETURN_TRUE;
317 }
318 /* }}} */
319 #endif
320 
321 /* {{{ proto bool chdir(string directory)
322    Change the current directory */
PHP_FUNCTION(chdir)323 PHP_FUNCTION(chdir)
324 {
325 	char *str;
326 	int ret, str_len;
327 
328 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &str, &str_len) == FAILURE) {
329 		RETURN_FALSE;
330 	}
331 
332 	if (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, "p|l", &pattern, &pattern_len, &flags) == FAILURE) {
440 		return;
441 	}
442 
443 	if (pattern_len >= MAXPATHLEN) {
444 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
445 		RETURN_FALSE;
446 	}
447 
448 	if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
449 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
450 		RETURN_FALSE;
451 	}
452 
453 #ifdef ZTS
454 	if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
455 		result = VCWD_GETCWD(cwd, MAXPATHLEN);
456 		if (!result) {
457 			cwd[0] = '\0';
458 		}
459 #ifdef PHP_WIN32
460 		if (IS_SLASH(*pattern)) {
461 			cwd[2] = '\0';
462 		}
463 #endif
464 		cwd_skip = strlen(cwd)+1;
465 
466 		snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
467 		pattern = work_pattern;
468 	}
469 #endif
470 
471 
472 	memset(&globbuf, 0, sizeof(glob_t));
473 	globbuf.gl_offs = 0;
474 	if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
475 #ifdef GLOB_NOMATCH
476 		if (GLOB_NOMATCH == ret) {
477 			/* Some glob implementation simply return no data if no matches
478 			   were found, others return the GLOB_NOMATCH error code.
479 			   We don't want to treat GLOB_NOMATCH as an error condition
480 			   so that PHP glob() behaves the same on both types of
481 			   implementations and so that 'foreach (glob() as ...'
482 			   can be used for simple glob() calls without further error
483 			   checking.
484 			*/
485 			goto no_results;
486 		}
487 #endif
488 		RETURN_FALSE;
489 	}
490 
491 	/* now catch the FreeBSD style of "no matches" */
492 	if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
493 no_results:
494 #ifndef PHP_WIN32
495 		/* Paths containing '*', '?' and some other chars are
496 		illegal on Windows but legit on other platforms. For
497 		this reason the direct basedir check against the glob
498 		query is senseless on windows. For instance while *.txt
499 		is a pretty valid filename on EXT3, it's invalid on NTFS. */
500 		if (PG(open_basedir) && *PG(open_basedir)) {
501 			if (php_check_open_basedir_ex(pattern, 0 TSRMLS_CC)) {
502 				RETURN_FALSE;
503 			}
504 		}
505 #endif
506 		array_init(return_value);
507 		return;
508 	}
509 
510 	array_init(return_value);
511 	for (n = 0; n < globbuf.gl_pathc; n++) {
512 		if (PG(open_basedir) && *PG(open_basedir)) {
513 			if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0 TSRMLS_CC)) {
514 				basedir_limit = 1;
515 				continue;
516 			}
517 		}
518 		/* we need to do this everytime since GLOB_ONLYDIR does not guarantee that
519 		 * all directories will be filtered. GNU libc documentation states the
520 		 * following:
521 		 * If the information about the type of the file is easily available
522 		 * non-directories will be rejected but no extra work will be done to
523 		 * determine the information for each file. I.e., the caller must still be
524 		 * able to filter directories out.
525 		 */
526 		if (flags & GLOB_ONLYDIR) {
527 			struct stat s;
528 
529 			if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
530 				continue;
531 			}
532 
533 			if (S_IFDIR != (s.st_mode & S_IFMT)) {
534 				continue;
535 			}
536 		}
537 		add_next_index_string(return_value, globbuf.gl_pathv[n]+cwd_skip, 1);
538 	}
539 
540 	globfree(&globbuf);
541 
542 	if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
543 		zval_dtor(return_value);
544 		RETURN_FALSE;
545 	}
546 }
547 /* }}} */
548 #endif
549 
550 /* {{{ proto array scandir(string dir [, int sorting_order [, resource context]])
551    List files & directories inside the specified path */
PHP_FUNCTION(scandir)552 PHP_FUNCTION(scandir)
553 {
554 	char *dirn;
555 	int dirn_len;
556 	long flags = 0;
557 	char **namelist;
558 	int n, i;
559 	zval *zcontext = NULL;
560 	php_stream_context *context = NULL;
561 
562 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p|lr", &dirn, &dirn_len, &flags, &zcontext) == FAILURE) {
563 		return;
564 	}
565 
566 	if (dirn_len < 1) {
567 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Directory name cannot be empty");
568 		RETURN_FALSE;
569 	}
570 
571 	if (zcontext) {
572 		context = php_stream_context_from_zval(zcontext, 0);
573 	}
574 
575 	if (flags == PHP_SCANDIR_SORT_ASCENDING) {
576 		n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
577 	} else if (flags == PHP_SCANDIR_SORT_NONE) {
578 		n = php_stream_scandir(dirn, &namelist, context, NULL);
579 	} else {
580 		n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
581 	}
582 	if (n < 0) {
583 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "(errno %d): %s", errno, strerror(errno));
584 		RETURN_FALSE;
585 	}
586 
587 	array_init(return_value);
588 
589 	for (i = 0; i < n; i++) {
590 		add_next_index_string(return_value, namelist[i], 0);
591 	}
592 
593 	if (n) {
594 		efree(namelist);
595 	}
596 }
597 /* }}} */
598 
599 /*
600  * Local variables:
601  * tab-width: 4
602  * c-basic-offset: 4
603  * End:
604  * vim600: sw=4 ts=4 fdm=marker
605  * vim<600: sw=4 ts=4
606  */
607