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