1 | /* |
2 | +----------------------------------------------------------------------+ |
3 | | PHP Version 5 | |
4 | +----------------------------------------------------------------------+ |
5 | | Copyright (c) 1997-2015 The PHP Group | |
6 | +----------------------------------------------------------------------+ |
7 | | This source file is subject to version 3.01 of the PHP license, | |
8 | | that is bundled with this package in the file LICENSE, and is | |
9 | | available through the world-wide-web at the following url: | |
10 | | http://www.php.net/license/3_01.txt | |
11 | | If you did not receive a copy of the PHP license and are unable to | |
12 | | obtain it through the world-wide-web, please send a note to | |
13 | | license@php.net so we can mail you a copy immediately. | |
14 | +----------------------------------------------------------------------+ |
15 | | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | |
16 | | Jim Winstead <jimw@php.net> | |
17 | +----------------------------------------------------------------------+ |
18 | */ |
19 | |
20 | /* $Id$ */ |
21 | |
22 | /* {{{ includes |
23 | */ |
24 | #include "php.h" |
25 | #include "php_globals.h" |
26 | #include "SAPI.h" |
27 | |
28 | #include <stdio.h> |
29 | #include <stdlib.h> |
30 | #include <errno.h> |
31 | #include <sys/types.h> |
32 | #include <sys/stat.h> |
33 | #include <fcntl.h> |
34 | |
35 | #ifdef PHP_WIN32 |
36 | #define O_RDONLY _O_RDONLY |
37 | #include "win32/param.h" |
38 | #else |
39 | #include <sys/param.h> |
40 | #endif |
41 | |
42 | #include "ext/standard/head.h" |
43 | #include "ext/standard/php_standard.h" |
44 | #include "zend_compile.h" |
45 | #include "php_network.h" |
46 | |
47 | #if HAVE_PWD_H |
48 | #include <pwd.h> |
49 | #endif |
50 | |
51 | #include <sys/types.h> |
52 | #if HAVE_SYS_SOCKET_H |
53 | #include <sys/socket.h> |
54 | #endif |
55 | |
56 | #ifndef S_ISREG |
57 | #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) |
58 | #endif |
59 | |
60 | #ifdef PHP_WIN32 |
61 | #include <winsock2.h> |
62 | #elif defined(NETWARE) && defined(USE_WINSOCK) |
63 | #include <novsock2.h> |
64 | #else |
65 | #include <netinet/in.h> |
66 | #include <netdb.h> |
67 | #if HAVE_ARPA_INET_H |
68 | #include <arpa/inet.h> |
69 | #endif |
70 | #endif |
71 | |
72 | #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) |
73 | #undef AF_UNIX |
74 | #endif |
75 | |
76 | #if defined(AF_UNIX) |
77 | #include <sys/un.h> |
78 | #endif |
79 | /* }}} */ |
80 | |
81 | /* {{{ OnUpdateBaseDir |
82 | Allows any change to open_basedir setting in during Startup and Shutdown events, |
83 | or a tightening during activation/runtime/deactivation */ |
84 | PHPAPI ZEND_INI_MH(OnUpdateBaseDir) |
85 | { |
86 | char **p, *pathbuf, *ptr, *end; |
87 | #ifndef ZTS |
88 | char *base = (char *) mh_arg2; |
89 | #else |
90 | char *base = (char *) ts_resource(*((int *) mh_arg2)); |
91 | #endif |
92 | |
93 | p = (char **) (base + (size_t) mh_arg1); |
94 | |
95 | if (stage == PHP_INI_STAGE_STARTUP || stage == PHP_INI_STAGE_SHUTDOWN || stage == PHP_INI_STAGE_ACTIVATE || stage == PHP_INI_STAGE_DEACTIVATE) { |
96 | /* We're in a PHP_INI_SYSTEM context, no restrictions */ |
97 | *p = new_value; |
98 | return SUCCESS; |
99 | } |
100 | |
101 | /* Otherwise we're in runtime */ |
102 | if (!*p || !**p) { |
103 | /* open_basedir not set yet, go ahead and give it a value */ |
104 | *p = new_value; |
105 | return SUCCESS; |
106 | } |
107 | |
108 | /* Shortcut: When we have a open_basedir and someone tries to unset, we know it'll fail */ |
109 | if (!new_value || !*new_value) { |
110 | return FAILURE; |
111 | } |
112 | |
113 | /* Is the proposed open_basedir at least as restrictive as the current setting? */ |
114 | ptr = pathbuf = estrdup(new_value); |
115 | while (ptr && *ptr) { |
116 | end = strchr(ptr, DEFAULT_DIR_SEPARATOR); |
117 | if (end != NULL) { |
118 | *end = '\0'; |
119 | end++; |
120 | } |
121 | if (php_check_open_basedir_ex(ptr, 0 TSRMLS_CC) != 0) { |
122 | /* At least one portion of this open_basedir is less restrictive than the prior one, FAIL */ |
123 | efree(pathbuf); |
124 | return FAILURE; |
125 | } |
126 | ptr = end; |
127 | } |
128 | efree(pathbuf); |
129 | |
130 | /* Everything checks out, set it */ |
131 | *p = new_value; |
132 | |
133 | return SUCCESS; |
134 | } |
135 | /* }}} */ |
136 | |
137 | /* {{{ php_check_specific_open_basedir |
138 | When open_basedir is not NULL, check if the given filename is located in |
139 | open_basedir. Returns -1 if error or not in the open_basedir, else 0. |
140 | When open_basedir is NULL, always return 0. |
141 | */ |
142 | PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path TSRMLS_DC) |
143 | { |
144 | char resolved_name[MAXPATHLEN]; |
145 | char resolved_basedir[MAXPATHLEN]; |
146 | char local_open_basedir[MAXPATHLEN]; |
147 | char path_tmp[MAXPATHLEN]; |
148 | char *path_file; |
149 | int resolved_basedir_len; |
150 | int resolved_name_len; |
151 | int path_len; |
152 | int nesting_level = 0; |
153 | |
154 | /* Special case basedir==".": Use script-directory */ |
155 | if (strcmp(basedir, "." ) || !VCWD_GETCWD(local_open_basedir, MAXPATHLEN)) { |
156 | /* Else use the unmodified path */ |
157 | strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir)); |
158 | } |
159 | |
160 | path_len = strlen(path); |
161 | if (path_len > (MAXPATHLEN - 1)) { |
162 | /* empty and too long paths are invalid */ |
163 | return -1; |
164 | } |
165 | |
166 | /* normalize and expand path */ |
167 | if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) { |
168 | return -1; |
169 | } |
170 | |
171 | path_len = strlen(resolved_name); |
172 | memcpy(path_tmp, resolved_name, path_len + 1); /* safe */ |
173 | |
174 | while (VCWD_REALPATH(path_tmp, resolved_name) == NULL) { |
175 | #if defined(PHP_WIN32) || defined(HAVE_SYMLINK) |
176 | #if defined(PHP_WIN32) |
177 | if (EG(windows_version_info).dwMajorVersion > 5) { |
178 | #endif |
179 | if (nesting_level == 0) { |
180 | int ret; |
181 | char buf[MAXPATHLEN]; |
182 | |
183 | ret = php_sys_readlink(path_tmp, buf, MAXPATHLEN - 1); |
184 | if (ret < 0) { |
185 | /* not a broken symlink, move along.. */ |
186 | } else { |
187 | /* put the real path into the path buffer */ |
188 | memcpy(path_tmp, buf, ret); |
189 | path_tmp[ret] = '\0'; |
190 | } |
191 | } |
192 | #if defined(PHP_WIN32) |
193 | } |
194 | #endif |
195 | #endif |
196 | |
197 | #if defined(PHP_WIN32) || defined(NETWARE) |
198 | path_file = strrchr(path_tmp, DEFAULT_SLASH); |
199 | if (!path_file) { |
200 | path_file = strrchr(path_tmp, '/'); |
201 | } |
202 | #else |
203 | path_file = strrchr(path_tmp, DEFAULT_SLASH); |
204 | #endif |
205 | if (!path_file) { |
206 | /* none of the path components exist. definitely not in open_basedir.. */ |
207 | return -1; |
208 | } else { |
209 | path_len = path_file - path_tmp + 1; |
210 | #if defined(PHP_WIN32) || defined(NETWARE) |
211 | if (path_len > 1 && path_tmp[path_len - 2] == ':') { |
212 | if (path_len != 3) { |
213 | return -1; |
214 | } |
215 | /* this is c:\ */ |
216 | path_tmp[path_len] = '\0'; |
217 | } else { |
218 | path_tmp[path_len - 1] = '\0'; |
219 | } |
220 | #else |
221 | path_tmp[path_len - 1] = '\0'; |
222 | #endif |
223 | } |
224 | nesting_level++; |
225 | } |
226 | |
227 | /* Resolve open_basedir to resolved_basedir */ |
228 | if (expand_filepath(local_open_basedir, resolved_basedir TSRMLS_CC) != NULL) { |
229 | /* Handler for basedirs that end with a / */ |
230 | resolved_basedir_len = strlen(resolved_basedir); |
231 | #if defined(PHP_WIN32) || defined(NETWARE) |
232 | if (basedir[strlen(basedir) - 1] == PHP_DIR_SEPARATOR || basedir[strlen(basedir) - 1] == '/') { |
233 | #else |
234 | if (basedir[strlen(basedir) - 1] == PHP_DIR_SEPARATOR) { |
235 | #endif |
236 | if (resolved_basedir[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) { |
237 | resolved_basedir[resolved_basedir_len] = PHP_DIR_SEPARATOR; |
238 | resolved_basedir[++resolved_basedir_len] = '\0'; |
239 | } |
240 | } else { |
241 | resolved_basedir[resolved_basedir_len++] = PHP_DIR_SEPARATOR; |
242 | resolved_basedir[resolved_basedir_len] = '\0'; |
243 | } |
244 | |
245 | resolved_name_len = strlen(resolved_name); |
246 | if (path_tmp[path_len - 1] == PHP_DIR_SEPARATOR) { |
247 | if (resolved_name[resolved_name_len - 1] != PHP_DIR_SEPARATOR) { |
248 | resolved_name[resolved_name_len] = PHP_DIR_SEPARATOR; |
249 | resolved_name[++resolved_name_len] = '\0'; |
250 | } |
251 | } |
252 | |
253 | /* Check the path */ |
254 | #if defined(PHP_WIN32) || defined(NETWARE) |
255 | if (strncasecmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) { |
256 | #else |
257 | if (strncmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) { |
258 | #endif |
259 | if (resolved_name_len > resolved_basedir_len && |
260 | resolved_name[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) { |
261 | return -1; |
262 | } else { |
263 | /* File is in the right directory */ |
264 | return 0; |
265 | } |
266 | } else { |
267 | /* /openbasedir/ and /openbasedir are the same directory */ |
268 | if (resolved_basedir_len == (resolved_name_len + 1) && resolved_basedir[resolved_basedir_len - 1] == PHP_DIR_SEPARATOR) { |
269 | #if defined(PHP_WIN32) || defined(NETWARE) |
270 | if (strncasecmp(resolved_basedir, resolved_name, resolved_name_len) == 0) { |
271 | #else |
272 | if (strncmp(resolved_basedir, resolved_name, resolved_name_len) == 0) { |
273 | #endif |
274 | return 0; |
275 | } |
276 | } |
277 | return -1; |
278 | } |
279 | } else { |
280 | /* Unable to resolve the real path, return -1 */ |
281 | return -1; |
282 | } |
283 | } |
284 | /* }}} */ |
285 | |
286 | PHPAPI int php_check_open_basedir(const char *path TSRMLS_DC) |
287 | { |
288 | return php_check_open_basedir_ex(path, 1 TSRMLS_CC); |
289 | } |
290 | |
291 | /* {{{ php_check_open_basedir |
292 | */ |
293 | PHPAPI int php_check_open_basedir_ex(const char *path, int warn TSRMLS_DC) |
294 | { |
295 | /* Only check when open_basedir is available */ |
296 | if (PG(open_basedir) && *PG(open_basedir)) { |
297 | char *pathbuf; |
298 | char *ptr; |
299 | char *end; |
300 | |
301 | /* Check if the path is too long so we can give a more useful error |
302 | * message. */ |
303 | if (strlen(path) > (MAXPATHLEN - 1)) { |
304 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "File name is longer than the maximum allowed path length on this platform (%d): %s" , MAXPATHLEN, path); |
305 | errno = EINVAL; |
306 | return -1; |
307 | } |
308 | |
309 | pathbuf = estrdup(PG(open_basedir)); |
310 | |
311 | ptr = pathbuf; |
312 | |
313 | while (ptr && *ptr) { |
314 | end = strchr(ptr, DEFAULT_DIR_SEPARATOR); |
315 | if (end != NULL) { |
316 | *end = '\0'; |
317 | end++; |
318 | } |
319 | |
320 | if (php_check_specific_open_basedir(ptr, path TSRMLS_CC) == 0) { |
321 | efree(pathbuf); |
322 | return 0; |
323 | } |
324 | |
325 | ptr = end; |
326 | } |
327 | if (warn) { |
328 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s)" , path, PG(open_basedir)); |
329 | } |
330 | efree(pathbuf); |
331 | errno = EPERM; /* we deny permission to open it */ |
332 | return -1; |
333 | } |
334 | |
335 | /* Nothing to check... */ |
336 | return 0; |
337 | } |
338 | /* }}} */ |
339 | |
340 | /* {{{ php_fopen_and_set_opened_path |
341 | */ |
342 | static FILE *php_fopen_and_set_opened_path(const char *path, const char *mode, char **opened_path TSRMLS_DC) |
343 | { |
344 | FILE *fp; |
345 | |
346 | if (php_check_open_basedir((char *)path TSRMLS_CC)) { |
347 | return NULL; |
348 | } |
349 | fp = VCWD_FOPEN(path, mode); |
350 | if (fp && opened_path) { |
351 | *opened_path = expand_filepath_with_mode(path, NULL, NULL, 0, CWD_EXPAND TSRMLS_CC); |
352 | } |
353 | return fp; |
354 | } |
355 | /* }}} */ |
356 | |
357 | /* {{{ php_fopen_primary_script |
358 | */ |
359 | PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle TSRMLS_DC) |
360 | { |
361 | char *path_info; |
362 | char *filename = NULL; |
363 | char *resolved_path = NULL; |
364 | int length; |
365 | zend_bool orig_display_errors; |
366 | |
367 | path_info = SG(request_info).request_uri; |
368 | #if HAVE_PWD_H |
369 | if (PG(user_dir) && *PG(user_dir) && path_info && '/' == path_info[0] && '~' == path_info[1]) { |
370 | char *s = strchr(path_info + 2, '/'); |
371 | |
372 | if (s) { /* if there is no path name after the file, do not bother */ |
373 | char user[32]; /* to try open the directory */ |
374 | struct passwd *pw; |
375 | #if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX) |
376 | struct passwd pwstruc; |
377 | long pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX); |
378 | char *pwbuf; |
379 | |
380 | if (pwbuflen < 1) { |
381 | return FAILURE; |
382 | } |
383 | |
384 | pwbuf = emalloc(pwbuflen); |
385 | #endif |
386 | length = s - (path_info + 2); |
387 | if (length > (int)sizeof(user) - 1) { |
388 | length = sizeof(user) - 1; |
389 | } |
390 | memcpy(user, path_info + 2, length); |
391 | user[length] = '\0'; |
392 | #if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX) |
393 | if (getpwnam_r(user, &pwstruc, pwbuf, pwbuflen, &pw)) { |
394 | efree(pwbuf); |
395 | return FAILURE; |
396 | } |
397 | #else |
398 | pw = getpwnam(user); |
399 | #endif |
400 | if (pw && pw->pw_dir) { |
401 | spprintf(&filename, 0, "%s%c%s%c%s" , pw->pw_dir, PHP_DIR_SEPARATOR, PG(user_dir), PHP_DIR_SEPARATOR, s + 1); /* Safe */ |
402 | } else { |
403 | filename = SG(request_info).path_translated; |
404 | } |
405 | #if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX) |
406 | efree(pwbuf); |
407 | #endif |
408 | } |
409 | } else |
410 | #endif |
411 | if (PG(doc_root) && path_info && (length = strlen(PG(doc_root))) && |
412 | IS_ABSOLUTE_PATH(PG(doc_root), length)) { |
413 | int path_len = strlen(path_info); |
414 | filename = emalloc(length + path_len + 2); |
415 | if (filename) { |
416 | memcpy(filename, PG(doc_root), length); |
417 | if (!IS_SLASH(filename[length - 1])) { /* length is never 0 */ |
418 | filename[length++] = PHP_DIR_SEPARATOR; |
419 | } |
420 | if (IS_SLASH(path_info[0])) { |
421 | length--; |
422 | } |
423 | strncpy(filename + length, path_info, path_len + 1); |
424 | } |
425 | } else { |
426 | filename = SG(request_info).path_translated; |
427 | } |
428 | |
429 | |
430 | if (filename) { |
431 | resolved_path = zend_resolve_path(filename, strlen(filename) TSRMLS_CC); |
432 | } |
433 | |
434 | if (!resolved_path) { |
435 | if (SG(request_info).path_translated != filename) { |
436 | STR_FREE(filename); |
437 | } |
438 | /* we have to free SG(request_info).path_translated here because |
439 | * php_destroy_request_info assumes that it will get |
440 | * freed when the include_names hash is emptied, but |
441 | * we're not adding it in this case */ |
442 | STR_FREE(SG(request_info).path_translated); |
443 | SG(request_info).path_translated = NULL; |
444 | return FAILURE; |
445 | } |
446 | efree(resolved_path); |
447 | |
448 | orig_display_errors = PG(display_errors); |
449 | PG(display_errors) = 0; |
450 | if (zend_stream_open(filename, file_handle TSRMLS_CC) == FAILURE) { |
451 | PG(display_errors) = orig_display_errors; |
452 | if (SG(request_info).path_translated != filename) { |
453 | STR_FREE(filename); |
454 | } |
455 | STR_FREE(SG(request_info).path_translated); /* for same reason as above */ |
456 | SG(request_info).path_translated = NULL; |
457 | return FAILURE; |
458 | } |
459 | PG(display_errors) = orig_display_errors; |
460 | |
461 | if (SG(request_info).path_translated != filename) { |
462 | STR_FREE(SG(request_info).path_translated); /* for same reason as above */ |
463 | SG(request_info).path_translated = filename; |
464 | } |
465 | |
466 | return SUCCESS; |
467 | } |
468 | /* }}} */ |
469 | |
470 | /* {{{ php_resolve_path |
471 | * Returns the realpath for given filename according to include path |
472 | */ |
473 | PHPAPI char *php_resolve_path(const char *filename, int filename_length, const char *path TSRMLS_DC) |
474 | { |
475 | char resolved_path[MAXPATHLEN]; |
476 | char trypath[MAXPATHLEN]; |
477 | const char *ptr, *end, *p; |
478 | const char *actual_path; |
479 | php_stream_wrapper *wrapper; |
480 | |
481 | if (!filename || CHECK_NULL_PATH(filename, filename_length)) { |
482 | return NULL; |
483 | } |
484 | |
485 | /* Don't resolve paths which contain protocol (except of file://) */ |
486 | for (p = filename; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); |
487 | if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) { |
488 | wrapper = php_stream_locate_url_wrapper(filename, &actual_path, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC); |
489 | if (wrapper == &php_plain_files_wrapper) { |
490 | if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) { |
491 | return estrdup(resolved_path); |
492 | } |
493 | } |
494 | return NULL; |
495 | } |
496 | |
497 | if ((*filename == '.' && |
498 | (IS_SLASH(filename[1]) || |
499 | ((filename[1] == '.') && IS_SLASH(filename[2])))) || |
500 | IS_ABSOLUTE_PATH(filename, filename_length) || |
501 | !path || |
502 | !*path) { |
503 | if (tsrm_realpath(filename, resolved_path TSRMLS_CC)) { |
504 | return estrdup(resolved_path); |
505 | } else { |
506 | return NULL; |
507 | } |
508 | } |
509 | |
510 | ptr = path; |
511 | while (ptr && *ptr) { |
512 | /* Check for stream wrapper */ |
513 | int is_stream_wrapper = 0; |
514 | |
515 | for (p = ptr; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); |
516 | if ((*p == ':') && (p - ptr > 1) && (p[1] == '/') && (p[2] == '/')) { |
517 | /* .:// or ..:// is not a stream wrapper */ |
518 | if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) { |
519 | p += 3; |
520 | is_stream_wrapper = 1; |
521 | } |
522 | } |
523 | end = strchr(p, DEFAULT_DIR_SEPARATOR); |
524 | if (end) { |
525 | if ((end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) { |
526 | ptr = end + 1; |
527 | continue; |
528 | } |
529 | memcpy(trypath, ptr, end-ptr); |
530 | trypath[end-ptr] = '/'; |
531 | memcpy(trypath+(end-ptr)+1, filename, filename_length+1); |
532 | ptr = end+1; |
533 | } else { |
534 | int len = strlen(ptr); |
535 | |
536 | if (len + 1 + filename_length + 1 >= MAXPATHLEN) { |
537 | break; |
538 | } |
539 | memcpy(trypath, ptr, len); |
540 | trypath[len] = '/'; |
541 | memcpy(trypath+len+1, filename, filename_length+1); |
542 | ptr = NULL; |
543 | } |
544 | actual_path = trypath; |
545 | if (is_stream_wrapper) { |
546 | wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC); |
547 | if (!wrapper) { |
548 | continue; |
549 | } else if (wrapper != &php_plain_files_wrapper) { |
550 | if (wrapper->wops->url_stat) { |
551 | php_stream_statbuf ssb; |
552 | |
553 | if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, 0, &ssb, NULL TSRMLS_CC)) { |
554 | return estrdup(trypath); |
555 | } |
556 | } |
557 | continue; |
558 | } |
559 | } |
560 | if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) { |
561 | return estrdup(resolved_path); |
562 | } |
563 | } /* end provided path */ |
564 | |
565 | /* check in calling scripts' current working directory as a fall back case |
566 | */ |
567 | if (zend_is_executing(TSRMLS_C)) { |
568 | const char *exec_fname = zend_get_executed_filename(TSRMLS_C); |
569 | int exec_fname_length = strlen(exec_fname); |
570 | |
571 | while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length])); |
572 | if (exec_fname && exec_fname[0] != '[' && |
573 | exec_fname_length > 0 && |
574 | exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) { |
575 | memcpy(trypath, exec_fname, exec_fname_length + 1); |
576 | memcpy(trypath+exec_fname_length + 1, filename, filename_length+1); |
577 | actual_path = trypath; |
578 | |
579 | /* Check for stream wrapper */ |
580 | for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); |
581 | if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') && (p[2] == '/')) { |
582 | wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC); |
583 | if (!wrapper) { |
584 | return NULL; |
585 | } else if (wrapper != &php_plain_files_wrapper) { |
586 | if (wrapper->wops->url_stat) { |
587 | php_stream_statbuf ssb; |
588 | |
589 | if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, 0, &ssb, NULL TSRMLS_CC)) { |
590 | return estrdup(trypath); |
591 | } |
592 | } |
593 | return NULL; |
594 | } |
595 | } |
596 | |
597 | if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) { |
598 | return estrdup(resolved_path); |
599 | } |
600 | } |
601 | } |
602 | |
603 | return NULL; |
604 | } |
605 | /* }}} */ |
606 | |
607 | /* {{{ php_fopen_with_path |
608 | * Tries to open a file with a PATH-style list of directories. |
609 | * If the filename starts with "." or "/", the path is ignored. |
610 | */ |
611 | PHPAPI FILE *php_fopen_with_path(const char *filename, const char *mode, const char *path, char **opened_path TSRMLS_DC) |
612 | { |
613 | char *pathbuf, *ptr, *end; |
614 | const char *exec_fname; |
615 | char trypath[MAXPATHLEN]; |
616 | FILE *fp; |
617 | int path_length; |
618 | int filename_length; |
619 | int exec_fname_length; |
620 | |
621 | if (opened_path) { |
622 | *opened_path = NULL; |
623 | } |
624 | |
625 | if (!filename) { |
626 | return NULL; |
627 | } |
628 | |
629 | filename_length = strlen(filename); |
630 | |
631 | /* Relative path open */ |
632 | if ((*filename == '.') |
633 | /* Absolute path open */ |
634 | || IS_ABSOLUTE_PATH(filename, filename_length) |
635 | || (!path || (path && !*path)) |
636 | ) { |
637 | return php_fopen_and_set_opened_path(filename, mode, opened_path TSRMLS_CC); |
638 | } |
639 | |
640 | /* check in provided path */ |
641 | /* append the calling scripts' current working directory |
642 | * as a fall back case |
643 | */ |
644 | if (zend_is_executing(TSRMLS_C)) { |
645 | exec_fname = zend_get_executed_filename(TSRMLS_C); |
646 | exec_fname_length = strlen(exec_fname); |
647 | path_length = strlen(path); |
648 | |
649 | while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length])); |
650 | if ((exec_fname && exec_fname[0] == '[') || exec_fname_length <= 0) { |
651 | /* [no active file] or no path */ |
652 | pathbuf = estrdup(path); |
653 | } else { |
654 | pathbuf = (char *) emalloc(exec_fname_length + path_length + 1 + 1); |
655 | memcpy(pathbuf, path, path_length); |
656 | pathbuf[path_length] = DEFAULT_DIR_SEPARATOR; |
657 | memcpy(pathbuf + path_length + 1, exec_fname, exec_fname_length); |
658 | pathbuf[path_length + exec_fname_length + 1] = '\0'; |
659 | } |
660 | } else { |
661 | pathbuf = estrdup(path); |
662 | } |
663 | |
664 | ptr = pathbuf; |
665 | |
666 | while (ptr && *ptr) { |
667 | end = strchr(ptr, DEFAULT_DIR_SEPARATOR); |
668 | if (end != NULL) { |
669 | *end = '\0'; |
670 | end++; |
671 | } |
672 | if (snprintf(trypath, MAXPATHLEN, "%s/%s" , ptr, filename) >= MAXPATHLEN) { |
673 | php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s/%s path was truncated to %d" , ptr, filename, MAXPATHLEN); |
674 | } |
675 | fp = php_fopen_and_set_opened_path(trypath, mode, opened_path TSRMLS_CC); |
676 | if (fp) { |
677 | efree(pathbuf); |
678 | return fp; |
679 | } |
680 | ptr = end; |
681 | } /* end provided path */ |
682 | |
683 | efree(pathbuf); |
684 | return NULL; |
685 | } |
686 | /* }}} */ |
687 | |
688 | /* {{{ php_strip_url_passwd |
689 | */ |
690 | PHPAPI char *php_strip_url_passwd(char *url) |
691 | { |
692 | register char *p, *url_start; |
693 | |
694 | if (url == NULL) { |
695 | return "" ; |
696 | } |
697 | |
698 | p = url; |
699 | |
700 | while (*p) { |
701 | if (*p == ':' && *(p + 1) == '/' && *(p + 2) == '/') { |
702 | /* found protocol */ |
703 | url_start = p = p + 3; |
704 | |
705 | while (*p) { |
706 | if (*p == '@') { |
707 | int i; |
708 | |
709 | for (i = 0; i < 3 && url_start < p; i++, url_start++) { |
710 | *url_start = '.'; |
711 | } |
712 | for (; *p; p++) { |
713 | *url_start++ = *p; |
714 | } |
715 | *url_start=0; |
716 | break; |
717 | } |
718 | p++; |
719 | } |
720 | return url; |
721 | } |
722 | p++; |
723 | } |
724 | return url; |
725 | } |
726 | /* }}} */ |
727 | |
728 | /* {{{ expand_filepath |
729 | */ |
730 | PHPAPI char *expand_filepath(const char *filepath, char *real_path TSRMLS_DC) |
731 | { |
732 | return expand_filepath_ex(filepath, real_path, NULL, 0 TSRMLS_CC); |
733 | } |
734 | /* }}} */ |
735 | |
736 | /* {{{ expand_filepath_ex |
737 | */ |
738 | PHPAPI char *expand_filepath_ex(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len TSRMLS_DC) |
739 | { |
740 | return expand_filepath_with_mode(filepath, real_path, relative_to, relative_to_len, CWD_FILEPATH TSRMLS_CC); |
741 | } |
742 | /* }}} */ |
743 | |
744 | /* {{{ expand_filepath_use_realpath |
745 | */ |
746 | PHPAPI char *expand_filepath_with_mode(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len, int realpath_mode TSRMLS_DC) |
747 | { |
748 | cwd_state new_state; |
749 | char cwd[MAXPATHLEN]; |
750 | int copy_len; |
751 | |
752 | if (!filepath[0]) { |
753 | return NULL; |
754 | } else if (IS_ABSOLUTE_PATH(filepath, strlen(filepath))) { |
755 | cwd[0] = '\0'; |
756 | } else { |
757 | const char *iam = SG(request_info).path_translated; |
758 | const char *result; |
759 | if (relative_to) { |
760 | if (relative_to_len > MAXPATHLEN-1U) { |
761 | return NULL; |
762 | } |
763 | result = relative_to; |
764 | memcpy(cwd, relative_to, relative_to_len+1U); |
765 | } else { |
766 | result = VCWD_GETCWD(cwd, MAXPATHLEN); |
767 | } |
768 | |
769 | if (!result && (iam != filepath)) { |
770 | int fdtest = -1; |
771 | |
772 | fdtest = VCWD_OPEN(filepath, O_RDONLY); |
773 | if (fdtest != -1) { |
774 | /* return a relative file path if for any reason |
775 | * we cannot cannot getcwd() and the requested, |
776 | * relatively referenced file is accessible */ |
777 | copy_len = strlen(filepath) > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : strlen(filepath); |
778 | if (real_path) { |
779 | memcpy(real_path, filepath, copy_len); |
780 | real_path[copy_len] = '\0'; |
781 | } else { |
782 | real_path = estrndup(filepath, copy_len); |
783 | } |
784 | close(fdtest); |
785 | return real_path; |
786 | } else { |
787 | cwd[0] = '\0'; |
788 | } |
789 | } else if (!result) { |
790 | cwd[0] = '\0'; |
791 | } |
792 | } |
793 | |
794 | new_state.cwd = estrdup(cwd); |
795 | new_state.cwd_length = strlen(cwd); |
796 | |
797 | if (virtual_file_ex(&new_state, filepath, NULL, realpath_mode TSRMLS_CC)) { |
798 | efree(new_state.cwd); |
799 | return NULL; |
800 | } |
801 | |
802 | if (real_path) { |
803 | copy_len = new_state.cwd_length > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : new_state.cwd_length; |
804 | memcpy(real_path, new_state.cwd, copy_len); |
805 | real_path[copy_len] = '\0'; |
806 | } else { |
807 | real_path = estrndup(new_state.cwd, new_state.cwd_length); |
808 | } |
809 | efree(new_state.cwd); |
810 | |
811 | return real_path; |
812 | } |
813 | /* }}} */ |
814 | |
815 | /* |
816 | * Local variables: |
817 | * tab-width: 4 |
818 | * c-basic-offset: 4 |
819 | * End: |
820 | * vim600: sw=4 ts=4 fdm=marker |
821 | * vim<600: sw=4 ts=4 |
822 | */ |
823 | |