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 /* $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 zend_resource *default_dir;
56 } php_dir_globals;
57
58 #ifdef ZTS
59 #define DIRG(v) ZEND_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 static zend_class_entry *dir_class_entry_ptr;
67
68 #define FETCH_DIRP() \
69 ZEND_PARSE_PARAMETERS_START(0, 1) \
70 Z_PARAM_OPTIONAL \
71 Z_PARAM_RESOURCE(id) \
72 ZEND_PARSE_PARAMETERS_END(); \
73 if (ZEND_NUM_ARGS() == 0) { \
74 myself = getThis(); \
75 if (myself) { \
76 if ((tmp = zend_hash_str_find(Z_OBJPROP_P(myself), "handle", sizeof("handle")-1)) == NULL) { \
77 php_error_docref(NULL, E_WARNING, "Unable to find my handle property"); \
78 RETURN_FALSE; \
79 } \
80 if ((dirp = (php_stream *)zend_fetch_resource_ex(tmp, "Directory", php_file_le_stream())) == NULL) { \
81 RETURN_FALSE; \
82 } \
83 } else { \
84 if (!DIRG(default_dir) || \
85 (dirp = (php_stream *)zend_fetch_resource(DIRG(default_dir), "Directory", php_file_le_stream())) == NULL) { \
86 RETURN_FALSE; \
87 } \
88 } \
89 } else { \
90 if ((dirp = (php_stream *)zend_fetch_resource(Z_RES_P(id), "Directory", php_file_le_stream())) == NULL) { \
91 RETURN_FALSE; \
92 } \
93 }
94
95 /* {{{ arginfo */
96 ZEND_BEGIN_ARG_INFO_EX(arginfo_dir, 0, 0, 0)
97 ZEND_ARG_INFO(0, dir_handle)
98 ZEND_END_ARG_INFO()
99 /* }}} */
100
101 static const zend_function_entry php_dir_class_functions[] = {
102 PHP_FALIAS(close, closedir, arginfo_dir)
103 PHP_FALIAS(rewind, rewinddir, arginfo_dir)
104 PHP_NAMED_FE(read, php_if_readdir, arginfo_dir)
105 PHP_FE_END
106 };
107
108
php_set_default_dir(zend_resource * res)109 static void php_set_default_dir(zend_resource *res)
110 {
111 if (DIRG(default_dir)) {
112 zend_list_delete(DIRG(default_dir));
113 }
114
115 if (res) {
116 GC_REFCOUNT(res)++;
117 }
118
119 DIRG(default_dir) = res;
120 }
121
PHP_RINIT_FUNCTION(dir)122 PHP_RINIT_FUNCTION(dir)
123 {
124 DIRG(default_dir) = NULL;
125 return SUCCESS;
126 }
127
PHP_MINIT_FUNCTION(dir)128 PHP_MINIT_FUNCTION(dir)
129 {
130 static char dirsep_str[2], pathsep_str[2];
131 zend_class_entry dir_class_entry;
132
133 INIT_CLASS_ENTRY(dir_class_entry, "Directory", php_dir_class_functions);
134 dir_class_entry_ptr = zend_register_internal_class(&dir_class_entry);
135
136 #ifdef ZTS
137 ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
138 #endif
139
140 dirsep_str[0] = DEFAULT_SLASH;
141 dirsep_str[1] = '\0';
142 REGISTER_STRING_CONSTANT("DIRECTORY_SEPARATOR", dirsep_str, CONST_CS|CONST_PERSISTENT);
143
144 pathsep_str[0] = ZEND_PATHS_SEPARATOR;
145 pathsep_str[1] = '\0';
146 REGISTER_STRING_CONSTANT("PATH_SEPARATOR", pathsep_str, CONST_CS|CONST_PERSISTENT);
147
148 REGISTER_LONG_CONSTANT("SCANDIR_SORT_ASCENDING", PHP_SCANDIR_SORT_ASCENDING, CONST_CS | CONST_PERSISTENT);
149 REGISTER_LONG_CONSTANT("SCANDIR_SORT_DESCENDING", PHP_SCANDIR_SORT_DESCENDING, CONST_CS | CONST_PERSISTENT);
150 REGISTER_LONG_CONSTANT("SCANDIR_SORT_NONE", PHP_SCANDIR_SORT_NONE, CONST_CS | CONST_PERSISTENT);
151
152 #ifdef HAVE_GLOB
153
154 #ifdef GLOB_BRACE
155 REGISTER_LONG_CONSTANT("GLOB_BRACE", GLOB_BRACE, CONST_CS | CONST_PERSISTENT);
156 #else
157 # define GLOB_BRACE 0
158 #endif
159
160 #ifdef GLOB_MARK
161 REGISTER_LONG_CONSTANT("GLOB_MARK", GLOB_MARK, CONST_CS | CONST_PERSISTENT);
162 #else
163 # define GLOB_MARK 0
164 #endif
165
166 #ifdef GLOB_NOSORT
167 REGISTER_LONG_CONSTANT("GLOB_NOSORT", GLOB_NOSORT, CONST_CS | CONST_PERSISTENT);
168 #else
169 # define GLOB_NOSORT 0
170 #endif
171
172 #ifdef GLOB_NOCHECK
173 REGISTER_LONG_CONSTANT("GLOB_NOCHECK", GLOB_NOCHECK, CONST_CS | CONST_PERSISTENT);
174 #else
175 # define GLOB_NOCHECK 0
176 #endif
177
178 #ifdef GLOB_NOESCAPE
179 REGISTER_LONG_CONSTANT("GLOB_NOESCAPE", GLOB_NOESCAPE, CONST_CS | CONST_PERSISTENT);
180 #else
181 # define GLOB_NOESCAPE 0
182 #endif
183
184 #ifdef GLOB_ERR
185 REGISTER_LONG_CONSTANT("GLOB_ERR", GLOB_ERR, CONST_CS | CONST_PERSISTENT);
186 #else
187 # define GLOB_ERR 0
188 #endif
189
190 #ifndef GLOB_ONLYDIR
191 # define GLOB_ONLYDIR (1<<30)
192 # define GLOB_EMULATE_ONLYDIR
193 # define GLOB_FLAGMASK (~GLOB_ONLYDIR)
194 #else
195 # define GLOB_FLAGMASK (~0)
196 #endif
197
198 /* This is used for checking validity of passed flags (passing invalid flags causes segfault in glob()!! */
199 #define GLOB_AVAILABLE_FLAGS (0 | GLOB_BRACE | GLOB_MARK | GLOB_NOSORT | GLOB_NOCHECK | GLOB_NOESCAPE | GLOB_ERR | GLOB_ONLYDIR)
200
201 REGISTER_LONG_CONSTANT("GLOB_ONLYDIR", GLOB_ONLYDIR, CONST_CS | CONST_PERSISTENT);
202 REGISTER_LONG_CONSTANT("GLOB_AVAILABLE_FLAGS", GLOB_AVAILABLE_FLAGS, CONST_CS | CONST_PERSISTENT);
203
204 #endif /* HAVE_GLOB */
205
206 return SUCCESS;
207 }
208 /* }}} */
209
210 /* {{{ internal functions */
_php_do_opendir(INTERNAL_FUNCTION_PARAMETERS,int createobject)211 static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
212 {
213 char *dirname;
214 size_t dir_len;
215 zval *zcontext = NULL;
216 php_stream_context *context = NULL;
217 php_stream *dirp;
218
219 ZEND_PARSE_PARAMETERS_START(1, 2)
220 Z_PARAM_PATH(dirname, dir_len)
221 Z_PARAM_OPTIONAL
222 Z_PARAM_RESOURCE(zcontext)
223 ZEND_PARSE_PARAMETERS_END();
224
225 context = php_stream_context_from_zval(zcontext, 0);
226
227 dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
228
229 if (dirp == NULL) {
230 RETURN_FALSE;
231 }
232
233 dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
234
235 php_set_default_dir(dirp->res);
236
237 if (createobject) {
238 object_init_ex(return_value, dir_class_entry_ptr);
239 add_property_stringl(return_value, "path", dirname, dir_len);
240 add_property_resource(return_value, "handle", dirp->res);
241 php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
242 } else {
243 php_stream_to_zval(dirp, return_value);
244 }
245 }
246 /* }}} */
247
248 /* {{{ proto mixed opendir(string path[, resource context])
249 Open a directory and return a dir_handle */
PHP_FUNCTION(opendir)250 PHP_FUNCTION(opendir)
251 {
252 _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
253 }
254 /* }}} */
255
256 /* {{{ proto object dir(string directory[, resource context])
257 Directory class with properties, handle and class and methods read, rewind and close */
PHP_FUNCTION(getdir)258 PHP_FUNCTION(getdir)
259 {
260 _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
261 }
262 /* }}} */
263
264 /* {{{ proto void closedir([resource dir_handle])
265 Close directory connection identified by the dir_handle */
PHP_FUNCTION(closedir)266 PHP_FUNCTION(closedir)
267 {
268 zval *id = NULL, *tmp, *myself;
269 php_stream *dirp;
270 zend_resource *res;
271
272 FETCH_DIRP();
273
274 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
275 php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
276 RETURN_FALSE;
277 }
278
279 res = dirp->res;
280 zend_list_close(dirp->res);
281
282 if (res == DIRG(default_dir)) {
283 php_set_default_dir(NULL);
284 }
285 }
286 /* }}} */
287
288 #if defined(HAVE_CHROOT) && !defined(ZTS) && ENABLE_CHROOT_FUNC
289 /* {{{ proto bool chroot(string directory)
290 Change root directory */
PHP_FUNCTION(chroot)291 PHP_FUNCTION(chroot)
292 {
293 char *str;
294 int ret;
295 size_t str_len;
296
297 ZEND_PARSE_PARAMETERS_START(1, 1)
298 Z_PARAM_PATH(str, str_len)
299 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
300
301 ret = chroot(str);
302 if (ret != 0) {
303 php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
304 RETURN_FALSE;
305 }
306
307 php_clear_stat_cache(1, NULL, 0);
308
309 ret = chdir("/");
310
311 if (ret != 0) {
312 php_error_docref(NULL, 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;
327 size_t str_len;
328
329 ZEND_PARSE_PARAMETERS_START(1, 1)
330 Z_PARAM_PATH(str, str_len)
331 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
332
333 if (php_check_open_basedir(str)) {
334 RETURN_FALSE;
335 }
336 ret = VCWD_CHDIR(str);
337
338 if (ret != 0) {
339 php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
340 RETURN_FALSE;
341 }
342
343 if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentStatFile), strlen(BG(CurrentStatFile)))) {
344 efree(BG(CurrentStatFile));
345 BG(CurrentStatFile) = NULL;
346 }
347 if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(BG(CurrentLStatFile), strlen(BG(CurrentLStatFile)))) {
348 efree(BG(CurrentLStatFile));
349 BG(CurrentLStatFile) = NULL;
350 }
351
352 RETURN_TRUE;
353 }
354 /* }}} */
355
356 /* {{{ proto mixed getcwd(void)
357 Gets the current directory */
PHP_FUNCTION(getcwd)358 PHP_FUNCTION(getcwd)
359 {
360 char path[MAXPATHLEN];
361 char *ret=NULL;
362
363 if (zend_parse_parameters_none() == FAILURE) {
364 return;
365 }
366
367 #if HAVE_GETCWD
368 ret = VCWD_GETCWD(path, MAXPATHLEN);
369 #elif HAVE_GETWD
370 ret = VCWD_GETWD(path);
371 #endif
372
373 if (ret) {
374 RETURN_STRING(path);
375 } else {
376 RETURN_FALSE;
377 }
378 }
379 /* }}} */
380
381 /* {{{ proto void rewinddir([resource dir_handle])
382 Rewind dir_handle back to the start */
PHP_FUNCTION(rewinddir)383 PHP_FUNCTION(rewinddir)
384 {
385 zval *id = NULL, *tmp, *myself;
386 php_stream *dirp;
387
388 FETCH_DIRP();
389
390 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
391 php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
392 RETURN_FALSE;
393 }
394
395 php_stream_rewinddir(dirp);
396 }
397 /* }}} */
398
399 /* {{{ proto string readdir([resource dir_handle])
400 Read directory entry from dir_handle */
PHP_NAMED_FUNCTION(php_if_readdir)401 PHP_NAMED_FUNCTION(php_if_readdir)
402 {
403 zval *id = NULL, *tmp, *myself;
404 php_stream *dirp;
405 php_stream_dirent entry;
406
407 FETCH_DIRP();
408
409 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
410 php_error_docref(NULL, E_WARNING, "%d is not a valid Directory resource", dirp->res->handle);
411 RETURN_FALSE;
412 }
413
414 if (php_stream_readdir(dirp, &entry)) {
415 RETURN_STRINGL(entry.d_name, strlen(entry.d_name));
416 }
417 RETURN_FALSE;
418 }
419 /* }}} */
420
421 #ifdef HAVE_GLOB
422 /* {{{ proto array glob(string pattern [, int flags])
423 Find pathnames matching a pattern */
PHP_FUNCTION(glob)424 PHP_FUNCTION(glob)
425 {
426 size_t cwd_skip = 0;
427 #ifdef ZTS
428 char cwd[MAXPATHLEN];
429 char work_pattern[MAXPATHLEN];
430 char *result;
431 #endif
432 char *pattern = NULL;
433 size_t pattern_len;
434 zend_long flags = 0;
435 glob_t globbuf;
436 size_t n;
437 int ret;
438 zend_bool basedir_limit = 0;
439
440 ZEND_PARSE_PARAMETERS_START(1, 2)
441 Z_PARAM_PATH(pattern, pattern_len)
442 Z_PARAM_OPTIONAL
443 Z_PARAM_LONG(flags)
444 ZEND_PARSE_PARAMETERS_END();
445
446 if (pattern_len >= MAXPATHLEN) {
447 php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
448 RETURN_FALSE;
449 }
450
451 if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
452 php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
453 RETURN_FALSE;
454 }
455
456 #ifdef ZTS
457 if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
458 result = VCWD_GETCWD(cwd, MAXPATHLEN);
459 if (!result) {
460 cwd[0] = '\0';
461 }
462 #ifdef PHP_WIN32
463 if (IS_SLASH(*pattern)) {
464 cwd[2] = '\0';
465 }
466 #endif
467 cwd_skip = strlen(cwd)+1;
468
469 snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
470 pattern = work_pattern;
471 }
472 #endif
473
474
475 memset(&globbuf, 0, sizeof(glob_t));
476 globbuf.gl_offs = 0;
477 if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
478 #ifdef GLOB_NOMATCH
479 if (GLOB_NOMATCH == ret) {
480 /* Some glob implementation simply return no data if no matches
481 were found, others return the GLOB_NOMATCH error code.
482 We don't want to treat GLOB_NOMATCH as an error condition
483 so that PHP glob() behaves the same on both types of
484 implementations and so that 'foreach (glob() as ...'
485 can be used for simple glob() calls without further error
486 checking.
487 */
488 goto no_results;
489 }
490 #endif
491 RETURN_FALSE;
492 }
493
494 /* now catch the FreeBSD style of "no matches" */
495 if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
496 no_results:
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 < 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 zval_dtor(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