xref: /PHP-8.4/main/streams/userspace.c (revision fd570fbf)
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/file.h"
21 #include "ext/standard/flock_compat.h"
22 #ifdef HAVE_SYS_FILE_H
23 #include <sys/file.h>
24 #endif
25 #include <stddef.h>
26 
27 #ifdef HAVE_UTIME
28 # ifdef PHP_WIN32
29 #  include <sys/utime.h>
30 # else
31 #  include <utime.h>
32 # endif
33 #endif
34 #include "userspace_arginfo.h"
35 
36 static int le_protocols;
37 
38 struct php_user_stream_wrapper {
39 	php_stream_wrapper wrapper;
40 	char * protoname;
41 	zend_class_entry *ce;
42 	zend_resource *resource;
43 };
44 
45 static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
46 static int user_wrapper_close(php_stream_wrapper *wrapper, php_stream *stream);
47 static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context);
48 static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
49 static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context);
50 static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context);
51 static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
52 static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context);
53 static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
54 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
55 
56 static const php_stream_wrapper_ops user_stream_wops = {
57 	user_wrapper_opener,
58 	user_wrapper_close,
59 	NULL, /* stat - the streams themselves know how */
60 	user_wrapper_stat_url,
61 	user_wrapper_opendir,
62 	"user-space",
63 	user_wrapper_unlink,
64 	user_wrapper_rename,
65 	user_wrapper_mkdir,
66 	user_wrapper_rmdir,
67 	user_wrapper_metadata
68 };
69 
70 
stream_wrapper_dtor(zend_resource * rsrc)71 static void stream_wrapper_dtor(zend_resource *rsrc)
72 {
73 	struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;
74 
75 	efree(uwrap->protoname);
76 	efree(uwrap);
77 }
78 
79 
PHP_MINIT_FUNCTION(user_streams)80 PHP_MINIT_FUNCTION(user_streams)
81 {
82 	le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
83 	if (le_protocols == FAILURE)
84 		return FAILURE;
85 
86 	register_userspace_symbols(module_number);
87 
88 	return SUCCESS;
89 }
90 
91 struct _php_userstream_data {
92 	struct php_user_stream_wrapper * wrapper;
93 	zval object;
94 };
95 typedef struct _php_userstream_data php_userstream_data_t;
96 
97 /* names of methods */
98 #define USERSTREAM_OPEN		"stream_open"
99 #define USERSTREAM_CLOSE	"stream_close"
100 #define USERSTREAM_READ		"stream_read"
101 #define USERSTREAM_WRITE	"stream_write"
102 #define USERSTREAM_FLUSH	"stream_flush"
103 #define USERSTREAM_SEEK		"stream_seek"
104 #define USERSTREAM_TELL		"stream_tell"
105 #define USERSTREAM_EOF		"stream_eof"
106 #define USERSTREAM_STAT		"stream_stat"
107 #define USERSTREAM_STATURL	"url_stat"
108 #define USERSTREAM_UNLINK	"unlink"
109 #define USERSTREAM_RENAME	"rename"
110 #define USERSTREAM_MKDIR	"mkdir"
111 #define USERSTREAM_RMDIR	"rmdir"
112 #define USERSTREAM_DIR_OPEN		"dir_opendir"
113 #define USERSTREAM_DIR_READ		"dir_readdir"
114 #define USERSTREAM_DIR_REWIND	"dir_rewinddir"
115 #define USERSTREAM_DIR_CLOSE	"dir_closedir"
116 #define USERSTREAM_LOCK     "stream_lock"
117 #define USERSTREAM_CAST		"stream_cast"
118 #define USERSTREAM_SET_OPTION	"stream_set_option"
119 #define USERSTREAM_TRUNCATE	"stream_truncate"
120 #define USERSTREAM_METADATA	"stream_metadata"
121 
122 /* {{{ class should have methods like these:
123 
124 	function stream_open($path, $mode, $options, &$opened_path)
125 	{
126 	  	return true/false;
127 	}
128 
129 	function stream_read($count)
130 	{
131 	   	return false on error;
132 		else return string;
133 	}
134 
135 	function stream_write($data)
136 	{
137 	   	return false on error;
138 		else return count written;
139 	}
140 
141 	function stream_close()
142 	{
143 	}
144 
145 	function stream_flush()
146 	{
147 		return true/false;
148 	}
149 
150 	function stream_seek($offset, $whence)
151 	{
152 		return true/false;
153 	}
154 
155 	function stream_tell()
156 	{
157 		return (int)$position;
158 	}
159 
160 	function stream_eof()
161 	{
162 		return true/false;
163 	}
164 
165 	function stream_stat()
166 	{
167 		return array( just like that returned by fstat() );
168 	}
169 
170 	function stream_cast($castas)
171 	{
172 		if ($castas == STREAM_CAST_FOR_SELECT) {
173 			return $this->underlying_stream;
174 		}
175 		return false;
176 	}
177 
178 	function stream_set_option($option, $arg1, $arg2)
179 	{
180 		switch($option) {
181 		case STREAM_OPTION_BLOCKING:
182 			$blocking = $arg1;
183 			...
184 		case STREAM_OPTION_READ_TIMEOUT:
185 			$sec = $arg1;
186 			$usec = $arg2;
187 			...
188 		case STREAM_OPTION_WRITE_BUFFER:
189 			$mode = $arg1;
190 			$size = $arg2;
191 			...
192 		default:
193 			return false;
194 		}
195 	}
196 
197 	function url_stat(string $url, int $flags)
198 	{
199 		return array( just like that returned by stat() );
200 	}
201 
202 	function unlink(string $url)
203 	{
204 		return true / false;
205 	}
206 
207 	function rename(string $from, string $to)
208 	{
209 		return true / false;
210 	}
211 
212 	function mkdir($dir, $mode, $options)
213 	{
214 		return true / false;
215 	}
216 
217 	function rmdir($dir, $options)
218 	{
219 		return true / false;
220 	}
221 
222 	function dir_opendir(string $url, int $options)
223 	{
224 		return true / false;
225 	}
226 
227 	function dir_readdir()
228 	{
229 		return string next filename in dir ;
230 	}
231 
232 	function dir_closedir()
233 	{
234 		release dir related resources;
235 	}
236 
237 	function dir_rewinddir()
238 	{
239 		reset to start of dir list;
240 	}
241 
242 	function stream_lock($operation)
243 	{
244 		return true / false;
245 	}
246 
247  	function stream_truncate($new_size)
248 	{
249 		return true / false;
250 	}
251 
252 	}}} **/
253 
call_method_if_exists(zval * object,zval * method_name,zval * retval,uint32_t param_count,zval * params)254 static zend_result call_method_if_exists(
255 		zval *object, zval *method_name, zval *retval, uint32_t param_count, zval *params)
256 {
257 	return zend_call_method_if_exists(
258 		Z_OBJ_P(object), Z_STR_P(method_name), retval, param_count, params);
259 }
260 
user_stream_create_object(struct php_user_stream_wrapper * uwrap,php_stream_context * context,zval * object)261 static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object)
262 {
263 	if (uwrap->ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
264 		ZVAL_UNDEF(object);
265 		return;
266 	}
267 
268 	/* create an instance of our class */
269 	if (object_init_ex(object, uwrap->ce) == FAILURE) {
270 		ZVAL_UNDEF(object);
271 		return;
272 	}
273 
274 	if (context) {
275 		GC_ADDREF(context->res);
276 		add_property_resource(object, "context", context->res);
277 	} else {
278 		add_property_null(object, "context");
279 	}
280 
281 	if (EG(exception) != NULL) {
282 		zval_ptr_dtor(object);
283 		ZVAL_UNDEF(object);
284 		return;
285 	}
286 
287 	if (uwrap->ce->constructor) {
288 		zend_call_known_instance_method_with_0_params(
289 			uwrap->ce->constructor, Z_OBJ_P(object), NULL);
290 	}
291 }
292 
user_wrapper_opener(php_stream_wrapper * wrapper,const char * filename,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)293 static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode,
294 									   int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
295 {
296 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
297 	php_userstream_data_t *us;
298 	zval zretval, zfuncname;
299 	zval args[4];
300 	int call_result;
301 	php_stream *stream = NULL;
302 	bool old_in_user_include;
303 
304 	/* Try to catch bad usage without preventing flexibility */
305 	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
306 		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
307 		return NULL;
308 	}
309 	FG(user_stream_current_filename) = filename;
310 
311 	/* if the user stream was registered as local and we are in include context,
312 		we add allow_url_include restrictions to allow_url_fopen ones */
313 	/* we need only is_url == 0 here since if is_url == 1 and remote wrappers
314 		were restricted we wouldn't get here */
315 	old_in_user_include = PG(in_user_include);
316 	if(uwrap->wrapper.is_url == 0 &&
317 		(options & STREAM_OPEN_FOR_INCLUDE) &&
318 		!PG(allow_url_include)) {
319 		PG(in_user_include) = 1;
320 	}
321 
322 	us = emalloc(sizeof(*us));
323 	us->wrapper = uwrap;
324 	/* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
325 	GC_ADDREF(us->wrapper->resource);
326 
327 	user_stream_create_object(uwrap, context, &us->object);
328 	if (Z_TYPE(us->object) == IS_UNDEF) {
329 		FG(user_stream_current_filename) = NULL;
330 		PG(in_user_include) = old_in_user_include;
331 		efree(us);
332 		return NULL;
333 	}
334 
335 	/* call it's stream_open method - set up params first */
336 	ZVAL_STRING(&args[0], filename);
337 	ZVAL_STRING(&args[1], mode);
338 	ZVAL_LONG(&args[2], options);
339 	ZVAL_NEW_REF(&args[3], &EG(uninitialized_zval));
340 
341 	ZVAL_STRING(&zfuncname, USERSTREAM_OPEN);
342 
343 	zend_try {
344 		call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 4, args);
345 	} zend_catch {
346 		FG(user_stream_current_filename) = NULL;
347 		zend_bailout();
348 	} zend_end_try();
349 
350 	if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) {
351 		/* the stream is now open! */
352 		stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);
353 
354 		/* if the opened path is set, copy it out */
355 		if (Z_ISREF(args[3]) && Z_TYPE_P(Z_REFVAL(args[3])) == IS_STRING && opened_path) {
356 			*opened_path = zend_string_copy(Z_STR_P(Z_REFVAL(args[3])));
357 		}
358 
359 		/* set wrapper data to be a reference to our object */
360 		ZVAL_COPY(&stream->wrapperdata, &us->object);
361 	} else {
362 		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed",
363 			ZSTR_VAL(us->wrapper->ce->name));
364 	}
365 
366 	/* destroy everything else */
367 	if (stream == NULL) {
368 		zval_ptr_dtor(&us->object);
369 		ZVAL_UNDEF(&us->object);
370 		zend_list_delete(us->wrapper->resource);
371 		efree(us);
372 	}
373 	zval_ptr_dtor(&zretval);
374 	zval_ptr_dtor(&zfuncname);
375 	zval_ptr_dtor(&args[3]);
376 	zval_ptr_dtor(&args[2]);
377 	zval_ptr_dtor(&args[1]);
378 	zval_ptr_dtor(&args[0]);
379 
380 	FG(user_stream_current_filename) = NULL;
381 
382 	PG(in_user_include) = old_in_user_include;
383 	return stream;
384 }
385 
user_wrapper_close(php_stream_wrapper * wrapper,php_stream * stream)386 static int user_wrapper_close(php_stream_wrapper *wrapper, php_stream *stream)
387 {
388 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
389 	zend_list_delete(uwrap->resource);
390 	// FIXME: Unused?
391 	return 0;
392 }
393 
user_wrapper_opendir(php_stream_wrapper * wrapper,const char * filename,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)394 static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
395 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
396 {
397 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
398 	php_userstream_data_t *us;
399 	zval zretval, zfuncname;
400 	zval args[2];
401 	int call_result;
402 	php_stream *stream = NULL;
403 
404 	/* Try to catch bad usage without preventing flexibility */
405 	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
406 		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
407 		return NULL;
408 	}
409 	FG(user_stream_current_filename) = filename;
410 
411 	us = emalloc(sizeof(*us));
412 	us->wrapper = uwrap;
413 	/* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
414 	GC_ADDREF(us->wrapper->resource);
415 
416 	user_stream_create_object(uwrap, context, &us->object);
417 	if (Z_TYPE(us->object) == IS_UNDEF) {
418 		FG(user_stream_current_filename) = NULL;
419 		efree(us);
420 		return NULL;
421 	}
422 
423 	/* call it's dir_open method - set up params first */
424 	ZVAL_STRING(&args[0], filename);
425 	ZVAL_LONG(&args[1], options);
426 
427 	ZVAL_STRING(&zfuncname, USERSTREAM_DIR_OPEN);
428 
429 	call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 2, args);
430 
431 	if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) {
432 		/* the stream is now open! */
433 		stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode);
434 
435 		/* set wrapper data to be a reference to our object */
436 		ZVAL_COPY(&stream->wrapperdata, &us->object);
437 	} else {
438 		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
439 			ZSTR_VAL(us->wrapper->ce->name));
440 	}
441 
442 	/* destroy everything else */
443 	if (stream == NULL) {
444 		zval_ptr_dtor(&us->object);
445 		ZVAL_UNDEF(&us->object);
446 		zend_list_delete(us->wrapper->resource);
447 		efree(us);
448 	}
449 	zval_ptr_dtor(&zretval);
450 
451 	zval_ptr_dtor(&zfuncname);
452 	zval_ptr_dtor(&args[1]);
453 	zval_ptr_dtor(&args[0]);
454 
455 	FG(user_stream_current_filename) = NULL;
456 
457 	return stream;
458 }
459 
460 
461 /* {{{ Registers a custom URL protocol handler class */
PHP_FUNCTION(stream_wrapper_register)462 PHP_FUNCTION(stream_wrapper_register)
463 {
464 	zend_string *protocol;
465 	struct php_user_stream_wrapper *uwrap;
466 	zend_class_entry *ce = NULL;
467 	zend_resource *rsrc;
468 	zend_long flags = 0;
469 
470 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SC|l", &protocol, &ce, &flags) == FAILURE) {
471 		RETURN_THROWS();
472 	}
473 
474 	uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
475 	uwrap->ce = ce;
476 	uwrap->protoname = estrndup(ZSTR_VAL(protocol), ZSTR_LEN(protocol));
477 	uwrap->wrapper.wops = &user_stream_wops;
478 	uwrap->wrapper.abstract = uwrap;
479 	uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
480 
481 	rsrc = zend_register_resource(uwrap, le_protocols);
482 
483 	if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) {
484 		uwrap->resource = rsrc;
485 		RETURN_TRUE;
486 	}
487 
488 	/* We failed.  But why? */
489 	if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol)) {
490 		php_error_docref(NULL, E_WARNING, "Protocol %s:// is already defined.", ZSTR_VAL(protocol));
491 	} else {
492 		/* Hash doesn't exist so it must have been an invalid protocol scheme */
493 		php_error_docref(NULL, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol));
494 	}
495 
496 	zend_list_delete(rsrc);
497 	RETURN_FALSE;
498 }
499 /* }}} */
500 
501 /* {{{ Unregister a wrapper for the life of the current request. */
PHP_FUNCTION(stream_wrapper_unregister)502 PHP_FUNCTION(stream_wrapper_unregister)
503 {
504 	zend_string *protocol;
505 
506 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
507 		RETURN_THROWS();
508 	}
509 
510 	php_stream_wrapper *wrapper = zend_hash_find_ptr(php_stream_get_url_stream_wrappers_hash(), protocol);
511 	if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) {
512 		/* We failed */
513 		php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
514 		RETURN_FALSE;
515 	}
516 
517 	ZEND_ASSERT(wrapper != NULL);
518 	if (wrapper->wops == &user_stream_wops) {
519 		struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper *)wrapper;
520 		// uwrap will be released by resource destructor
521 		zend_list_delete(uwrap->resource);
522 	}
523 
524 	RETURN_TRUE;
525 }
526 /* }}} */
527 
528 /* {{{ Restore the original protocol handler, overriding if necessary */
PHP_FUNCTION(stream_wrapper_restore)529 PHP_FUNCTION(stream_wrapper_restore)
530 {
531 	zend_string *protocol;
532 	php_stream_wrapper *wrapper;
533 	HashTable *global_wrapper_hash, *wrapper_hash;
534 
535 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
536 		RETURN_THROWS();
537 	}
538 
539 	global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
540 	if ((wrapper = zend_hash_find_ptr(global_wrapper_hash, protocol)) == NULL) {
541 		php_error_docref(NULL, E_WARNING, "%s:// never existed, nothing to restore", ZSTR_VAL(protocol));
542 		RETURN_FALSE;
543 	}
544 
545 	wrapper_hash = php_stream_get_url_stream_wrappers_hash();
546 	if (wrapper_hash == global_wrapper_hash || zend_hash_find_ptr(wrapper_hash, protocol) == wrapper) {
547 		php_error_docref(NULL, E_NOTICE, "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
548 		RETURN_TRUE;
549 	}
550 
551 	/* A failure here could be okay given that the protocol might have been merely unregistered */
552 	php_unregister_url_stream_wrapper_volatile(protocol);
553 
554 	if (php_register_url_stream_wrapper_volatile(protocol, wrapper) == FAILURE) {
555 		php_error_docref(NULL, E_WARNING, "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
556 		RETURN_FALSE;
557 	}
558 
559 	RETURN_TRUE;
560 }
561 /* }}} */
562 
php_userstreamop_write(php_stream * stream,const char * buf,size_t count)563 static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count)
564 {
565 	zval func_name;
566 	zval retval;
567 	int call_result;
568 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
569 	zval args[1];
570 	ssize_t didwrite;
571 
572 	assert(us != NULL);
573 
574 	ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1);
575 
576 	ZVAL_STRINGL(&args[0], (char*)buf, count);
577 
578 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
579 	zval_ptr_dtor(&args[0]);
580 	zval_ptr_dtor(&func_name);
581 
582 	if (EG(exception)) {
583 		return -1;
584 	}
585 
586 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
587 		if (Z_TYPE(retval) == IS_FALSE) {
588 			didwrite = -1;
589 		} else {
590 			convert_to_long(&retval);
591 			didwrite = Z_LVAL(retval);
592 		}
593 	} else {
594 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
595 				ZSTR_VAL(us->wrapper->ce->name));
596 		didwrite = -1;
597 	}
598 
599 	/* don't allow strange buffer overruns due to bogus return */
600 	if (didwrite > 0 && didwrite > count) {
601 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)",
602 				ZSTR_VAL(us->wrapper->ce->name),
603 				(zend_long)(didwrite - count), (zend_long)didwrite, (zend_long)count);
604 		didwrite = count;
605 	}
606 
607 	zval_ptr_dtor(&retval);
608 
609 	return didwrite;
610 }
611 
php_userstreamop_read(php_stream * stream,char * buf,size_t count)612 static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count)
613 {
614 	zval func_name;
615 	zval retval;
616 	zval args[1];
617 	int call_result;
618 	size_t didread = 0;
619 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
620 
621 	assert(us != NULL);
622 
623 	ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1);
624 
625 	ZVAL_LONG(&args[0], count);
626 
627 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
628 
629 	zval_ptr_dtor(&args[0]);
630 	zval_ptr_dtor(&func_name);
631 
632 	if (EG(exception)) {
633 		return -1;
634 	}
635 
636 	if (call_result == FAILURE) {
637 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
638 				ZSTR_VAL(us->wrapper->ce->name));
639 		return -1;
640 	}
641 
642 	if (Z_TYPE(retval) == IS_FALSE) {
643 		return -1;
644 	}
645 
646 	if (!try_convert_to_string(&retval)) {
647 		zval_ptr_dtor(&retval);
648 		return -1;
649 	}
650 
651 	didread = Z_STRLEN(retval);
652 	if (didread > 0) {
653 		if (didread > count) {
654 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " read, " ZEND_LONG_FMT " max) - excess data will be lost",
655 					ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count), (zend_long)didread, (zend_long)count);
656 			didread = count;
657 		}
658 		memcpy(buf, Z_STRVAL(retval), didread);
659 	}
660 
661 	zval_ptr_dtor(&retval);
662 	ZVAL_UNDEF(&retval);
663 
664 	/* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */
665 
666 	ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1);
667 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
668 	zval_ptr_dtor(&func_name);
669 
670 	if (EG(exception)) {
671 		stream->eof = 1;
672 		return -1;
673 	}
674 
675 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
676 		stream->eof = 1;
677 	} else if (call_result == FAILURE) {
678 		php_error_docref(NULL, E_WARNING,
679 				"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
680 				ZSTR_VAL(us->wrapper->ce->name));
681 
682 		stream->eof = 1;
683 	}
684 
685 	zval_ptr_dtor(&retval);
686 
687 	return didread;
688 }
689 
php_userstreamop_close(php_stream * stream,int close_handle)690 static int php_userstreamop_close(php_stream *stream, int close_handle)
691 {
692 	zval func_name;
693 	zval retval;
694 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
695 
696 	assert(us != NULL);
697 
698 	ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1);
699 
700 	call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
701 
702 	zval_ptr_dtor(&retval);
703 	zval_ptr_dtor(&func_name);
704 
705 	zval_ptr_dtor(&us->object);
706 	ZVAL_UNDEF(&us->object);
707 
708 	efree(us);
709 
710 	return 0;
711 }
712 
php_userstreamop_flush(php_stream * stream)713 static int php_userstreamop_flush(php_stream *stream)
714 {
715 	zval func_name;
716 	zval retval;
717 	int call_result;
718 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
719 
720 	assert(us != NULL);
721 
722 	ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1);
723 
724 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
725 
726 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval))
727 		call_result = 0;
728 	else
729 		call_result = -1;
730 
731 	zval_ptr_dtor(&retval);
732 	zval_ptr_dtor(&func_name);
733 
734 	return call_result;
735 }
736 
php_userstreamop_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)737 static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
738 {
739 	zval func_name;
740 	zval retval;
741 	int call_result, ret;
742 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
743 	zval args[2];
744 
745 	assert(us != NULL);
746 
747 	ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1);
748 
749 	ZVAL_LONG(&args[0], offset);
750 	ZVAL_LONG(&args[1], whence);
751 
752 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args);
753 
754 	zval_ptr_dtor(&args[0]);
755 	zval_ptr_dtor(&args[1]);
756 	zval_ptr_dtor(&func_name);
757 
758 	if (call_result == FAILURE) {
759 		/* stream_seek is not implemented, so disable seeks for this stream */
760 		stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
761 		/* there should be no retval to clean up */
762 
763 		zval_ptr_dtor(&retval);
764 
765 		return -1;
766 	} else if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) {
767 		ret = 0;
768 	} else {
769 		ret = -1;
770 	}
771 
772 	zval_ptr_dtor(&retval);
773 	ZVAL_UNDEF(&retval);
774 
775 	if (ret) {
776 		return ret;
777 	}
778 
779 	/* now determine where we are */
780 	ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1);
781 
782 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
783 
784 	if (call_result == SUCCESS && Z_TYPE(retval) == IS_LONG) {
785 		*newoffs = Z_LVAL(retval);
786 		ret = 0;
787 	} else if (call_result == FAILURE) {
788 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
789 		ret = -1;
790 	} else {
791 		ret = -1;
792 	}
793 
794 	zval_ptr_dtor(&retval);
795 	zval_ptr_dtor(&func_name);
796 	return ret;
797 }
798 
799 /* parse the return value from one of the stat functions and store the
800  * relevant fields into the statbuf provided */
statbuf_from_array(zval * array,php_stream_statbuf * ssb)801 static void statbuf_from_array(zval *array, php_stream_statbuf *ssb)
802 {
803 	zval *elem;
804 
805 #define STAT_PROP_ENTRY_EX(name, name2)                        \
806 	if (NULL != (elem = zend_hash_str_find(Z_ARRVAL_P(array), #name, sizeof(#name)-1))) {     \
807 		ssb->sb.st_##name2 = zval_get_long(elem);                                                      \
808 	}
809 
810 #define STAT_PROP_ENTRY(name) STAT_PROP_ENTRY_EX(name,name)
811 
812 	memset(ssb, 0, sizeof(php_stream_statbuf));
813 	STAT_PROP_ENTRY(dev);
814 	STAT_PROP_ENTRY(ino);
815 	STAT_PROP_ENTRY(mode);
816 	STAT_PROP_ENTRY(nlink);
817 	STAT_PROP_ENTRY(uid);
818 	STAT_PROP_ENTRY(gid);
819 #ifdef HAVE_STRUCT_STAT_ST_RDEV
820 	STAT_PROP_ENTRY(rdev);
821 #endif
822 	STAT_PROP_ENTRY(size);
823 	STAT_PROP_ENTRY(atime);
824 	STAT_PROP_ENTRY(mtime);
825 	STAT_PROP_ENTRY(ctime);
826 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
827 	STAT_PROP_ENTRY(blksize);
828 #endif
829 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
830 	STAT_PROP_ENTRY(blocks);
831 #endif
832 
833 #undef STAT_PROP_ENTRY
834 #undef STAT_PROP_ENTRY_EX
835 }
836 
php_userstreamop_stat(php_stream * stream,php_stream_statbuf * ssb)837 static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
838 {
839 	zval func_name;
840 	zval retval;
841 	int call_result;
842 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
843 	int ret = -1;
844 
845 	ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1);
846 
847 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
848 
849 	if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) {
850 		statbuf_from_array(&retval, ssb);
851 		ret = 0;
852 	} else {
853 		if (call_result == FAILURE) {
854 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
855 					ZSTR_VAL(us->wrapper->ce->name));
856 		}
857 	}
858 
859 	zval_ptr_dtor(&retval);
860 	zval_ptr_dtor(&func_name);
861 
862 	return ret;
863 }
864 
865 
php_userstreamop_set_option(php_stream * stream,int option,int value,void * ptrparam)866 static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) {
867 	zval func_name;
868 	zval retval;
869 	int call_result;
870 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
871 	int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
872 	zval args[3];
873 
874 	switch (option) {
875 	case PHP_STREAM_OPTION_CHECK_LIVENESS:
876 		ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1);
877 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
878 		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
879 			ret = zval_is_true(&retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
880 		} else {
881 			ret = PHP_STREAM_OPTION_RETURN_ERR;
882 			php_error_docref(NULL, E_WARNING,
883 					"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
884 					ZSTR_VAL(us->wrapper->ce->name));
885 		}
886 		zval_ptr_dtor(&retval);
887 		zval_ptr_dtor(&func_name);
888 		break;
889 
890 	case PHP_STREAM_OPTION_LOCKING:
891 		ZVAL_LONG(&args[0], 0);
892 
893 		if (value & LOCK_NB) {
894 			Z_LVAL_P(&args[0]) |= PHP_LOCK_NB;
895 		}
896 		switch(value & ~LOCK_NB) {
897 		case LOCK_SH:
898 			Z_LVAL_P(&args[0]) |= PHP_LOCK_SH;
899 			break;
900 		case LOCK_EX:
901 			Z_LVAL_P(&args[0]) |= PHP_LOCK_EX;
902 			break;
903 		case LOCK_UN:
904 			Z_LVAL_P(&args[0]) |= PHP_LOCK_UN;
905 			break;
906 		}
907 
908 		/* TODO wouldblock */
909 		ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1);
910 
911 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
912 
913 		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
914 			ret = (Z_TYPE(retval) == IS_FALSE);
915 		} else if (call_result == FAILURE) {
916 			if (value == 0) {
917 			   	/* lock support test (TODO: more check) */
918 				ret = PHP_STREAM_OPTION_RETURN_OK;
919 			} else {
920 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!",
921 								 ZSTR_VAL(us->wrapper->ce->name));
922 				ret = PHP_STREAM_OPTION_RETURN_ERR;
923 			}
924 		}
925 
926 		zval_ptr_dtor(&retval);
927 		zval_ptr_dtor(&func_name);
928 		zval_ptr_dtor(&args[0]);
929 		break;
930 
931 	case PHP_STREAM_OPTION_TRUNCATE_API:
932 		ZVAL_STRINGL(&func_name, USERSTREAM_TRUNCATE, sizeof(USERSTREAM_TRUNCATE)-1);
933 
934 		switch (value) {
935 		case PHP_STREAM_TRUNCATE_SUPPORTED:
936 			if (zend_is_callable_ex(&func_name, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL))
937 				ret = PHP_STREAM_OPTION_RETURN_OK;
938 			else
939 				ret = PHP_STREAM_OPTION_RETURN_ERR;
940 			break;
941 
942 		case PHP_STREAM_TRUNCATE_SET_SIZE: {
943 			ptrdiff_t new_size = *(ptrdiff_t*) ptrparam;
944 			if (new_size >= 0 && new_size <= (ptrdiff_t)LONG_MAX) {
945 				ZVAL_LONG(&args[0], (zend_long)new_size);
946 				call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
947 				if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
948 					if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
949 						ret = (Z_TYPE(retval) == IS_TRUE) ? PHP_STREAM_OPTION_RETURN_OK :
950 											   PHP_STREAM_OPTION_RETURN_ERR;
951 					} else {
952 						php_error_docref(NULL, E_WARNING,
953 								"%s::" USERSTREAM_TRUNCATE " did not return a boolean!",
954 								ZSTR_VAL(us->wrapper->ce->name));
955 					}
956 				} else {
957 					php_error_docref(NULL, E_WARNING,
958 							"%s::" USERSTREAM_TRUNCATE " is not implemented!",
959 							ZSTR_VAL(us->wrapper->ce->name));
960 				}
961 				zval_ptr_dtor(&retval);
962 				zval_ptr_dtor(&args[0]);
963 			} else { /* bad new size */
964 				ret = PHP_STREAM_OPTION_RETURN_ERR;
965 			}
966 			break;
967 		}
968 		}
969 		zval_ptr_dtor(&func_name);
970 		break;
971 
972 	case PHP_STREAM_OPTION_READ_BUFFER:
973 	case PHP_STREAM_OPTION_WRITE_BUFFER:
974 	case PHP_STREAM_OPTION_READ_TIMEOUT:
975 	case PHP_STREAM_OPTION_BLOCKING: {
976 
977 		ZVAL_STRINGL(&func_name, USERSTREAM_SET_OPTION, sizeof(USERSTREAM_SET_OPTION)-1);
978 
979 		ZVAL_LONG(&args[0], option);
980 		ZVAL_NULL(&args[1]);
981 		ZVAL_NULL(&args[2]);
982 
983 		switch(option) {
984 		case PHP_STREAM_OPTION_READ_BUFFER:
985 		case PHP_STREAM_OPTION_WRITE_BUFFER:
986 			ZVAL_LONG(&args[1], value);
987 			if (ptrparam) {
988 				ZVAL_LONG(&args[2], *(long *)ptrparam);
989 			} else {
990 				ZVAL_LONG(&args[2], BUFSIZ);
991 			}
992 			break;
993 		case PHP_STREAM_OPTION_READ_TIMEOUT: {
994 			struct timeval tv = *(struct timeval*)ptrparam;
995 			ZVAL_LONG(&args[1], tv.tv_sec);
996 			ZVAL_LONG(&args[2], tv.tv_usec);
997 			break;
998 			}
999 		case PHP_STREAM_OPTION_BLOCKING:
1000 			ZVAL_LONG(&args[1], value);
1001 			break;
1002 		default:
1003 			break;
1004 		}
1005 
1006 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 3, args);
1007 
1008 		if (call_result == FAILURE) {
1009 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_SET_OPTION " is not implemented!",
1010 					ZSTR_VAL(us->wrapper->ce->name));
1011 			ret = PHP_STREAM_OPTION_RETURN_ERR;
1012 		} else if (zend_is_true(&retval)) {
1013 			ret = PHP_STREAM_OPTION_RETURN_OK;
1014 		} else {
1015 			ret = PHP_STREAM_OPTION_RETURN_ERR;
1016 		}
1017 
1018 		zval_ptr_dtor(&retval);
1019 		zval_ptr_dtor(&args[2]);
1020 		zval_ptr_dtor(&args[1]);
1021 		zval_ptr_dtor(&args[0]);
1022 		zval_ptr_dtor(&func_name);
1023 
1024 		break;
1025 		}
1026 	}
1027 
1028 	return ret;
1029 }
1030 
1031 
user_wrapper_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1032 static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1033 {
1034 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1035 	zval zfuncname, zretval;
1036 	zval args[1];
1037 	int call_result;
1038 	zval object;
1039 	int ret = 0;
1040 
1041 	/* create an instance of our class */
1042 	user_stream_create_object(uwrap, context, &object);
1043 	if (Z_TYPE(object) == IS_UNDEF) {
1044 		return ret;
1045 	}
1046 
1047 	/* call the unlink method */
1048 	ZVAL_STRING(&args[0], url);
1049 
1050 	ZVAL_STRING(&zfuncname, USERSTREAM_UNLINK);
1051 
1052 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 1, args);
1053 
1054 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1055 		ret = (Z_TYPE(zretval) == IS_TRUE);
1056 	} else if (call_result == FAILURE) {
1057 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1058 	}
1059 
1060 	/* clean up */
1061 	zval_ptr_dtor(&object);
1062 	zval_ptr_dtor(&zretval);
1063 	zval_ptr_dtor(&zfuncname);
1064 
1065 	zval_ptr_dtor(&args[0]);
1066 
1067 	return ret;
1068 }
1069 
user_wrapper_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1070 static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to,
1071 							   int options, php_stream_context *context)
1072 {
1073 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1074 	zval zfuncname, zretval;
1075 	zval args[2];
1076 	int call_result;
1077 	zval object;
1078 	int ret = 0;
1079 
1080 	/* create an instance of our class */
1081 	user_stream_create_object(uwrap, context, &object);
1082 	if (Z_TYPE(object) == IS_UNDEF) {
1083 		return ret;
1084 	}
1085 
1086 	/* call the rename method */
1087 	ZVAL_STRING(&args[0], url_from);
1088 	ZVAL_STRING(&args[1], url_to);
1089 
1090 	ZVAL_STRING(&zfuncname, USERSTREAM_RENAME);
1091 
1092 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1093 
1094 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1095 		ret = (Z_TYPE(zretval) == IS_TRUE);
1096 	} else if (call_result == FAILURE) {
1097 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1098 	}
1099 
1100 	/* clean up */
1101 	zval_ptr_dtor(&object);
1102 	zval_ptr_dtor(&zretval);
1103 
1104 	zval_ptr_dtor(&zfuncname);
1105 	zval_ptr_dtor(&args[1]);
1106 	zval_ptr_dtor(&args[0]);
1107 
1108 	return ret;
1109 }
1110 
user_wrapper_mkdir(php_stream_wrapper * wrapper,const char * url,int mode,int options,php_stream_context * context)1111 static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode,
1112 							  int options, php_stream_context *context)
1113 {
1114 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1115 	zval zfuncname, zretval;
1116 	zval args[3];
1117 	int call_result;
1118 	zval object;
1119 	int ret = 0;
1120 
1121 	/* create an instance of our class */
1122 	user_stream_create_object(uwrap, context, &object);
1123 	if (Z_TYPE(object) == IS_UNDEF) {
1124 		return ret;
1125 	}
1126 
1127 	/* call the mkdir method */
1128 	ZVAL_STRING(&args[0], url);
1129 	ZVAL_LONG(&args[1], mode);
1130 	ZVAL_LONG(&args[2], options);
1131 
1132 	ZVAL_STRING(&zfuncname, USERSTREAM_MKDIR);
1133 
1134 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
1135 
1136 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1137 		ret = (Z_TYPE(zretval) == IS_TRUE);
1138 	} else if (call_result == FAILURE) {
1139 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1140 	}
1141 
1142 	/* clean up */
1143 	zval_ptr_dtor(&object);
1144 	zval_ptr_dtor(&zretval);
1145 
1146 	zval_ptr_dtor(&zfuncname);
1147 	zval_ptr_dtor(&args[2]);
1148 	zval_ptr_dtor(&args[1]);
1149 	zval_ptr_dtor(&args[0]);
1150 
1151 	return ret;
1152 }
1153 
user_wrapper_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1154 static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
1155 							  int options, php_stream_context *context)
1156 {
1157 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1158 	zval zfuncname, zretval;
1159 	zval args[2];
1160 	int call_result;
1161 	zval object;
1162 	int ret = 0;
1163 
1164 	/* create an instance of our class */
1165 	user_stream_create_object(uwrap, context, &object);
1166 	if (Z_TYPE(object) == IS_UNDEF) {
1167 		return ret;
1168 	}
1169 
1170 	/* call the rmdir method */
1171 	ZVAL_STRING(&args[0], url);
1172 	ZVAL_LONG(&args[1], options);
1173 
1174 	ZVAL_STRING(&zfuncname, USERSTREAM_RMDIR);
1175 
1176 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1177 
1178 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1179 		ret = (Z_TYPE(zretval) == IS_TRUE);
1180 	} else if (call_result == FAILURE) {
1181 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1182 	}
1183 
1184 	/* clean up */
1185 	zval_ptr_dtor(&object);
1186 	zval_ptr_dtor(&zretval);
1187 
1188 	zval_ptr_dtor(&zfuncname);
1189 	zval_ptr_dtor(&args[1]);
1190 	zval_ptr_dtor(&args[0]);
1191 
1192 	return ret;
1193 }
1194 
user_wrapper_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1195 static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option,
1196 								 void *value, php_stream_context *context)
1197 {
1198 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1199 	zval zfuncname, zretval;
1200 	zval args[3];
1201 	int call_result;
1202 	zval object;
1203 	int ret = 0;
1204 
1205 	switch(option) {
1206 		case PHP_STREAM_META_TOUCH:
1207 			array_init(&args[2]);
1208 			if(value) {
1209 				struct utimbuf *newtime = (struct utimbuf *)value;
1210 				add_index_long(&args[2], 0, newtime->modtime);
1211 				add_index_long(&args[2], 1, newtime->actime);
1212 			}
1213 			break;
1214 		case PHP_STREAM_META_GROUP:
1215 		case PHP_STREAM_META_OWNER:
1216 		case PHP_STREAM_META_ACCESS:
1217 			ZVAL_LONG(&args[2], *(long *)value);
1218 			break;
1219 		case PHP_STREAM_META_GROUP_NAME:
1220 		case PHP_STREAM_META_OWNER_NAME:
1221 			ZVAL_STRING(&args[2], value);
1222 			break;
1223 		default:
1224 			php_error_docref(NULL, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option);
1225 			zval_ptr_dtor(&args[2]);
1226 			return ret;
1227 	}
1228 
1229 	/* create an instance of our class */
1230 	user_stream_create_object(uwrap, context, &object);
1231 	if (Z_TYPE(object) == IS_UNDEF) {
1232 		zval_ptr_dtor(&args[2]);
1233 		return ret;
1234 	}
1235 
1236 	/* call the mkdir method */
1237 	ZVAL_STRING(&args[0], url);
1238 	ZVAL_LONG(&args[1], option);
1239 
1240 	ZVAL_STRING(&zfuncname, USERSTREAM_METADATA);
1241 
1242 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
1243 
1244 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1245 		ret = Z_TYPE(zretval) == IS_TRUE;
1246 	} else if (call_result == FAILURE) {
1247 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1248 	}
1249 
1250 	/* clean up */
1251 	zval_ptr_dtor(&object);
1252 	zval_ptr_dtor(&zretval);
1253 
1254 	zval_ptr_dtor(&zfuncname);
1255 	zval_ptr_dtor(&args[0]);
1256 	zval_ptr_dtor(&args[1]);
1257 	zval_ptr_dtor(&args[2]);
1258 
1259 	return ret;
1260 }
1261 
1262 
user_wrapper_stat_url(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1263 static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags,
1264 								 php_stream_statbuf *ssb, php_stream_context *context)
1265 {
1266 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1267 	zval zfuncname, zretval;
1268 	zval args[2];
1269 	int call_result;
1270 	zval object;
1271 	int ret = -1;
1272 
1273 	/* create an instance of our class */
1274 	user_stream_create_object(uwrap, context, &object);
1275 	if (Z_TYPE(object) == IS_UNDEF) {
1276 		return ret;
1277 	}
1278 
1279 	/* call it's stat_url method - set up params first */
1280 	ZVAL_STRING(&args[0], url);
1281 	ZVAL_LONG(&args[1], flags);
1282 
1283 	ZVAL_STRING(&zfuncname, USERSTREAM_STATURL);
1284 
1285 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1286 
1287 	if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) {
1288 		/* We got the info we needed */
1289 		statbuf_from_array(&zretval, ssb);
1290 		ret = 0;
1291 	} else {
1292 		if (call_result == FAILURE) {
1293 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
1294 					ZSTR_VAL(uwrap->ce->name));
1295 		}
1296 	}
1297 
1298 	/* clean up */
1299 	zval_ptr_dtor(&object);
1300 	zval_ptr_dtor(&zretval);
1301 
1302 	zval_ptr_dtor(&zfuncname);
1303 	zval_ptr_dtor(&args[1]);
1304 	zval_ptr_dtor(&args[0]);
1305 
1306 	return ret;
1307 
1308 }
1309 
php_userstreamop_readdir(php_stream * stream,char * buf,size_t count)1310 static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count)
1311 {
1312 	zval func_name;
1313 	zval retval;
1314 	int call_result;
1315 	size_t didread = 0;
1316 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1317 	php_stream_dirent *ent = (php_stream_dirent*)buf;
1318 
1319 	/* avoid problems if someone mis-uses the stream */
1320 	if (count != sizeof(php_stream_dirent))
1321 		return -1;
1322 
1323 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1);
1324 
1325 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1326 
1327 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
1328 		convert_to_string(&retval);
1329 		PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval));
1330 		ent->d_type = DT_UNKNOWN;
1331 
1332 		didread = sizeof(php_stream_dirent);
1333 	} else if (call_result == FAILURE) {
1334 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
1335 				ZSTR_VAL(us->wrapper->ce->name));
1336 	}
1337 
1338 	zval_ptr_dtor(&retval);
1339 	zval_ptr_dtor(&func_name);
1340 
1341 	return didread;
1342 }
1343 
php_userstreamop_closedir(php_stream * stream,int close_handle)1344 static int php_userstreamop_closedir(php_stream *stream, int close_handle)
1345 {
1346 	zval func_name;
1347 	zval retval;
1348 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1349 
1350 	assert(us != NULL);
1351 
1352 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1);
1353 
1354 	call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1355 
1356 	zval_ptr_dtor(&retval);
1357 	zval_ptr_dtor(&func_name);
1358 	zval_ptr_dtor(&us->object);
1359 	ZVAL_UNDEF(&us->object);
1360 
1361 	efree(us);
1362 
1363 	return 0;
1364 }
1365 
php_userstreamop_rewinddir(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)1366 static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1367 {
1368 	zval func_name;
1369 	zval retval;
1370 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1371 
1372 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1);
1373 
1374 	call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1375 
1376 	zval_ptr_dtor(&retval);
1377 	zval_ptr_dtor(&func_name);
1378 
1379 	return 0;
1380 
1381 }
1382 
php_userstreamop_cast(php_stream * stream,int castas,void ** retptr)1383 static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
1384 {
1385 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1386 	zval func_name;
1387 	zval retval;
1388 	zval args[1];
1389 	php_stream * intstream = NULL;
1390 	int call_result;
1391 	int ret = FAILURE;
1392 	/* If we are checking if the stream can cast, no return pointer is provided, so do not emit errors */
1393 	bool report_errors = retptr;
1394 
1395 	ZVAL_STRINGL(&func_name, USERSTREAM_CAST, sizeof(USERSTREAM_CAST)-1);
1396 
1397 	switch(castas) {
1398 	case PHP_STREAM_AS_FD_FOR_SELECT:
1399 		ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT);
1400 		break;
1401 	default:
1402 		ZVAL_LONG(&args[0], PHP_STREAM_AS_STDIO);
1403 		break;
1404 	}
1405 
1406 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
1407 
1408 	do {
1409 		if (call_result == FAILURE) {
1410 			if (report_errors) {
1411 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
1412 						ZSTR_VAL(us->wrapper->ce->name));
1413 			}
1414 			break;
1415 		}
1416 		if (!zend_is_true(&retval)) {
1417 			break;
1418 		}
1419 		php_stream_from_zval_no_verify(intstream, &retval);
1420 		if (!intstream) {
1421 			if (report_errors) {
1422 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource",
1423 						ZSTR_VAL(us->wrapper->ce->name));
1424 			}
1425 			break;
1426 		}
1427 		if (intstream == stream) {
1428 			if (report_errors) {
1429 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself",
1430 						ZSTR_VAL(us->wrapper->ce->name));
1431 			}
1432 			intstream = NULL;
1433 			break;
1434 		}
1435 		ret = php_stream_cast(intstream, castas, retptr, 1);
1436 	} while (0);
1437 
1438 	zval_ptr_dtor(&retval);
1439 	zval_ptr_dtor(&func_name);
1440 	zval_ptr_dtor(&args[0]);
1441 
1442 	return ret;
1443 }
1444 
1445 const php_stream_ops php_stream_userspace_ops = {
1446 	php_userstreamop_write, php_userstreamop_read,
1447 	php_userstreamop_close, php_userstreamop_flush,
1448 	"user-space",
1449 	php_userstreamop_seek,
1450 	php_userstreamop_cast,
1451 	php_userstreamop_stat,
1452 	php_userstreamop_set_option,
1453 };
1454 
1455 const php_stream_ops php_stream_userspace_dir_ops = {
1456 	NULL, /* write */
1457 	php_userstreamop_readdir,
1458 	php_userstreamop_closedir,
1459 	NULL, /* flush */
1460 	"user-space-dir",
1461 	php_userstreamop_rewinddir,
1462 	NULL, /* cast */
1463 	NULL, /* stat */
1464 	NULL  /* set_option */
1465 };
1466