xref: /PHP-7.4/ext/standard/php_fopen_wrapper.c (revision 2fdd142f)
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