summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/standard/basic_functions.c1
-rw-r--r--ext/standard/file.h1
-rw-r--r--ext/standard/ftp_fopen_wrapper.c3
-rw-r--r--ext/standard/http_fopen_wrapper.c5
-rw-r--r--ext/standard/php_fopen_wrapper.c3
-rw-r--r--ext/standard/php_fopen_wrappers.h4
-rw-r--r--ext/zlib/zlib.c2
-rwxr-xr-xmain/php_streams.h9
-rwxr-xr-xmain/streams.c6
-rw-r--r--main/user_streams.c484
10 files changed, 507 insertions, 11 deletions
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index f77eda437c..a056db2fa0 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -624,6 +624,7 @@ function_entry basic_functions[] = {
PHP_FE(socket_set_blocking, NULL)
PHP_FE(fgetwrapperdata, NULL)
+ PHP_FE(file_register_wrapper, NULL)
#if HAVE_SYS_TIME_H
PHP_FE(socket_set_timeout, NULL)
diff --git a/ext/standard/file.h b/ext/standard/file.h
index a6929ad658..994ec7be87 100644
--- a/ext/standard/file.h
+++ b/ext/standard/file.h
@@ -71,6 +71,7 @@ PHP_NAMED_FUNCTION(php_if_ftruncate);
PHP_NAMED_FUNCTION(php_if_fstat);
PHP_FUNCTION(fgetwrapperdata);
+PHP_FUNCTION(file_register_wrapper);
PHPAPI int php_set_sock_blocking(int socketd, int block);
PHPAPI int php_file_le_stream(void);
diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c
index f204d1549f..6ef61b9e7b 100644
--- a/ext/standard/ftp_fopen_wrapper.c
+++ b/ext/standard/ftp_fopen_wrapper.c
@@ -79,13 +79,14 @@ static int php_get_ftp_result(php_stream *stream TSRMLS_DC)
php_stream_wrapper php_stream_ftp_wrapper = {
php_stream_url_wrap_ftp,
+ NULL,
NULL
};
/* {{{ php_fopen_url_wrap_ftp
*/
-php_stream * php_stream_url_wrap_ftp(char *path, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC)
+php_stream * php_stream_url_wrap_ftp(char *path, char *mode, int options, char **opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC)
{
php_stream *stream=NULL;
php_url *resource=NULL;
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index aeaec330a8..06508b4c5a 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -71,7 +71,7 @@
#define HTTP_HEADER_BLOCK_SIZE 1024
-php_stream *php_stream_url_wrap_http(char *path, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC)
+php_stream *php_stream_url_wrap_http(char *path, char *mode, int options, char **opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC)
{
php_stream *stream = NULL;
php_url *resource = NULL;
@@ -268,7 +268,7 @@ php_stream *php_stream_url_wrap_http(char *path, char *mode, int options, char *
else {
strlcpy(new_path, location, sizeof(new_path));
}
- stream = php_stream_url_wrap_http(new_path, mode, options, opened_path STREAMS_CC TSRMLS_CC);
+ stream = php_stream_url_wrap_http(new_path, mode, options, opened_path, NULL STREAMS_CC TSRMLS_CC);
if (stream->wrapperdata) {
entryp = &entry;
MAKE_STD_ZVAL(entry);
@@ -311,6 +311,7 @@ out:
php_stream_wrapper php_stream_http_wrapper = {
php_stream_url_wrap_http,
+ NULL,
NULL
};
diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c
index cd1929c768..8677486c52 100644
--- a/ext/standard/php_fopen_wrapper.c
+++ b/ext/standard/php_fopen_wrapper.c
@@ -30,7 +30,7 @@
#include "php_standard.h"
#include "php_fopen_wrappers.h"
-php_stream * php_stream_url_wrap_php(char * path, char * mode, int options, char ** opened_path STREAMS_DC TSRMLS_DC)
+php_stream * php_stream_url_wrap_php(char * path, char * mode, int options, char ** opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC)
{
FILE * fp = NULL;
php_stream * stream = NULL;
@@ -54,6 +54,7 @@ php_stream * php_stream_url_wrap_php(char * path, char * mode, int options, char
php_stream_wrapper php_stream_php_wrapper = {
php_stream_url_wrap_php,
+ NULL,
NULL
};
diff --git a/ext/standard/php_fopen_wrappers.h b/ext/standard/php_fopen_wrappers.h
index 3d23975b44..5178d74ce1 100644
--- a/ext/standard/php_fopen_wrappers.h
+++ b/ext/standard/php_fopen_wrappers.h
@@ -23,8 +23,8 @@
#ifndef PHP_FOPEN_WRAPPERS_H
#define PHP_FOPEN_WRAPPERS_H
-php_stream *php_stream_url_wrap_http(char *path, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC);
-php_stream *php_stream_url_wrap_ftp(char *path, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC);
+php_stream *php_stream_url_wrap_http(char *path, char *mode, int options, char **opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC);
+php_stream *php_stream_url_wrap_ftp(char *path, char *mode, int options, char **opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC);
php_stream_wrapper php_stream_http_wrapper;
php_stream_wrapper php_stream_ftp_wrapper;
php_stream_wrapper php_stream_php_wrapper;
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index 69060165bf..76aafb3ee6 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -237,9 +237,7 @@ PHP_MINFO_FUNCTION(zlib)
{
php_info_print_table_start();
php_info_print_table_row(2, "ZLib Support", "enabled");
-#if HAVE_FOPENCOOKIE
php_info_print_table_row(2, "'zlib:' fopen wrapper", "enabled");
-#endif
php_info_print_table_row(2, "Compiled Version", ZLIB_VERSION );
php_info_print_table_row(2, "Linked Version", (char *)zlibVersion() );
php_info_print_table_end();
diff --git a/main/php_streams.h b/main/php_streams.h
index 28f9e89794..4a46902536 100755
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -101,12 +101,13 @@ typedef struct _php_stream_ops {
} php_stream_ops;
/* options uses the IGNORE_URL family of defines from fopen_wrappers.h */
-typedef php_stream *(*php_stream_factory_func_t)(char *filename, char *mode, int options, char **opened_path STREAMS_DC TSRMLS_DC);
+typedef php_stream *(*php_stream_factory_func_t)(char *filename, char *mode, int options, char **opened_path, void * wrappercontext STREAMS_DC TSRMLS_DC);
typedef void (*php_stream_wrapper_dtor_func_t)(php_stream *stream TSRMLS_DC);
typedef struct _php_stream_wrapper {
php_stream_factory_func_t create;
php_stream_wrapper_dtor_func_t destroy;
+ void * wrappercontext;
} php_stream_wrapper;
struct _php_stream {
@@ -253,6 +254,7 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
# define IGNORE_URL_WIN 0
#endif
+int php_init_user_streams(TSRMLS_D);
int php_init_stream_wrappers(TSRMLS_D);
int php_shutdown_stream_wrappers(TSRMLS_D);
PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC);
@@ -268,6 +270,11 @@ PHPAPI php_stream *_php_stream_open_wrapper(char *path, char *mode, int options,
PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream STREAMS_DC TSRMLS_DC);
#define php_stream_make_seekable(origstream, newstream) _php_stream_make_seekable(origstream, newstream STREAMS_CC TSRMLS_CC)
+
+/* for user-space streams */
+extern php_stream_ops php_stream_userspace_ops;
+#define PHP_STREAM_IS_USERSPACE &php_stream_userspace_ops
+
#endif
/*
diff --git a/main/streams.c b/main/streams.c
index a448fa2a99..a4f3d6ee21 100755
--- a/main/streams.c
+++ b/main/streams.c
@@ -891,7 +891,9 @@ exit_success:
int php_init_stream_wrappers(TSRMLS_D)
{
if (PG(allow_url_fopen)) {
- return zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1);
+ if (zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS)
+ return php_init_user_streams(TSRMLS_C);
+ return FAILURE;
}
return SUCCESS;
}
@@ -940,7 +942,7 @@ static php_stream *php_stream_open_url(char *path, char *mode, int options, char
protocol = NULL;
}
if (wrapper) {
- php_stream *stream = wrapper->create(path, mode, options, opened_path STREAMS_REL_CC TSRMLS_CC);
+ php_stream *stream = wrapper->create(path, mode, options, opened_path, wrapper->wrappercontext STREAMS_REL_CC TSRMLS_CC);
if (stream)
stream->wrapper = wrapper;
return stream;
diff --git a/main/user_streams.c b/main/user_streams.c
new file mode 100644
index 0000000000..164639e1fd
--- /dev/null
+++ b/main/user_streams.c
@@ -0,0 +1,484 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 4 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2002 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 2.02 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available at through the world-wide-web at |
+ | http://www.php.net/license/2_02.txt. |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: |
+ | Wez Furlong (wez@thebrainroom.com) |
+ +----------------------------------------------------------------------+
+ */
+
+#include "php.h"
+#include "php_globals.h"
+
+static int le_protocols;
+
+struct php_user_stream_wrapper {
+ char * protoname;
+ char * classname;
+ zend_class_entry *ce;
+ php_stream_wrapper wrapper;
+};
+
+static php_stream *user_wrapper_factory(char *filename, char *mode, int options, char **opened_path, void * wrappercontext STREAMS_DC TSRMLS_DC);
+
+static void stream_wrapper_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+ struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;
+
+ php_unregister_url_stream_wrapper(uwrap->protoname TSRMLS_CC);
+ efree(uwrap->protoname);
+ efree(uwrap->classname);
+ efree(uwrap);
+}
+
+int php_init_user_streams(TSRMLS_D)
+{
+ le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
+ return le_protocols == FAILURE ? FAILURE : SUCCESS;
+}
+
+struct _php_userstream_data {
+ struct php_user_stream_wrapper * wrapper;
+ zval * object;
+};
+typedef struct _php_userstream_data php_userstream_data_t;
+
+/* names of methods */
+#define USERSTREAM_OPEN "stream_open"
+#define USERSTREAM_CLOSE "stream_close"
+#define USERSTREAM_READ "stream_read"
+#define USERSTREAM_WRITE "stream_write"
+#define USERSTREAM_FLUSH "stream_flush"
+#define USERSTREAM_SEEK "stream_seek"
+#define USERSTREAM_GETS "stream_gets"
+#define USERSTREAM_TELL "stream_tell"
+#define USERSTREAM_EOF "stream_eof"
+
+/* class should have methods like these:
+
+function stream_open($path, $mode, $options, &$opened_path)
+ {
+ return true/false;
+ }
+ function stream_read($count)
+ {
+ return false on error;
+ else return string;
+ }
+ function stream_write($data)
+ {
+ return false on error;
+ else return count written;
+ }
+ function stream_close()
+ {
+ }
+ function stream_flush()
+ {
+ }
+ function stream_seek($offset, $whence)
+ {
+ }
+ function stream_gets($size)
+ {
+ return false on error;
+ else return string;
+ }
+
+ * */
+
+static php_stream *user_wrapper_factory(char *filename, char *mode, int options, char **opened_path, void *wrappercontext STREAMS_DC TSRMLS_DC)
+{
+ struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrappercontext;
+ php_userstream_data_t *us;
+ zval *zfilename, *zmode, *zopened, *zoptions, *zretval = NULL, *zfuncname;
+ zval **args[4];
+ int call_result;
+ php_stream *stream = NULL;
+
+ us = emalloc(sizeof(*us));
+ us->wrapper = uwrap;
+
+ /* create an instance of our class */
+ ALLOC_ZVAL(us->object);
+ object_init_ex(us->object, uwrap->ce);
+ ZVAL_REFCOUNT(us->object) = 1;
+ PZVAL_IS_REF(us->object) = 1;
+
+ /* call it's stream_open method - set up params first */
+ MAKE_STD_ZVAL(zfilename);
+ ZVAL_STRING(zfilename, filename, 1);
+ args[0] = &zfilename;
+
+ MAKE_STD_ZVAL(zmode);
+ ZVAL_STRING(zmode, mode, 1);
+ args[1] = &zmode;
+
+ MAKE_STD_ZVAL(zoptions);
+ ZVAL_LONG(zoptions, options);
+ args[2] = &zoptions;
+
+ MAKE_STD_ZVAL(zopened);
+ ZVAL_REFCOUNT(zopened) = 1;
+ PZVAL_IS_REF(zopened) = 1;
+ ZVAL_NULL(zopened);
+ args[3] = &zopened;
+
+ MAKE_STD_ZVAL(zfuncname);
+ ZVAL_STRING(zfuncname, USERSTREAM_OPEN, 1);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ zfuncname,
+ &zretval,
+ 4, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && zretval != NULL) {
+ /* the stream is now open! */
+ stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);
+
+ /* if the opened path is set, copy it out */
+ if (Z_TYPE_P(zopened) == IS_STRING && opened_path) {
+ *opened_path = estrndup(Z_STRVAL_P(zopened), Z_STRLEN_P(zopened));
+ }
+ } else {
+ /* destroy the object */
+ zval_ptr_dtor(&us->object);
+ efree(us);
+ }
+
+ /* destroy everything else */
+ if (zretval)
+ zval_ptr_dtor(&zretval);
+
+ zval_ptr_dtor(&zfuncname);
+ zval_ptr_dtor(&zopened);
+ zval_ptr_dtor(&zoptions);
+ zval_ptr_dtor(&zmode);
+ zval_ptr_dtor(&zfilename);
+
+ return stream;
+}
+
+/* {{{ proto bool file_register_wrapper(string protocol, string classname)
+ Registers a custom URL protocol handler class */
+PHP_FUNCTION(file_register_wrapper)
+{
+ char *protocol, *classname;
+ int protocol_len, classname_len;
+ struct php_user_stream_wrapper * uwrap;
+ int rsrc_id;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &protocol, &protocol_len, &classname, &classname_len) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (!PG(allow_url_fopen)) {
+ zend_error(E_WARNING, "%s(): fopen wrappers have been disabled", get_active_function_name(TSRMLS_C));
+ RETURN_FALSE;
+ }
+
+ uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
+ uwrap->protoname = estrndup(protocol, protocol_len);
+ uwrap->classname = estrndup(classname, classname_len);
+ uwrap->wrapper.create = user_wrapper_factory;
+ uwrap->wrapper.wrappercontext = uwrap;
+
+ zend_str_tolower(uwrap->classname, classname_len);
+ rsrc_id = ZEND_REGISTER_RESOURCE(NULL, uwrap, le_protocols);
+
+ if (zend_hash_find(EG(class_table), uwrap->classname, classname_len + 1, (void**)&uwrap->ce) == SUCCESS) {
+#ifdef ZEND_ENGINE_2
+ uwrap->ce = *(zend_class_entry**)uwrap->ce;
+#endif
+ if (php_register_url_stream_wrapper(protocol, &uwrap->wrapper TSRMLS_CC) == SUCCESS) {
+ RETURN_TRUE;
+ }
+ } else {
+ zend_error(E_WARNING, "%s(): class '%s' is undefined", get_active_function_name(TSRMLS_C),
+ classname);
+ }
+
+ zend_list_delete(rsrc_id);
+ RETURN_FALSE;
+}
+/* }}} */
+
+
+static size_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+ zval **args[1];
+ zval zbuff, *zbufptr;
+ size_t didwrite = 0;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1, 0);
+
+ ZVAL_STRINGL(&zbuff, (char*)buf, count, 0);
+ zbufptr = &zbuff;
+ args[0] = &zbufptr;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG)
+ didwrite = Z_LVAL_P(retval);
+ else
+ didwrite = 0;
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return didwrite;
+}
+
+static size_t php_userstreamop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ zval **args[1];
+ int call_result;
+ size_t didread = 0;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ if (buf == NULL && count == 0) {
+ ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && zval_is_true(retval))
+ didread = 0;
+ else
+ didread = EOF;
+
+ } else {
+ zval *zcount;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1, 0);
+
+ MAKE_STD_ZVAL(zcount);
+ ZVAL_LONG(zcount, count);
+ args[0] = &zcount;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+
+ if (retval && Z_TYPE_P(retval) == IS_STRING) {
+ didread = Z_STRLEN_P(retval);
+ if (didread > count) {
+ zend_error(E_WARNING, "%s::" USERSTREAM_READ " - read more data than requested; some data will be lost",
+ us->wrapper->classname);
+ didread = count;
+ }
+ if (didread > 0)
+ memcpy(buf, Z_STRVAL_P(retval), didread);
+ }
+
+ zval_ptr_dtor(&zcount);
+ }
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return didread;
+}
+
+static int php_userstreamop_close(php_stream *stream, int close_handle TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ zval_ptr_dtor(&us->object);
+
+ efree(us);
+
+ return 0;
+}
+
+static int php_userstreamop_flush(php_stream *stream TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+ if (call_result == SUCCESS && retval != NULL && zval_is_true(retval))
+ call_result = 0;
+ else
+ call_result = -1;
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return call_result;
+}
+
+static int php_userstreamop_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ int call_result;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ if (offset == 0 && whence == SEEK_CUR) {
+ ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1, 0);
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 0, NULL, 0, NULL TSRMLS_CC);
+
+
+ } else {
+ zval **args[2];
+ zval *zoffs, *zwhence;
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1, 0);
+
+ MAKE_STD_ZVAL(zoffs);
+ ZVAL_LONG(zoffs, offset);
+ args[0] = &zoffs;
+
+ MAKE_STD_ZVAL(zwhence);
+ ZVAL_LONG(zwhence, whence);
+ args[1] = &zwhence;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 2, args,
+ 0, NULL TSRMLS_CC);
+
+ zval_ptr_dtor(&zoffs);
+ zval_ptr_dtor(&zwhence);
+
+ }
+
+ if (call_result == SUCCESS && retval != NULL && Z_TYPE_P(retval) == IS_LONG)
+ call_result = Z_LVAL_P(retval);
+ else
+ call_result = -1;
+
+ if (retval)
+ zval_ptr_dtor(&retval);
+
+ return 0;
+}
+
+static char *php_userstreamop_gets(php_stream *stream, char *buf, size_t size TSRMLS_DC)
+{
+ zval func_name;
+ zval *retval = NULL;
+ zval *zcount;
+ zval **args[2];
+ int call_result;
+ size_t didread = 0;
+ php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
+
+ assert(us != NULL);
+
+ /* TODO: when the gets method is not available, fall back on emulated version using read */
+
+ ZVAL_STRINGL(&func_name, USERSTREAM_GETS, sizeof(USERSTREAM_GETS)-1, 0);
+
+ MAKE_STD_ZVAL(zcount);
+ ZVAL_LONG(zcount, size);
+ args[0] = &zcount;
+
+ call_result = call_user_function_ex(NULL,
+ &us->object,
+ &func_name,
+ &retval,
+ 1, args,
+ 0, NULL TSRMLS_CC);
+
+ if (retval && Z_TYPE_P(retval) == IS_STRING) {
+ didread = Z_STRLEN_P(retval);
+ if (didread > size) {
+ zend_error(E_WARNING, "%s::" USERSTREAM_GETS " - read more data than requested; some data will be lost",
+ us->wrapper->classname);
+ didread = size;
+ }
+ if (didread > 0)
+ memcpy(buf, Z_STRVAL_P(retval), didread);
+
+ zval_ptr_dtor(&retval);
+ }
+
+ if (retval)
+ zval_ptr_dtor(&zcount);
+
+ if (didread)
+ return buf;
+
+ return 0;
+}
+
+php_stream_ops php_stream_userspace_ops = {
+ php_userstreamop_write, php_userstreamop_read,
+ php_userstreamop_close, php_userstreamop_flush,
+ php_userstreamop_seek, php_userstreamop_gets,
+ NULL, /* cast */
+ "user-space"
+};
+
+