/src/php-src/ext/standard/php_fopen_wrapper.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@php.net> | |
12 | | | Jim Winstead <jimw@php.net> | |
13 | | | Hartmut Holzgraefe <hholzgra@php.net> | |
14 | | +----------------------------------------------------------------------+ |
15 | | */ |
16 | | |
17 | | #include <stdio.h> |
18 | | #include <stdlib.h> |
19 | | #ifdef HAVE_UNISTD_H |
20 | | # include <unistd.h> |
21 | | #endif |
22 | | |
23 | | #include "php.h" |
24 | | #include "php_globals.h" |
25 | | #include "php_standard.h" |
26 | | #include "php_memory_streams.h" |
27 | | #include "php_fopen_wrappers.h" |
28 | | #include "SAPI.h" |
29 | | |
30 | | static ssize_t php_stream_output_write(php_stream *stream, const char *buf, size_t count) /* {{{ */ |
31 | 0 | { |
32 | 0 | PHPWRITE(buf, count); |
33 | 0 | return count; |
34 | 0 | } |
35 | | /* }}} */ |
36 | | |
37 | | static ssize_t php_stream_output_read(php_stream *stream, char *buf, size_t count) /* {{{ */ |
38 | 0 | { |
39 | 0 | stream->eof = 1; |
40 | 0 | return -1; |
41 | 0 | } |
42 | | /* }}} */ |
43 | | |
44 | | static int php_stream_output_close(php_stream *stream, int close_handle) /* {{{ */ |
45 | 0 | { |
46 | 0 | return 0; |
47 | 0 | } |
48 | | /* }}} */ |
49 | | |
50 | | static const php_stream_ops php_stream_output_ops = { |
51 | | php_stream_output_write, |
52 | | php_stream_output_read, |
53 | | php_stream_output_close, |
54 | | NULL, /* flush */ |
55 | | "Output", |
56 | | NULL, /* seek */ |
57 | | NULL, /* cast */ |
58 | | NULL, /* stat */ |
59 | | NULL /* set_option */ |
60 | | }; |
61 | | |
62 | | typedef struct php_stream_input { /* {{{ */ |
63 | | php_stream *body; |
64 | | zend_off_t position; |
65 | | } php_stream_input_t; |
66 | | /* }}} */ |
67 | | |
68 | | static ssize_t php_stream_input_write(php_stream *stream, const char *buf, size_t count) /* {{{ */ |
69 | 0 | { |
70 | 0 | return -1; |
71 | 0 | } |
72 | | /* }}} */ |
73 | | |
74 | | static ssize_t php_stream_input_read(php_stream *stream, char *buf, size_t count) /* {{{ */ |
75 | 0 | { |
76 | 0 | php_stream_input_t *input = stream->abstract; |
77 | 0 | ssize_t read; |
78 | |
|
79 | 0 | if (!SG(post_read) && SG(read_post_bytes) < (int64_t)(input->position + count)) { |
80 | | /* read requested data from SAPI */ |
81 | 0 | size_t read_bytes = sapi_read_post_block(buf, count); |
82 | |
|
83 | 0 | if (read_bytes > 0) { |
84 | 0 | php_stream_seek(input->body, 0, SEEK_END); |
85 | 0 | php_stream_write(input->body, buf, read_bytes); |
86 | 0 | } |
87 | 0 | } |
88 | |
|
89 | 0 | if (!input->body->readfilters.head) { |
90 | | /* If the input stream contains filters, it's not really seekable. The |
91 | | input->position is likely to be wrong for unfiltered data. */ |
92 | 0 | php_stream_seek(input->body, input->position, SEEK_SET); |
93 | 0 | } |
94 | 0 | read = php_stream_read(input->body, buf, count); |
95 | |
|
96 | 0 | if (!read || read == (size_t) -1) { |
97 | 0 | stream->eof = 1; |
98 | 0 | } else { |
99 | 0 | input->position += read; |
100 | 0 | } |
101 | |
|
102 | 0 | return read; |
103 | 0 | } |
104 | | /* }}} */ |
105 | | |
106 | | static int php_stream_input_close(php_stream *stream, int close_handle) /* {{{ */ |
107 | 0 | { |
108 | 0 | efree(stream->abstract); |
109 | 0 | stream->abstract = NULL; |
110 | |
|
111 | 0 | return 0; |
112 | 0 | } |
113 | | /* }}} */ |
114 | | |
115 | | static int php_stream_input_flush(php_stream *stream) /* {{{ */ |
116 | 0 | { |
117 | 0 | return -1; |
118 | 0 | } |
119 | | /* }}} */ |
120 | | |
121 | | static int php_stream_input_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */ |
122 | 0 | { |
123 | 0 | php_stream_input_t *input = stream->abstract; |
124 | |
|
125 | 0 | if (input->body) { |
126 | 0 | int sought = php_stream_seek(input->body, offset, whence); |
127 | 0 | *newoffset = input->position = (input->body)->position; |
128 | 0 | return sought; |
129 | 0 | } |
130 | | |
131 | 0 | return -1; |
132 | 0 | } |
133 | | /* }}} */ |
134 | | |
135 | | static const php_stream_ops php_stream_input_ops = { |
136 | | php_stream_input_write, |
137 | | php_stream_input_read, |
138 | | php_stream_input_close, |
139 | | php_stream_input_flush, |
140 | | "Input", |
141 | | php_stream_input_seek, |
142 | | NULL, /* cast */ |
143 | | NULL, /* stat */ |
144 | | NULL /* set_option */ |
145 | | }; |
146 | | |
147 | | static const zend_long max_filter_count_default = 16; |
148 | | |
149 | | static zend_result php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain, php_stream_context *context) /* {{{ */ |
150 | 1 | { |
151 | 1 | char *p, *token = NULL; |
152 | 1 | php_stream_filter *temp_filter; |
153 | | |
154 | 1 | zend_long max_filter_count = max_filter_count_default; |
155 | 1 | if (context != NULL) { |
156 | 0 | zval *option_val = php_stream_context_get_option(context, "filter", "max_filter_count"); |
157 | 0 | if (option_val) { |
158 | 0 | max_filter_count = zval_get_long(option_val); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | 1 | p = php_strtok_r(filterlist, "|", &token); |
163 | 2 | while (p) { |
164 | 1 | zend_long read_count = read_chain ? stream->readfilters.num_filters : 0; |
165 | 1 | zend_long write_count = write_chain ? stream->writefilters.num_filters : 0; |
166 | | |
167 | 1 | if (read_count == max_filter_count || write_count == max_filter_count) { |
168 | 0 | return FAILURE; |
169 | 0 | } |
170 | | |
171 | 1 | php_url_decode(p, strlen(p)); |
172 | 1 | if (read_chain) { |
173 | 1 | if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { |
174 | 1 | php_stream_filter_append(&stream->readfilters, temp_filter); |
175 | 1 | } else { |
176 | 0 | php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, |
177 | 0 | CreateFailed, |
178 | 0 | "Unable to create filter (%s)", p); |
179 | 0 | } |
180 | 1 | } |
181 | 1 | if (write_chain) { |
182 | 0 | if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { |
183 | 0 | php_stream_filter_append(&stream->writefilters, temp_filter); |
184 | 0 | } else { |
185 | 0 | php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, |
186 | 0 | CreateFailed, |
187 | 0 | "Unable to create filter (%s)", p); |
188 | 0 | } |
189 | 0 | } |
190 | 1 | p = php_strtok_r(NULL, "|", &token); |
191 | 1 | } |
192 | 1 | return SUCCESS; |
193 | 1 | } |
194 | | /* }}} */ |
195 | | |
196 | | static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, |
197 | | zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */ |
198 | 4 | { |
199 | 4 | int fd = -1; |
200 | 4 | int mode_rw = 0; |
201 | 4 | php_stream * stream = NULL; |
202 | 4 | char *p, *token = NULL, *pathdup; |
203 | 4 | zend_long max_memory; |
204 | 4 | FILE *file = NULL; |
205 | | #ifdef PHP_WIN32 |
206 | | int pipe_requested = 0; |
207 | | #endif |
208 | | |
209 | 4 | if (!strncasecmp(path, "php://", 6)) { |
210 | 4 | path += 6; |
211 | 4 | } |
212 | | |
213 | 4 | if (!strncasecmp(path, "temp", 4)) { |
214 | 0 | path += 4; |
215 | 0 | max_memory = PHP_STREAM_MAX_MEM; |
216 | 0 | if (!strncasecmp(path, "/maxmemory:", 11)) { |
217 | 0 | path += 11; |
218 | 0 | max_memory = ZEND_STRTOL(path, NULL, 10); |
219 | 0 | if (max_memory < 0) { |
220 | 0 | zend_argument_value_error(2, "must be greater than or equal to 0"); |
221 | 0 | return NULL; |
222 | 0 | } |
223 | 0 | } |
224 | 0 | mode_rw = php_stream_mode_from_str(mode); |
225 | 0 | return php_stream_temp_create(mode_rw, max_memory); |
226 | 0 | } |
227 | | |
228 | 4 | if (!strcasecmp(path, "memory")) { |
229 | 0 | mode_rw = php_stream_mode_from_str(mode); |
230 | 0 | return php_stream_memory_create(mode_rw); |
231 | 0 | } |
232 | | |
233 | 4 | if (!strcasecmp(path, "output")) { |
234 | 0 | return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb"); |
235 | 0 | } |
236 | | |
237 | 4 | if (!strcasecmp(path, "input")) { |
238 | 0 | php_stream_input_t *input; |
239 | |
|
240 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
241 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
242 | 0 | Disabled, |
243 | 0 | "URL file-access is disabled in the server configuration"); |
244 | 0 | return NULL; |
245 | 0 | } |
246 | | |
247 | 0 | input = ecalloc(1, sizeof(*input)); |
248 | 0 | if ((input->body = SG(request_info).request_body)) { |
249 | 0 | php_stream_rewind(input->body); |
250 | 0 | } else { |
251 | 0 | input->body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir)); |
252 | 0 | SG(request_info).request_body = input->body; |
253 | 0 | } |
254 | |
|
255 | 0 | return php_stream_alloc(&php_stream_input_ops, input, 0, "rb"); |
256 | 0 | } |
257 | | |
258 | 4 | if (!strcasecmp(path, "stdin")) { |
259 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
260 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
261 | 0 | Disabled, |
262 | 0 | "URL file-access is disabled in the server configuration"); |
263 | 0 | return NULL; |
264 | 0 | } |
265 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
266 | 0 | static int cli_in = 0; |
267 | 0 | fd = STDIN_FILENO; |
268 | 0 | if (cli_in) { |
269 | 0 | fd = dup(fd); |
270 | 0 | } else { |
271 | 0 | cli_in = 1; |
272 | 0 | file = stdin; |
273 | 0 | } |
274 | 0 | } else { |
275 | 0 | fd = dup(STDIN_FILENO); |
276 | 0 | } |
277 | | #ifdef PHP_WIN32 |
278 | | pipe_requested = 1; |
279 | | #endif |
280 | 4 | } else if (!strcasecmp(path, "stdout")) { |
281 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
282 | 0 | static int cli_out = 0; |
283 | 0 | fd = STDOUT_FILENO; |
284 | 0 | if (cli_out++) { |
285 | 0 | fd = dup(fd); |
286 | 0 | } else { |
287 | 0 | cli_out = 1; |
288 | 0 | file = stdout; |
289 | 0 | } |
290 | 0 | } else { |
291 | 0 | fd = dup(STDOUT_FILENO); |
292 | 0 | } |
293 | | #ifdef PHP_WIN32 |
294 | | pipe_requested = 1; |
295 | | #endif |
296 | 4 | } else if (!strcasecmp(path, "stderr")) { |
297 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
298 | 0 | static int cli_err = 0; |
299 | 0 | fd = STDERR_FILENO; |
300 | 0 | if (cli_err++) { |
301 | 0 | fd = dup(fd); |
302 | 0 | } else { |
303 | 0 | cli_err = 1; |
304 | 0 | file = stderr; |
305 | 0 | } |
306 | 0 | } else { |
307 | 0 | fd = dup(STDERR_FILENO); |
308 | 0 | } |
309 | | #ifdef PHP_WIN32 |
310 | | pipe_requested = 1; |
311 | | #endif |
312 | 4 | } else if (!strncasecmp(path, "fd/", 3)) { |
313 | 0 | const char *start; |
314 | 0 | char *end; |
315 | 0 | zend_long fildes_ori; |
316 | 0 | int dtablesize; |
317 | |
|
318 | 0 | if (strcmp(sapi_module.name, "cli")) { |
319 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
320 | 0 | Disabled, |
321 | 0 | "Direct access to file descriptors is only available from command-line PHP"); |
322 | 0 | return NULL; |
323 | 0 | } |
324 | | |
325 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
326 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
327 | 0 | Disabled, |
328 | 0 | "URL file-access is disabled in the server configuration"); |
329 | 0 | return NULL; |
330 | 0 | } |
331 | | |
332 | 0 | start = &path[3]; |
333 | 0 | fildes_ori = ZEND_STRTOL(start, &end, 10); |
334 | 0 | if (end == start || *end != '\0') { |
335 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
336 | 0 | InvalidUrl, |
337 | 0 | "php://fd/ stream must be specified in the form php://fd/<orig fd>"); |
338 | 0 | return NULL; |
339 | 0 | } |
340 | | |
341 | 0 | #ifdef HAVE_UNISTD_H |
342 | 0 | dtablesize = getdtablesize(); |
343 | | #else |
344 | | dtablesize = INT_MAX; |
345 | | #endif |
346 | |
|
347 | 0 | if (fildes_ori < 0 || fildes_ori >= dtablesize) { |
348 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
349 | 0 | InvalidParam, |
350 | 0 | "The file descriptors must be non-negative numbers smaller than %d", dtablesize); |
351 | 0 | return NULL; |
352 | 0 | } |
353 | | |
354 | 0 | fd = dup((int)fildes_ori); |
355 | 0 | if (fd == -1) { |
356 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
357 | 0 | DupFailed, |
358 | 0 | "Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: " |
359 | 0 | "[%d]: %s", fildes_ori, errno, strerror(errno)); |
360 | 0 | return NULL; |
361 | 0 | } |
362 | 4 | } else if (!strncasecmp(path, "filter/", 7)) { |
363 | | /* Save time/memory when chain isn't specified */ |
364 | 2 | if (strchr(mode, 'r') || strchr(mode, '+')) { |
365 | 2 | mode_rw |= PHP_STREAM_FILTER_READ; |
366 | 2 | } |
367 | 2 | if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) { |
368 | 0 | mode_rw |= PHP_STREAM_FILTER_WRITE; |
369 | 0 | } |
370 | 2 | pathdup = estrndup(path + 6, strlen(path + 6)); |
371 | 2 | p = strstr(pathdup, "/resource="); |
372 | 2 | if (!p) { |
373 | 1 | zend_throw_error(NULL, "No URL resource specified"); |
374 | 1 | efree(pathdup); |
375 | 1 | return NULL; |
376 | 1 | } |
377 | | |
378 | 1 | if (!(stream = php_stream_open_wrapper_ex(p + 10, mode, options, opened_path, context))) { |
379 | 0 | efree(pathdup); |
380 | 0 | return NULL; |
381 | 0 | } |
382 | | |
383 | 1 | zend_result safl_result = SUCCESS; |
384 | 1 | *p = '\0'; |
385 | | |
386 | 1 | p = php_strtok_r(pathdup + 1, "/", &token); |
387 | 2 | while (p) { |
388 | 1 | if (!strncasecmp(p, "read=", 5)) { |
389 | 1 | safl_result = php_stream_apply_filter_list(stream, p + 5, 1, 0, context); |
390 | 1 | } else if (!strncasecmp(p, "write=", 6)) { |
391 | 0 | safl_result = php_stream_apply_filter_list(stream, p + 6, 0, 1, context); |
392 | 0 | } else { |
393 | 0 | safl_result = php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE, context); |
394 | 0 | } |
395 | | |
396 | 1 | if (safl_result == FAILURE) { |
397 | 0 | break; |
398 | 0 | } |
399 | | |
400 | 1 | p = php_strtok_r(NULL, "/", &token); |
401 | 1 | } |
402 | 1 | efree(pathdup); |
403 | | |
404 | 1 | if (EG(exception)) { |
405 | 0 | php_stream_close(stream); |
406 | 0 | return NULL; |
407 | 0 | } |
408 | | |
409 | 1 | if (safl_result == FAILURE) { |
410 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
411 | 0 | PathTooLong, "too many filters"); |
412 | 0 | php_stream_close(stream); |
413 | 0 | return NULL; |
414 | 0 | } |
415 | | |
416 | 1 | return stream; |
417 | 2 | } else { |
418 | | /* invalid php://thingy */ |
419 | 2 | php_stream_wrapper_warn(wrapper, context, options, |
420 | 2 | InvalidUrl, "Invalid php:// URL specified"); |
421 | 2 | return NULL; |
422 | 2 | } |
423 | | |
424 | | /* must be stdin, stderr or stdout */ |
425 | 0 | if (fd == -1) { |
426 | | /* failed to dup */ |
427 | 0 | return NULL; |
428 | 0 | } |
429 | | |
430 | 0 | #if defined(S_IFSOCK) && !defined(PHP_WIN32) |
431 | 0 | do { |
432 | 0 | zend_stat_t st = {0}; |
433 | 0 | memset(&st, 0, sizeof(st)); |
434 | 0 | if (zend_fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) { |
435 | 0 | stream = php_stream_sock_open_from_socket(fd, NULL); |
436 | 0 | if (stream) { |
437 | 0 | stream->ops = &php_stream_socket_ops; |
438 | 0 | return stream; |
439 | 0 | } |
440 | 0 | } |
441 | 0 | } while (0); |
442 | 0 | #endif |
443 | | |
444 | 0 | if (file) { |
445 | 0 | stream = php_stream_fopen_from_file(file, mode); |
446 | 0 | } else { |
447 | 0 | stream = php_stream_fopen_from_fd(fd, mode, NULL); |
448 | 0 | if (stream == NULL) { |
449 | 0 | close(fd); |
450 | 0 | } |
451 | 0 | } |
452 | |
|
453 | | #ifdef PHP_WIN32 |
454 | | if (pipe_requested && stream && context) { |
455 | | zval *blocking_pipes = php_stream_context_get_option(context, "pipe", "blocking"); |
456 | | if (blocking_pipes) { |
457 | | php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, zval_get_long(blocking_pipes), NULL); |
458 | | } |
459 | | } |
460 | | #endif |
461 | 0 | return stream; |
462 | 0 | } |
463 | | /* }}} */ |
464 | | |
465 | | static const php_stream_wrapper_ops php_stdio_wops = { |
466 | | php_stream_url_wrap_php, |
467 | | NULL, /* close */ |
468 | | NULL, /* fstat */ |
469 | | NULL, /* stat */ |
470 | | NULL, /* opendir */ |
471 | | "PHP", |
472 | | NULL, /* unlink */ |
473 | | NULL, /* rename */ |
474 | | NULL, /* mkdir */ |
475 | | NULL, /* rmdir */ |
476 | | NULL |
477 | | }; |
478 | | |
479 | | PHPAPI const php_stream_wrapper php_stream_php_wrapper = { |
480 | | &php_stdio_wops, |
481 | | NULL, |
482 | | 0, /* is_url */ |
483 | | }; |