xref: /PHP-7.0/ext/standard/php_fopen_wrapper.c (revision 478f119a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 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 /* $Id$ */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #if HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif
27 
28 #include "php.h"
29 #include "php_globals.h"
30 #include "php_standard.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 size_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 size_t php_stream_output_read(php_stream *stream, char *buf, size_t count) /* {{{ */
42 {
43 	stream->eof = 1;
44 	return 0;
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 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 size_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 size_t php_stream_input_read(php_stream *stream, char *buf, size_t count) /* {{{ */
79 {
80 	php_stream_input_t *input = stream->abstract;
81 	size_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->body)->position;
132 		return sought;
133 	}
134 
135 	return -1;
136 }
137 /* }}} */
138 
139 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, *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 				php_error_docref(NULL, E_RECOVERABLE_ERROR, "Max memory must be >= 0");
203 				return NULL;
204 			}
205 		}
206 		if (strpbrk(mode, "wa+")) {
207 			mode_rw = TEMP_STREAM_DEFAULT;
208 		} else {
209 			mode_rw = TEMP_STREAM_READONLY;
210 		}
211 		return php_stream_temp_create(mode_rw, max_memory);
212 	}
213 
214 	if (!strcasecmp(path, "memory")) {
215 		if (strpbrk(mode, "wa+")) {
216 			mode_rw = TEMP_STREAM_DEFAULT;
217 		} else {
218 			mode_rw = TEMP_STREAM_READONLY;
219 		}
220 		return php_stream_memory_create(mode_rw);
221 	}
222 
223 	if (!strcasecmp(path, "output")) {
224 		return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb");
225 	}
226 
227 	if (!strcasecmp(path, "input")) {
228 		php_stream_input_t *input;
229 
230 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
231 			if (options & REPORT_ERRORS) {
232 				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
233 			}
234 			return NULL;
235 		}
236 
237 		input = ecalloc(1, sizeof(*input));
238 		if ((input->body = SG(request_info).request_body)) {
239 			php_stream_rewind(input->body);
240 		} else {
241 			input->body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir));
242 			SG(request_info).request_body = input->body;
243 		}
244 
245 		return php_stream_alloc(&php_stream_input_ops, input, 0, "rb");
246 	}
247 
248 	if (!strcasecmp(path, "stdin")) {
249 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
250 			if (options & REPORT_ERRORS) {
251 				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
252 			}
253 			return NULL;
254 		}
255 		if (!strcmp(sapi_module.name, "cli")) {
256 			static int cli_in = 0;
257 			fd = STDIN_FILENO;
258 			if (cli_in) {
259 				fd = dup(fd);
260 			} else {
261 				cli_in = 1;
262 				file = stdin;
263 			}
264 		} else {
265 			fd = dup(STDIN_FILENO);
266 		}
267 #ifdef PHP_WIN32
268 		pipe_requested = 1;
269 #endif
270 	} else if (!strcasecmp(path, "stdout")) {
271 		if (!strcmp(sapi_module.name, "cli")) {
272 			static int cli_out = 0;
273 			fd = STDOUT_FILENO;
274 			if (cli_out++) {
275 				fd = dup(fd);
276 			} else {
277 				cli_out = 1;
278 				file = stdout;
279 			}
280 		} else {
281 			fd = dup(STDOUT_FILENO);
282 		}
283 #ifdef PHP_WIN32
284 		pipe_requested = 1;
285 #endif
286 	} else if (!strcasecmp(path, "stderr")) {
287 		if (!strcmp(sapi_module.name, "cli")) {
288 			static int cli_err = 0;
289 			fd = STDERR_FILENO;
290 			if (cli_err++) {
291 				fd = dup(fd);
292 			} else {
293 				cli_err = 1;
294 				file = stderr;
295 			}
296 		} else {
297 			fd = dup(STDERR_FILENO);
298 		}
299 #ifdef PHP_WIN32
300 		pipe_requested = 1;
301 #endif
302 	} else if (!strncasecmp(path, "fd/", 3)) {
303 		const char *start;
304 		char       *end;
305 		zend_long  fildes_ori;
306 		int		   dtablesize;
307 
308 		if (strcmp(sapi_module.name, "cli")) {
309 			if (options & REPORT_ERRORS) {
310 				php_error_docref(NULL, E_WARNING, "Direct access to file descriptors is only available from command-line PHP");
311 			}
312 			return NULL;
313 		}
314 
315 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
316 			if (options & REPORT_ERRORS) {
317 				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
318 			}
319 			return NULL;
320 		}
321 
322 		start = &path[3];
323 		fildes_ori = ZEND_STRTOL(start, &end, 10);
324 		if (end == start || *end != '\0') {
325 			php_stream_wrapper_log_error(wrapper, options,
326 				"php://fd/ stream must be specified in the form php://fd/<orig fd>");
327 			return NULL;
328 		}
329 
330 #if HAVE_UNISTD_H
331 		dtablesize = getdtablesize();
332 #else
333 		dtablesize = INT_MAX;
334 #endif
335 
336 		if (fildes_ori < 0 || fildes_ori >= dtablesize) {
337 			php_stream_wrapper_log_error(wrapper, options,
338 				"The file descriptors must be non-negative numbers smaller than %d", dtablesize);
339 			return NULL;
340 		}
341 
342 		fd = dup((int)fildes_ori);
343 		if (fd == -1) {
344 			php_stream_wrapper_log_error(wrapper, options,
345 				"Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: "
346 				"[%d]: %s", fildes_ori, errno, strerror(errno));
347 			return NULL;
348 		}
349 	} else if (!strncasecmp(path, "filter/", 7)) {
350 		/* Save time/memory when chain isn't specified */
351 		if (strchr(mode, 'r') || strchr(mode, '+')) {
352 			mode_rw |= PHP_STREAM_FILTER_READ;
353 		}
354 		if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) {
355 			mode_rw |= PHP_STREAM_FILTER_WRITE;
356 		}
357 		pathdup = estrndup(path + 6, strlen(path + 6));
358 		p = strstr(pathdup, "/resource=");
359 		if (!p) {
360 			php_error_docref(NULL, E_RECOVERABLE_ERROR, "No URL resource specified");
361 			efree(pathdup);
362 			return NULL;
363 		}
364 
365 		if (!(stream = php_stream_open_wrapper(p + 10, mode, options, opened_path))) {
366 			efree(pathdup);
367 			return NULL;
368 		}
369 
370 		*p = '\0';
371 
372 		p = php_strtok_r(pathdup + 1, "/", &token);
373 		while (p) {
374 			if (!strncasecmp(p, "read=", 5)) {
375 				php_stream_apply_filter_list(stream, p + 5, 1, 0);
376 			} else if (!strncasecmp(p, "write=", 6)) {
377 				php_stream_apply_filter_list(stream, p + 6, 0, 1);
378 			} else {
379 				php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE);
380 			}
381 			p = php_strtok_r(NULL, "/", &token);
382 		}
383 		efree(pathdup);
384 
385 		return stream;
386 	} else {
387 		/* invalid php://thingy */
388 		php_error_docref(NULL, E_WARNING, "Invalid php:// URL specified");
389 		return NULL;
390 	}
391 
392 	/* must be stdin, stderr or stdout */
393 	if (fd == -1)	{
394 		/* failed to dup */
395 		return NULL;
396 	}
397 
398 #if defined(S_IFSOCK) && !defined(WIN32) && !defined(__BEOS__)
399 	do {
400 		zend_stat_t st;
401 		memset(&st, 0, sizeof(st));
402 		if (zend_fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) {
403 			stream = php_stream_sock_open_from_socket(fd, NULL);
404 			if (stream) {
405 				stream->ops = &php_stream_socket_ops;
406 				return stream;
407 			}
408 		}
409 	} while (0);
410 #endif
411 
412 	if (file) {
413 		stream = php_stream_fopen_from_file(file, mode);
414 	} else {
415 		stream = php_stream_fopen_from_fd(fd, mode, NULL);
416 		if (stream == NULL) {
417 			close(fd);
418 		}
419 	}
420 
421 #ifdef PHP_WIN32
422 	if (pipe_requested && stream && context) {
423 		zval *blocking_pipes = php_stream_context_get_option(context, "pipe", "blocking");
424 		if (blocking_pipes) {
425 			convert_to_long(blocking_pipes);
426 			php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, Z_LVAL_P(blocking_pipes), NULL);
427 		}
428 	}
429 #endif
430 	return stream;
431 }
432 /* }}} */
433 
434 static php_stream_wrapper_ops php_stdio_wops = {
435 	php_stream_url_wrap_php,
436 	NULL, /* close */
437 	NULL, /* fstat */
438 	NULL, /* stat */
439 	NULL, /* opendir */
440 	"PHP",
441 	NULL, /* unlink */
442 	NULL, /* rename */
443 	NULL, /* mkdir */
444 	NULL  /* rmdir */
445 };
446 
447 PHPAPI php_stream_wrapper php_stream_php_wrapper =	{
448 	&php_stdio_wops,
449 	NULL,
450 	0, /* is_url */
451 };
452 
453 
454 /*
455  * Local variables:
456  * tab-width: 4
457  * c-basic-offset: 4
458  * End:
459  * vim600: sw=4 ts=4 fdm=marker
460  * vim<600: sw=4 ts=4
461  */
462