xref: /PHP-8.3/main/streams/userspace.c (revision 1b8be9ac)
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 #if 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 int 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 #if 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 	return SUCCESS;
836 }
837 
php_userstreamop_stat(php_stream * stream,php_stream_statbuf * ssb)838 static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
839 {
840 	zval func_name;
841 	zval retval;
842 	int call_result;
843 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
844 	int ret = -1;
845 
846 	ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1);
847 
848 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
849 
850 	if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) {
851 		if (SUCCESS == statbuf_from_array(&retval, ssb))
852 			ret = 0;
853 	} else {
854 		if (call_result == FAILURE) {
855 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
856 					ZSTR_VAL(us->wrapper->ce->name));
857 		}
858 	}
859 
860 	zval_ptr_dtor(&retval);
861 	zval_ptr_dtor(&func_name);
862 
863 	return ret;
864 }
865 
866 
php_userstreamop_set_option(php_stream * stream,int option,int value,void * ptrparam)867 static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) {
868 	zval func_name;
869 	zval retval;
870 	int call_result;
871 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
872 	int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
873 	zval args[3];
874 
875 	switch (option) {
876 	case PHP_STREAM_OPTION_CHECK_LIVENESS:
877 		ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1);
878 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
879 		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
880 			ret = zval_is_true(&retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
881 		} else {
882 			ret = PHP_STREAM_OPTION_RETURN_ERR;
883 			php_error_docref(NULL, E_WARNING,
884 					"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
885 					ZSTR_VAL(us->wrapper->ce->name));
886 		}
887 		zval_ptr_dtor(&retval);
888 		zval_ptr_dtor(&func_name);
889 		break;
890 
891 	case PHP_STREAM_OPTION_LOCKING:
892 		ZVAL_LONG(&args[0], 0);
893 
894 		if (value & LOCK_NB) {
895 			Z_LVAL_P(&args[0]) |= PHP_LOCK_NB;
896 		}
897 		switch(value & ~LOCK_NB) {
898 		case LOCK_SH:
899 			Z_LVAL_P(&args[0]) |= PHP_LOCK_SH;
900 			break;
901 		case LOCK_EX:
902 			Z_LVAL_P(&args[0]) |= PHP_LOCK_EX;
903 			break;
904 		case LOCK_UN:
905 			Z_LVAL_P(&args[0]) |= PHP_LOCK_UN;
906 			break;
907 		}
908 
909 		/* TODO wouldblock */
910 		ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1);
911 
912 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
913 
914 		if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
915 			ret = (Z_TYPE(retval) == IS_FALSE);
916 		} else if (call_result == FAILURE) {
917 			if (value == 0) {
918 			   	/* lock support test (TODO: more check) */
919 				ret = PHP_STREAM_OPTION_RETURN_OK;
920 			} else {
921 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!",
922 								 ZSTR_VAL(us->wrapper->ce->name));
923 				ret = PHP_STREAM_OPTION_RETURN_ERR;
924 			}
925 		}
926 
927 		zval_ptr_dtor(&retval);
928 		zval_ptr_dtor(&func_name);
929 		zval_ptr_dtor(&args[0]);
930 		break;
931 
932 	case PHP_STREAM_OPTION_TRUNCATE_API:
933 		ZVAL_STRINGL(&func_name, USERSTREAM_TRUNCATE, sizeof(USERSTREAM_TRUNCATE)-1);
934 
935 		switch (value) {
936 		case PHP_STREAM_TRUNCATE_SUPPORTED:
937 			if (zend_is_callable_ex(&func_name, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL))
938 				ret = PHP_STREAM_OPTION_RETURN_OK;
939 			else
940 				ret = PHP_STREAM_OPTION_RETURN_ERR;
941 			break;
942 
943 		case PHP_STREAM_TRUNCATE_SET_SIZE: {
944 			ptrdiff_t new_size = *(ptrdiff_t*) ptrparam;
945 			if (new_size >= 0 && new_size <= (ptrdiff_t)LONG_MAX) {
946 				ZVAL_LONG(&args[0], (zend_long)new_size);
947 				call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
948 				if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
949 					if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
950 						ret = (Z_TYPE(retval) == IS_TRUE) ? PHP_STREAM_OPTION_RETURN_OK :
951 											   PHP_STREAM_OPTION_RETURN_ERR;
952 					} else {
953 						php_error_docref(NULL, E_WARNING,
954 								"%s::" USERSTREAM_TRUNCATE " did not return a boolean!",
955 								ZSTR_VAL(us->wrapper->ce->name));
956 					}
957 				} else {
958 					php_error_docref(NULL, E_WARNING,
959 							"%s::" USERSTREAM_TRUNCATE " is not implemented!",
960 							ZSTR_VAL(us->wrapper->ce->name));
961 				}
962 				zval_ptr_dtor(&retval);
963 				zval_ptr_dtor(&args[0]);
964 			} else { /* bad new size */
965 				ret = PHP_STREAM_OPTION_RETURN_ERR;
966 			}
967 			break;
968 		}
969 		}
970 		zval_ptr_dtor(&func_name);
971 		break;
972 
973 	case PHP_STREAM_OPTION_READ_BUFFER:
974 	case PHP_STREAM_OPTION_WRITE_BUFFER:
975 	case PHP_STREAM_OPTION_READ_TIMEOUT:
976 	case PHP_STREAM_OPTION_BLOCKING: {
977 
978 		ZVAL_STRINGL(&func_name, USERSTREAM_SET_OPTION, sizeof(USERSTREAM_SET_OPTION)-1);
979 
980 		ZVAL_LONG(&args[0], option);
981 		ZVAL_NULL(&args[1]);
982 		ZVAL_NULL(&args[2]);
983 
984 		switch(option) {
985 		case PHP_STREAM_OPTION_READ_BUFFER:
986 		case PHP_STREAM_OPTION_WRITE_BUFFER:
987 			ZVAL_LONG(&args[1], value);
988 			if (ptrparam) {
989 				ZVAL_LONG(&args[2], *(long *)ptrparam);
990 			} else {
991 				ZVAL_LONG(&args[2], BUFSIZ);
992 			}
993 			break;
994 		case PHP_STREAM_OPTION_READ_TIMEOUT: {
995 			struct timeval tv = *(struct timeval*)ptrparam;
996 			ZVAL_LONG(&args[1], tv.tv_sec);
997 			ZVAL_LONG(&args[2], tv.tv_usec);
998 			break;
999 			}
1000 		case PHP_STREAM_OPTION_BLOCKING:
1001 			ZVAL_LONG(&args[1], value);
1002 			break;
1003 		default:
1004 			break;
1005 		}
1006 
1007 		call_result = call_method_if_exists(&us->object, &func_name, &retval, 3, args);
1008 
1009 		if (call_result == FAILURE) {
1010 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_SET_OPTION " is not implemented!",
1011 					ZSTR_VAL(us->wrapper->ce->name));
1012 			ret = PHP_STREAM_OPTION_RETURN_ERR;
1013 		} else if (zend_is_true(&retval)) {
1014 			ret = PHP_STREAM_OPTION_RETURN_OK;
1015 		} else {
1016 			ret = PHP_STREAM_OPTION_RETURN_ERR;
1017 		}
1018 
1019 		zval_ptr_dtor(&retval);
1020 		zval_ptr_dtor(&args[2]);
1021 		zval_ptr_dtor(&args[1]);
1022 		zval_ptr_dtor(&args[0]);
1023 		zval_ptr_dtor(&func_name);
1024 
1025 		break;
1026 		}
1027 	}
1028 
1029 	return ret;
1030 }
1031 
1032 
user_wrapper_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1033 static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1034 {
1035 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1036 	zval zfuncname, zretval;
1037 	zval args[1];
1038 	int call_result;
1039 	zval object;
1040 	int ret = 0;
1041 
1042 	/* create an instance of our class */
1043 	user_stream_create_object(uwrap, context, &object);
1044 	if (Z_TYPE(object) == IS_UNDEF) {
1045 		return ret;
1046 	}
1047 
1048 	/* call the unlink method */
1049 	ZVAL_STRING(&args[0], url);
1050 
1051 	ZVAL_STRING(&zfuncname, USERSTREAM_UNLINK);
1052 
1053 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 1, args);
1054 
1055 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1056 		ret = (Z_TYPE(zretval) == IS_TRUE);
1057 	} else if (call_result == FAILURE) {
1058 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1059 	}
1060 
1061 	/* clean up */
1062 	zval_ptr_dtor(&object);
1063 	zval_ptr_dtor(&zretval);
1064 	zval_ptr_dtor(&zfuncname);
1065 
1066 	zval_ptr_dtor(&args[0]);
1067 
1068 	return ret;
1069 }
1070 
user_wrapper_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1071 static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to,
1072 							   int options, php_stream_context *context)
1073 {
1074 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1075 	zval zfuncname, zretval;
1076 	zval args[2];
1077 	int call_result;
1078 	zval object;
1079 	int ret = 0;
1080 
1081 	/* create an instance of our class */
1082 	user_stream_create_object(uwrap, context, &object);
1083 	if (Z_TYPE(object) == IS_UNDEF) {
1084 		return ret;
1085 	}
1086 
1087 	/* call the rename method */
1088 	ZVAL_STRING(&args[0], url_from);
1089 	ZVAL_STRING(&args[1], url_to);
1090 
1091 	ZVAL_STRING(&zfuncname, USERSTREAM_RENAME);
1092 
1093 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1094 
1095 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1096 		ret = (Z_TYPE(zretval) == IS_TRUE);
1097 	} else if (call_result == FAILURE) {
1098 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1099 	}
1100 
1101 	/* clean up */
1102 	zval_ptr_dtor(&object);
1103 	zval_ptr_dtor(&zretval);
1104 
1105 	zval_ptr_dtor(&zfuncname);
1106 	zval_ptr_dtor(&args[1]);
1107 	zval_ptr_dtor(&args[0]);
1108 
1109 	return ret;
1110 }
1111 
user_wrapper_mkdir(php_stream_wrapper * wrapper,const char * url,int mode,int options,php_stream_context * context)1112 static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode,
1113 							  int options, php_stream_context *context)
1114 {
1115 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1116 	zval zfuncname, zretval;
1117 	zval args[3];
1118 	int call_result;
1119 	zval object;
1120 	int ret = 0;
1121 
1122 	/* create an instance of our class */
1123 	user_stream_create_object(uwrap, context, &object);
1124 	if (Z_TYPE(object) == IS_UNDEF) {
1125 		return ret;
1126 	}
1127 
1128 	/* call the mkdir method */
1129 	ZVAL_STRING(&args[0], url);
1130 	ZVAL_LONG(&args[1], mode);
1131 	ZVAL_LONG(&args[2], options);
1132 
1133 	ZVAL_STRING(&zfuncname, USERSTREAM_MKDIR);
1134 
1135 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
1136 
1137 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1138 		ret = (Z_TYPE(zretval) == IS_TRUE);
1139 	} else if (call_result == FAILURE) {
1140 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1141 	}
1142 
1143 	/* clean up */
1144 	zval_ptr_dtor(&object);
1145 	zval_ptr_dtor(&zretval);
1146 
1147 	zval_ptr_dtor(&zfuncname);
1148 	zval_ptr_dtor(&args[2]);
1149 	zval_ptr_dtor(&args[1]);
1150 	zval_ptr_dtor(&args[0]);
1151 
1152 	return ret;
1153 }
1154 
user_wrapper_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1155 static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
1156 							  int options, php_stream_context *context)
1157 {
1158 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1159 	zval zfuncname, zretval;
1160 	zval args[2];
1161 	int call_result;
1162 	zval object;
1163 	int ret = 0;
1164 
1165 	/* create an instance of our class */
1166 	user_stream_create_object(uwrap, context, &object);
1167 	if (Z_TYPE(object) == IS_UNDEF) {
1168 		return ret;
1169 	}
1170 
1171 	/* call the rmdir method */
1172 	ZVAL_STRING(&args[0], url);
1173 	ZVAL_LONG(&args[1], options);
1174 
1175 	ZVAL_STRING(&zfuncname, USERSTREAM_RMDIR);
1176 
1177 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1178 
1179 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1180 		ret = (Z_TYPE(zretval) == IS_TRUE);
1181 	} else if (call_result == FAILURE) {
1182 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1183 	}
1184 
1185 	/* clean up */
1186 	zval_ptr_dtor(&object);
1187 	zval_ptr_dtor(&zretval);
1188 
1189 	zval_ptr_dtor(&zfuncname);
1190 	zval_ptr_dtor(&args[1]);
1191 	zval_ptr_dtor(&args[0]);
1192 
1193 	return ret;
1194 }
1195 
user_wrapper_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1196 static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option,
1197 								 void *value, php_stream_context *context)
1198 {
1199 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1200 	zval zfuncname, zretval;
1201 	zval args[3];
1202 	int call_result;
1203 	zval object;
1204 	int ret = 0;
1205 
1206 	switch(option) {
1207 		case PHP_STREAM_META_TOUCH:
1208 			array_init(&args[2]);
1209 			if(value) {
1210 				struct utimbuf *newtime = (struct utimbuf *)value;
1211 				add_index_long(&args[2], 0, newtime->modtime);
1212 				add_index_long(&args[2], 1, newtime->actime);
1213 			}
1214 			break;
1215 		case PHP_STREAM_META_GROUP:
1216 		case PHP_STREAM_META_OWNER:
1217 		case PHP_STREAM_META_ACCESS:
1218 			ZVAL_LONG(&args[2], *(long *)value);
1219 			break;
1220 		case PHP_STREAM_META_GROUP_NAME:
1221 		case PHP_STREAM_META_OWNER_NAME:
1222 			ZVAL_STRING(&args[2], value);
1223 			break;
1224 		default:
1225 			php_error_docref(NULL, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option);
1226 			zval_ptr_dtor(&args[2]);
1227 			return ret;
1228 	}
1229 
1230 	/* create an instance of our class */
1231 	user_stream_create_object(uwrap, context, &object);
1232 	if (Z_TYPE(object) == IS_UNDEF) {
1233 		zval_ptr_dtor(&args[2]);
1234 		return ret;
1235 	}
1236 
1237 	/* call the mkdir method */
1238 	ZVAL_STRING(&args[0], url);
1239 	ZVAL_LONG(&args[1], option);
1240 
1241 	ZVAL_STRING(&zfuncname, USERSTREAM_METADATA);
1242 
1243 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
1244 
1245 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
1246 		ret = Z_TYPE(zretval) == IS_TRUE;
1247 	} else if (call_result == FAILURE) {
1248 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1249 	}
1250 
1251 	/* clean up */
1252 	zval_ptr_dtor(&object);
1253 	zval_ptr_dtor(&zretval);
1254 
1255 	zval_ptr_dtor(&zfuncname);
1256 	zval_ptr_dtor(&args[0]);
1257 	zval_ptr_dtor(&args[1]);
1258 	zval_ptr_dtor(&args[2]);
1259 
1260 	return ret;
1261 }
1262 
1263 
user_wrapper_stat_url(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1264 static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags,
1265 								 php_stream_statbuf *ssb, php_stream_context *context)
1266 {
1267 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1268 	zval zfuncname, zretval;
1269 	zval args[2];
1270 	int call_result;
1271 	zval object;
1272 	int ret = -1;
1273 
1274 	/* create an instance of our class */
1275 	user_stream_create_object(uwrap, context, &object);
1276 	if (Z_TYPE(object) == IS_UNDEF) {
1277 		return ret;
1278 	}
1279 
1280 	/* call it's stat_url method - set up params first */
1281 	ZVAL_STRING(&args[0], url);
1282 	ZVAL_LONG(&args[1], flags);
1283 
1284 	ZVAL_STRING(&zfuncname, USERSTREAM_STATURL);
1285 
1286 	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
1287 
1288 	if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) {
1289 		/* We got the info we needed */
1290 		if (SUCCESS == statbuf_from_array(&zretval, ssb))
1291 			ret = 0;
1292 	} else {
1293 		if (call_result == FAILURE) {
1294 			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
1295 					ZSTR_VAL(uwrap->ce->name));
1296 		}
1297 	}
1298 
1299 	/* clean up */
1300 	zval_ptr_dtor(&object);
1301 	zval_ptr_dtor(&zretval);
1302 
1303 	zval_ptr_dtor(&zfuncname);
1304 	zval_ptr_dtor(&args[1]);
1305 	zval_ptr_dtor(&args[0]);
1306 
1307 	return ret;
1308 
1309 }
1310 
php_userstreamop_readdir(php_stream * stream,char * buf,size_t count)1311 static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count)
1312 {
1313 	zval func_name;
1314 	zval retval;
1315 	int call_result;
1316 	size_t didread = 0;
1317 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1318 	php_stream_dirent *ent = (php_stream_dirent*)buf;
1319 
1320 	/* avoid problems if someone mis-uses the stream */
1321 	if (count != sizeof(php_stream_dirent))
1322 		return -1;
1323 
1324 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1);
1325 
1326 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1327 
1328 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
1329 		convert_to_string(&retval);
1330 		PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval));
1331 		ent->d_type = DT_UNKNOWN;
1332 
1333 		didread = sizeof(php_stream_dirent);
1334 	} else if (call_result == FAILURE) {
1335 		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
1336 				ZSTR_VAL(us->wrapper->ce->name));
1337 	}
1338 
1339 	zval_ptr_dtor(&retval);
1340 	zval_ptr_dtor(&func_name);
1341 
1342 	return didread;
1343 }
1344 
php_userstreamop_closedir(php_stream * stream,int close_handle)1345 static int php_userstreamop_closedir(php_stream *stream, int close_handle)
1346 {
1347 	zval func_name;
1348 	zval retval;
1349 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1350 
1351 	assert(us != NULL);
1352 
1353 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1);
1354 
1355 	call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1356 
1357 	zval_ptr_dtor(&retval);
1358 	zval_ptr_dtor(&func_name);
1359 	zval_ptr_dtor(&us->object);
1360 	ZVAL_UNDEF(&us->object);
1361 
1362 	efree(us);
1363 
1364 	return 0;
1365 }
1366 
php_userstreamop_rewinddir(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)1367 static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1368 {
1369 	zval func_name;
1370 	zval retval;
1371 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1372 
1373 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1);
1374 
1375 	call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
1376 
1377 	zval_ptr_dtor(&retval);
1378 	zval_ptr_dtor(&func_name);
1379 
1380 	return 0;
1381 
1382 }
1383 
php_userstreamop_cast(php_stream * stream,int castas,void ** retptr)1384 static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
1385 {
1386 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1387 	zval func_name;
1388 	zval retval;
1389 	zval args[1];
1390 	php_stream * intstream = NULL;
1391 	int call_result;
1392 	int ret = FAILURE;
1393 	/* If we are checking if the stream can cast, no return pointer is provided, so do not emit errors */
1394 	bool report_errors = retptr;
1395 
1396 	ZVAL_STRINGL(&func_name, USERSTREAM_CAST, sizeof(USERSTREAM_CAST)-1);
1397 
1398 	switch(castas) {
1399 	case PHP_STREAM_AS_FD_FOR_SELECT:
1400 		ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT);
1401 		break;
1402 	default:
1403 		ZVAL_LONG(&args[0], PHP_STREAM_AS_STDIO);
1404 		break;
1405 	}
1406 
1407 	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
1408 
1409 	do {
1410 		if (call_result == FAILURE) {
1411 			if (report_errors) {
1412 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
1413 						ZSTR_VAL(us->wrapper->ce->name));
1414 			}
1415 			break;
1416 		}
1417 		if (!zend_is_true(&retval)) {
1418 			break;
1419 		}
1420 		php_stream_from_zval_no_verify(intstream, &retval);
1421 		if (!intstream) {
1422 			if (report_errors) {
1423 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource",
1424 						ZSTR_VAL(us->wrapper->ce->name));
1425 			}
1426 			break;
1427 		}
1428 		if (intstream == stream) {
1429 			if (report_errors) {
1430 				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself",
1431 						ZSTR_VAL(us->wrapper->ce->name));
1432 			}
1433 			intstream = NULL;
1434 			break;
1435 		}
1436 		ret = php_stream_cast(intstream, castas, retptr, 1);
1437 	} while (0);
1438 
1439 	zval_ptr_dtor(&retval);
1440 	zval_ptr_dtor(&func_name);
1441 	zval_ptr_dtor(&args[0]);
1442 
1443 	return ret;
1444 }
1445 
1446 const php_stream_ops php_stream_userspace_ops = {
1447 	php_userstreamop_write, php_userstreamop_read,
1448 	php_userstreamop_close, php_userstreamop_flush,
1449 	"user-space",
1450 	php_userstreamop_seek,
1451 	php_userstreamop_cast,
1452 	php_userstreamop_stat,
1453 	php_userstreamop_set_option,
1454 };
1455 
1456 const php_stream_ops php_stream_userspace_dir_ops = {
1457 	NULL, /* write */
1458 	php_userstreamop_readdir,
1459 	php_userstreamop_closedir,
1460 	NULL, /* flush */
1461 	"user-space-dir",
1462 	php_userstreamop_rewinddir,
1463 	NULL, /* cast */
1464 	NULL, /* stat */
1465 	NULL  /* set_option */
1466 };
1467