]> granicus.if.org Git - php/commitdiff
Implement filter API for streams.
authorWez Furlong <wez@php.net>
Tue, 20 Aug 2002 20:47:47 +0000 (20:47 +0000)
committerWez Furlong <wez@php.net>
Tue, 20 Aug 2002 20:47:47 +0000 (20:47 +0000)
Filters can be stacked onto a stream; more details will follow in docs and
on php-dev.

Implement "string.rot13" filter

Allows the following script:

$fp = fopen("file.txt", "r");
stream_filter_prepend($fp, "string.rot13");

// File contents will be subject to a rot13 transformation before
// being output.
fpassthru($fp);
fclose($fp);

ext/standard/basic_functions.c
ext/standard/file.c
ext/standard/file.h
ext/standard/php_string.h
ext/standard/string.c
main/php_streams.h
main/streams.c

index e9abf4e74967e8bd91a3ec2665ff4c1fb102f682..c25f874a9e4e93ebd6fd2e715dcf238d325d6ef7 100644 (file)
@@ -607,6 +607,8 @@ function_entry basic_functions[] = {
        PHP_FE(stream_context_set_params,                                                                               NULL)
        PHP_FE(stream_context_set_option,                                                                               NULL)
        PHP_FE(stream_context_get_options,                                                                              NULL)
+       PHP_FE(stream_filter_prepend,                                                                                   NULL)
+       PHP_FE(stream_filter_append,                                                                                    NULL)
        PHP_FE(fgetcsv,                                                                                                                 NULL)
        PHP_FE(flock,                                                                                                                   NULL)
        PHP_FE(get_meta_tags,                                                                                                   NULL)
@@ -980,6 +982,7 @@ PHP_MINIT_FUNCTION(basic)
        PHP_MINIT(file) (INIT_FUNC_ARGS_PASSTHRU);
        PHP_MINIT(pack) (INIT_FUNC_ARGS_PASSTHRU);
        PHP_MINIT(browscap) (INIT_FUNC_ARGS_PASSTHRU);
+       PHP_MINIT(string_filters) (INIT_FUNC_ARGS_PASSTHRU);
 
 #if defined(HAVE_LOCALECONV) && defined(ZTS)
        PHP_MINIT(localeconv) (INIT_FUNC_ARGS_PASSTHRU);
@@ -1043,6 +1046,7 @@ PHP_MSHUTDOWN_FUNCTION(basic)
        PHP_MSHUTDOWN(assert) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
        PHP_MSHUTDOWN(url_scanner_ex) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
        PHP_MSHUTDOWN(file) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
+       PHP_MSHUTDOWN(string_filters) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
 #if defined(HAVE_LOCALECONV) && defined(ZTS)
        PHP_MSHUTDOWN(localeconv) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
 #endif
index 0404fa0a1b683c8f90d077df969af25c903d3439..bd0202f0182c7852379ba6183878ad5ca1ebb7ca 100644 (file)
@@ -724,7 +724,7 @@ PHP_FUNCTION(stream_context_set_option)
        zval *options = NULL, *zcontext = NULL, *zvalue = NULL;
        php_stream_context *context;
        char *wrappername, *optionname;
-       long wrapperlen, optionlen;
+       int wrapperlen, optionlen;
 
        if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC,
                                "rssz", &zcontext, &wrappername, &wrapperlen,
@@ -791,6 +791,49 @@ PHP_FUNCTION(stream_context_create)
 }
 /* }}} */
 
+static void apply_filter_to_stream(int append, INTERNAL_FUNCTION_PARAMETERS)
+{
+       zval *zstream;
+       php_stream *stream;
+       char *filtername, *filterparams = NULL;
+       int filternamelen, filterparamslen = 0;
+       php_stream_filter *filter;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|s", &zstream,
+                               &filtername, &filternamelen, &filterparams, &filterparamslen) == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       ZEND_FETCH_RESOURCE(stream, php_stream*, &zstream, -1, "stream", le_stream);
+       
+       filter = php_stream_filter_create(filtername, filterparams, filterparamslen, php_stream_is_persistent(stream) TSRMLS_CC);
+       if (filter == NULL)
+               RETURN_FALSE;
+
+       if (append)
+               php_stream_filter_append(stream, filter);
+       else
+               php_stream_filter_prepend(stream, filter);
+
+       RETURN_TRUE;
+}
+
+/* {{{ proto bool stream_filter_prepend(resource stream, string filtername[, string filterparams])
+   Prepend a filter to a stream */
+PHP_FUNCTION(stream_filter_prepend)
+{
+       apply_filter_to_stream(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+/* }}} */
+
+/* {{{ proto bool stream_filter_append(resource stream, string filtername[, string filterparams])
+   Append a filter to a stream */
+PHP_FUNCTION(stream_filter_append)
+{
+       apply_filter_to_stream(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+/* }}} */
+
 /* {{{ proto resource fopen(string filename, string mode [, bool use_include_path [, resource context]])
    Open a file or a URL and return a file pointer */
 PHP_NAMED_FUNCTION(php_if_fopen)
index 0ea36c7907f76db89dd66ff1e373c698615c8ca2..c2af1656791aa9a55ffb931e613df49895bcdc57 100644 (file)
@@ -77,6 +77,8 @@ PHP_FUNCTION(stream_context_create);
 PHP_FUNCTION(stream_context_set_params);
 PHP_FUNCTION(stream_context_set_option);
 PHP_FUNCTION(stream_context_get_options);
+PHP_FUNCTION(stream_filter_prepend);
+PHP_FUNCTION(stream_filter_append);
 PHP_MINIT_FUNCTION(user_streams);
 
 PHPAPI int php_set_sock_blocking(int socketd, int block TSRMLS_DC);
index d63f91b2549b5c88c9998796a0c03ed66e603df1..6fbc57bd4257b38f7f7de7060351af25919993a8 100644 (file)
@@ -89,6 +89,9 @@ PHP_FUNCTION(strcoll);
 PHP_FUNCTION(money_format);
 #endif
 
+PHP_MINIT_FUNCTION(string_filters);
+PHP_MSHUTDOWN_FUNCTION(string_filters);
+
 #if defined(HAVE_LOCALECONV) && defined(ZTS)
 PHP_MINIT_FUNCTION(localeconv);
 PHP_MSHUTDOWN_FUNCTION(localeconv);
index 0940eab539785b42e19e9e222090b240dce8b580..3b62ea00c44ce52fe374467d1e7b42808da1cd4f 100644 (file)
@@ -3883,15 +3883,15 @@ PHP_FUNCTION(sscanf)
 }
 /* }}} */
 
+static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
+
 /* {{{ proto string str_rot13(string str)
    Perform the rot13 transform on a string */
 PHP_FUNCTION(str_rot13)
 {
        zval **arg;
 
-    static char xfrom[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
-    static char xto[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
-
        if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg)) {
                WRONG_PARAM_COUNT;
        }
@@ -3899,7 +3899,7 @@ PHP_FUNCTION(str_rot13)
        *return_value = **arg;
        zval_copy_ctor(return_value);
 
-    php_strtr(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), xfrom, xto, 52);
+    php_strtr(Z_STRVAL_P(return_value), Z_STRLEN_P(return_value), rot13_from, rot13_to, 52);
 }
 /* }}} */
 
@@ -3926,6 +3926,82 @@ PHP_FUNCTION(money_format) {
 /* }}} */
 #endif
 
+/* {{{ rot13 stream filter implementation */
+static size_t strfilter_rot13_write(php_stream *stream, php_stream_filter *thisfilter,
+                       const char *buf, size_t count TSRMLS_DC)
+{
+       char rotbuf[1024];
+       size_t chunk;
+       size_t wrote = 0;
+
+       while (count > 0) {
+               chunk = count;
+               if (chunk > sizeof(rotbuf))
+                       chunk = sizeof(rotbuf);
+
+               PHP_STRLCPY(rotbuf, buf, sizeof(rotbuf), chunk);
+               buf += chunk;
+               count -= chunk;
+
+               php_strtr(rotbuf, chunk, rot13_from, rot13_to, 52);
+               wrote += php_stream_filter_write_next(stream, thisfilter, rotbuf, chunk);
+       }
+
+       return wrote;
+}
+
+static size_t strfilter_rot13_read(php_stream *stream, php_stream_filter *thisfilter,
+                       char *buf, size_t count TSRMLS_DC)
+{
+       size_t read;
+
+       read = php_stream_filter_read_next(stream, thisfilter, buf, count);
+       php_strtr(buf, read, rot13_from, rot13_to, 52);
+
+       return read;
+}
+
+static int strfilter_rot13_flush(php_stream *stream, php_stream_filter *thisfilter TSRMLS_DC)
+{
+       return php_stream_filter_flush_next(stream, thisfilter);
+}
+
+static int strfilter_rot13_eof(php_stream *stream, php_stream_filter *thisfilter TSRMLS_DC)
+{
+       return php_stream_filter_eof_next(stream, thisfilter);
+}
+
+
+static php_stream_filter_ops strfilter_rot13_ops = {
+       strfilter_rot13_write,
+       strfilter_rot13_read,
+       strfilter_rot13_flush,
+       strfilter_rot13_eof,
+       NULL,
+       "string.rot13"
+};
+
+static php_stream_filter *strfilter_rot13_create(const char *filtername, const char *filterparams,
+               int filterparamslen, int persistent TSRMLS_DC)
+{
+       return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent);
+}
+
+static php_stream_filter_factory strfilter_rot13_factory = {
+       strfilter_rot13_create
+};
+
+PHP_MINIT_FUNCTION(string_filters)
+{
+       return php_stream_filter_register_factory("string.rot13", &strfilter_rot13_factory);
+}
+
+PHP_MSHUTDOWN_FUNCTION(string_filters)
+{
+       return php_stream_filter_unregister_factory("string.rot13");
+}
+/* }}} */
+
 /*
  * Local variables:
  * tab-width: 4
index e226d93195bc95f0f0cc59483dd989adaab5ec14..867048f4a8930b64328441f100182929eb781cc3 100755 (executable)
@@ -91,6 +91,7 @@
 typedef struct _php_stream php_stream;
 typedef struct _php_stream_wrapper php_stream_wrapper;
 typedef struct _php_stream_context php_stream_context;
+typedef struct _php_stream_filter php_stream_filter;
 
 /* callback for status notifications */
 typedef void (*php_stream_notification_func)(php_stream_context *context,
@@ -176,13 +177,51 @@ struct _php_stream_wrapper        {
        char **err_stack;
 };
 
-/* pushes an error message onto the stack for a wrapper instance */
-PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...);
+typedef struct _php_stream_filter_ops {
+       size_t (*write)(php_stream *stream, php_stream_filter *thisfilter,
+                       const char *buf, size_t count TSRMLS_DC);
+       size_t (*read)(php_stream *stream, php_stream_filter *thisfilter,
+                       char *buf, size_t count TSRMLS_DC);
+       int (*flush)(php_stream *stream, php_stream_filter *thisfilter TSRMLS_DC);
+       int (*eof)(php_stream *stream, php_stream_filter *thisfilter TSRMLS_DC);
+       void (*dtor)(php_stream_filter *thisfilter TSRMLS_DC);
+       const char *label;
+} php_stream_filter_ops;
+
+struct _php_stream_filter {
+       php_stream_filter_ops *fops;
+       void *abstract; /* for use by filter implementation */
+       php_stream_filter *next;
+       php_stream_filter *prev;
+       int is_persistent;
+       php_stream *stream;
+};
+
+#define php_stream_filter_write_next(stream, thisfilter, buf, size) \
+       (thisfilter)->next ? (thisfilter)->next->fops->write((stream), (thisfilter)->next, (buf), (size) TSRMLS_CC) \
+       : (stream)->ops->write((stream), (buf), (size) TSRMLS_CC)
+
+#define php_stream_filter_read_next(stream, thisfilter, buf, size) \
+       (thisfilter)->next ? (thisfilter)->next->fops->read((stream), (thisfilter)->next, (buf), (size) TSRMLS_CC) \
+       : (stream)->ops->read((stream), (buf), (size) TSRMLS_CC)
 
+#define php_stream_filter_flush_next(stream, thisfilter) \
+       (thisfilter)->next ? (thisfilter)->next->fops->flush((stream), (thisfilter) TSRMLS_CC) \
+       : (stream)->ops->flush((stream) TSRMLS_CC)
+
+#define php_stream_filter_eof_next(stream, thisfilter) \
+       (thisfilter)->next ? (thisfilter)->next->fops->eof((stream), (thisfilter) TSRMLS_CC) \
+       : (stream)->ops->read((stream), NULL, 0 TSRMLS_CC) == EOF ? 1 : 0
+
+
+       
 struct _php_stream  {
        php_stream_ops *ops;
        void *abstract;                 /* convenience pointer for abstraction */
 
+       php_stream_filter *filterhead;
+       php_stream_filter *filtertail;
+       
        php_stream_wrapper *wrapper; /* which wrapper was used to open the stream */
        void *wrapperthis;              /* convenience pointer for a instance of a wrapper */
        zval *wrapperdata;              /* fgetwrapperdata retrieves this */
@@ -198,6 +237,7 @@ struct _php_stream  {
        FILE *stdiocast;    /* cache this, otherwise we might leak! */
 #if ZEND_DEBUG
        int __exposed;  /* non-zero if exposed as a zval somewhere */
+       char *__orig_path; /* it really helps when debugging "unclosed" streams */
 #endif
 
        php_stream_context *context;
@@ -213,8 +253,28 @@ PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract,
                int persistent, const char *mode STREAMS_DC TSRMLS_DC);
 #define php_stream_alloc(ops, thisptr, persistent, mode)       _php_stream_alloc((ops), (thisptr), (persistent), (mode) STREAMS_CC TSRMLS_CC)
 
-#define php_stream_get_resource_id(stream)             (stream)->rsrc_id
+/* stack filter onto a stream */
+PHPAPI void php_stream_filter_prepend(php_stream *stream, php_stream_filter *filter);
+PHPAPI void php_stream_filter_append(php_stream *stream, php_stream_filter *filter);
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream *stream, php_stream_filter *filter, int call_dtor TSRMLS_DC);
+PHPAPI void php_stream_filter_free(php_stream_filter *filter TSRMLS_DC);
+PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, int persistent STREAMS_DC TSRMLS_DC);
+#define php_stream_filter_alloc(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_CC TSRMLS_CC)
+#define php_stream_filter_alloc_rel(fops, thisptr, persistent) _php_stream_filter_alloc((fops), (thisptr), (persistent) STREAMS_REL_CC TSRMLS_CC)
 
+#define php_stream_filter_remove_head(stream, call_dtor)       php_stream_filter_remove((stream), (stream)->filterhead, (call_dtor) TSRMLS_CC)
+#define php_stream_filter_remove_tail(stream, call_dtor)       php_stream_filter_remove((stream), (stream)->filtertail, (call_dtor) TSRMLS_CC)
+
+typedef struct _php_stream_filter_factory {
+       php_stream_filter *(*create_filter)(const char *filtername, const char *filterparams, int filterparamslen, int persistent TSRMLS_DC);
+} php_stream_filter_factory;
+
+PHPAPI int php_stream_filter_register_factory(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC);
+PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern TSRMLS_DC);
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, const char *filterparams, int filterparamslen, int persistent TSRMLS_DC);
+
+
+#define php_stream_get_resource_id(stream)             (stream)->rsrc_id
 #if ZEND_DEBUG
 /* use this to tell the stream that it is OK if we don't explicitly close it */
 # define php_stream_auto_cleanup(stream)       { (stream)->__exposed++; }
@@ -403,13 +463,17 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char
 #define php_stream_open_wrapper(path, mode, options, opened)   _php_stream_open_wrapper_ex((path), (mode), (options), (opened), NULL STREAMS_CC TSRMLS_CC)
 #define php_stream_open_wrapper_ex(path, mode, options, opened, context)       _php_stream_open_wrapper_ex((path), (mode), (options), (opened), (context) STREAMS_CC TSRMLS_CC)
 
+/* pushes an error message onto the stack for a wrapper instance */
+PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...);
+
+
 #define PHP_STREAM_UNCHANGED   0 /* orig stream was seekable anyway */
 #define PHP_STREAM_RELEASED            1 /* newstream should be used; origstream is no longer valid */
 #define PHP_STREAM_FAILED              2 /* an error occurred while attempting conversion */
 #define PHP_STREAM_CRITICAL            3 /* an error occurred; origstream is in an unknown state; you should close origstream */
-/* DO NOT call this on streams that are referenced by resources! */
 #define PHP_STREAM_NO_PREFERENCE       0
 #define PHP_STREAM_PREFER_STDIO                1
+/* DO NOT call this on streams that are referenced by resources! */
 PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC TSRMLS_DC);
 #define php_stream_make_seekable(origstream, newstream, flags) _php_stream_make_seekable((origstream), (newstream), (flags) STREAMS_CC TSRMLS_CC)
 
index 50b2099d63c13a58629abe4c4b1ad546678dd7ee..0cfe5d38b37e82aab11aa96e35d4e8a3ba438b1a 100755 (executable)
@@ -111,6 +111,8 @@ fprintf(stderr, "stream_free: %s:%p in_free=%d opts=%08x\n", stream->ops->label,
 
        stream->in_free++;
 
+       php_stream_flush(stream);
+       
        if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0) {
                /* Remove entry from the resource list */
                zend_list_delete(stream->rsrc_id);
@@ -139,6 +141,10 @@ fprintf(stderr, "stream_free: %s:%p in_free=%d opts=%08x\n", stream->ops->label,
        }
 
        if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
+               
+               while (stream->filterhead) {
+                       php_stream_filter_remove_head(stream, 1);
+               }
 
                if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
                        stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
@@ -156,7 +162,7 @@ fprintf(stderr, "stream_free: %s:%p in_free=%d opts=%08x\n", stream->ops->label,
                         * as leaked; it will log a warning, but lets help it out and display what kind
                         * of stream it was. */
                        char leakbuf[512];
-                       snprintf(leakbuf, sizeof(leakbuf), __FILE__ "(%d) : Stream of type '%s' 0x%08X was not closed\n", __LINE__, stream->ops->label, (unsigned int)stream);
+                       snprintf(leakbuf, sizeof(leakbuf), __FILE__ "(%d) : Stream of type '%s' 0x%08X (path:%s) was not closed\n", __LINE__, stream->ops->label, (unsigned int)stream, stream->__orig_path);
 # if defined(PHP_WIN32)
                        OutputDebugString(leakbuf);
 # else
@@ -172,10 +178,133 @@ fprintf(stderr, "stream_free: %s:%p in_free=%d opts=%08x\n", stream->ops->label,
 }
 /* }}} */
 
+static HashTable stream_filters_hash;
+
+PHPAPI int php_stream_filter_register_factory(const char *filterpattern, php_stream_filter_factory *factory TSRMLS_DC)
+{
+       return zend_hash_add(&stream_filters_hash, (char*)filterpattern, strlen(filterpattern), factory, sizeof(*factory), NULL);
+}
+
+PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern TSRMLS_DC)
+{
+       return zend_hash_del(&stream_filters_hash, (char*)filterpattern, strlen(filterpattern));
+}
+
+/* We allow very simple pattern matching for filter factories:
+ * if "charset.utf-8/sjis" is requested, we search first for an exact
+ * match. If that fails, we try "charset.*".
+ * This means that we don't need to clog up the hashtable with a zillion
+ * charsets (for example) but still be able to provide them all as filters */
+PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, const char *filterparams, int filterparamslen, int persistent TSRMLS_DC)
+{
+       php_stream_filter_factory *factory;
+       php_stream_filter *filter = NULL;
+       int n;
+       char *period;
+
+       n = strlen(filtername);
+       
+       if (SUCCESS == zend_hash_find(&stream_filters_hash, (char*)filtername, n, (void**)&factory)) {
+               filter = factory->create_filter(filtername, filterparams, filterparamslen, persistent TSRMLS_CC);
+       } else if ((period = strchr(filtername, '.'))) {
+               /* try a wildcard */
+               char wildname[128];
+
+               PHP_STRLCPY(wildname, filtername, sizeof(wildname) - 1, period-filtername + 1);
+               strcat(wildname, "*");
+               
+               if (SUCCESS == zend_hash_find(&stream_filters_hash, wildname, strlen(wildname), (void**)&factory)) {
+                       filter = factory->create_filter(filtername, filterparams, filterparamslen, persistent TSRMLS_CC);
+               }
+       }
+
+       if (filter == NULL) {
+               /* TODO: these need correct docrefs */
+               if (factory == NULL)
+                       php_error_docref(NULL, E_WARNING TSRMLS_CC, "unable to locate filter \"%s\"", filtername);
+               else
+                       php_error_docref(NULL, E_WARNING TSRMLS_CC, "unable to create or locate filter \"%s\"", filtername);
+       }
+       
+       return filter;
+}
+
+PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, int persistent STREAMS_DC TSRMLS_DC)
+{
+       php_stream_filter *filter;
+
+       filter = (php_stream_filter*) pemalloc_rel_orig(sizeof(php_stream_filter), persistent);
+       memset(filter, 0, sizeof(php_stream_filter));
+
+       filter->fops = fops;
+       filter->abstract = abstract;
+       filter->is_persistent = persistent;
+       
+       return filter;
+}
+
+PHPAPI void php_stream_filter_free(php_stream_filter *filter TSRMLS_DC)
+{
+       if (filter->fops->dtor)
+               filter->fops->dtor(filter TSRMLS_CC);
+       pefree(filter, filter->is_persistent);
+}
+
+PHPAPI void php_stream_filter_prepend(php_stream *stream, php_stream_filter *filter)
+{
+       filter->next = stream->filterhead;
+       filter->prev = NULL;
+
+       if (stream->filterhead) {
+               stream->filterhead->prev = filter;
+       } else {
+               stream->filtertail = filter;
+       }
+       stream->filterhead = filter;
+       filter->stream = stream;
+}
+
+PHPAPI void php_stream_filter_append(php_stream *stream, php_stream_filter *filter)
+{
+       filter->prev = stream->filtertail;
+       filter->next = NULL;
+       if (stream->filtertail) {
+               stream->filtertail->next = filter;
+       } else {
+               stream->filterhead = filter;
+       }
+       stream->filtertail = filter;
+       filter->stream = stream;
+}
+
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream *stream, php_stream_filter *filter, int call_dtor TSRMLS_DC)
+{
+       assert(stream == filter->stream);
+       
+       if (filter->prev) {
+               filter->prev->next = filter->next;
+       } else {
+               stream->filterhead = filter->next;
+       }
+       if (filter->next) {
+               filter->next->prev = filter->prev;
+       } else {
+               stream->filtertail = filter->prev;
+       }
+       if (call_dtor) {
+               php_stream_filter_free(filter TSRMLS_CC);
+               return NULL;
+       }
+       return filter;
+}
+
 /* {{{ generic stream operations */
 PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
 {
-       return stream->ops->read == NULL ? 0 : stream->ops->read(stream, buf, size TSRMLS_CC);
+       if (stream->filterhead)
+               return stream->filterhead->fops->read(stream, stream->filterhead, buf, size TSRMLS_CC);
+       
+       return stream->ops->read(stream, buf, size TSRMLS_CC);
 }
 
 PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
@@ -183,7 +312,11 @@ PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
        /* we define our stream reading function so that it
           must return EOF when an EOF condition occurs, when
           working in unbuffered mode and called with these args */
-       return stream->ops->read == NULL ? -1 : stream->ops->read(stream, NULL, 0 TSRMLS_CC) == EOF ? 1 : 0;
+
+       if (stream->filterhead)
+               return stream->filterhead->fops->eof(stream, stream->filterhead TSRMLS_CC);
+       
+       return stream->ops->read(stream, NULL, 0 TSRMLS_CC) == EOF ? 1 : 0;
 }
 
 PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
@@ -240,30 +373,33 @@ PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_D
 
 PHPAPI char *_php_stream_gets(php_stream *stream, char *buf, size_t maxlen TSRMLS_DC)
 {
-
-       if (maxlen == 0) {
-               buf[0] = 0;
-               return buf;
-       }
-
-       if (stream->ops->gets) {
-               return stream->ops->gets(stream, buf, maxlen TSRMLS_CC);
-       } else if (stream->ops->read == NULL) {
+       if (maxlen == 0)
                return NULL;
-       } else {
-               /* unbuffered fgets - poor performance ! */
+
+       if (stream->filterhead || stream->ops->gets == NULL) {
+               /* unbuffered fgets - performance not so good! */
                char *c = buf;
 
                /* TODO: look at error returns? */
 
-               while (--maxlen > 0 && stream->ops->read(stream, buf, 1 TSRMLS_CC) == 1 && *buf++ != '\n');
+               while (--maxlen > 0 && php_stream_read(stream, buf, 1 TSRMLS_CC) == 1 && *buf++ != '\n')
+                       ;
                *buf = '\0';
+
                return c == buf && maxlen > 0 ? NULL : c;
+
+       } else if (stream->ops->gets) {
+               return stream->ops->gets(stream, buf, maxlen TSRMLS_CC);
        }
+       /* should not happen */
+       return NULL;
 }
 
 PHPAPI int _php_stream_flush(php_stream *stream TSRMLS_DC)
 {
+       if (stream->filterhead)
+               stream->filterhead->fops->flush(stream, stream->filterhead TSRMLS_CC);
+
        if (stream->ops->flush) {
                return stream->ops->flush(stream TSRMLS_CC);
        }
@@ -275,7 +411,12 @@ PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t coun
        assert(stream);
        if (buf == NULL || count == 0 || stream->ops->write == NULL)
                return 0;
-       return stream->ops->write(stream, buf, count TSRMLS_CC);
+
+       if (stream->filterhead) {
+               return stream->filterhead->fops->write(stream, stream->filterhead, buf, count TSRMLS_CC);
+       } else {
+               return stream->ops->write(stream, buf, count TSRMLS_CC);
+       }
 }
 
 PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
@@ -291,6 +432,10 @@ PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
 PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
 {
        if (stream->ops->seek) {
+
+               if (stream->filterhead)
+                       stream->filterhead->fops->flush(stream, stream->filterhead TSRMLS_CC);
+               
                return stream->ops->seek(stream, offset, whence TSRMLS_CC);
        }
 
@@ -331,6 +476,7 @@ PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
 
 #ifdef HAVE_MMAP
        if (!php_stream_is(stream, PHP_STREAM_IS_SOCKET)
+                       && stream->filterhead == NULL
                        && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fd, 0))
        {
                struct stat sbuf;
@@ -394,6 +540,7 @@ PHPAPI size_t _php_stream_copy_to_mem(php_stream *src, char **buf, size_t maxlen
         * buffering layer.
         * */
        if ( php_stream_is(src, PHP_STREAM_IS_STDIO) &&
+                       src->filterhead == NULL &&
                        php_stream_tell(src) == 0 &&
                        SUCCESS == php_stream_cast(src, PHP_STREAM_AS_FD, (void**)&srcfd, 0))
        {
@@ -475,6 +622,7 @@ PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size
         * buffering layer.
         * */
        if ( php_stream_is(src, PHP_STREAM_IS_STDIO) &&
+                       src->filterhead == NULL &&
                        php_stream_tell(src) == 0 &&
                        SUCCESS == php_stream_cast(src, PHP_STREAM_AS_FD, (void**)&srcfd, 0))
        {
@@ -1014,6 +1162,8 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
        int flags = castas & PHP_STREAM_CAST_MASK;
        castas &= ~PHP_STREAM_CAST_MASK;
 
+       /* filtered streams can only be cast as stdio, and only when fopencookie is present */
+       
        if (castas == PHP_STREAM_AS_STDIO) {
                if (stream->stdiocast) {
                        if (ret) {
@@ -1026,6 +1176,7 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
                 * first, to avoid doubling up the layers of stdio with an fopencookie */
                if (php_stream_is(stream, PHP_STREAM_IS_STDIO) &&
                                stream->ops->cast &&
+                               stream->filterhead == NULL &&
                                stream->ops->cast(stream, castas, ret TSRMLS_CC) == SUCCESS)
                {
                        goto exit_success;
@@ -1062,6 +1213,12 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
 #endif
 
        }
+
+       if (stream->filterhead) {
+               php_error_docref(NULL, E_WARNING TSRMLS_CC, "cannot cast a filtered stream on this system");
+               return FAILURE;
+       }
+       
        if (stream->ops->cast && stream->ops->cast(stream, castas, ret TSRMLS_CC) == SUCCESS)
                goto exit_success;
 
@@ -1105,12 +1262,13 @@ exit_success:
 /* {{{ wrapper init and registration */
 int php_init_stream_wrappers(TSRMLS_D)
 {
-       return zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1);
+       return zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS && zend_hash_init(&stream_filters_hash, 0, NULL, NULL, 1) == SUCCESS ? SUCCESS : FAILURE;
 }
 
 int php_shutdown_stream_wrappers(TSRMLS_D)
 {
        zend_hash_destroy(&url_stream_wrappers_hash);
+       zend_hash_destroy(&stream_filters_hash);
        return SUCCESS;
 }
 
@@ -1381,6 +1539,10 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio
        php_stream *stream = NULL;
        php_stream_wrapper *wrapper = NULL;
        char *path_to_open;
+#if ZEND_DEBUG
+       char *copy_of_path = NULL;
+#endif
+       
        
        if (opened_path)
                *opened_path = NULL;
@@ -1405,6 +1567,13 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio
                        stream->wrapper = wrapper;
        }
 
+#if ZEND_DEBUG
+       if (stream) {
+               copy_of_path = estrdup(path);
+               stream->__orig_path = copy_of_path;
+       }
+#endif
+       
        if (stream != NULL && (options & STREAM_MUST_SEEK)) {
                php_stream *newstream;
 
@@ -1412,6 +1581,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio
                        case PHP_STREAM_UNCHANGED:
                                return stream;
                        case PHP_STREAM_RELEASED:
+#if ZEND_DEBUG
+                               newstream->__orig_path = copy_of_path;
+#endif
                                return newstream;
                        default:
                                php_stream_close(stream);
@@ -1484,6 +1656,10 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int optio
                        efree(wrapper->err_stack);
                wrapper->err_stack = NULL;
        }
+#if ZEND_DEBUG
+       if (stream == NULL && copy_of_path != NULL)
+               efree(copy_of_path);
+#endif
        return stream;
 }
 /* }}} */
@@ -1498,7 +1674,7 @@ PHPAPI FILE * _php_stream_open_wrapper_as_file(char *path, char *mode, int optio
 
        if (stream == NULL)
                return NULL;
-
+       
        if (php_stream_cast(stream, PHP_STREAM_AS_STDIO|PHP_STREAM_CAST_TRY_HARD|PHP_STREAM_CAST_RELEASE,
                                (void**)&fp, REPORT_ERRORS) == FAILURE)
        {