xref: /php-src/ext/standard/php_fopen_wrapper.c (revision c8955c07)
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 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 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 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(p + 10, mode, options, opened_path))) {
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