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