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