]> granicus.if.org Git - php/commitdiff
Implement user-space streams.
authorWez Furlong <wez@php.net>
Tue, 19 Mar 2002 03:51:01 +0000 (03:51 +0000)
committerWez Furlong <wez@php.net>
Tue, 19 Mar 2002 03:51:01 +0000 (03:51 +0000)
There's probably room for improvement,
docs will following some time this week.

ext/standard/basic_functions.c
ext/standard/file.h
ext/standard/ftp_fopen_wrapper.c
ext/standard/http_fopen_wrapper.c
ext/standard/php_fopen_wrapper.c
ext/standard/php_fopen_wrappers.h
ext/zlib/zlib.c
main/php_streams.h
main/streams.c
main/user_streams.c [new file with mode: 0644]

index f77eda437c194164bae8e8382b10d524b542601a..a056db2fa0fe0b2d828352e8687c003770f0beb2 100644 (file)
@@ -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)
index a6929ad6582036817595097e01bc2e554bed4630..994ec7be875b453b43a763abdd2791eb8290886f 100644 (file)
@@ -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);
index f204d1549fc42f373f7f9cbd1e8626ab72577e1e..6ef61b9e7bb46c4d7954c79f09aa0618a26f23a4 100644 (file)
@@ -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;
index aeaec330a81e745cab26b20be3f7d0b90e6075fb..06508b4c5ac0ae6cced5042ff17f8b0be3527a6c 100644 (file)
@@ -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
 };
 
index cd1929c7683b36f846c762277286e35fed451f6a..8677486c52bbcd1815c22b86947ab438ccb9627a 100644 (file)
@@ -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
 };
 
index 3d23975b44602766fc9fed570e9999cbe3ccd357..5178d74ce15ce2d77ff15909017c853c32f3a4d7 100644 (file)
@@ -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;
index 69060165bf1c1c04a9519cb0d1bff66ab7b88128..76aafb3ee6792a5168eb43e05a90eeefbd5d182d 100644 (file)
@@ -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();
index 28f9e89794820a17da8e36a18ad368831b684930..4a4690253638cdc83a91d5245bbd252aae6ed8a4 100755 (executable)
@@ -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
 
 /*
index a448fa2a9951206a3c0c0e8187d970567977de84..a4f3d6ee21ebc01bbb07dad4fcc7c8a1f3add852 100755 (executable)
@@ -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 (file)
index 0000000..164639e
--- /dev/null
@@ -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"
+};
+
+