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 #ifdef 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) && defined(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 #ifdef HAVE_GETCWD
346 ret = VCWD_GETCWD(path, MAXPATHLEN);
347 #elif defined(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 array_init(return_value);
476 return;
477 }
478
479 array_init(return_value);
480 for (n = 0; n < (size_t)globbuf.gl_pathc; n++) {
481 if (PG(open_basedir) && *PG(open_basedir)) {
482 if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) {
483 basedir_limit = 1;
484 continue;
485 }
486 }
487 /* we need to do this every time since GLOB_ONLYDIR does not guarantee that
488 * all directories will be filtered. GNU libc documentation states the
489 * following:
490 * If the information about the type of the file is easily available
491 * non-directories will be rejected but no extra work will be done to
492 * determine the information for each file. I.e., the caller must still be
493 * able to filter directories out.
494 */
495 if (flags & GLOB_ONLYDIR) {
496 zend_stat_t s = {0};
497
498 if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
499 continue;
500 }
501
502 if (S_IFDIR != (s.st_mode & S_IFMT)) {
503 continue;
504 }
505 }
506 ZVAL_STRING(&tmp, globbuf.gl_pathv[n]+cwd_skip);
507 zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
508 }
509
510 globfree(&globbuf);
511
512 if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
513 zend_array_destroy(Z_ARR_P(return_value));
514 RETURN_FALSE;
515 }
516 }
517 /* }}} */
518 #endif
519
520 /* {{{ List files & directories inside the specified path */
PHP_FUNCTION(scandir)521 PHP_FUNCTION(scandir)
522 {
523 char *dirn;
524 size_t dirn_len;
525 zend_long flags = PHP_SCANDIR_SORT_ASCENDING;
526 zend_string **namelist;
527 int n, i;
528 zval *zcontext = NULL;
529 php_stream_context *context = NULL;
530
531 ZEND_PARSE_PARAMETERS_START(1, 3)
532 Z_PARAM_PATH(dirn, dirn_len)
533 Z_PARAM_OPTIONAL
534 Z_PARAM_LONG(flags)
535 Z_PARAM_RESOURCE_OR_NULL(zcontext)
536 ZEND_PARSE_PARAMETERS_END();
537
538 if (dirn_len < 1) {
539 zend_argument_value_error(1, "cannot be empty");
540 RETURN_THROWS();
541 }
542
543 if (zcontext) {
544 context = php_stream_context_from_zval(zcontext, 0);
545 }
546
547 if (flags == PHP_SCANDIR_SORT_ASCENDING) {
548 n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
549 } else if (flags == PHP_SCANDIR_SORT_NONE) {
550 n = php_stream_scandir(dirn, &namelist, context, NULL);
551 } else {
552 n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
553 }
554 if (n < 0) {
555 php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno));
556 RETURN_FALSE;
557 }
558
559 array_init(return_value);
560
561 for (i = 0; i < n; i++) {
562 add_next_index_str(return_value, namelist[i]);
563 }
564
565 if (n) {
566 efree(namelist);
567 }
568 }
569 /* }}} */
570