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