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_dir_int.h"
24 #include "php_string.h"
25 #include "php_scandir.h"
26 #include "basic_functions.h"
27 #include "dir_arginfo.h"
28
29 #ifdef 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 typedef struct {
40 zend_resource *default_dir;
41 } php_dir_globals;
42
43 #ifdef ZTS
44 #define DIRG(v) ZEND_TSRMG(dir_globals_id, php_dir_globals *, v)
45 int dir_globals_id;
46 #else
47 #define DIRG(v) (dir_globals.v)
48 php_dir_globals dir_globals;
49 #endif
50
51 static zend_class_entry *dir_class_entry_ptr;
52
53 #define Z_DIRECTORY_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 0)
54 #define Z_DIRECTORY_HANDLE_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 1)
55
56 #define FETCH_DIRP() \
57 myself = getThis(); \
58 if (!myself) { \
59 ZEND_PARSE_PARAMETERS_START(0, 1) \
60 Z_PARAM_OPTIONAL \
61 Z_PARAM_RESOURCE_OR_NULL(id) \
62 ZEND_PARSE_PARAMETERS_END(); \
63 if (id) { \
64 if ((dirp = (php_stream *)zend_fetch_resource(Z_RES_P(id), "Directory", php_file_le_stream())) == NULL) { \
65 RETURN_THROWS(); \
66 } \
67 } else { \
68 if (!DIRG(default_dir)) { \
69 zend_type_error("No resource supplied"); \
70 RETURN_THROWS(); \
71 } \
72 if ((dirp = (php_stream *)zend_fetch_resource(DIRG(default_dir), "Directory", php_file_le_stream())) == NULL) { \
73 RETURN_THROWS(); \
74 } \
75 } \
76 } else { \
77 ZEND_PARSE_PARAMETERS_NONE(); \
78 zval *handle_zv = Z_DIRECTORY_HANDLE_P(myself); \
79 if (Z_TYPE_P(handle_zv) != IS_RESOURCE) { \
80 zend_throw_error(NULL, "Unable to find my handle property"); \
81 RETURN_THROWS(); \
82 } \
83 if ((dirp = (php_stream *)zend_fetch_resource_ex(handle_zv, "Directory", php_file_le_stream())) == NULL) { \
84 RETURN_THROWS(); \
85 } \
86 }
87
88
php_set_default_dir(zend_resource * res)89 static void php_set_default_dir(zend_resource *res)
90 {
91 if (DIRG(default_dir)) {
92 zend_list_delete(DIRG(default_dir));
93 }
94
95 if (res) {
96 GC_ADDREF(res);
97 }
98
99 DIRG(default_dir) = res;
100 }
101
PHP_RINIT_FUNCTION(dir)102 PHP_RINIT_FUNCTION(dir)
103 {
104 DIRG(default_dir) = NULL;
105 return SUCCESS;
106 }
107
PHP_MINIT_FUNCTION(dir)108 PHP_MINIT_FUNCTION(dir)
109 {
110 dirsep_str[0] = DEFAULT_SLASH;
111 dirsep_str[1] = '\0';
112
113 pathsep_str[0] = ZEND_PATHS_SEPARATOR;
114 pathsep_str[1] = '\0';
115
116 register_dir_symbols(module_number);
117
118 dir_class_entry_ptr = register_class_Directory();
119
120 #ifdef ZTS
121 ts_allocate_id(&dir_globals_id, sizeof(php_dir_globals), NULL, NULL);
122 #endif
123
124 return SUCCESS;
125 }
126 /* }}} */
127
128 /* {{{ internal functions */
_php_do_opendir(INTERNAL_FUNCTION_PARAMETERS,int createobject)129 static void _php_do_opendir(INTERNAL_FUNCTION_PARAMETERS, int createobject)
130 {
131 char *dirname;
132 size_t dir_len;
133 zval *zcontext = NULL;
134 php_stream_context *context = NULL;
135 php_stream *dirp;
136
137 ZEND_PARSE_PARAMETERS_START(1, 2)
138 Z_PARAM_PATH(dirname, dir_len)
139 Z_PARAM_OPTIONAL
140 Z_PARAM_RESOURCE_OR_NULL(zcontext)
141 ZEND_PARSE_PARAMETERS_END();
142
143 context = php_stream_context_from_zval(zcontext, 0);
144
145 dirp = php_stream_opendir(dirname, REPORT_ERRORS, context);
146
147 if (dirp == NULL) {
148 RETURN_FALSE;
149 }
150
151 dirp->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
152
153 php_set_default_dir(dirp->res);
154
155 if (createobject) {
156 object_init_ex(return_value, dir_class_entry_ptr);
157 ZVAL_STRINGL(Z_DIRECTORY_PATH_P(return_value), dirname, dir_len);
158 ZVAL_RES(Z_DIRECTORY_HANDLE_P(return_value), dirp->res);
159 php_stream_auto_cleanup(dirp); /* so we don't get warnings under debug */
160 } else {
161 php_stream_to_zval(dirp, return_value);
162 }
163 }
164 /* }}} */
165
166 /* {{{ Open a directory and return a dir_handle */
PHP_FUNCTION(opendir)167 PHP_FUNCTION(opendir)
168 {
169 _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
170 }
171 /* }}} */
172
173 /* {{{ Directory class with properties, handle and class and methods read, rewind and close */
PHP_FUNCTION(dir)174 PHP_FUNCTION(dir)
175 {
176 _php_do_opendir(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
177 }
178 /* }}} */
179
180 /* {{{ Close directory connection identified by the dir_handle */
PHP_FUNCTION(closedir)181 PHP_FUNCTION(closedir)
182 {
183 zval *id = NULL, *myself;
184 php_stream *dirp;
185 zend_resource *res;
186
187 FETCH_DIRP();
188
189 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
190 zend_argument_type_error(1, "must be a valid Directory resource");
191 RETURN_THROWS();
192 }
193
194 res = dirp->res;
195 zend_list_close(dirp->res);
196
197 if (res == DIRG(default_dir)) {
198 php_set_default_dir(NULL);
199 }
200 }
201 /* }}} */
202
203 #if defined(HAVE_CHROOT) && !defined(ZTS) && defined(ENABLE_CHROOT_FUNC)
204 /* {{{ Change root directory */
PHP_FUNCTION(chroot)205 PHP_FUNCTION(chroot)
206 {
207 char *str;
208 int ret;
209 size_t str_len;
210
211 ZEND_PARSE_PARAMETERS_START(1, 1)
212 Z_PARAM_PATH(str, str_len)
213 ZEND_PARSE_PARAMETERS_END();
214
215 ret = chroot(str);
216 if (ret != 0) {
217 php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
218 RETURN_FALSE;
219 }
220
221 php_clear_stat_cache(1, NULL, 0);
222
223 ret = chdir("/");
224
225 if (ret != 0) {
226 php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
227 RETURN_FALSE;
228 }
229
230 RETURN_TRUE;
231 }
232 /* }}} */
233 #endif
234
235 /* {{{ Change the current directory */
PHP_FUNCTION(chdir)236 PHP_FUNCTION(chdir)
237 {
238 char *str;
239 int ret;
240 size_t str_len;
241
242 ZEND_PARSE_PARAMETERS_START(1, 1)
243 Z_PARAM_PATH(str, str_len)
244 ZEND_PARSE_PARAMETERS_END();
245
246 if (php_check_open_basedir(str)) {
247 RETURN_FALSE;
248 }
249 ret = VCWD_CHDIR(str);
250
251 if (ret != 0) {
252 php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno);
253 RETURN_FALSE;
254 }
255
256 if (BG(CurrentStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentStatFile)), ZSTR_LEN(BG(CurrentStatFile)))) {
257 zend_string_release(BG(CurrentStatFile));
258 BG(CurrentStatFile) = NULL;
259 }
260 if (BG(CurrentLStatFile) && !IS_ABSOLUTE_PATH(ZSTR_VAL(BG(CurrentLStatFile)), ZSTR_LEN(BG(CurrentLStatFile)))) {
261 zend_string_release(BG(CurrentLStatFile));
262 BG(CurrentLStatFile) = NULL;
263 }
264
265 RETURN_TRUE;
266 }
267 /* }}} */
268
269 /* {{{ Gets the current directory */
PHP_FUNCTION(getcwd)270 PHP_FUNCTION(getcwd)
271 {
272 char path[MAXPATHLEN];
273 char *ret=NULL;
274
275 ZEND_PARSE_PARAMETERS_NONE();
276
277 #ifdef HAVE_GETCWD
278 ret = VCWD_GETCWD(path, MAXPATHLEN);
279 #elif defined(HAVE_GETWD)
280 ret = VCWD_GETWD(path);
281 #endif
282
283 if (ret) {
284 RETURN_STRING(path);
285 } else {
286 RETURN_FALSE;
287 }
288 }
289 /* }}} */
290
291 /* {{{ Rewind dir_handle back to the start */
PHP_FUNCTION(rewinddir)292 PHP_FUNCTION(rewinddir)
293 {
294 zval *id = NULL, *myself;
295 php_stream *dirp;
296
297 FETCH_DIRP();
298
299 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
300 zend_argument_type_error(1, "must be a valid Directory resource");
301 RETURN_THROWS();
302 }
303
304 php_stream_rewinddir(dirp);
305 }
306 /* }}} */
307
308 /* {{{ Read directory entry from dir_handle */
PHP_FUNCTION(readdir)309 PHP_FUNCTION(readdir)
310 {
311 zval *id = NULL, *myself;
312 php_stream *dirp;
313 php_stream_dirent entry;
314
315 FETCH_DIRP();
316
317 if (!(dirp->flags & PHP_STREAM_FLAG_IS_DIR)) {
318 zend_argument_type_error(1, "must be a valid Directory resource");
319 RETURN_THROWS();
320 }
321
322 if (php_stream_readdir(dirp, &entry)) {
323 RETURN_STRINGL(entry.d_name, strlen(entry.d_name));
324 }
325 RETURN_FALSE;
326 }
327 /* }}} */
328
329 #ifdef HAVE_GLOB
330 /* {{{ Find pathnames matching a pattern */
PHP_FUNCTION(glob)331 PHP_FUNCTION(glob)
332 {
333 size_t cwd_skip = 0;
334 #ifdef ZTS
335 char cwd[MAXPATHLEN];
336 char work_pattern[MAXPATHLEN];
337 char *result;
338 #endif
339 char *pattern = NULL;
340 size_t pattern_len;
341 zend_long flags = 0;
342 glob_t globbuf;
343 size_t n;
344 int ret;
345 bool basedir_limit = 0;
346 zval tmp;
347
348 ZEND_PARSE_PARAMETERS_START(1, 2)
349 Z_PARAM_PATH(pattern, pattern_len)
350 Z_PARAM_OPTIONAL
351 Z_PARAM_LONG(flags)
352 ZEND_PARSE_PARAMETERS_END();
353
354 if (pattern_len >= MAXPATHLEN) {
355 php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN);
356 RETURN_FALSE;
357 }
358
359 if ((GLOB_AVAILABLE_FLAGS & flags) != flags) {
360 php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform");
361 RETURN_FALSE;
362 }
363
364 #ifdef ZTS
365 if (!IS_ABSOLUTE_PATH(pattern, pattern_len)) {
366 result = VCWD_GETCWD(cwd, MAXPATHLEN);
367 if (!result) {
368 cwd[0] = '\0';
369 }
370 #ifdef PHP_WIN32
371 if (IS_SLASH(*pattern)) {
372 cwd[2] = '\0';
373 }
374 #endif
375 cwd_skip = strlen(cwd)+1;
376
377 snprintf(work_pattern, MAXPATHLEN, "%s%c%s", cwd, DEFAULT_SLASH, pattern);
378 pattern = work_pattern;
379 }
380 #endif
381
382
383 memset(&globbuf, 0, sizeof(glob_t));
384 globbuf.gl_offs = 0;
385 if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) {
386 #ifdef GLOB_NOMATCH
387 if (GLOB_NOMATCH == ret) {
388 /* Some glob implementation simply return no data if no matches
389 were found, others return the GLOB_NOMATCH error code.
390 We don't want to treat GLOB_NOMATCH as an error condition
391 so that PHP glob() behaves the same on both types of
392 implementations and so that 'foreach (glob() as ...'
393 can be used for simple glob() calls without further error
394 checking.
395 */
396 goto no_results;
397 }
398 #endif
399 RETURN_FALSE;
400 }
401
402 /* now catch the FreeBSD style of "no matches" */
403 if (!globbuf.gl_pathc || !globbuf.gl_pathv) {
404 #ifdef GLOB_NOMATCH
405 no_results:
406 #endif
407 array_init(return_value);
408 return;
409 }
410
411 array_init(return_value);
412 for (n = 0; n < (size_t)globbuf.gl_pathc; n++) {
413 if (PG(open_basedir) && *PG(open_basedir)) {
414 if (php_check_open_basedir_ex(globbuf.gl_pathv[n], 0)) {
415 basedir_limit = 1;
416 continue;
417 }
418 }
419 /* we need to do this every time since GLOB_ONLYDIR does not guarantee that
420 * all directories will be filtered. GNU libc documentation states the
421 * following:
422 * If the information about the type of the file is easily available
423 * non-directories will be rejected but no extra work will be done to
424 * determine the information for each file. I.e., the caller must still be
425 * able to filter directories out.
426 */
427 if (flags & GLOB_ONLYDIR) {
428 zend_stat_t s = {0};
429
430 if (0 != VCWD_STAT(globbuf.gl_pathv[n], &s)) {
431 continue;
432 }
433
434 if (S_IFDIR != (s.st_mode & S_IFMT)) {
435 continue;
436 }
437 }
438 ZVAL_STRING(&tmp, globbuf.gl_pathv[n]+cwd_skip);
439 zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
440 }
441
442 globfree(&globbuf);
443
444 if (basedir_limit && !zend_hash_num_elements(Z_ARRVAL_P(return_value))) {
445 zend_array_destroy(Z_ARR_P(return_value));
446 RETURN_FALSE;
447 }
448 }
449 /* }}} */
450 #endif
451
452 /* {{{ List files & directories inside the specified path */
PHP_FUNCTION(scandir)453 PHP_FUNCTION(scandir)
454 {
455 char *dirn;
456 size_t dirn_len;
457 zend_long flags = PHP_SCANDIR_SORT_ASCENDING;
458 zend_string **namelist;
459 int n, i;
460 zval *zcontext = NULL;
461 php_stream_context *context = NULL;
462
463 ZEND_PARSE_PARAMETERS_START(1, 3)
464 Z_PARAM_PATH(dirn, dirn_len)
465 Z_PARAM_OPTIONAL
466 Z_PARAM_LONG(flags)
467 Z_PARAM_RESOURCE_OR_NULL(zcontext)
468 ZEND_PARSE_PARAMETERS_END();
469
470 if (dirn_len < 1) {
471 zend_argument_value_error(1, "cannot be empty");
472 RETURN_THROWS();
473 }
474
475 if (zcontext) {
476 context = php_stream_context_from_zval(zcontext, 0);
477 }
478
479 if (flags == PHP_SCANDIR_SORT_ASCENDING) {
480 n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasort);
481 } else if (flags == PHP_SCANDIR_SORT_NONE) {
482 n = php_stream_scandir(dirn, &namelist, context, NULL);
483 } else {
484 n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr);
485 }
486 if (n < 0) {
487 php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno));
488 RETURN_FALSE;
489 }
490
491 array_init(return_value);
492
493 for (i = 0; i < n; i++) {
494 add_next_index_str(return_value, namelist[i]);
495 }
496
497 if (n) {
498 efree(namelist);
499 }
500 }
501 /* }}} */
502