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