/src/php-src/ext/standard/dir.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Copyright © The PHP Group and Contributors. | |
4 | | +----------------------------------------------------------------------+ |
5 | | | This source file is subject to the Modified BSD License that is | |
6 | | | bundled with this package in the file LICENSE, and is available | |
7 | | | through the World Wide Web at <https://www.php.net/license/>. | |
8 | | | | |
9 | | | SPDX-License-Identifier: BSD-3-Clause | |
10 | | +----------------------------------------------------------------------+ |
11 | | | Author: Thies C. Arntzen <thies@thieso.net> | |
12 | | +----------------------------------------------------------------------+ |
13 | | */ |
14 | | |
15 | | /* {{{ includes/startup/misc */ |
16 | | |
17 | | #include "php.h" |
18 | | #include "fopen_wrappers.h" |
19 | | #include "file.h" |
20 | | #include "php_dir.h" |
21 | | #include "php_dir_int.h" |
22 | | #include "php_scandir.h" |
23 | | #include "basic_functions.h" |
24 | | #include "dir_arginfo.h" |
25 | | |
26 | | #ifdef HAVE_UNISTD_H |
27 | | #include <unistd.h> |
28 | | #endif |
29 | | |
30 | | #include <errno.h> |
31 | | |
32 | | #ifdef PHP_WIN32 |
33 | | #include "win32/readdir.h" |
34 | | #endif |
35 | | |
36 | | typedef struct { |
37 | | zend_resource *default_dir; |
38 | | } php_dir_globals; |
39 | | |
40 | | #ifdef ZTS |
41 | | #define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v) |
42 | | int dir_globals_id; |
43 | | #else |
44 | 44.5k | #define DIRG(v) (dir_globals.v) |
45 | | php_dir_globals dir_globals; |
46 | | #endif |
47 | | |
48 | | static zend_class_entry *dir_class_entry_ptr; |
49 | | static zend_object_handlers dir_class_object_handlers; |
50 | | |
51 | | #define Z_DIRECTORY_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0) |
52 | 0 | #define Z_DIRECTORY_HANDLE_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 1) |
53 | | |
54 | | static zend_function *dir_class_get_constructor(zend_object *object) |
55 | 2 | { |
56 | 2 | zend_throw_error(NULL, "Cannot directly construct Directory, use dir() instead"); |
57 | 2 | return NULL; |
58 | 2 | } |
59 | | |
60 | | static void php_set_default_dir(zend_resource *res) |
61 | 59 | { |
62 | 59 | if (DIRG(default_dir)) { |
63 | 28 | zend_list_delete(DIRG(default_dir)); |
64 | 28 | } |
65 | | |
66 | 59 | if (res) { |
67 | 31 | GC_ADDREF(res); |
68 | 31 | } |
69 | | |
70 | 59 | DIRG(default_dir) = res; |
71 | 59 | } |
72 | | |
73 | | PHP_RINIT_FUNCTION(dir) |
74 | 44.4k | { |
75 | 44.4k | DIRG(default_dir) = NULL; |
76 | 44.4k | return SUCCESS; |
77 | 44.4k | } |
78 | | |
79 | | PHP_MINIT_FUNCTION(dir) |
80 | 2 | { |
81 | 2 | dirsep_str[0] = DEFAULT_SLASH; |
82 | 2 | dirsep_str[1] = '\0'; |
83 | | |
84 | 2 | pathsep_str[0] = ZEND_PATHS_SEPARATOR; |
85 | 2 | pathsep_str[1] = '\0'; |
86 | | |
87 | 2 | register_dir_symbols(module_number); |
88 | | |
89 | 2 | dir_class_entry_ptr = register_class_Directory(); |
90 | 2 | dir_class_entry_ptr->default_object_handlers = &dir_class_object_handlers; |
91 | | |
92 | 2 | memcpy(&dir_class_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); |
93 | 2 | dir_class_object_handlers.get_constructor = dir_class_get_constructor; |
94 | 2 | dir_class_object_handlers.clone_obj = NULL; |
95 | 2 | dir_class_object_handlers.compare = zend_objects_not_comparable; |
96 | | |
97 | | #ifdef ZTS |
98 | | ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL); |
99 | | #endif |
100 | | |
101 | 2 | return SUCCESS; |
102 | 2 | } |
103 | | /* }}} */ |
104 | | |
105 | | /* {{{ internal functions */ |
106 | | static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject) |
107 | 40 | { |
108 | 40 | char *dirname; |
109 | 40 | size_t dir_len; |
110 | 40 | zval *zcontext = NULL; |
111 | 40 | php_stream_context *context = NULL; |
112 | 40 | php_stream *dirp; |
113 | | |
114 | 120 | ZEND_PARSE_PARAMETERS_START(1, 2) |
115 | 160 | Z_PARAM_PATH(dirname, dir_len) |
116 | 40 | Z_PARAM_OPTIONAL |
117 | 80 | Z_PARAM_RESOURCE_OR_NULL(zcontext) |
118 | 40 | ZEND_PARSE_PARAMETERS_END(); |
119 | | |
120 | 40 | context = php_stream_context_from_zval(zcontext, 0); |
121 | | |
122 | 40 | dirp = php_stream_opendir(dirname, REPORT_ERRORS, context); |
123 | | |
124 | 40 | if (dirp == NULL) { |
125 | 9 | RETURN_FALSE; |
126 | 9 | } |
127 | | |
128 | 31 | dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE; |
129 | | |
130 | 31 | php_set_default_dir(dirp->res); |
131 | | |
132 | 31 | if (createobject) { |
133 | 0 | object_init_ex(return_value, dir_class_entry_ptr); |
134 | 0 | ZVAL_STRINGL(Z_DIRECTORY_PATH_P(return_value), dirname, dir_len); |
135 | 0 | ZVAL_RES(Z_DIRECTORY_HANDLE_P(return_value), dirp->res); |
136 | 0 | php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */ |
137 | 31 | } else { |
138 | 31 | php_stream_to_zval(dirp, return_value); |
139 | 31 | } |
140 | 31 | } |
141 | | /* }}} */ |
142 | | |
143 | | /* {{{ Open a directory and return a dir_handle */ |
144 | | PHP_FUNCTION(opendir) |
145 | 37 | { |
146 | 37 | _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); |
147 | 37 | } |
148 | | /* }}} */ |
149 | | |
150 | | /* {{{ Directory class with properties, handle and class and methods read, rewind and close */ |
151 | | PHP_FUNCTION(dir) |
152 | 3 | { |
153 | 3 | _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); |
154 | 3 | } |
155 | | /* }}} */ |
156 | | |
157 | | |
158 | | static php_stream* php_dir_get_directory_stream_from_user_arg(php_stream *dir_stream) |
159 | 28 | { |
160 | 28 | if (dir_stream == NULL) { |
161 | 0 | php_error_docref(NULL, E_DEPRECATED, |
162 | 0 | "Passing null is deprecated, instead the last opened directory stream should be provided"); |
163 | 0 | if (UNEXPECTED(DIRG(default_dir) == NULL)) { |
164 | 0 | zend_type_error("No resource supplied"); |
165 | 0 | return NULL; |
166 | 0 | } |
167 | 0 | zend_resource *res = DIRG(default_dir); |
168 | 0 | ZEND_ASSERT(res->type == php_file_le_stream()); |
169 | 0 | dir_stream = (php_stream*) res->ptr; |
170 | 0 | } |
171 | | |
172 | 28 | if (UNEXPECTED((dir_stream->flags & PHP_STREAM_FLAG_IS_DIR)) == 0) { |
173 | 0 | zend_argument_type_error(1, "must be a valid Directory resource"); |
174 | 0 | return NULL; |
175 | 0 | } |
176 | 28 | return dir_stream; |
177 | 28 | } |
178 | | |
179 | | static php_stream* php_dir_get_directory_stream_from_this(zval *this_z) |
180 | 0 | { |
181 | 0 | zval *handle_zv = Z_DIRECTORY_HANDLE_P(this_z); |
182 | 0 | if (UNEXPECTED(Z_TYPE_P(handle_zv) != IS_RESOURCE)) { |
183 | 0 | zend_throw_error(NULL, "Internal directory stream has been altered"); |
184 | 0 | return NULL; |
185 | 0 | } |
186 | 0 | zend_resource *res = Z_RES_P(handle_zv); |
187 | | /* Assume the close() method was called |
188 | | * (instead of the hacky case where a different resource would have been set via the ArrayObject "hack") */ |
189 | 0 | if (UNEXPECTED(res->type != php_file_le_stream())) { |
190 | | /* TypeError is used for BC, TODO: Use base Error in PHP 9 */ |
191 | 0 | zend_type_error("Directory::%s(): cannot use Directory resource after it has been closed", get_active_function_name()); |
192 | 0 | return NULL; |
193 | 0 | } |
194 | 0 | php_stream *dir_stream = (php_stream*) res->ptr; |
195 | 0 | if (UNEXPECTED((dir_stream->flags & PHP_STREAM_FLAG_IS_DIR)) == 0) { |
196 | 0 | zend_throw_error(NULL, "Internal directory stream has been altered"); |
197 | 0 | return NULL; |
198 | 0 | } |
199 | 0 | return dir_stream; |
200 | 0 | } |
201 | | |
202 | | /* {{{ Close directory connection identified by the dir_handle */ |
203 | | PHP_FUNCTION(closedir) |
204 | 28 | { |
205 | 28 | php_stream *dirp = NULL; |
206 | | |
207 | 84 | ZEND_PARSE_PARAMETERS_START(0, 1) |
208 | 84 | Z_PARAM_OPTIONAL |
209 | 112 | PHP_Z_PARAM_STREAM_OR_NULL(dirp) |
210 | 28 | ZEND_PARSE_PARAMETERS_END(); |
211 | | |
212 | 28 | dirp = php_dir_get_directory_stream_from_user_arg(dirp); |
213 | 28 | if (UNEXPECTED(dirp == NULL)) { |
214 | 0 | RETURN_THROWS(); |
215 | 0 | } |
216 | 28 | zend_resource *res = dirp->res; |
217 | 28 | zend_list_close(res); |
218 | | |
219 | 28 | if (res == DIRG(default_dir)) { |
220 | 28 | php_set_default_dir(NULL); |
221 | 28 | } |
222 | 28 | } |
223 | | /* }}} */ |
224 | | |
225 | | PHP_METHOD(Directory, close) |
226 | 0 | { |
227 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
228 | | |
229 | 0 | php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS); |
230 | 0 | if (UNEXPECTED(dirp == NULL)) { |
231 | 0 | RETURN_THROWS(); |
232 | 0 | } |
233 | | |
234 | 0 | zend_resource *res = dirp->res; |
235 | 0 | zend_list_close(res); |
236 | |
|
237 | 0 | if (res == DIRG(default_dir)) { |
238 | 0 | php_set_default_dir(NULL); |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | /* {{{ Rewind dir_handle back to the start */ |
243 | | PHP_FUNCTION(rewinddir) |
244 | 0 | { |
245 | 0 | php_stream *dirp = NULL; |
246 | |
|
247 | 0 | ZEND_PARSE_PARAMETERS_START(0, 1) |
248 | 0 | Z_PARAM_OPTIONAL |
249 | 0 | PHP_Z_PARAM_STREAM_OR_NULL(dirp) |
250 | 0 | ZEND_PARSE_PARAMETERS_END(); |
251 | | |
252 | 0 | dirp = php_dir_get_directory_stream_from_user_arg(dirp); |
253 | 0 | if (UNEXPECTED(dirp == NULL)) { |
254 | 0 | RETURN_THROWS(); |
255 | 0 | } |
256 | | |
257 | 0 | php_stream_rewinddir(dirp); |
258 | 0 | } |
259 | | /* }}} */ |
260 | | |
261 | | PHP_METHOD(Directory, rewind) |
262 | 0 | { |
263 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
264 | | |
265 | 0 | php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS); |
266 | 0 | if (UNEXPECTED(dirp == NULL)) { |
267 | 0 | RETURN_THROWS(); |
268 | 0 | } |
269 | | |
270 | 0 | php_stream_rewinddir(dirp); |
271 | 0 | } |
272 | | |
273 | | /* {{{ Read directory entry from dir_handle */ |
274 | | PHP_FUNCTION(readdir) |
275 | 0 | { |
276 | 0 | php_stream *dirp = NULL; |
277 | |
|
278 | 0 | ZEND_PARSE_PARAMETERS_START(0, 1) |
279 | 0 | Z_PARAM_OPTIONAL |
280 | 0 | PHP_Z_PARAM_STREAM_OR_NULL(dirp) |
281 | 0 | ZEND_PARSE_PARAMETERS_END(); |
282 | | |
283 | 0 | dirp = php_dir_get_directory_stream_from_user_arg(dirp); |
284 | 0 | if (UNEXPECTED(dirp == NULL)) { |
285 | 0 | RETURN_THROWS(); |
286 | 0 | } |
287 | | |
288 | 0 | php_stream_dirent entry; |
289 | 0 | if (php_stream_readdir(dirp, &entry)) { |
290 | 0 | RETURN_STRING(entry.d_name); |
291 | 0 | } |
292 | 0 | RETURN_FALSE; |
293 | 0 | } |
294 | | /* }}} */ |
295 | | |
296 | | PHP_METHOD(Directory, read) |
297 | 0 | { |
298 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
299 | | |
300 | 0 | php_stream *dirp = php_dir_get_directory_stream_from_this(ZEND_THIS); |
301 | 0 | if (UNEXPECTED(dirp == NULL)) { |
302 | 0 | RETURN_THROWS(); |
303 | 0 | } |
304 | | |
305 | 0 | php_stream_dirent entry; |
306 | 0 | if (php_stream_readdir(dirp, &entry)) { |
307 | 0 | RETURN_STRING(entry.d_name); |
308 | 0 | } |
309 | 0 | RETURN_FALSE; |
310 | 0 | } |
311 | | |
312 | | #if defined(HAVE_CHROOT) && !defined(ZTS) && defined(ENABLE_CHROOT_FUNC) |
313 | | /* {{{ Change root directory */ |
314 | | PHP_FUNCTION(chroot) |
315 | | { |
316 | | char *str; |
317 | | int ret; |
318 | | size_t str_len; |
319 | | |
320 | | ZEND_PARSE_PARAMETERS_START(1, 1) |
321 | | Z_PARAM_PATH(str, str_len) |
322 | | ZEND_PARSE_PARAMETERS_END(); |
323 | | |
324 | | ret = chroot(str); |
325 | | if (ret != 0) { |
326 | | php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); |
327 | | RETURN_FALSE; |
328 | | } |
329 | | |
330 | | php_clear_stat_cache(1, NULL, 0); |
331 | | |
332 | | ret = chdir("/"); |
333 | | |
334 | | if (ret != 0) { |
335 | | php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); |
336 | | RETURN_FALSE; |
337 | | } |
338 | | |
339 | | RETURN_TRUE; |
340 | | } |
341 | | /* }}} */ |
342 | | #endif |
343 | | |
344 | | /* {{{ Change the current directory */ |
345 | | PHP_FUNCTION(chdir) |
346 | 0 | { |
347 | 0 | char *str; |
348 | 0 | int ret; |
349 | 0 | size_t str_len; |
350 | |
|
351 | 0 | ZEND_PARSE_PARAMETERS_START(1, 1) |
352 | 0 | Z_PARAM_PATH(str, str_len) |
353 | 0 | ZEND_PARSE_PARAMETERS_END(); |
354 | | |
355 | 0 | if (php_check_open_basedir(str)) { |
356 | 0 | RETURN_FALSE; |
357 | 0 | } |
358 | 0 | ret = VCWD_CHDIR(str); |
359 | |
|
360 | 0 | if (ret != 0) { |
361 | 0 | php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); |
362 | 0 | RETURN_FALSE; |
363 | 0 | } |
364 | | |
365 | 0 | if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentStatFile)), ZSTR_LEN(BG(CurrentStatFile)))) { |
366 | 0 | zend_string_release(BG(CurrentStatFile)); |
367 | 0 | BG(CurrentStatFile) = NULL; |
368 | 0 | } |
369 | 0 | if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentLStatFile)), ZSTR_LEN(BG(CurrentLStatFile)))) { |
370 | 0 | zend_string_release(BG(CurrentLStatFile)); |
371 | 0 | BG(CurrentLStatFile) = NULL; |
372 | 0 | } |
373 | |
|
374 | 0 | RETURN_TRUE; |
375 | 0 | } |
376 | | /* }}} */ |
377 | | |
378 | | /* {{{ Gets the current directory */ |
379 | | PHP_FUNCTION(getcwd) |
380 | 3 | { |
381 | 3 | char path[MAXPATHLEN]; |
382 | 3 | char *ret=NULL; |
383 | | |
384 | 3 | ZEND_PARSE_PARAMETERS_NONE(); |
385 | | |
386 | 3 | #ifdef HAVE_GETCWD |
387 | 3 | ret = VCWD_GETCWD(path, MAXPATHLEN); |
388 | | #elif defined(HAVE_GETWD) |
389 | | ret = VCWD_GETWD(path); |
390 | | #endif |
391 | | |
392 | 3 | if (ret) { |
393 | 3 | RETURN_STRING(path); |
394 | 3 | } else { |
395 | 0 | RETURN_FALSE; |
396 | 0 | } |
397 | 3 | } |
398 | | /* }}} */ |
399 | | |
400 | | /* {{{ Find pathnames matching a pattern */ |
401 | | #if defined(ZTS) && defined(PHP_GLOB_ALTDIRFUNC) |
402 | | static void *php_glob_opendir_wrapper(const char *path) |
403 | | { |
404 | | return VCWD_OPENDIR(path); |
405 | | } |
406 | | |
407 | | static void php_glob_closedir_wrapper(void *dir) |
408 | | { |
409 | | (void) closedir(dir); |
410 | | } |
411 | | |
412 | | static int php_glob_lstat_wrapper(const char *buf, zend_stat_t *sb) |
413 | | { |
414 | | return VCWD_LSTAT(buf, sb); |
415 | | } |
416 | | |
417 | | static int php_glob_stat_wrapper(const char *buf, zend_stat_t *sb) |
418 | | { |
419 | | return VCWD_STAT(buf, sb); |
420 | | } |
421 | | #endif |
422 | | |
423 | | PHP_FUNCTION(glob) |
424 | 0 | { |
425 | 0 | size_t cwd_skip = 0; |
426 | | #if defined(ZTS) && !defined(PHP_GLOB_ALTDIRFUNC) |
427 | | char cwd[MAXPATHLEN]; |
428 | | char work_pattern[MAXPATHLEN]; |
429 | | #endif |
430 | 0 | char *pattern = NULL; |
431 | 0 | size_t pattern_len; |
432 | 0 | zend_long flags = 0; |
433 | 0 | php_glob_t globbuf; |
434 | 0 | size_t n; |
435 | 0 | int ret; |
436 | 0 | bool basedir_limit = 0; |
437 | 0 | zval tmp; |
438 | |
|
439 | 0 | ZEND_PARSE_PARAMETERS_START(1, 2) |
440 | 0 | Z_PARAM_PATH(pattern, pattern_len) |
441 | 0 | Z_PARAM_OPTIONAL |
442 | 0 | Z_PARAM_LONG(flags) |
443 | 0 | ZEND_PARSE_PARAMETERS_END(); |
444 | | |
445 | 0 | if (pattern_len >= MAXPATHLEN) { |
446 | 0 | php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN); |
447 | 0 | RETURN_FALSE; |
448 | 0 | } |
449 | | |
450 | 0 | if ((PHP_GLOB_AVAILABLE_FLAGS & flags) != flags) { |
451 | 0 | php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform"); |
452 | 0 | RETURN_FALSE; |
453 | 0 | } |
454 | | |
455 | 0 | memset(&globbuf, 0, sizeof(globbuf)); |
456 | |
|
457 | 0 | int passed_glob_flags = flags & PHP_GLOB_FLAGMASK; |
458 | |
|
459 | | #ifdef ZTS |
460 | | if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) { |
461 | | /* System glob uses the current work directory which is not thread safe. |
462 | | * The first fix is to override the functions used to open/read/... paths |
463 | | * with the VCWD ones used in PHP. |
464 | | * If that functionality is unavailable for whatever reason, fall back |
465 | | * to prepending the current working directory to the passed path. |
466 | | * However, that comes with limitations regarding meta characters |
467 | | * that is not solvable in general (GH-13204). */ |
468 | | # ifdef PHP_GLOB_ALTDIRFUNC |
469 | | globbuf.gl_opendir = php_glob_opendir_wrapper; |
470 | | globbuf.gl_readdir = (struct dirent *(*)(void *)) readdir; |
471 | | globbuf.gl_closedir = php_glob_closedir_wrapper; |
472 | | globbuf.gl_lstat = php_glob_lstat_wrapper; |
473 | | globbuf.gl_stat = php_glob_stat_wrapper; |
474 | | passed_glob_flags |= PHP_GLOB_ALTDIRFUNC; |
475 | | # else |
476 | | char *result = VCWD_GETCWD(cwd, MAXPATHLEN); |
477 | | if (!result) { |
478 | | cwd[0] = '\0'; |
479 | | } |
480 | | # ifdef PHP_WIN32 |
481 | | if (IS_SLASH(*pattern)) { |
482 | | cwd[2] = '\0'; |
483 | | } |
484 | | # endif |
485 | | cwd_skip = strlen(cwd)+1; |
486 | | |
487 | | snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern); |
488 | | pattern = work_pattern; |
489 | | # endif |
490 | | } |
491 | | #endif |
492 | |
|
493 | 0 | if (0 != (ret = php_glob(pattern, passed_glob_flags, NULL, &globbuf))) { |
494 | 0 | #ifdef PHP_GLOB_NOMATCH |
495 | 0 | if (PHP_GLOB_NOMATCH == ret) { |
496 | | /* Some glob implementation simply return no data if no matches |
497 | | were found, others return the PHP_GLOB_NOMATCH error code. |
498 | | We don't want to treat PHP_GLOB_NOMATCH as an error condition |
499 | | so that PHP glob() behaves the same on both types of |
500 | | implementations and so that 'foreach (glob() as ...' |
501 | | can be used for simple glob() calls without further error |
502 | | checking. |
503 | | */ |
504 | 0 | goto no_results; |
505 | 0 | } |
506 | 0 | #endif |
507 | 0 | RETURN_FALSE; |
508 | 0 | } |
509 | | |
510 | | /* now catch the FreeBSD style of "no matches" */ |
511 | 0 | if (!globbuf.gl_pathc || !globbuf.gl_pathv) { |
512 | 0 | #ifdef PHP_GLOB_NOMATCH |
513 | 0 | no_results: |
514 | 0 | #endif |
515 | 0 | RETURN_EMPTY_ARRAY(); |
516 | 0 | } |
517 | | |
518 | 0 | array_init(return_value); |
519 | 0 | for (n = 0; n < (size_t)globbuf.gl_pathc; n++) { |
520 | 0 | if (PG(open_basedir) && *PG(open_basedir)) { |
521 | 0 | if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) { |
522 | 0 | basedir_limit = 1; |
523 | 0 | continue; |
524 | 0 | } |
525 | 0 | } |
526 | | /* we need to do this every time since PHP_GLOB_ONLYDIR does not guarantee that |
527 | | * all directories will be filtered. GNU libc documentation states the |
528 | | * following: |
529 | | * If the information about the type of the file is easily available |
530 | | * non-directories will be rejected but no extra work will be done to |
531 | | * determine the information for each file. I.e., the caller must still be |
532 | | * able to filter directories out. |
533 | | */ |
534 | 0 | if (flags & PHP_GLOB_ONLYDIR) { |
535 | 0 | zend_stat_t s = {0}; |
536 | |
|
537 | 0 | if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) { |
538 | 0 | continue; |
539 | 0 | } |
540 | | |
541 | 0 | if (S_IFDIR != (s.st_mode & S_IFMT)) { |
542 | 0 | continue; |
543 | 0 | } |
544 | 0 | } |
545 | 0 | ZVAL_STRING(&tmp, globbuf.gl_pathv[n]+cwd_skip); |
546 | 0 | zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); |
547 | 0 | } |
548 | |
|
549 | 0 | php_globfree(&globbuf); |
550 | |
|
551 | 0 | if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) { |
552 | 0 | zend_array_destroy(Z_ARR_P(return_value)); |
553 | 0 | RETURN_FALSE; |
554 | 0 | } |
555 | 0 | } |
556 | | /* }}} */ |
557 | | |
558 | | /* {{{ List files & directories inside the specified path */ |
559 | | PHP_FUNCTION(scandir) |
560 | 0 | { |
561 | 0 | char *dirn; |
562 | 0 | size_t dirn_len; |
563 | 0 | zend_long flags = PHP_SCANDIR_SORT_ASCENDING; |
564 | 0 | zend_string **namelist; |
565 | 0 | int n, i; |
566 | 0 | zval *zcontext = NULL; |
567 | 0 | php_stream_context *context = NULL; |
568 | |
|
569 | 0 | ZEND_PARSE_PARAMETERS_START(1, 3) |
570 | 0 | Z_PARAM_PATH(dirn, dirn_len) |
571 | 0 | Z_PARAM_OPTIONAL |
572 | 0 | Z_PARAM_LONG(flags) |
573 | 0 | Z_PARAM_RESOURCE_OR_NULL(zcontext) |
574 | 0 | ZEND_PARSE_PARAMETERS_END(); |
575 | | |
576 | 0 | if (dirn_len < 1) { |
577 | 0 | zend_argument_must_not_be_empty_error(1); |
578 | 0 | RETURN_THROWS(); |
579 | 0 | } |
580 | | |
581 | 0 | if (zcontext) { |
582 | 0 | context = php_stream_context_from_zval(zcontext, 0); |
583 | 0 | } |
584 | |
|
585 | 0 | if (flags == PHP_SCANDIR_SORT_ASCENDING) { |
586 | 0 | n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort); |
587 | 0 | } else if (flags == PHP_SCANDIR_SORT_NONE) { |
588 | 0 | n = php_stream_scandir(dirn, &namelist, context, NULL); |
589 | 0 | } else if (flags == PHP_SCANDIR_SORT_DESCENDING) { |
590 | 0 | n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr); |
591 | 0 | } else { |
592 | 0 | zend_argument_value_error(2, "must be one of the SCANDIR_SORT_ASCENDING, SCANDIR_SORT_DESCENDING, or SCANDIR_SORT_NONE constants"); |
593 | 0 | RETURN_THROWS(); |
594 | 0 | } |
595 | | |
596 | 0 | if (n < 0) { |
597 | 0 | php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno)); |
598 | 0 | RETURN_FALSE; |
599 | 0 | } |
600 | | |
601 | 0 | array_init_size(return_value, n); |
602 | 0 | zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); |
603 | |
|
604 | 0 | ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { |
605 | 0 | for (i = 0; i < n; i++) { |
606 | 0 | ZEND_HASH_FILL_SET_STR(namelist[i]); |
607 | 0 | ZEND_HASH_FILL_NEXT(); |
608 | 0 | } |
609 | 0 | } ZEND_HASH_FILL_END(); |
610 | |
|
611 | 0 | if (n) { |
612 | | efree(namelist); |
613 | 0 | } |
614 | 0 | } |
615 | | /* }}} */ |