/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 void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */ |
148 | 123 | { |
149 | 123 | char *p, *token = NULL; |
150 | 123 | php_stream_filter *temp_filter; |
151 | | |
152 | 123 | p = php_strtok_r(filterlist, "|", &token); |
153 | 249 | while (p) { |
154 | 126 | php_url_decode(p, strlen(p)); |
155 | 126 | if (read_chain) { |
156 | 126 | if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { |
157 | 48 | php_stream_filter_append(&stream->readfilters, temp_filter); |
158 | 78 | } else { |
159 | 78 | php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, |
160 | 78 | CreateFailed, |
161 | 78 | "Unable to create filter (%s)", p); |
162 | 78 | } |
163 | 126 | } |
164 | 126 | if (write_chain) { |
165 | 0 | if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { |
166 | 0 | php_stream_filter_append(&stream->writefilters, temp_filter); |
167 | 0 | } else { |
168 | 0 | php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, |
169 | 0 | CreateFailed, |
170 | 0 | "Unable to create filter (%s)", p); |
171 | 0 | } |
172 | 0 | } |
173 | 126 | p = php_strtok_r(NULL, "|", &token); |
174 | 126 | } |
175 | 123 | } |
176 | | /* }}} */ |
177 | | |
178 | | static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, |
179 | | zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */ |
180 | 39 | { |
181 | 39 | int fd = -1; |
182 | 39 | int mode_rw = 0; |
183 | 39 | php_stream * stream = NULL; |
184 | 39 | char *p, *token = NULL, *pathdup; |
185 | 39 | zend_long max_memory; |
186 | 39 | FILE *file = NULL; |
187 | | #ifdef PHP_WIN32 |
188 | | int pipe_requested = 0; |
189 | | #endif |
190 | | |
191 | 39 | if (!strncasecmp(path, "php://", 6)) { |
192 | 39 | path += 6; |
193 | 39 | } |
194 | | |
195 | 39 | if (!strncasecmp(path, "temp", 4)) { |
196 | 0 | path += 4; |
197 | 0 | max_memory = PHP_STREAM_MAX_MEM; |
198 | 0 | if (!strncasecmp(path, "/maxmemory:", 11)) { |
199 | 0 | path += 11; |
200 | 0 | max_memory = ZEND_STRTOL(path, NULL, 10); |
201 | 0 | if (max_memory < 0) { |
202 | 0 | zend_argument_value_error(2, "must be greater than or equal to 0"); |
203 | 0 | return NULL; |
204 | 0 | } |
205 | 0 | } |
206 | 0 | mode_rw = php_stream_mode_from_str(mode); |
207 | 0 | return php_stream_temp_create(mode_rw, max_memory); |
208 | 0 | } |
209 | | |
210 | 39 | if (!strcasecmp(path, "memory")) { |
211 | 0 | mode_rw = php_stream_mode_from_str(mode); |
212 | 0 | return php_stream_memory_create(mode_rw); |
213 | 0 | } |
214 | | |
215 | 39 | if (!strcasecmp(path, "output")) { |
216 | 0 | return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb"); |
217 | 0 | } |
218 | | |
219 | 39 | if (!strcasecmp(path, "input")) { |
220 | 0 | php_stream_input_t *input; |
221 | |
|
222 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
223 | 0 | if (options & REPORT_ERRORS) { |
224 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
225 | 0 | Disabled, |
226 | 0 | "URL file-access is disabled in the server configuration"); |
227 | 0 | } |
228 | 0 | return NULL; |
229 | 0 | } |
230 | | |
231 | 0 | input = ecalloc(1, sizeof(*input)); |
232 | 0 | if ((input->body = SG(request_info).request_body)) { |
233 | 0 | php_stream_rewind(input->body); |
234 | 0 | } else { |
235 | 0 | input->body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir)); |
236 | 0 | SG(request_info).request_body = input->body; |
237 | 0 | } |
238 | |
|
239 | 0 | return php_stream_alloc(&php_stream_input_ops, input, 0, "rb"); |
240 | 0 | } |
241 | | |
242 | 39 | if (!strcasecmp(path, "stdin")) { |
243 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
244 | 0 | if (options & REPORT_ERRORS) { |
245 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
246 | 0 | Disabled, |
247 | 0 | "URL file-access is disabled in the server configuration"); |
248 | 0 | } |
249 | 0 | return NULL; |
250 | 0 | } |
251 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
252 | 0 | static int cli_in = 0; |
253 | 0 | fd = STDIN_FILENO; |
254 | 0 | if (cli_in) { |
255 | 0 | fd = dup(fd); |
256 | 0 | } else { |
257 | 0 | cli_in = 1; |
258 | 0 | file = stdin; |
259 | 0 | } |
260 | 0 | } else { |
261 | 0 | fd = dup(STDIN_FILENO); |
262 | 0 | } |
263 | | #ifdef PHP_WIN32 |
264 | | pipe_requested = 1; |
265 | | #endif |
266 | 39 | } else if (!strcasecmp(path, "stdout")) { |
267 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
268 | 0 | static int cli_out = 0; |
269 | 0 | fd = STDOUT_FILENO; |
270 | 0 | if (cli_out++) { |
271 | 0 | fd = dup(fd); |
272 | 0 | } else { |
273 | 0 | cli_out = 1; |
274 | 0 | file = stdout; |
275 | 0 | } |
276 | 0 | } else { |
277 | 0 | fd = dup(STDOUT_FILENO); |
278 | 0 | } |
279 | | #ifdef PHP_WIN32 |
280 | | pipe_requested = 1; |
281 | | #endif |
282 | 39 | } else if (!strcasecmp(path, "stderr")) { |
283 | 0 | if (!strcmp(sapi_module.name, "cli")) { |
284 | 0 | static int cli_err = 0; |
285 | 0 | fd = STDERR_FILENO; |
286 | 0 | if (cli_err++) { |
287 | 0 | fd = dup(fd); |
288 | 0 | } else { |
289 | 0 | cli_err = 1; |
290 | 0 | file = stderr; |
291 | 0 | } |
292 | 0 | } else { |
293 | 0 | fd = dup(STDERR_FILENO); |
294 | 0 | } |
295 | | #ifdef PHP_WIN32 |
296 | | pipe_requested = 1; |
297 | | #endif |
298 | 39 | } else if (!strncasecmp(path, "fd/", 3)) { |
299 | 0 | const char *start; |
300 | 0 | char *end; |
301 | 0 | zend_long fildes_ori; |
302 | 0 | int dtablesize; |
303 | |
|
304 | 0 | if (strcmp(sapi_module.name, "cli")) { |
305 | 0 | if (options & REPORT_ERRORS) { |
306 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
307 | 0 | Disabled, |
308 | 0 | "Direct access to file descriptors is only available from command-line PHP"); |
309 | 0 | } |
310 | 0 | return NULL; |
311 | 0 | } |
312 | | |
313 | 0 | if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { |
314 | 0 | if (options & REPORT_ERRORS) { |
315 | 0 | php_stream_wrapper_warn(wrapper, context, options, |
316 | 0 | Disabled, |
317 | 0 | "URL file-access is disabled in the server configuration"); |
318 | 0 | } |
319 | 0 | return NULL; |
320 | 0 | } |
321 | | |
322 | 0 | start = &path[3]; |
323 | 0 | fildes_ori = ZEND_STRTOL(start, &end, 10); |
324 | 0 | if (end == start || *end != '\0') { |
325 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
326 | 0 | InvalidUrl, |
327 | 0 | "php://fd/ stream must be specified in the form php://fd/<orig fd>"); |
328 | 0 | return NULL; |
329 | 0 | } |
330 | | |
331 | 0 | #ifdef HAVE_UNISTD_H |
332 | 0 | dtablesize = getdtablesize(); |
333 | | #else |
334 | | dtablesize = INT_MAX; |
335 | | #endif |
336 | |
|
337 | 0 | if (fildes_ori < 0 || fildes_ori >= dtablesize) { |
338 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
339 | 0 | InvalidParam, |
340 | 0 | "The file descriptors must be non-negative numbers smaller than %d", dtablesize); |
341 | 0 | return NULL; |
342 | 0 | } |
343 | | |
344 | 0 | fd = dup((int)fildes_ori); |
345 | 0 | if (fd == -1) { |
346 | 0 | php_stream_wrapper_log_warn(wrapper, context, options, |
347 | 0 | DupFailed, |
348 | 0 | "Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: " |
349 | 0 | "[%d]: %s", fildes_ori, errno, strerror(errno)); |
350 | 0 | return NULL; |
351 | 0 | } |
352 | 39 | } else if (!strncasecmp(path, "filter/", 7)) { |
353 | | /* Save time/memory when chain isn't specified */ |
354 | 36 | if (strchr(mode, 'r') || strchr(mode, '+')) { |
355 | 36 | mode_rw |= PHP_STREAM_FILTER_READ; |
356 | 36 | } |
357 | 36 | if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) { |
358 | 0 | mode_rw |= PHP_STREAM_FILTER_WRITE; |
359 | 0 | } |
360 | 36 | pathdup = estrndup(path + 6, strlen(path + 6)); |
361 | 36 | p = strstr(pathdup, "/resource="); |
362 | 36 | if (!p) { |
363 | 0 | zend_throw_error(NULL, "No URL resource specified"); |
364 | 0 | efree(pathdup); |
365 | 0 | return NULL; |
366 | 0 | } |
367 | | |
368 | 36 | if (!(stream = php_stream_open_wrapper_ex(p + 10, mode, options, opened_path, context))) { |
369 | 0 | efree(pathdup); |
370 | 0 | return NULL; |
371 | 0 | } |
372 | | |
373 | 36 | *p = '\0'; |
374 | | |
375 | 36 | p = php_strtok_r(pathdup + 1, "/", &token); |
376 | 159 | while (p) { |
377 | 123 | if (!strncasecmp(p, "read=", 5)) { |
378 | 60 | php_stream_apply_filter_list(stream, p + 5, 1, 0); |
379 | 63 | } else if (!strncasecmp(p, "write=", 6)) { |
380 | 0 | php_stream_apply_filter_list(stream, p + 6, 0, 1); |
381 | 63 | } else { |
382 | 63 | php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE); |
383 | 63 | } |
384 | 123 | p = php_strtok_r(NULL, "/", &token); |
385 | 123 | } |
386 | 36 | efree(pathdup); |
387 | | |
388 | 36 | if (EG(exception)) { |
389 | 0 | php_stream_close(stream); |
390 | 0 | return NULL; |
391 | 0 | } |
392 | | |
393 | 36 | return stream; |
394 | 36 | } else { |
395 | | /* invalid php://thingy */ |
396 | 3 | php_stream_wrapper_warn(wrapper, context, options, |
397 | 3 | InvalidUrl, "Invalid php:// URL specified"); |
398 | 3 | return NULL; |
399 | 3 | } |
400 | | |
401 | | /* must be stdin, stderr or stdout */ |
402 | 0 | if (fd == -1) { |
403 | | /* failed to dup */ |
404 | 0 | return NULL; |
405 | 0 | } |
406 | | |
407 | 0 | #if defined(S_IFSOCK) && !defined(PHP_WIN32) |
408 | 0 | do { |
409 | 0 | zend_stat_t st = {0}; |
410 | 0 | memset(&st, 0, sizeof(st)); |
411 | 0 | if (zend_fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) { |
412 | 0 | stream = php_stream_sock_open_from_socket(fd, NULL); |
413 | 0 | if (stream) { |
414 | 0 | stream->ops = &php_stream_socket_ops; |
415 | 0 | return stream; |
416 | 0 | } |
417 | 0 | } |
418 | 0 | } while (0); |
419 | 0 | #endif |
420 | | |
421 | 0 | if (file) { |
422 | 0 | stream = php_stream_fopen_from_file(file, mode); |
423 | 0 | } else { |
424 | 0 | stream = php_stream_fopen_from_fd(fd, mode, NULL); |
425 | 0 | if (stream == NULL) { |
426 | 0 | close(fd); |
427 | 0 | } |
428 | 0 | } |
429 | |
|
430 | | #ifdef PHP_WIN32 |
431 | | if (pipe_requested && stream && context) { |
432 | | zval *blocking_pipes = php_stream_context_get_option(context, "pipe", "blocking"); |
433 | | if (blocking_pipes) { |
434 | | php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, zval_get_long(blocking_pipes), NULL); |
435 | | } |
436 | | } |
437 | | #endif |
438 | 0 | return stream; |
439 | 0 | } |
440 | | /* }}} */ |
441 | | |
442 | | static const php_stream_wrapper_ops php_stdio_wops = { |
443 | | php_stream_url_wrap_php, |
444 | | NULL, /* close */ |
445 | | NULL, /* fstat */ |
446 | | NULL, /* stat */ |
447 | | NULL, /* opendir */ |
448 | | "PHP", |
449 | | NULL, /* unlink */ |
450 | | NULL, /* rename */ |
451 | | NULL, /* mkdir */ |
452 | | NULL, /* rmdir */ |
453 | | NULL |
454 | | }; |
455 | | |
456 | | PHPAPI const php_stream_wrapper php_stream_php_wrapper = { |
457 | | &php_stdio_wops, |
458 | | NULL, |
459 | | 0, /* is_url */ |
460 | | }; |