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