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