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
php_stream_output_write(php_stream * stream,const char * buf,size_t count)32 static ssize_t php_stream_output_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
33 {
34 PHPWRITE(buf, count);
35 return count;
36 }
37 /* }}} */
38
php_stream_output_read(php_stream * stream,char * buf,size_t count)39 static ssize_t php_stream_output_read(php_stream *stream, char *buf, size_t count) /* {{{ */
40 {
41 stream->eof = 1;
42 return -1;
43 }
44 /* }}} */
45
php_stream_output_close(php_stream * stream,int close_handle)46 static int php_stream_output_close(php_stream *stream, int close_handle) /* {{{ */
47 {
48 return 0;
49 }
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
php_stream_input_write(php_stream * stream,const char * buf,size_t count)70 static ssize_t php_stream_input_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
71 {
72 return -1;
73 }
74 /* }}} */
75
php_stream_input_read(php_stream * stream,char * buf,size_t count)76 static ssize_t php_stream_input_read(php_stream *stream, char *buf, size_t count) /* {{{ */
77 {
78 php_stream_input_t *input = stream->abstract;
79 ssize_t read;
80
81 if (!SG(post_read) && SG(read_post_bytes) < (int64_t)(input->position + count)) {
82 /* read requested data from SAPI */
83 size_t read_bytes = sapi_read_post_block(buf, count);
84
85 if (read_bytes > 0) {
86 php_stream_seek(input->body, 0, SEEK_END);
87 php_stream_write(input->body, buf, read_bytes);
88 }
89 }
90
91 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 php_stream_seek(input->body, input->position, SEEK_SET);
95 }
96 read = php_stream_read(input->body, buf, count);
97
98 if (!read || read == (size_t) -1) {
99 stream->eof = 1;
100 } else {
101 input->position += read;
102 }
103
104 return read;
105 }
106 /* }}} */
107
php_stream_input_close(php_stream * stream,int close_handle)108 static int php_stream_input_close(php_stream *stream, int close_handle) /* {{{ */
109 {
110 efree(stream->abstract);
111 stream->abstract = NULL;
112
113 return 0;
114 }
115 /* }}} */
116
php_stream_input_flush(php_stream * stream)117 static int php_stream_input_flush(php_stream *stream) /* {{{ */
118 {
119 return -1;
120 }
121 /* }}} */
122
php_stream_input_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)123 static int php_stream_input_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
124 {
125 php_stream_input_t *input = stream->abstract;
126
127 if (input->body) {
128 int sought = php_stream_seek(input->body, offset, whence);
129 *newoffset = input->position = (input->body)->position;
130 return sought;
131 }
132
133 return -1;
134 }
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
php_stream_apply_filter_list(php_stream * stream,char * filterlist,int read_chain,int write_chain)149 static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */
150 {
151 char *p, *token = NULL;
152 php_stream_filter *temp_filter;
153
154 p = php_strtok_r(filterlist, "|", &token);
155 while (p) {
156 php_url_decode(p, strlen(p));
157 if (read_chain) {
158 if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
159 php_stream_filter_append(&stream->readfilters, temp_filter);
160 } else {
161 php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
162 }
163 }
164 if (write_chain) {
165 if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
166 php_stream_filter_append(&stream->writefilters, temp_filter);
167 } else {
168 php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
169 }
170 }
171 p = php_strtok_r(NULL, "|", &token);
172 }
173 }
174 /* }}} */
175
php_stream_url_wrap_php(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)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 {
179 int fd = -1;
180 int mode_rw = 0;
181 php_stream * stream = NULL;
182 char *p, *token = NULL, *pathdup;
183 zend_long max_memory;
184 FILE *file = NULL;
185 #ifdef PHP_WIN32
186 int pipe_requested = 0;
187 #endif
188
189 if (!strncasecmp(path, "php://", 6)) {
190 path += 6;
191 }
192
193 if (!strncasecmp(path, "temp", 4)) {
194 path += 4;
195 max_memory = PHP_STREAM_MAX_MEM;
196 if (!strncasecmp(path, "/maxmemory:", 11)) {
197 path += 11;
198 max_memory = ZEND_STRTOL(path, NULL, 10);
199 if (max_memory < 0) {
200 zend_argument_value_error(2, "must be greater than or equal to 0");
201 return NULL;
202 }
203 }
204 mode_rw = php_stream_mode_from_str(mode);
205 return php_stream_temp_create(mode_rw, max_memory);
206 }
207
208 if (!strcasecmp(path, "memory")) {
209 mode_rw = php_stream_mode_from_str(mode);
210 return php_stream_memory_create(mode_rw);
211 }
212
213 if (!strcasecmp(path, "output")) {
214 return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb");
215 }
216
217 if (!strcasecmp(path, "input")) {
218 php_stream_input_t *input;
219
220 if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
221 if (options & REPORT_ERRORS) {
222 php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
223 }
224 return NULL;
225 }
226
227 input = ecalloc(1, sizeof(*input));
228 if ((input->body = SG(request_info).request_body)) {
229 php_stream_rewind(input->body);
230 } else {
231 input->body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir));
232 SG(request_info).request_body = input->body;
233 }
234
235 return php_stream_alloc(&php_stream_input_ops, input, 0, "rb");
236 }
237
238 if (!strcasecmp(path, "stdin")) {
239 if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
240 if (options & REPORT_ERRORS) {
241 php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
242 }
243 return NULL;
244 }
245 if (!strcmp(sapi_module.name, "cli")) {
246 static int cli_in = 0;
247 fd = STDIN_FILENO;
248 if (cli_in) {
249 fd = dup(fd);
250 } else {
251 cli_in = 1;
252 file = stdin;
253 }
254 } else {
255 fd = dup(STDIN_FILENO);
256 }
257 #ifdef PHP_WIN32
258 pipe_requested = 1;
259 #endif
260 } else if (!strcasecmp(path, "stdout")) {
261 if (!strcmp(sapi_module.name, "cli")) {
262 static int cli_out = 0;
263 fd = STDOUT_FILENO;
264 if (cli_out++) {
265 fd = dup(fd);
266 } else {
267 cli_out = 1;
268 file = stdout;
269 }
270 } else {
271 fd = dup(STDOUT_FILENO);
272 }
273 #ifdef PHP_WIN32
274 pipe_requested = 1;
275 #endif
276 } else if (!strcasecmp(path, "stderr")) {
277 if (!strcmp(sapi_module.name, "cli")) {
278 static int cli_err = 0;
279 fd = STDERR_FILENO;
280 if (cli_err++) {
281 fd = dup(fd);
282 } else {
283 cli_err = 1;
284 file = stderr;
285 }
286 } else {
287 fd = dup(STDERR_FILENO);
288 }
289 #ifdef PHP_WIN32
290 pipe_requested = 1;
291 #endif
292 } else if (!strncasecmp(path, "fd/", 3)) {
293 const char *start;
294 char *end;
295 zend_long fildes_ori;
296 int dtablesize;
297
298 if (strcmp(sapi_module.name, "cli")) {
299 if (options & REPORT_ERRORS) {
300 php_error_docref(NULL, E_WARNING, "Direct access to file descriptors is only available from command-line PHP");
301 }
302 return NULL;
303 }
304
305 if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
306 if (options & REPORT_ERRORS) {
307 php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
308 }
309 return NULL;
310 }
311
312 start = &path[3];
313 fildes_ori = ZEND_STRTOL(start, &end, 10);
314 if (end == start || *end != '\0') {
315 php_stream_wrapper_log_error(wrapper, options,
316 "php://fd/ stream must be specified in the form php://fd/<orig fd>");
317 return NULL;
318 }
319
320 #ifdef HAVE_UNISTD_H
321 dtablesize = getdtablesize();
322 #else
323 dtablesize = INT_MAX;
324 #endif
325
326 if (fildes_ori < 0 || fildes_ori >= dtablesize) {
327 php_stream_wrapper_log_error(wrapper, options,
328 "The file descriptors must be non-negative numbers smaller than %d", dtablesize);
329 return NULL;
330 }
331
332 fd = dup((int)fildes_ori);
333 if (fd == -1) {
334 php_stream_wrapper_log_error(wrapper, options,
335 "Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: "
336 "[%d]: %s", fildes_ori, errno, strerror(errno));
337 return NULL;
338 }
339 } else if (!strncasecmp(path, "filter/", 7)) {
340 /* Save time/memory when chain isn't specified */
341 if (strchr(mode, 'r') || strchr(mode, '+')) {
342 mode_rw |= PHP_STREAM_FILTER_READ;
343 }
344 if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) {
345 mode_rw |= PHP_STREAM_FILTER_WRITE;
346 }
347 pathdup = estrndup(path + 6, strlen(path + 6));
348 p = strstr(pathdup, "/resource=");
349 if (!p) {
350 zend_throw_error(NULL, "No URL resource specified");
351 efree(pathdup);
352 return NULL;
353 }
354
355 if (!(stream = php_stream_open_wrapper_ex(p + 10, mode, options, opened_path, context))) {
356 efree(pathdup);
357 return NULL;
358 }
359
360 *p = '\0';
361
362 p = php_strtok_r(pathdup + 1, "/", &token);
363 while (p) {
364 if (!strncasecmp(p, "read=", 5)) {
365 php_stream_apply_filter_list(stream, p + 5, 1, 0);
366 } else if (!strncasecmp(p, "write=", 6)) {
367 php_stream_apply_filter_list(stream, p + 6, 0, 1);
368 } else {
369 php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE);
370 }
371 p = php_strtok_r(NULL, "/", &token);
372 }
373 efree(pathdup);
374
375 if (EG(exception)) {
376 php_stream_close(stream);
377 return NULL;
378 }
379
380 return stream;
381 } else {
382 /* invalid php://thingy */
383 php_error_docref(NULL, E_WARNING, "Invalid php:// URL specified");
384 return NULL;
385 }
386
387 /* must be stdin, stderr or stdout */
388 if (fd == -1) {
389 /* failed to dup */
390 return NULL;
391 }
392
393 #if defined(S_IFSOCK) && !defined(PHP_WIN32)
394 do {
395 zend_stat_t st = {0};
396 memset(&st, 0, sizeof(st));
397 if (zend_fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) {
398 stream = php_stream_sock_open_from_socket(fd, NULL);
399 if (stream) {
400 stream->ops = &php_stream_socket_ops;
401 return stream;
402 }
403 }
404 } while (0);
405 #endif
406
407 if (file) {
408 stream = php_stream_fopen_from_file(file, mode);
409 } else {
410 stream = php_stream_fopen_from_fd(fd, mode, NULL);
411 if (stream == NULL) {
412 close(fd);
413 }
414 }
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 return stream;
425 }
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 };
447