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