1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2014 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