xref: /PHP-8.0/ext/standard/streamsfuncs.c (revision 80232de0)
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   | http://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: Wez Furlong <wez@thebrainroom.com>                          |
14   |          Sara Golemon <pollita@php.net>                              |
15   +----------------------------------------------------------------------+
16 */
17 
18 #include "php.h"
19 #include "php_globals.h"
20 #include "ext/standard/flock_compat.h"
21 #include "ext/standard/file.h"
22 #include "ext/standard/php_filestat.h"
23 #include "php_open_temporary_file.h"
24 #include "ext/standard/basic_functions.h"
25 #include "php_ini.h"
26 #include "streamsfuncs.h"
27 #include "php_network.h"
28 #include "php_string.h"
29 #if HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 
33 #ifndef PHP_WIN32
34 #define php_select(m, r, w, e, t)	select(m, r, w, e, t)
35 typedef unsigned long long php_timeout_ull;
36 #else
37 #include "win32/select.h"
38 #include "win32/sockets.h"
39 #include "win32/console.h"
40 typedef unsigned __int64 php_timeout_ull;
41 #endif
42 
43 #define GET_CTX_OPT(stream, wrapper, name, val) (PHP_STREAM_CONTEXT(stream) && NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), wrapper, name)))
44 
45 static php_stream_context *decode_context_param(zval *contextresource);
46 
47 /* Streams based network functions */
48 
49 #if HAVE_SOCKETPAIR
50 /* {{{ Creates a pair of connected, indistinguishable socket streams */
PHP_FUNCTION(stream_socket_pair)51 PHP_FUNCTION(stream_socket_pair)
52 {
53 	zend_long domain, type, protocol;
54 	php_stream *s1, *s2;
55 	php_socket_t pair[2];
56 
57 	ZEND_PARSE_PARAMETERS_START(3, 3)
58 		Z_PARAM_LONG(domain)
59 		Z_PARAM_LONG(type)
60 		Z_PARAM_LONG(protocol)
61 	ZEND_PARSE_PARAMETERS_END();
62 
63 	if (0 != socketpair((int)domain, (int)type, (int)protocol, pair)) {
64 		char errbuf[256];
65 		php_error_docref(NULL, E_WARNING, "Failed to create sockets: [%d]: %s",
66 			php_socket_errno(), php_socket_strerror(php_socket_errno(), errbuf, sizeof(errbuf)));
67 		RETURN_FALSE;
68 	}
69 
70     s1 = php_stream_sock_open_from_socket(pair[0], 0);
71     if (s1 == NULL) {
72         close(pair[0]);
73         close(pair[1]);
74         php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
75         RETURN_FALSE;
76     }
77     s2 = php_stream_sock_open_from_socket(pair[1], 0);
78     if (s2 == NULL) {
79         php_stream_free(s1, PHP_STREAM_FREE_CLOSE);
80         close(pair[1]);
81         php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
82         RETURN_FALSE;
83     }
84 
85     array_init(return_value);
86 
87 	/* set the __exposed flag.
88 	 * php_stream_to_zval() does, add_next_index_resource() does not */
89 	php_stream_auto_cleanup(s1);
90 	php_stream_auto_cleanup(s2);
91 
92 	add_next_index_resource(return_value, s1->res);
93 	add_next_index_resource(return_value, s2->res);
94 }
95 /* }}} */
96 #endif
97 
98 /* {{{ Open a client connection to a remote address */
PHP_FUNCTION(stream_socket_client)99 PHP_FUNCTION(stream_socket_client)
100 {
101 	zend_string *host;
102 	zval *zerrno = NULL, *zerrstr = NULL, *zcontext = NULL;
103 	double timeout;
104 	zend_bool timeout_is_null = 1;
105 	php_timeout_ull conv;
106 	struct timeval tv;
107 	char *hashkey = NULL;
108 	php_stream *stream = NULL;
109 	int err;
110 	zend_long flags = PHP_STREAM_CLIENT_CONNECT;
111 	zend_string *errstr = NULL;
112 	php_stream_context *context = NULL;
113 
114 	ZEND_PARSE_PARAMETERS_START(1, 6)
115 		Z_PARAM_STR(host)
116 		Z_PARAM_OPTIONAL
117 		Z_PARAM_ZVAL(zerrno)
118 		Z_PARAM_ZVAL(zerrstr)
119 		Z_PARAM_DOUBLE_OR_NULL(timeout, timeout_is_null)
120 		Z_PARAM_LONG(flags)
121 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
122 	ZEND_PARSE_PARAMETERS_END();
123 
124 	RETVAL_FALSE;
125 
126 	if (timeout_is_null) {
127 		timeout = (double)FG(default_socket_timeout);
128 	}
129 
130 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);
131 
132 	if (flags & PHP_STREAM_CLIENT_PERSISTENT) {
133 		spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(host));
134 	}
135 
136 	/* prepare the timeout value for use */
137 	conv = (php_timeout_ull) (timeout * 1000000.0);
138 #ifdef PHP_WIN32
139 	tv.tv_sec = (long)(conv / 1000000);
140 	tv.tv_usec =(long)(conv % 1000000);
141 #else
142 	tv.tv_sec = conv / 1000000;
143 	tv.tv_usec = conv % 1000000;
144 #endif
145 	if (zerrno) {
146 		ZEND_TRY_ASSIGN_REF_LONG(zerrno, 0);
147 	}
148 	if (zerrstr) {
149 		ZEND_TRY_ASSIGN_REF_EMPTY_STRING(zerrstr);
150 	}
151 
152 	stream = php_stream_xport_create(ZSTR_VAL(host), ZSTR_LEN(host), REPORT_ERRORS,
153 			STREAM_XPORT_CLIENT | (flags & PHP_STREAM_CLIENT_CONNECT ? STREAM_XPORT_CONNECT : 0) |
154 			(flags & PHP_STREAM_CLIENT_ASYNC_CONNECT ? STREAM_XPORT_CONNECT_ASYNC : 0),
155 			hashkey, &tv, context, &errstr, &err);
156 
157 
158 	if (stream == NULL) {
159 		/* host might contain binary characters */
160 		zend_string *quoted_host = php_addslashes(host);
161 
162 		php_error_docref(NULL, E_WARNING, "Unable to connect to %s (%s)", ZSTR_VAL(quoted_host), errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr));
163 		zend_string_release_ex(quoted_host, 0);
164 	}
165 
166 	if (hashkey) {
167 		efree(hashkey);
168 	}
169 
170 	if (stream == NULL) {
171 		if (zerrno) {
172 			ZEND_TRY_ASSIGN_REF_LONG(zerrno, err);
173 		}
174 		if (zerrstr && errstr) {
175 			ZEND_TRY_ASSIGN_REF_STR(zerrstr, errstr);
176 		} else if (errstr) {
177 			zend_string_release_ex(errstr, 0);
178 		}
179 		RETURN_FALSE;
180 	}
181 
182 	if (errstr) {
183 		zend_string_release_ex(errstr, 0);
184 	}
185 
186 	php_stream_to_zval(stream, return_value);
187 
188 }
189 /* }}} */
190 
191 /* {{{ Create a server socket bound to localaddress */
PHP_FUNCTION(stream_socket_server)192 PHP_FUNCTION(stream_socket_server)
193 {
194 	char *host;
195 	size_t host_len;
196 	zval *zerrno = NULL, *zerrstr = NULL, *zcontext = NULL;
197 	php_stream *stream = NULL;
198 	int err = 0;
199 	zend_long flags = STREAM_XPORT_BIND | STREAM_XPORT_LISTEN;
200 	zend_string *errstr = NULL;
201 	php_stream_context *context = NULL;
202 
203 	RETVAL_FALSE;
204 
205 	ZEND_PARSE_PARAMETERS_START(1, 5)
206 		Z_PARAM_STRING(host, host_len)
207 		Z_PARAM_OPTIONAL
208 		Z_PARAM_ZVAL(zerrno)
209 		Z_PARAM_ZVAL(zerrstr)
210 		Z_PARAM_LONG(flags)
211 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
212 	ZEND_PARSE_PARAMETERS_END();
213 
214 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);
215 
216 	if (context) {
217 		GC_ADDREF(context->res);
218 	}
219 
220 	if (zerrno) {
221 		ZEND_TRY_ASSIGN_REF_LONG(zerrno, 0);
222 	}
223 	if (zerrstr) {
224 		ZEND_TRY_ASSIGN_REF_EMPTY_STRING(zerrstr);
225 	}
226 
227 	stream = php_stream_xport_create(host, host_len, REPORT_ERRORS,
228 			STREAM_XPORT_SERVER | (int)flags,
229 			NULL, NULL, context, &errstr, &err);
230 
231 	if (stream == NULL) {
232 		php_error_docref(NULL, E_WARNING, "Unable to connect to %s (%s)", host, errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr));
233 	}
234 
235 	if (stream == NULL) {
236 		if (zerrno) {
237 			ZEND_TRY_ASSIGN_REF_LONG(zerrno, err);
238 		}
239 		if (zerrstr && errstr) {
240 			ZEND_TRY_ASSIGN_REF_STR(zerrstr, errstr);
241 		} else if (errstr) {
242 			zend_string_release_ex(errstr, 0);
243 		}
244 		RETURN_FALSE;
245 	}
246 
247 	if (errstr) {
248 		zend_string_release_ex(errstr, 0);
249 	}
250 
251 	php_stream_to_zval(stream, return_value);
252 }
253 /* }}} */
254 
255 /* {{{ Accept a client connection from a server socket */
PHP_FUNCTION(stream_socket_accept)256 PHP_FUNCTION(stream_socket_accept)
257 {
258 	double timeout;
259 	zend_bool timeout_is_null = 1;
260 	zval *zpeername = NULL;
261 	zend_string *peername = NULL;
262 	php_timeout_ull conv;
263 	struct timeval tv;
264 	php_stream *stream = NULL, *clistream = NULL;
265 	zval *zstream;
266 	zend_string *errstr = NULL;
267 
268 	ZEND_PARSE_PARAMETERS_START(1, 3)
269 		Z_PARAM_RESOURCE(zstream)
270 		Z_PARAM_OPTIONAL
271 		Z_PARAM_DOUBLE_OR_NULL(timeout, timeout_is_null)
272 		Z_PARAM_ZVAL(zpeername)
273 	ZEND_PARSE_PARAMETERS_END();
274 
275 	if (timeout_is_null) {
276 		timeout = (double)FG(default_socket_timeout);
277 	}
278 
279 	php_stream_from_zval(stream, zstream);
280 
281 	/* prepare the timeout value for use */
282 	conv = (php_timeout_ull) (timeout * 1000000.0);
283 #ifdef PHP_WIN32
284 	tv.tv_sec = (long)(conv / 1000000);
285 	tv.tv_usec = (long)(conv % 1000000);
286 #else
287 	tv.tv_sec = conv / 1000000;
288 	tv.tv_usec = conv % 1000000;
289 #endif
290 
291 	if (0 == php_stream_xport_accept(stream, &clistream,
292 				zpeername ? &peername : NULL,
293 				NULL, NULL,
294 				&tv, &errstr
295 				) && clistream) {
296 
297 		if (peername) {
298 			ZEND_TRY_ASSIGN_REF_STR(zpeername, peername);
299 		}
300 		php_stream_to_zval(clistream, return_value);
301 	} else {
302 		if (peername) {
303 			zend_string_release(peername);
304 		}
305 		php_error_docref(NULL, E_WARNING, "Accept failed: %s", errstr ? ZSTR_VAL(errstr) : "Unknown error");
306 		RETVAL_FALSE;
307 	}
308 
309 	if (errstr) {
310 		zend_string_release_ex(errstr, 0);
311 	}
312 }
313 /* }}} */
314 
315 /* {{{ Returns either the locally bound or remote name for a socket stream */
PHP_FUNCTION(stream_socket_get_name)316 PHP_FUNCTION(stream_socket_get_name)
317 {
318 	php_stream *stream;
319 	zval *zstream;
320 	zend_bool want_peer;
321 	zend_string *name = NULL;
322 
323 	ZEND_PARSE_PARAMETERS_START(2, 2)
324 		Z_PARAM_RESOURCE(zstream)
325 		Z_PARAM_BOOL(want_peer)
326 	ZEND_PARSE_PARAMETERS_END();
327 
328 	php_stream_from_zval(stream, zstream);
329 
330 	if (0 != php_stream_xport_get_name(stream, want_peer,
331 				&name,
332 				NULL, NULL
333 				) || !name) {
334 		RETURN_FALSE;
335 	}
336 
337 	if ((ZSTR_LEN(name) == 0) || (ZSTR_VAL(name)[0] == 0)) {
338 		zend_string_release_ex(name, 0);
339 		RETURN_FALSE;
340 	}
341 
342 	RETVAL_STR(name);
343 }
344 /* }}} */
345 
346 /* {{{ Send data to a socket stream.  If target_addr is specified it must be in dotted quad (or [ipv6]) format */
PHP_FUNCTION(stream_socket_sendto)347 PHP_FUNCTION(stream_socket_sendto)
348 {
349 	php_stream *stream;
350 	zval *zstream;
351 	zend_long flags = 0;
352 	char *data, *target_addr = NULL;
353 	size_t datalen, target_addr_len = 0;
354 	php_sockaddr_storage sa;
355 	socklen_t sl = 0;
356 
357 	ZEND_PARSE_PARAMETERS_START(2, 4)
358 		Z_PARAM_RESOURCE(zstream)
359 		Z_PARAM_STRING(data, datalen)
360 		Z_PARAM_OPTIONAL
361 		Z_PARAM_LONG(flags)
362 		Z_PARAM_STRING(target_addr, target_addr_len)
363 	ZEND_PARSE_PARAMETERS_END();
364 	php_stream_from_zval(stream, zstream);
365 
366 	if (target_addr_len) {
367 		/* parse the address */
368 		if (FAILURE == php_network_parse_network_address_with_port(target_addr, target_addr_len, (struct sockaddr*)&sa, &sl)) {
369 			php_error_docref(NULL, E_WARNING, "Failed to parse `%s' into a valid network address", target_addr);
370 			RETURN_FALSE;
371 		}
372 	}
373 
374 	RETURN_LONG(php_stream_xport_sendto(stream, data, datalen, (int)flags, target_addr_len ? &sa : NULL, sl));
375 }
376 /* }}} */
377 
378 /* {{{ Receives data from a socket stream */
PHP_FUNCTION(stream_socket_recvfrom)379 PHP_FUNCTION(stream_socket_recvfrom)
380 {
381 	php_stream *stream;
382 	zval *zstream, *zremote = NULL;
383 	zend_string *remote_addr = NULL;
384 	zend_long to_read = 0;
385 	zend_string *read_buf;
386 	zend_long flags = 0;
387 	int recvd;
388 
389 	ZEND_PARSE_PARAMETERS_START(2, 4)
390 		Z_PARAM_RESOURCE(zstream)
391 		Z_PARAM_LONG(to_read)
392 		Z_PARAM_OPTIONAL
393 		Z_PARAM_LONG(flags)
394 		Z_PARAM_ZVAL(zremote)
395 	ZEND_PARSE_PARAMETERS_END();
396 
397 	php_stream_from_zval(stream, zstream);
398 
399 	if (zremote) {
400 		ZEND_TRY_ASSIGN_REF_NULL(zremote);
401 	}
402 
403 	if (to_read <= 0) {
404 		zend_argument_value_error(2, "must be greater than 0");
405 		RETURN_THROWS();
406 	}
407 
408 	read_buf = zend_string_alloc(to_read, 0);
409 
410 	recvd = php_stream_xport_recvfrom(stream, ZSTR_VAL(read_buf), to_read, (int)flags, NULL, NULL,
411 			zremote ? &remote_addr : NULL
412 			);
413 
414 	if (recvd >= 0) {
415 		if (zremote && remote_addr) {
416 			ZEND_TRY_ASSIGN_REF_STR(zremote, remote_addr);
417 		}
418 		ZSTR_VAL(read_buf)[recvd] = '\0';
419 		ZSTR_LEN(read_buf) = recvd;
420 		RETURN_NEW_STR(read_buf);
421 	}
422 
423 	zend_string_efree(read_buf);
424 	RETURN_FALSE;
425 }
426 /* }}} */
427 
428 /* {{{ Reads all remaining bytes (or up to maxlen bytes) from a stream and returns them as a string. */
PHP_FUNCTION(stream_get_contents)429 PHP_FUNCTION(stream_get_contents)
430 {
431 	php_stream *stream;
432 	zval *zsrc;
433 	zend_long maxlen, desiredpos = -1L;
434 	zend_bool maxlen_is_null = 1;
435 	zend_string *contents;
436 
437 	ZEND_PARSE_PARAMETERS_START(1, 3)
438 		Z_PARAM_RESOURCE(zsrc)
439 		Z_PARAM_OPTIONAL
440 		Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null)
441 		Z_PARAM_LONG(desiredpos)
442 	ZEND_PARSE_PARAMETERS_END();
443 
444 	if (maxlen_is_null) {
445 		maxlen = (ssize_t) PHP_STREAM_COPY_ALL;
446 	} else if (maxlen < 0 && maxlen != (ssize_t)PHP_STREAM_COPY_ALL) {
447 		zend_argument_value_error(2, "must be greater than or equal to -1");
448 		RETURN_THROWS();
449 	}
450 
451 	php_stream_from_zval(stream, zsrc);
452 
453 	if (desiredpos >= 0) {
454 		int		seek_res = 0;
455 		zend_off_t	position;
456 
457 		position = php_stream_tell(stream);
458 		if (position >= 0 && desiredpos > position) {
459 			/* use SEEK_CUR to allow emulation in streams that don't support seeking */
460 			seek_res = php_stream_seek(stream, desiredpos - position, SEEK_CUR);
461 		} else if (desiredpos < position)  {
462 			/* desired position before position or error on tell */
463 			seek_res = php_stream_seek(stream, desiredpos, SEEK_SET);
464 		}
465 
466 		if (seek_res != 0) {
467 			php_error_docref(NULL, E_WARNING,
468 				"Failed to seek to position " ZEND_LONG_FMT " in the stream", desiredpos);
469 			RETURN_FALSE;
470 		}
471 	}
472 
473 	if ((contents = php_stream_copy_to_mem(stream, maxlen, 0))) {
474 		RETURN_STR(contents);
475 	} else {
476 		RETURN_EMPTY_STRING();
477 	}
478 }
479 /* }}} */
480 
481 /* {{{ Reads up to maxlen bytes from source stream and writes them to dest stream. */
PHP_FUNCTION(stream_copy_to_stream)482 PHP_FUNCTION(stream_copy_to_stream)
483 {
484 	php_stream *src, *dest;
485 	zval *zsrc, *zdest;
486 	zend_long maxlen, pos = 0;
487 	zend_bool maxlen_is_null = 1;
488 	size_t len;
489 	int ret;
490 
491 	ZEND_PARSE_PARAMETERS_START(2, 4)
492 		Z_PARAM_RESOURCE(zsrc)
493 		Z_PARAM_RESOURCE(zdest)
494 		Z_PARAM_OPTIONAL
495 		Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null)
496 		Z_PARAM_LONG(pos)
497 	ZEND_PARSE_PARAMETERS_END();
498 
499 	if (maxlen_is_null) {
500 		maxlen = PHP_STREAM_COPY_ALL;
501 	}
502 
503 	php_stream_from_zval(src, zsrc);
504 	php_stream_from_zval(dest, zdest);
505 
506 	if (pos > 0 && php_stream_seek(src, pos, SEEK_SET) < 0) {
507 		php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", pos);
508 		RETURN_FALSE;
509 	}
510 
511 	ret = php_stream_copy_to_stream_ex(src, dest, maxlen, &len);
512 
513 	if (ret != SUCCESS) {
514 		RETURN_FALSE;
515 	}
516 	RETURN_LONG(len);
517 }
518 /* }}} */
519 
520 /* {{{ Retrieves header/meta data from streams/file pointers */
PHP_FUNCTION(stream_get_meta_data)521 PHP_FUNCTION(stream_get_meta_data)
522 {
523 	zval *zstream;
524 	php_stream *stream;
525 
526 	ZEND_PARSE_PARAMETERS_START(1, 1)
527 		Z_PARAM_RESOURCE(zstream)
528 	ZEND_PARSE_PARAMETERS_END();
529 
530 	php_stream_from_zval(stream, zstream);
531 
532 	array_init(return_value);
533 
534 	if (!php_stream_populate_meta_data(stream, return_value)) {
535 		add_assoc_bool(return_value, "timed_out", 0);
536 		add_assoc_bool(return_value, "blocked", 1);
537 		add_assoc_bool(return_value, "eof", php_stream_eof(stream));
538 	}
539 
540 	if (!Z_ISUNDEF(stream->wrapperdata)) {
541 		Z_ADDREF_P(&stream->wrapperdata);
542 		add_assoc_zval(return_value, "wrapper_data", &stream->wrapperdata);
543 	}
544 	if (stream->wrapper) {
545 		add_assoc_string(return_value, "wrapper_type", (char *)stream->wrapper->wops->label);
546 	}
547 	add_assoc_string(return_value, "stream_type", (char *)stream->ops->label);
548 
549 	add_assoc_string(return_value, "mode", stream->mode);
550 
551 #if 0	/* TODO: needs updating for new filter API */
552 	if (stream->filterhead) {
553 		php_stream_filter *filter;
554 
555 		MAKE_STD_ZVAL(newval);
556 		array_init(newval);
557 
558 		for (filter = stream->filterhead; filter != NULL; filter = filter->next) {
559 			add_next_index_string(newval, (char *)filter->fops->label);
560 		}
561 
562 		add_assoc_zval(return_value, "filters", newval);
563 	}
564 #endif
565 
566 	add_assoc_long(return_value, "unread_bytes", stream->writepos - stream->readpos);
567 
568 	add_assoc_bool(return_value, "seekable", (stream->ops->seek) && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0);
569 	if (stream->orig_path) {
570 		add_assoc_string(return_value, "uri", stream->orig_path);
571 	}
572 
573 }
574 /* }}} */
575 
576 /* {{{ Retrieves list of registered socket transports */
PHP_FUNCTION(stream_get_transports)577 PHP_FUNCTION(stream_get_transports)
578 {
579 	HashTable *stream_xport_hash;
580 	zend_string *stream_xport;
581 
582 	ZEND_PARSE_PARAMETERS_NONE();
583 
584 	stream_xport_hash = php_stream_xport_get_hash();
585 	array_init(return_value);
586 	ZEND_HASH_FOREACH_STR_KEY(stream_xport_hash, stream_xport) {
587 		add_next_index_str(return_value, zend_string_copy(stream_xport));
588 	} ZEND_HASH_FOREACH_END();
589 }
590 /* }}} */
591 
592 /* {{{ Retrieves list of registered stream wrappers */
PHP_FUNCTION(stream_get_wrappers)593 PHP_FUNCTION(stream_get_wrappers)
594 {
595 	HashTable *url_stream_wrappers_hash;
596 	zend_string *stream_protocol;
597 
598 	ZEND_PARSE_PARAMETERS_NONE();
599 
600 	url_stream_wrappers_hash = php_stream_get_url_stream_wrappers_hash();
601 	array_init(return_value);
602 	ZEND_HASH_FOREACH_STR_KEY(url_stream_wrappers_hash, stream_protocol) {
603 		if (stream_protocol) {
604 			add_next_index_str(return_value, zend_string_copy(stream_protocol));
605 		}
606 	} ZEND_HASH_FOREACH_END();
607 
608 }
609 /* }}} */
610 
611 /* {{{ stream_select related functions */
stream_array_to_fd_set(zval * stream_array,fd_set * fds,php_socket_t * max_fd)612 static int stream_array_to_fd_set(zval *stream_array, fd_set *fds, php_socket_t *max_fd)
613 {
614 	zval *elem;
615 	php_stream *stream;
616 	int cnt = 0;
617 
618 	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
619 		return 0;
620 	}
621 
622 	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(stream_array), elem) {
623 		/* Temporary int fd is needed for the STREAM data type on windows, passing this_fd directly to php_stream_cast()
624 			would eventually bring a wrong result on x64. php_stream_cast() casts to int internally, and this will leave
625 			the higher bits of a SOCKET variable uninitialized on systems with little endian. */
626 		php_socket_t this_fd;
627 
628 		ZVAL_DEREF(elem);
629 		php_stream_from_zval_no_verify(stream, elem);
630 		if (stream == NULL) {
631 			continue;
632 		}
633 		/* get the fd.
634 		 * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag
635 		 * when casting.  It is only used here so that the buffered data warning
636 		 * is not displayed.
637 		 * */
638 		if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&this_fd, 1) && this_fd != -1) {
639 
640 			PHP_SAFE_FD_SET(this_fd, fds);
641 
642 			if (this_fd > *max_fd) {
643 				*max_fd = this_fd;
644 			}
645 			cnt++;
646 		}
647 	} ZEND_HASH_FOREACH_END();
648 	return cnt ? 1 : 0;
649 }
650 
stream_array_from_fd_set(zval * stream_array,fd_set * fds)651 static int stream_array_from_fd_set(zval *stream_array, fd_set *fds)
652 {
653 	zval *elem, *dest_elem;
654 	HashTable *ht;
655 	php_stream *stream;
656 	int ret = 0;
657 	zend_string *key;
658 	zend_ulong num_ind;
659 
660 	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
661 		return 0;
662 	}
663 	ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array)));
664 
665 	ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) {
666 		php_socket_t this_fd;
667 
668 		ZVAL_DEREF(elem);
669 		php_stream_from_zval_no_verify(stream, elem);
670 		if (stream == NULL) {
671 			continue;
672 		}
673 		/* get the fd
674 		 * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag
675 		 * when casting.  It is only used here so that the buffered data warning
676 		 * is not displayed.
677 		 */
678 		if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&this_fd, 1) && this_fd != SOCK_ERR) {
679 			if (PHP_SAFE_FD_ISSET(this_fd, fds)) {
680 				if (!key) {
681 					dest_elem = zend_hash_index_update(ht, num_ind, elem);
682 				} else {
683 					dest_elem = zend_hash_update(ht, key, elem);
684 				}
685 
686 				zval_add_ref(dest_elem);
687 				ret++;
688 				continue;
689 			}
690 		}
691 	} ZEND_HASH_FOREACH_END();
692 
693 	/* destroy old array and add new one */
694 	zval_ptr_dtor(stream_array);
695 	ZVAL_ARR(stream_array, ht);
696 
697 	return ret;
698 }
699 
stream_array_emulate_read_fd_set(zval * stream_array)700 static int stream_array_emulate_read_fd_set(zval *stream_array)
701 {
702 	zval *elem, *dest_elem;
703 	HashTable *ht;
704 	php_stream *stream;
705 	int ret = 0;
706 	zend_ulong num_ind;
707 	zend_string *key;
708 
709 	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
710 		return 0;
711 	}
712 	ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array)));
713 
714 	ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) {
715 		ZVAL_DEREF(elem);
716 		php_stream_from_zval_no_verify(stream, elem);
717 		if (stream == NULL) {
718 			continue;
719 		}
720 		if ((stream->writepos - stream->readpos) > 0) {
721 			/* allow readable non-descriptor based streams to participate in stream_select.
722 			 * Non-descriptor streams will only "work" if they have previously buffered the
723 			 * data.  Not ideal, but better than nothing.
724 			 * This branch of code also allows blocking streams with buffered data to
725 			 * operate correctly in stream_select.
726 			 * */
727 			if (!key) {
728 				dest_elem = zend_hash_index_update(ht, num_ind, elem);
729 			} else {
730 				dest_elem = zend_hash_update(ht, key, elem);
731 			}
732 			zval_add_ref(dest_elem);
733 			ret++;
734 			continue;
735 		}
736 	} ZEND_HASH_FOREACH_END();
737 
738 	if (ret > 0) {
739 		/* destroy old array and add new one */
740 		zval_ptr_dtor(stream_array);
741 		ZVAL_ARR(stream_array, ht);
742 	} else {
743 		zend_array_destroy(ht);
744 	}
745 
746 	return ret;
747 }
748 /* }}} */
749 
750 /* {{{ Runs the select() system call on the sets of streams with a timeout specified by tv_sec and tv_usec */
PHP_FUNCTION(stream_select)751 PHP_FUNCTION(stream_select)
752 {
753 	zval *r_array, *w_array, *e_array;
754 	struct timeval tv, *tv_p = NULL;
755 	fd_set rfds, wfds, efds;
756 	php_socket_t max_fd = 0;
757 	int retval, sets = 0;
758 	zend_long sec, usec = 0;
759 	zend_bool secnull;
760 	int set_count, max_set_count = 0;
761 
762 	ZEND_PARSE_PARAMETERS_START(4, 5)
763 		Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0)
764 		Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0)
765 		Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0)
766 		Z_PARAM_LONG_OR_NULL(sec, secnull)
767 		Z_PARAM_OPTIONAL
768 		Z_PARAM_LONG(usec)
769 	ZEND_PARSE_PARAMETERS_END();
770 
771 	FD_ZERO(&rfds);
772 	FD_ZERO(&wfds);
773 	FD_ZERO(&efds);
774 
775 	if (r_array != NULL) {
776 		set_count = stream_array_to_fd_set(r_array, &rfds, &max_fd);
777 		if (set_count > max_set_count)
778 			max_set_count = set_count;
779 		sets += set_count;
780 	}
781 
782 	if (w_array != NULL) {
783 		set_count = stream_array_to_fd_set(w_array, &wfds, &max_fd);
784 		if (set_count > max_set_count)
785 			max_set_count = set_count;
786 		sets += set_count;
787 	}
788 
789 	if (e_array != NULL) {
790 		set_count = stream_array_to_fd_set(e_array, &efds, &max_fd);
791 		if (set_count > max_set_count)
792 			max_set_count = set_count;
793 		sets += set_count;
794 	}
795 
796 	if (!sets) {
797 		zend_value_error("No stream arrays were passed");
798 		RETURN_THROWS();
799 	}
800 
801 	if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) {
802 		RETURN_FALSE;
803 	}
804 
805 	/* If seconds is not set to null, build the timeval, else we wait indefinitely */
806 	if (!secnull) {
807 		if (sec < 0) {
808 			zend_argument_value_error(4, "must be greater than or equal to 0");
809 			RETURN_THROWS();
810 		} else if (usec < 0) {
811 			zend_argument_value_error(5, "must be greater than or equal to 0");
812 			RETURN_THROWS();
813 		}
814 
815 		/* Windows, Solaris and BSD do not like microsecond values which are >= 1 sec */
816 		tv.tv_sec = (long)(sec + (usec / 1000000));
817 		tv.tv_usec = (long)(usec % 1000000);
818 		tv_p = &tv;
819 	}
820 
821 	/* slight hack to support buffered data; if there is data sitting in the
822 	 * read buffer of any of the streams in the read array, let's pretend
823 	 * that we selected, but return only the readable sockets */
824 	if (r_array != NULL) {
825 		retval = stream_array_emulate_read_fd_set(r_array);
826 		if (retval > 0) {
827 			if (w_array != NULL) {
828 				zval_ptr_dtor(w_array);
829 				ZVAL_EMPTY_ARRAY(w_array);
830 			}
831 			if (e_array != NULL) {
832 				zval_ptr_dtor(e_array);
833 				ZVAL_EMPTY_ARRAY(e_array);
834 			}
835 			RETURN_LONG(retval);
836 		}
837 	}
838 
839 	retval = php_select(max_fd+1, &rfds, &wfds, &efds, tv_p);
840 
841 	if (retval == -1) {
842 		php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s (max_fd=%d)",
843 				errno, strerror(errno), max_fd);
844 		RETURN_FALSE;
845 	}
846 
847 	if (r_array != NULL) stream_array_from_fd_set(r_array, &rfds);
848 	if (w_array != NULL) stream_array_from_fd_set(w_array, &wfds);
849 	if (e_array != NULL) stream_array_from_fd_set(e_array, &efds);
850 
851 	RETURN_LONG(retval);
852 }
853 /* }}} */
854 
855 /* {{{ stream_context related functions */
user_space_stream_notifier(php_stream_context * context,int notifycode,int severity,char * xmsg,int xcode,size_t bytes_sofar,size_t bytes_max,void * ptr)856 static void user_space_stream_notifier(php_stream_context *context, int notifycode, int severity,
857 		char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr)
858 {
859 	zval *callback = &context->notifier->ptr;
860 	zval retval;
861 	zval zvs[6];
862 	int i;
863 
864 	ZVAL_LONG(&zvs[0], notifycode);
865 	ZVAL_LONG(&zvs[1], severity);
866 	if (xmsg) {
867 		ZVAL_STRING(&zvs[2], xmsg);
868 	} else {
869 		ZVAL_NULL(&zvs[2]);
870 	}
871 	ZVAL_LONG(&zvs[3], xcode);
872 	ZVAL_LONG(&zvs[4], bytes_sofar);
873 	ZVAL_LONG(&zvs[5], bytes_max);
874 
875 	if (FAILURE == call_user_function(NULL, NULL, callback, &retval, 6, zvs)) {
876 		php_error_docref(NULL, E_WARNING, "Failed to call user notifier");
877 	}
878 	for (i = 0; i < 6; i++) {
879 		zval_ptr_dtor(&zvs[i]);
880 	}
881 	zval_ptr_dtor(&retval);
882 }
883 
user_space_stream_notifier_dtor(php_stream_notifier * notifier)884 static void user_space_stream_notifier_dtor(php_stream_notifier *notifier)
885 {
886 	if (notifier && Z_TYPE(notifier->ptr) != IS_UNDEF) {
887 		zval_ptr_dtor(&notifier->ptr);
888 		ZVAL_UNDEF(&notifier->ptr);
889 	}
890 }
891 
parse_context_options(php_stream_context * context,HashTable * options)892 static int parse_context_options(php_stream_context *context, HashTable *options)
893 {
894 	zval *wval, *oval;
895 	zend_string *wkey, *okey;
896 	int ret = SUCCESS;
897 
898 	ZEND_HASH_FOREACH_STR_KEY_VAL(options, wkey, wval) {
899 		ZVAL_DEREF(wval);
900 		if (wkey && Z_TYPE_P(wval) == IS_ARRAY) {
901 			ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(wval), okey, oval) {
902 				if (okey) {
903 					php_stream_context_set_option(context, ZSTR_VAL(wkey), ZSTR_VAL(okey), oval);
904 				}
905 			} ZEND_HASH_FOREACH_END();
906 		} else {
907 			zend_value_error("Options should have the form [\"wrappername\"][\"optionname\"] = $value");
908 			return FAILURE;
909 		}
910 	} ZEND_HASH_FOREACH_END();
911 
912 	return ret;
913 }
914 
parse_context_params(php_stream_context * context,HashTable * params)915 static int parse_context_params(php_stream_context *context, HashTable *params)
916 {
917 	int ret = SUCCESS;
918 	zval *tmp;
919 
920 	if (NULL != (tmp = zend_hash_str_find(params, "notification", sizeof("notification")-1))) {
921 
922 		if (context->notifier) {
923 			php_stream_notification_free(context->notifier);
924 			context->notifier = NULL;
925 		}
926 
927 		context->notifier = php_stream_notification_alloc();
928 		context->notifier->func = user_space_stream_notifier;
929 		ZVAL_COPY(&context->notifier->ptr, tmp);
930 		context->notifier->dtor = user_space_stream_notifier_dtor;
931 	}
932 	if (NULL != (tmp = zend_hash_str_find(params, "options", sizeof("options")-1))) {
933 		if (Z_TYPE_P(tmp) == IS_ARRAY) {
934 			return parse_context_options(context, Z_ARRVAL_P(tmp));
935 		} else {
936 			zend_type_error("Invalid stream/context parameter");
937 			return FAILURE;
938 		}
939 	}
940 
941 	return ret;
942 }
943 
944 /* given a zval which is either a stream or a context, return the underlying
945  * stream_context.  If it is a stream that does not have a context assigned, it
946  * will create and assign a context and return that.  */
decode_context_param(zval * contextresource)947 static php_stream_context *decode_context_param(zval *contextresource)
948 {
949 	php_stream_context *context = NULL;
950 
951 	context = zend_fetch_resource_ex(contextresource, NULL, php_le_stream_context());
952 	if (context == NULL) {
953 		php_stream *stream;
954 
955 		stream = zend_fetch_resource2_ex(contextresource, NULL, php_file_le_stream(), php_file_le_pstream());
956 
957 		if (stream) {
958 			context = PHP_STREAM_CONTEXT(stream);
959 			if (context == NULL) {
960 				/* Only way this happens is if file is opened with NO_DEFAULT_CONTEXT
961 				   param, but then something is called which requires a context.
962 				   Don't give them the default one though since they already said they
963 	 			   didn't want it. */
964 				context = php_stream_context_alloc();
965 				stream->ctx = context->res;
966 			}
967 		}
968 	}
969 
970 	return context;
971 }
972 /* }}} */
973 
974 /* {{{ Retrieve options for a stream/wrapper/context */
PHP_FUNCTION(stream_context_get_options)975 PHP_FUNCTION(stream_context_get_options)
976 {
977 	zval *zcontext;
978 	php_stream_context *context;
979 
980 	ZEND_PARSE_PARAMETERS_START(1, 1)
981 		Z_PARAM_RESOURCE(zcontext)
982 	ZEND_PARSE_PARAMETERS_END();
983 
984 	context = decode_context_param(zcontext);
985 	if (!context) {
986 		zend_argument_type_error(1, "must be a valid stream/context");
987 		RETURN_THROWS();
988 	}
989 
990 	ZVAL_COPY(return_value, &context->options);
991 }
992 /* }}} */
993 
994 /* {{{ Set an option for a wrapper */
PHP_FUNCTION(stream_context_set_option)995 PHP_FUNCTION(stream_context_set_option)
996 {
997 	zval *zcontext = NULL;
998 	php_stream_context *context;
999 	zend_string *wrappername;
1000 	HashTable *options;
1001 	char *optionname = NULL;
1002 	size_t optionname_len;
1003 	zval *zvalue = NULL;
1004 
1005 	ZEND_PARSE_PARAMETERS_START(2, 4)
1006 		Z_PARAM_RESOURCE(zcontext)
1007 		Z_PARAM_ARRAY_HT_OR_STR(options, wrappername)
1008 		Z_PARAM_OPTIONAL
1009 		Z_PARAM_STRING_OR_NULL(optionname, optionname_len)
1010 		Z_PARAM_ZVAL(zvalue)
1011 	ZEND_PARSE_PARAMETERS_END();
1012 
1013 	/* figure out where the context is coming from exactly */
1014 	if (!(context = decode_context_param(zcontext))) {
1015 		zend_argument_type_error(1, "must be a valid stream/context");
1016 		RETURN_THROWS();
1017 	}
1018 
1019 	if (options) {
1020 		if (optionname) {
1021 			zend_argument_value_error(3, "must be null when argument #2 ($wrapper_or_options) is an array");
1022 			RETURN_THROWS();
1023 		}
1024 
1025 		if (zvalue) {
1026 			zend_argument_value_error(4, "cannot be provided when argument #2 ($wrapper_or_options) is an array");
1027 			RETURN_THROWS();
1028 		}
1029 
1030 		RETURN_BOOL(parse_context_options(context, options) == SUCCESS);
1031 	} else {
1032 		if (!optionname) {
1033 			zend_argument_value_error(3, "cannot be null when argument #2 ($wrapper_or_options) is a string");
1034 			RETURN_THROWS();
1035 		}
1036 		if (!zvalue) {
1037 			zend_argument_value_error(4, "must be provided when argument #2 ($wrapper_or_options) is a string");
1038 			RETURN_THROWS();
1039 		}
1040 
1041 		RETURN_BOOL(php_stream_context_set_option(context, ZSTR_VAL(wrappername), optionname, zvalue) == SUCCESS);
1042 	}
1043 }
1044 /* }}} */
1045 
1046 /* {{{ Set parameters for a file context */
PHP_FUNCTION(stream_context_set_params)1047 PHP_FUNCTION(stream_context_set_params)
1048 {
1049 	HashTable *params;
1050 	zval *zcontext;
1051 	php_stream_context *context;
1052 
1053 	ZEND_PARSE_PARAMETERS_START(2, 2)
1054 		Z_PARAM_RESOURCE(zcontext)
1055 		Z_PARAM_ARRAY_HT(params)
1056 	ZEND_PARSE_PARAMETERS_END();
1057 
1058 	context = decode_context_param(zcontext);
1059 	if (!context) {
1060 		zend_argument_type_error(1, "must be a valid stream/context");
1061 		RETURN_THROWS();
1062 	}
1063 
1064 	RETVAL_BOOL(parse_context_params(context, params) == SUCCESS);
1065 }
1066 /* }}} */
1067 
1068 /* {{{ Get parameters of a file context */
PHP_FUNCTION(stream_context_get_params)1069 PHP_FUNCTION(stream_context_get_params)
1070 {
1071 	zval *zcontext;
1072 	php_stream_context *context;
1073 
1074 	ZEND_PARSE_PARAMETERS_START(1, 1)
1075 		Z_PARAM_RESOURCE(zcontext)
1076 	ZEND_PARSE_PARAMETERS_END();
1077 
1078 	context = decode_context_param(zcontext);
1079 	if (!context) {
1080 		zend_argument_type_error(1, "must be a valid stream/context");
1081 		RETURN_THROWS();
1082 	}
1083 
1084 	array_init(return_value);
1085 	if (context->notifier && Z_TYPE(context->notifier->ptr) != IS_UNDEF && context->notifier->func == user_space_stream_notifier) {
1086 		Z_TRY_ADDREF(context->notifier->ptr);
1087 		add_assoc_zval_ex(return_value, "notification", sizeof("notification")-1, &context->notifier->ptr);
1088 	}
1089 	Z_TRY_ADDREF(context->options);
1090 	add_assoc_zval_ex(return_value, "options", sizeof("options")-1, &context->options);
1091 }
1092 /* }}} */
1093 
1094 /* {{{ Get a handle on the default file/stream context and optionally set parameters */
PHP_FUNCTION(stream_context_get_default)1095 PHP_FUNCTION(stream_context_get_default)
1096 {
1097 	HashTable *params = NULL;
1098 	php_stream_context *context;
1099 
1100 	ZEND_PARSE_PARAMETERS_START(0, 1)
1101 		Z_PARAM_OPTIONAL
1102 		Z_PARAM_ARRAY_HT_OR_NULL(params)
1103 	ZEND_PARSE_PARAMETERS_END();
1104 
1105 	if (FG(default_context) == NULL) {
1106 		FG(default_context) = php_stream_context_alloc();
1107 	}
1108 	context = FG(default_context);
1109 
1110 	if (params) {
1111 		if (parse_context_options(context, params) == FAILURE) {
1112 			RETURN_THROWS();
1113 		}
1114 	}
1115 
1116 	php_stream_context_to_zval(context, return_value);
1117 }
1118 /* }}} */
1119 
1120 /* {{{ Set default file/stream context, returns the context as a resource */
PHP_FUNCTION(stream_context_set_default)1121 PHP_FUNCTION(stream_context_set_default)
1122 {
1123 	HashTable *options;
1124 	php_stream_context *context;
1125 
1126 	ZEND_PARSE_PARAMETERS_START(1, 1)
1127 		Z_PARAM_ARRAY_HT(options)
1128 	ZEND_PARSE_PARAMETERS_END();
1129 
1130 	if (FG(default_context) == NULL) {
1131 		FG(default_context) = php_stream_context_alloc();
1132 	}
1133 	context = FG(default_context);
1134 
1135 	if (parse_context_options(context, options) == FAILURE) {
1136 		RETURN_THROWS();
1137 	}
1138 
1139 	php_stream_context_to_zval(context, return_value);
1140 }
1141 /* }}} */
1142 
1143 /* {{{ Create a file context and optionally set parameters */
PHP_FUNCTION(stream_context_create)1144 PHP_FUNCTION(stream_context_create)
1145 {
1146 	HashTable *options = NULL;
1147 	HashTable *params = NULL;
1148 	php_stream_context *context;
1149 
1150 	ZEND_PARSE_PARAMETERS_START(0, 2)
1151 		Z_PARAM_OPTIONAL
1152 		Z_PARAM_ARRAY_HT_OR_NULL(options)
1153 		Z_PARAM_ARRAY_HT_OR_NULL(params)
1154 	ZEND_PARSE_PARAMETERS_END();
1155 
1156 	context = php_stream_context_alloc();
1157 
1158 	if (options) {
1159 		parse_context_options(context, options);
1160 	}
1161 
1162 	if (params) {
1163 		parse_context_params(context, params);
1164 	}
1165 
1166 	RETURN_RES(context->res);
1167 }
1168 /* }}} */
1169 
1170 /* {{{ streams filter functions */
apply_filter_to_stream(int append,INTERNAL_FUNCTION_PARAMETERS)1171 static void apply_filter_to_stream(int append, INTERNAL_FUNCTION_PARAMETERS)
1172 {
1173 	zval *zstream;
1174 	php_stream *stream;
1175 	char *filtername;
1176 	size_t filternamelen;
1177 	zend_long read_write = 0;
1178 	zval *filterparams = NULL;
1179 	php_stream_filter *filter = NULL;
1180 	int ret;
1181 
1182 	ZEND_PARSE_PARAMETERS_START(2, 4)
1183 		Z_PARAM_RESOURCE(zstream)
1184 		Z_PARAM_STRING(filtername, filternamelen)
1185 		Z_PARAM_OPTIONAL
1186 		Z_PARAM_LONG(read_write)
1187 		Z_PARAM_ZVAL(filterparams)
1188 	ZEND_PARSE_PARAMETERS_END();
1189 
1190 	php_stream_from_zval(stream, zstream);
1191 
1192 	if ((read_write & PHP_STREAM_FILTER_ALL) == 0) {
1193 		/* Chain not specified.
1194 		 * Examine stream->mode to determine which filters are needed
1195 		 * There's no harm in attaching a filter to an unused chain,
1196 		 * but why waste the memory and clock cycles?
1197 		 */
1198 		if (strchr(stream->mode, 'r') || strchr(stream->mode, '+')) {
1199 			read_write |= PHP_STREAM_FILTER_READ;
1200 		}
1201 		if (strchr(stream->mode, 'w') || strchr(stream->mode, '+') || strchr(stream->mode, 'a')) {
1202 			read_write |= PHP_STREAM_FILTER_WRITE;
1203 		}
1204 	}
1205 
1206 	if (read_write & PHP_STREAM_FILTER_READ) {
1207 		filter = php_stream_filter_create(filtername, filterparams, php_stream_is_persistent(stream));
1208 		if (filter == NULL) {
1209 			RETURN_FALSE;
1210 		}
1211 
1212 		if (append) {
1213 			ret = php_stream_filter_append_ex(&stream->readfilters, filter);
1214 		} else {
1215 			ret = php_stream_filter_prepend_ex(&stream->readfilters, filter);
1216 		}
1217 		if (ret != SUCCESS) {
1218 			php_stream_filter_remove(filter, 1);
1219 			RETURN_FALSE;
1220 		}
1221 	}
1222 
1223 	if (read_write & PHP_STREAM_FILTER_WRITE) {
1224 		filter = php_stream_filter_create(filtername, filterparams, php_stream_is_persistent(stream));
1225 		if (filter == NULL) {
1226 			RETURN_FALSE;
1227 		}
1228 
1229 		if (append) {
1230 			ret = php_stream_filter_append_ex(&stream->writefilters, filter);
1231 		} else {
1232 			ret = php_stream_filter_prepend_ex(&stream->writefilters, filter);
1233 		}
1234 		if (ret != SUCCESS) {
1235 			php_stream_filter_remove(filter, 1);
1236 			RETURN_FALSE;
1237 		}
1238 	}
1239 
1240 	if (filter) {
1241 		filter->res = zend_register_resource(filter, php_file_le_stream_filter());
1242 		GC_ADDREF(filter->res);
1243 		RETURN_RES(filter->res);
1244 	} else {
1245 		RETURN_FALSE;
1246 	}
1247 }
1248 /* }}} */
1249 
1250 /* {{{ Prepend a filter to a stream */
PHP_FUNCTION(stream_filter_prepend)1251 PHP_FUNCTION(stream_filter_prepend)
1252 {
1253 	apply_filter_to_stream(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
1254 }
1255 /* }}} */
1256 
1257 /* {{{ Append a filter to a stream */
PHP_FUNCTION(stream_filter_append)1258 PHP_FUNCTION(stream_filter_append)
1259 {
1260 	apply_filter_to_stream(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
1261 }
1262 /* }}} */
1263 
1264 /* {{{ Flushes any data in the filter's internal buffer, removes it from the chain, and frees the resource */
PHP_FUNCTION(stream_filter_remove)1265 PHP_FUNCTION(stream_filter_remove)
1266 {
1267 	zval *zfilter;
1268 	php_stream_filter *filter;
1269 
1270 	ZEND_PARSE_PARAMETERS_START(1, 1)
1271 		Z_PARAM_RESOURCE(zfilter)
1272 	ZEND_PARSE_PARAMETERS_END();
1273 
1274 	filter = zend_fetch_resource(Z_RES_P(zfilter), "stream filter", php_file_le_stream_filter());
1275 	if (!filter) {
1276 		RETURN_THROWS();
1277 	}
1278 
1279 	if (php_stream_filter_flush(filter, 1) == FAILURE) {
1280 		php_error_docref(NULL, E_WARNING, "Unable to flush filter, not removing");
1281 		RETURN_FALSE;
1282 	}
1283 
1284 	zend_list_close(Z_RES_P(zfilter));
1285 	php_stream_filter_remove(filter, 1);
1286 	RETURN_TRUE;
1287 }
1288 /* }}} */
1289 
1290 /* {{{ Read up to maxlen bytes from a stream or until the ending string is found */
PHP_FUNCTION(stream_get_line)1291 PHP_FUNCTION(stream_get_line)
1292 {
1293 	char *str = NULL;
1294 	size_t str_len = 0;
1295 	zend_long max_length;
1296 	zval *zstream;
1297 	zend_string *buf;
1298 	php_stream *stream;
1299 
1300 	ZEND_PARSE_PARAMETERS_START(2, 3)
1301 		Z_PARAM_RESOURCE(zstream)
1302 		Z_PARAM_LONG(max_length)
1303 		Z_PARAM_OPTIONAL
1304 		Z_PARAM_STRING(str, str_len)
1305 	ZEND_PARSE_PARAMETERS_END();
1306 
1307 	if (max_length < 0) {
1308 		zend_argument_value_error(2, "must be greater than or equal to 0");
1309 		RETURN_THROWS();
1310 	}
1311 	if (!max_length) {
1312 		max_length = PHP_SOCK_CHUNK_SIZE;
1313 	}
1314 
1315 	php_stream_from_zval(stream, zstream);
1316 
1317 	if ((buf = php_stream_get_record(stream, max_length, str, str_len))) {
1318 		RETURN_STR(buf);
1319 	} else {
1320 		RETURN_FALSE;
1321 	}
1322 }
1323 
1324 /* }}} */
1325 
1326 /* {{{ Set blocking/non-blocking mode on a socket or stream */
PHP_FUNCTION(stream_set_blocking)1327 PHP_FUNCTION(stream_set_blocking)
1328 {
1329 	zval *zstream;
1330 	zend_bool block;
1331 	php_stream *stream;
1332 
1333 	ZEND_PARSE_PARAMETERS_START(2, 2)
1334 		Z_PARAM_RESOURCE(zstream)
1335 		Z_PARAM_BOOL(block)
1336 	ZEND_PARSE_PARAMETERS_END();
1337 
1338 	php_stream_from_zval(stream, zstream);
1339 
1340 	if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL) == -1) {
1341 		RETURN_FALSE;
1342 	}
1343 
1344 	RETURN_TRUE;
1345 }
1346 
1347 /* }}} */
1348 
1349 /* {{{ Set timeout on stream read to seconds + microseonds */
1350 #if HAVE_SYS_TIME_H || defined(PHP_WIN32)
PHP_FUNCTION(stream_set_timeout)1351 PHP_FUNCTION(stream_set_timeout)
1352 {
1353 	zval *socket;
1354 	zend_long seconds, microseconds = 0;
1355 	struct timeval t;
1356 	php_stream *stream;
1357 	int argc = ZEND_NUM_ARGS();
1358 
1359 	ZEND_PARSE_PARAMETERS_START(2, 3)
1360 		Z_PARAM_RESOURCE(socket)
1361 		Z_PARAM_LONG(seconds)
1362 		Z_PARAM_OPTIONAL
1363 		Z_PARAM_LONG(microseconds)
1364 	ZEND_PARSE_PARAMETERS_END();
1365 
1366 	php_stream_from_zval(stream, socket);
1367 
1368 #ifdef PHP_WIN32
1369 	t.tv_sec = (long)seconds;
1370 
1371 	if (argc == 3) {
1372 		t.tv_usec = (long)(microseconds % 1000000);
1373 		t.tv_sec +=(long)(microseconds / 1000000);
1374 	} else {
1375 		t.tv_usec = 0;
1376 	}
1377 #else
1378 	t.tv_sec = seconds;
1379 
1380 	if (argc == 3) {
1381 		t.tv_usec = microseconds % 1000000;
1382 		t.tv_sec += microseconds / 1000000;
1383 	} else {
1384 		t.tv_usec = 0;
1385 	}
1386 #endif
1387 
1388 	if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)) {
1389 		RETURN_TRUE;
1390 	}
1391 
1392 	RETURN_FALSE;
1393 }
1394 #endif /* HAVE_SYS_TIME_H || defined(PHP_WIN32) */
1395 /* }}} */
1396 
1397 /* {{{ Set file write buffer */
PHP_FUNCTION(stream_set_write_buffer)1398 PHP_FUNCTION(stream_set_write_buffer)
1399 {
1400 	zval *arg1;
1401 	int ret;
1402 	zend_long arg2;
1403 	size_t buff;
1404 	php_stream *stream;
1405 
1406 	ZEND_PARSE_PARAMETERS_START(2, 2)
1407 		Z_PARAM_RESOURCE(arg1)
1408 		Z_PARAM_LONG(arg2)
1409 	ZEND_PARSE_PARAMETERS_END();
1410 
1411 	php_stream_from_zval(stream, arg1);
1412 
1413 	buff = arg2;
1414 
1415 	/* if buff is 0 then set to non-buffered */
1416 	if (buff == 0) {
1417 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
1418 	} else {
1419 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buff);
1420 	}
1421 
1422 	RETURN_LONG(ret == 0 ? 0 : EOF);
1423 }
1424 /* }}} */
1425 
1426 /* {{{ Set the stream chunk size */
PHP_FUNCTION(stream_set_chunk_size)1427 PHP_FUNCTION(stream_set_chunk_size)
1428 {
1429 	int			ret;
1430 	zend_long		csize;
1431 	zval		*zstream;
1432 	php_stream	*stream;
1433 
1434 	ZEND_PARSE_PARAMETERS_START(2, 2)
1435 		Z_PARAM_RESOURCE(zstream)
1436 		Z_PARAM_LONG(csize)
1437 	ZEND_PARSE_PARAMETERS_END();
1438 
1439 	if (csize <= 0) {
1440 		zend_argument_value_error(2, "must be greater than 0");
1441 		RETURN_THROWS();
1442 	}
1443 	/* stream.chunk_size is actually a size_t, but php_stream_set_option
1444 	 * can only use an int to accept the new value and return the old one.
1445 	 * In any case, values larger than INT_MAX for a chunk size make no sense.
1446 	 */
1447 	if (csize > INT_MAX) {
1448 		zend_argument_value_error(2, "is too large");
1449 		RETURN_THROWS();
1450 	}
1451 
1452 	php_stream_from_zval(stream, zstream);
1453 
1454 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_SET_CHUNK_SIZE, (int)csize, NULL);
1455 
1456 	RETURN_LONG(ret > 0 ? (zend_long)ret : (zend_long)EOF);
1457 }
1458 /* }}} */
1459 
1460 /* {{{ Set file read buffer */
PHP_FUNCTION(stream_set_read_buffer)1461 PHP_FUNCTION(stream_set_read_buffer)
1462 {
1463 	zval *arg1;
1464 	int ret;
1465 	zend_long arg2;
1466 	size_t buff;
1467 	php_stream *stream;
1468 
1469 	ZEND_PARSE_PARAMETERS_START(2, 2)
1470 		Z_PARAM_RESOURCE(arg1)
1471 		Z_PARAM_LONG(arg2)
1472 	ZEND_PARSE_PARAMETERS_END();
1473 
1474 	php_stream_from_zval(stream, arg1);
1475 
1476 	buff = arg2;
1477 
1478 	/* if buff is 0 then set to non-buffered */
1479 	if (buff == 0) {
1480 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
1481 	} else {
1482 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_FULL, &buff);
1483 	}
1484 
1485 	RETURN_LONG(ret == 0 ? 0 : EOF);
1486 }
1487 /* }}} */
1488 
1489 /* {{{ Enable or disable a specific kind of crypto on the stream */
PHP_FUNCTION(stream_socket_enable_crypto)1490 PHP_FUNCTION(stream_socket_enable_crypto)
1491 {
1492 	zend_long cryptokind = 0;
1493 	zval *zstream, *zsessstream = NULL;
1494 	php_stream *stream, *sessstream = NULL;
1495 	zend_bool enable, cryptokindnull = 1;
1496 	int ret;
1497 
1498 	ZEND_PARSE_PARAMETERS_START(2, 4)
1499 		Z_PARAM_RESOURCE(zstream)
1500 		Z_PARAM_BOOL(enable)
1501 		Z_PARAM_OPTIONAL
1502 		Z_PARAM_LONG_OR_NULL(cryptokind, cryptokindnull)
1503 		Z_PARAM_RESOURCE_OR_NULL(zsessstream)
1504 	ZEND_PARSE_PARAMETERS_END();
1505 
1506 	php_stream_from_zval(stream, zstream);
1507 
1508 	if (enable) {
1509 		if (cryptokindnull) {
1510 			zval *val;
1511 
1512 			if (!GET_CTX_OPT(stream, "ssl", "crypto_method", val)) {
1513 				zend_argument_value_error(3, "must be specified when enabling encryption");
1514 				RETURN_THROWS();
1515 			}
1516 
1517 			cryptokind = Z_LVAL_P(val);
1518 		}
1519 
1520 		if (zsessstream) {
1521 			php_stream_from_zval(sessstream, zsessstream);
1522 		}
1523 
1524 		if (php_stream_xport_crypto_setup(stream, cryptokind, sessstream) < 0) {
1525 			RETURN_FALSE;
1526 		}
1527 	}
1528 
1529 	ret = php_stream_xport_crypto_enable(stream, enable);
1530 	switch (ret) {
1531 		case -1:
1532 			RETURN_FALSE;
1533 
1534 		case 0:
1535 			RETURN_LONG(0);
1536 
1537 		default:
1538 			RETURN_TRUE;
1539 	}
1540 }
1541 /* }}} */
1542 
1543 /* {{{ Determine what file will be opened by calls to fopen() with a relative path */
PHP_FUNCTION(stream_resolve_include_path)1544 PHP_FUNCTION(stream_resolve_include_path)
1545 {
1546 	char *filename;
1547 	size_t filename_len;
1548 	zend_string *resolved_path;
1549 
1550 	ZEND_PARSE_PARAMETERS_START(1, 1)
1551 		Z_PARAM_PATH(filename, filename_len)
1552 	ZEND_PARSE_PARAMETERS_END();
1553 
1554 	resolved_path = zend_resolve_path(filename, filename_len);
1555 
1556 	if (resolved_path) {
1557 		RETURN_STR(resolved_path);
1558 	}
1559 	RETURN_FALSE;
1560 }
1561 /* }}} */
1562 
1563 /* {{{ */
PHP_FUNCTION(stream_is_local)1564 PHP_FUNCTION(stream_is_local)
1565 {
1566 	zval *zstream;
1567 	php_stream *stream = NULL;
1568 	php_stream_wrapper *wrapper = NULL;
1569 
1570 	ZEND_PARSE_PARAMETERS_START(1, 1)
1571 		Z_PARAM_ZVAL(zstream)
1572 	ZEND_PARSE_PARAMETERS_END();
1573 
1574 	if (Z_TYPE_P(zstream) == IS_RESOURCE) {
1575 		php_stream_from_zval(stream, zstream);
1576 		if (stream == NULL) {
1577 			RETURN_FALSE;
1578 		}
1579 		wrapper = stream->wrapper;
1580 	} else {
1581 		if (!try_convert_to_string(zstream)) {
1582 			RETURN_THROWS();
1583 		}
1584 
1585 		wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, 0);
1586 	}
1587 
1588 	if (!wrapper) {
1589 		RETURN_FALSE;
1590 	}
1591 
1592 	RETURN_BOOL(wrapper->is_url==0);
1593 }
1594 /* }}} */
1595 
1596 /* {{{ Tells whether the stream supports locking through flock(). */
PHP_FUNCTION(stream_supports_lock)1597 PHP_FUNCTION(stream_supports_lock)
1598 {
1599 	php_stream *stream;
1600 	zval *zsrc;
1601 
1602 	ZEND_PARSE_PARAMETERS_START(1, 1)
1603 		Z_PARAM_RESOURCE(zsrc)
1604 	ZEND_PARSE_PARAMETERS_END();
1605 
1606 	php_stream_from_zval(stream, zsrc);
1607 
1608 	if (!php_stream_supports_lock(stream)) {
1609 		RETURN_FALSE;
1610 	}
1611 
1612 	RETURN_TRUE;
1613 }
1614 
1615 /* {{{ Check if a stream is a TTY. */
PHP_FUNCTION(stream_isatty)1616 PHP_FUNCTION(stream_isatty)
1617 {
1618 	zval *zsrc;
1619 	php_stream *stream;
1620 	php_socket_t fileno;
1621 
1622 	ZEND_PARSE_PARAMETERS_START(1, 1)
1623 		Z_PARAM_RESOURCE(zsrc)
1624 	ZEND_PARSE_PARAMETERS_END();
1625 
1626 	php_stream_from_zval(stream, zsrc);
1627 
1628 	if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
1629 		php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
1630 	} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
1631 		php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
1632 	} else {
1633 		RETURN_FALSE;
1634 	}
1635 
1636 #ifdef PHP_WIN32
1637 	/* Check if the Windows standard handle is redirected to file */
1638 	RETVAL_BOOL(php_win32_console_fileno_is_console(fileno));
1639 #elif HAVE_UNISTD_H
1640 	/* Check if the file descriptor identifier is a terminal */
1641 	RETVAL_BOOL(isatty(fileno));
1642 #else
1643 	{
1644 		zend_stat_t stat = {0};
1645 		RETVAL_BOOL(zend_fstat(fileno, &stat) == 0 && (stat.st_mode & /*S_IFMT*/0170000) == /*S_IFCHR*/0020000);
1646 	}
1647 #endif
1648 }
1649 
1650 #ifdef PHP_WIN32
1651 /* {{{ Get or set VT100 support for the specified stream associated to an
1652    output buffer of a Windows console.
1653 */
PHP_FUNCTION(sapi_windows_vt100_support)1654 PHP_FUNCTION(sapi_windows_vt100_support)
1655 {
1656 	zval *zsrc;
1657 	php_stream *stream;
1658 	zend_bool enable, enable_is_null = 1;
1659 	zend_long fileno;
1660 
1661 	ZEND_PARSE_PARAMETERS_START(1, 2)
1662 		Z_PARAM_RESOURCE(zsrc)
1663 		Z_PARAM_OPTIONAL
1664 		Z_PARAM_BOOL_OR_NULL(enable, enable_is_null)
1665 	ZEND_PARSE_PARAMETERS_END();
1666 
1667 	php_stream_from_zval(stream, zsrc);
1668 
1669 	if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
1670 		php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
1671 	}
1672 	else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
1673 		php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
1674 	}
1675 	else {
1676 		if (!enable_is_null) {
1677 			php_error_docref(
1678 				NULL,
1679 				E_WARNING,
1680 				"not able to analyze the specified stream"
1681 			);
1682 		}
1683 		RETURN_FALSE;
1684 	}
1685 
1686 	/* Check if the file descriptor is a console */
1687 	if (!php_win32_console_fileno_is_console(fileno)) {
1688 		RETURN_FALSE;
1689 	}
1690 
1691 	if (enable_is_null) {
1692 		/* Check if the Windows standard handle has VT100 control codes enabled */
1693 		if (php_win32_console_fileno_has_vt100(fileno)) {
1694 			RETURN_TRUE;
1695 		}
1696 		else {
1697 			RETURN_FALSE;
1698 		}
1699 	}
1700 	else {
1701 		/* Enable/disable VT100 control codes support for the specified Windows standard handle */
1702 		if (php_win32_console_fileno_set_vt100(fileno, enable ? TRUE : FALSE)) {
1703 			RETURN_TRUE;
1704 		}
1705 		else {
1706 			RETURN_FALSE;
1707 		}
1708 	}
1709 }
1710 #endif
1711 
1712 #ifdef HAVE_SHUTDOWN
1713 /* {{{ causes all or part of a full-duplex connection on the socket associated
1714 	with stream to be shut down.  If how is SHUT_RD,  further receptions will
1715 	be disallowed. If how is SHUT_WR, further transmissions will be disallowed.
1716 	If how is SHUT_RDWR,  further  receptions and transmissions will be
1717 	disallowed. */
PHP_FUNCTION(stream_socket_shutdown)1718 PHP_FUNCTION(stream_socket_shutdown)
1719 {
1720 	zend_long how;
1721 	zval *zstream;
1722 	php_stream *stream;
1723 
1724 	ZEND_PARSE_PARAMETERS_START(2, 2)
1725 		Z_PARAM_RESOURCE(zstream)
1726 		Z_PARAM_LONG(how)
1727 	ZEND_PARSE_PARAMETERS_END();
1728 
1729 	if (how != STREAM_SHUT_RD &&
1730 	    how != STREAM_SHUT_WR &&
1731 	    how != STREAM_SHUT_RDWR) {
1732 	    zend_argument_value_error(2, "must be one of STREAM_SHUT_RD, STREAM_SHUT_WR, or STREAM_SHUT_RDWR");
1733 		RETURN_THROWS();
1734 	}
1735 
1736 	php_stream_from_zval(stream, zstream);
1737 
1738 	RETURN_BOOL(php_stream_xport_shutdown(stream, (stream_shutdown_t)how) == 0);
1739 }
1740 /* }}} */
1741 #endif
1742