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