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