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