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