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