]> granicus.if.org Git - php/commitdiff
Implement user-space filters.
authorWez Furlong <wez@php.net>
Tue, 31 Dec 2002 18:39:36 +0000 (18:39 +0000)
committerWez Furlong <wez@php.net>
Tue, 31 Dec 2002 18:39:36 +0000 (18:39 +0000)
See ext/standard/tests/file/userfilters.phpt for an example of their use.

ext/standard/basic_functions.c
ext/standard/basic_functions.h
ext/standard/config.m4
ext/standard/tests/file/userfilters.phpt [new file with mode: 0644]
ext/standard/user_filters.c [new file with mode: 0644]

index 49b3adde87750dc26fb72f1546480356974ae570..ff7418c588fd4ef7b0fdbea5e09d77362aae5d32 100644 (file)
@@ -862,6 +862,7 @@ function_entry basic_functions[] = {
 #endif
 
        PHP_FE(str_rot13, NULL)
+       PHP_FE(stream_register_filter, NULL)
 
        /* functions from aggregate.c */
        PHP_FE(aggregate,                                               first_arg_force_ref)
@@ -964,6 +965,7 @@ static void basic_globals_ctor(php_basic_globals *basic_globals_p TSRMLS_DC)
        BG(left) = -1;
        BG(user_tick_functions) = NULL;
        BG(aggregation_table) = NULL;
+       BG(user_filter_map) = NULL;
        zend_hash_init(&BG(sm_protected_env_vars), 5, NULL, NULL, 1);
        BG(sm_allowed_env_vars) = NULL;
 
@@ -1030,6 +1032,7 @@ PHP_MINIT_FUNCTION(basic)
        PHP_MINIT(pack) (INIT_FUNC_ARGS_PASSTHRU);
        PHP_MINIT(browscap) (INIT_FUNC_ARGS_PASSTHRU);
        PHP_MINIT(string_filters) (INIT_FUNC_ARGS_PASSTHRU);
+       PHP_MINIT(user_filters) (INIT_FUNC_ARGS_PASSTHRU);
 
 #if defined(HAVE_LOCALECONV) && defined(ZTS)
        PHP_MINIT(localeconv) (INIT_FUNC_ARGS_PASSTHRU);
@@ -1191,6 +1194,12 @@ PHP_RSHUTDOWN_FUNCTION(basic)
                efree(BG(aggregation_table));
                BG(aggregation_table) = NULL;
        }
+
+       if (BG(user_filter_map)) {
+               zend_hash_destroy(BG(user_filter_map));
+               efree(BG(user_filter_map));
+               BG(user_filter_map) = NULL;
+       }
        
 #ifdef HAVE_MMAP
        if (BG(mmap_file)) {
index c002ad5c98e8d871750a36ba118da0bba2fd6db7..f1b9f4b4fc2b134adf5b948cb9cfd4fe44c1b3fa 100644 (file)
@@ -105,6 +105,8 @@ PHP_FUNCTION(move_uploaded_file);
 PHP_FUNCTION(parse_ini_file);
 
 PHP_FUNCTION(str_rot13);
+PHP_FUNCTION(stream_register_filter);
+PHP_MINIT_FUNCTION(user_filters);
 
 #ifdef PHP_WIN32
 typedef unsigned int php_stat_len;
@@ -187,6 +189,7 @@ typedef struct {
 #endif
 
        HashTable *aggregation_table;
+       HashTable *user_filter_map;
 } php_basic_globals;
 
 #ifdef ZTS
index cf0f0c2eeb1e77de36000ad8c9acd9baab55222e..98964bce2e5338d3240889cd713fd7f6c0cd98a9 100644 (file)
@@ -255,6 +255,6 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32.
                             url_scanner.c var.c versioning.c assert.c strnatcmp.c levenshtein.c \
                             incomplete_class.c url_scanner_ex.c ftp_fopen_wrapper.c \
                             http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \
-                            var_unserializer.c ftok.c aggregation.c sha1.c )
+                            var_unserializer.c ftok.c aggregation.c sha1.c user_filters.c )
 
 PHP_ADD_MAKEFILE_FRAGMENT
diff --git a/ext/standard/tests/file/userfilters.phpt b/ext/standard/tests/file/userfilters.phpt
new file mode 100644 (file)
index 0000000..543e6a3
--- /dev/null
@@ -0,0 +1,60 @@
+--TEST--
+User-space filters
+--FILE--
+<?php
+# vim600:syn=php:
+
+class UpperCaseFilter extends php_user_filter {
+       function oncreate()
+       {
+               echo "oncreate:\n";
+               var_dump($this->filtername);
+               var_dump($this->params);
+       }
+
+       function flush($closing)
+       {
+               echo "flush:\n";
+       }
+
+       function onclose()
+       {
+               echo "onclose:\n";
+       }
+
+       function write($data)
+       {
+               echo "write:\n";
+               $x = parent::write($data);
+               return $x;
+       }
+
+       function read($bytes)
+       {
+               echo "read:\n";
+               $x = parent::read($bytes);
+               return strtoupper($x);
+       }
+};
+
+var_dump(stream_register_filter("string.uppercase", "UpperCaseFilter"));
+$fp = tmpfile();
+
+fwrite($fp, "hello there");
+rewind($fp);
+
+var_dump(stream_filter_prepend($fp, "string.uppercase"));
+var_dump(fgets($fp));
+
+?>
+--EXPECT--
+bool(true)
+oncreate:
+string(16) "string.uppercase"
+NULL
+bool(true)
+read:
+read:
+string(11) "HELLO THERE"
+flush:
+onclose:
diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c
new file mode 100644 (file)
index 0000000..0edca81
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 4                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2003 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)                                   |
+   +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_globals.h"
+#include "ext/standard/basic_functions.h"
+#include "ext/standard/file.h"
+
+struct php_user_filter_data {
+       zend_class_entry *ce;
+       /* variable length; this *must* be last in the structure */
+       char classname[1];
+};
+
+/* to provide context for calling into the next filter from user-space */
+static int le_userfilters;
+
+#define GET_FILTER_FROM_OBJ()  { \
+       zval **tmp; \
+       if (FAILURE == zend_hash_index_find(Z_OBJPROP_P(this_ptr), 0, (void**)&tmp)) { \
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "filter property vanished"); \
+               RETURN_FALSE; \
+       } \
+       ZEND_FETCH_RESOURCE(filter, php_stream_filter*, tmp, -1, "filter", le_userfilters); \
+}
+
+/* define the base filter class */
+
+/* Descendants call this function to actually send the data on to the next
+ * filter (or the stream itself).
+ * The intention is to invoke it as parent::write($data)
+ * */
+PHP_FUNCTION(user_filter_write)
+{
+       char *data;
+       int data_len;
+       size_t wrote = 0;
+       php_stream_filter *filter;
+
+       GET_FILTER_FROM_OBJ();
+       
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       wrote = php_stream_filter_write_next(filter->stream, filter, data, data_len);
+
+       RETURN_LONG(wrote);
+}
+
+PHP_FUNCTION(user_filter_read)
+{
+       long data_to_read;
+       char *data;
+       size_t didread = 0;
+       php_stream_filter *filter;
+
+       RETVAL_FALSE;
+       
+       GET_FILTER_FROM_OBJ();
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &data_to_read) == FAILURE) {
+               RETURN_FALSE;
+       }
+       
+       data = emalloc(data_to_read + 1);
+       didread = php_stream_filter_read_next(filter->stream, filter, data, data_to_read);
+       
+       if (didread > 0) {
+               data = erealloc(data, didread + 1);
+               RETURN_STRINGL(data, didread, 0);
+       } else {
+               efree(data);
+               RETURN_FALSE;
+       }
+}
+
+PHP_FUNCTION(user_filter_flush)
+{
+       zend_bool closing;
+       php_stream_filter *filter;
+
+       GET_FILTER_FROM_OBJ();
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &closing) == FAILURE) {
+               RETURN_FALSE;
+       }
+       
+       RETURN_LONG(php_stream_filter_flush_next(filter->stream, filter, closing));
+}
+
+PHP_FUNCTION(user_filter_nop)
+{
+}
+
+static zend_function_entry user_filter_class_funcs[] = {
+       PHP_NAMED_FE(write,             PHP_FN(user_filter_write),              NULL)
+       PHP_NAMED_FE(read,              PHP_FN(user_filter_read),               NULL)
+       PHP_NAMED_FE(flush,             PHP_FN(user_filter_flush),              NULL)
+       PHP_NAMED_FE(oncreate,  PHP_FN(user_filter_nop),                NULL)
+       PHP_NAMED_FE(onclose,   PHP_FN(user_filter_nop),                NULL)
+       { NULL, NULL, NULL }
+};
+
+static zend_class_entry user_filter_class_entry;
+
+PHP_MINIT_FUNCTION(user_filters)
+{
+       /* init the filter class ancestor */
+       INIT_CLASS_ENTRY(user_filter_class_entry, "php_user_filter", user_filter_class_funcs);
+       if (NULL == zend_register_internal_class(&user_filter_class_entry TSRMLS_CC)) {
+               return FAILURE;
+       }
+
+       /* init the filter resource; it has no dtor, as streams will always clean it up
+        * at the correct time */
+       le_userfilters = zend_register_list_destructors_ex(NULL, NULL, "stream filter", 0);
+
+       if (le_userfilters == FAILURE)
+               return FAILURE;
+       
+       return SUCCESS;
+}
+
+static size_t userfilter_write(php_stream *stream, php_stream_filter *thisfilter,
+                       const char *buf, size_t count TSRMLS_DC)
+{
+       size_t wrote = 0;
+       zval *obj = (zval*)thisfilter->abstract;
+       zval func_name;
+       zval *retval = NULL;
+       zval **args[1];
+       zval *zbuf;
+       int call_result;
+
+       ZVAL_STRINGL(&func_name, "write", sizeof("write")-1, 0);
+
+       ALLOC_INIT_ZVAL(zbuf);
+       ZVAL_STRINGL(zbuf, (char*)buf, count, 1);
+
+       args[0] = &zbuf;
+
+       call_result = call_user_function_ex(NULL,
+                       &obj,
+                       &func_name,
+                       &retval,
+                       1, args,
+                       0, NULL TSRMLS_CC);
+
+       if (call_result == SUCCESS && retval != NULL) {
+               convert_to_long(retval);
+               wrote = Z_LVAL_P(retval);
+       } else if (call_result == FAILURE) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to call user-filter write function!?");
+       }
+
+       /* beware of buffer overruns */
+       if (wrote > count) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                               "wrote %d bytes more data than requested (%d written, %d max)",
+                               wrote - count,
+                               wrote,
+                               count);
+               wrote = count;
+       }
+
+       if (retval)
+               zval_ptr_dtor(&retval);
+       
+       return wrote;
+}
+
+static size_t userfilter_read(php_stream *stream, php_stream_filter *thisfilter,
+                       char *buf, size_t count TSRMLS_DC)
+{
+       size_t didread = 0;
+       zval *obj = (zval*)thisfilter->abstract;
+       zval func_name;
+       zval *retval = NULL;
+       zval **args[1];
+       zval *zcount;
+       int call_result;
+
+       ZVAL_STRINGL(&func_name, "read", sizeof("read")-1, 0);
+
+       ALLOC_INIT_ZVAL(zcount);
+       ZVAL_LONG(zcount, count);
+       args[0] = &zcount;
+
+       call_result = call_user_function_ex(NULL,
+                       &obj,
+                       &func_name,
+                       &retval,
+                       1, args,
+                       0, NULL TSRMLS_CC);
+
+       if (call_result == SUCCESS && retval != NULL) {
+               convert_to_string(retval);
+               didread = Z_STRLEN_P(retval);
+
+               if (didread > count) {
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                                       "read %d bytes more data than requested (%d read, %d max) - excess data will be lost",
+                                       didread - count, didread, count);
+                       didread = count;
+               }
+               if (didread > 0)
+                       memcpy(buf, Z_STRVAL_P(retval), didread);
+       } else if (call_result == FAILURE) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to call read function!");
+       }
+
+       if (retval)
+               zval_ptr_dtor(&retval);
+
+       zval_ptr_dtor(&zcount);
+       
+       return didread;
+}
+
+static int userfilter_flush(php_stream *stream, php_stream_filter *thisfilter, int closing TSRMLS_DC)
+{
+       int ret = EOF;
+       zval *obj = (zval*)thisfilter->abstract;
+       zval func_name;
+       zval *retval = NULL;
+       zval **args[1];
+       zval *zcount;
+       int call_result;
+
+       ZVAL_STRINGL(&func_name, "flush", sizeof("flush")-1, 0);
+
+       ALLOC_INIT_ZVAL(zcount);
+       ZVAL_BOOL(zcount, closing);
+       args[0] = &zcount;
+
+       call_result = call_user_function_ex(NULL,
+                       &obj,
+                       &func_name,
+                       &retval,
+                       1, args,
+                       0, NULL TSRMLS_CC);
+
+       if (call_result == SUCCESS && retval != NULL) {
+               convert_to_long(retval);
+               ret = Z_LVAL_P(retval);
+       } else if (call_result == FAILURE) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to call flush function");
+       }
+
+       if (retval)
+               zval_ptr_dtor(&retval);
+       zval_ptr_dtor(&zcount);
+
+       return ret;
+}
+
+static int userfilter_eof(php_stream *stream, php_stream_filter *thisfilter TSRMLS_DC)
+{
+       /* TODO: does this actually ever get called!? */
+       return php_stream_filter_eof_next(stream, thisfilter);
+}
+
+static void userfilter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
+{
+       zval *obj = (zval*)thisfilter->abstract;
+       zval func_name;
+       zval *retval = NULL;
+       zval **tmp; 
+
+       ZVAL_STRINGL(&func_name, "onclose", sizeof("onclose")-1, 0);
+
+       call_user_function_ex(NULL,
+                       &obj,
+                       &func_name,
+                       &retval,
+                       0, NULL,
+                       0, NULL TSRMLS_CC);
+
+       if (retval)
+               zval_ptr_dtor(&retval);
+
+       if (SUCCESS == zend_hash_index_find(Z_OBJPROP_P(obj), 0, (void**)&tmp)) { 
+               zend_list_delete(Z_LVAL_PP(tmp));
+               FREE_ZVAL(*tmp);
+       } 
+
+       /* kill the object */
+       zval_ptr_dtor(&obj);
+}
+
+static php_stream_filter_ops userfilter_ops = {
+       userfilter_write,
+       userfilter_read,
+       userfilter_flush,
+       userfilter_eof,
+       userfilter_dtor,
+       "user-filter"
+};
+
+static php_stream_filter *user_filter_factory_create(const char *filtername,
+               const char *filterparams, int filterparamslen, int persistent TSRMLS_DC)
+{
+       struct php_user_filter_data *fdat = NULL;
+       php_stream_filter *filter;
+       zval *obj, *zfilter;
+       zval func_name;
+       zval *retval = NULL;
+       zval **tmp; 
+       
+       /* some sanity checks */
+       if (persistent) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                               "cannot use a user-space filter with a persistent stream");
+               return NULL;
+       }
+
+       /* determine the classname/class entry */
+       if (FAILURE == zend_hash_find(BG(user_filter_map), (char*)filtername,
+                               strlen(filtername), (void**)&fdat)) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                               "Err, filter \"%s\" is not in the user-filter map, but somehow the user-filter-factory was invoked for it!?", filtername);
+               return NULL;
+       }
+
+       /* bind the classname to the actual class */
+       if (fdat->ce == NULL) {
+               if (FAILURE == zend_hash_find(EG(class_table), fdat->classname, strlen(fdat->classname)+1,
+                                       (void **)&fdat->ce)) {
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                                       "user-filter \"%s\" requires class \"%s\", but that class is not defined",
+                                       filtername, fdat->classname);
+                       return NULL;
+               }
+#ifdef ZEND_ENGINE_2
+               fdat->ce = *(zend_class_entry**)fdat->ce;
+#endif
+
+               /* the class *must* be a descendant of the user-space filter
+                * base class, otherwise it will never work */
+               /* TODO: make this sanity check */
+       }
+
+       filter = php_stream_filter_alloc(&userfilter_ops, NULL, 0);
+       if (filter == NULL) {
+               return NULL;
+       }
+
+       ALLOC_INIT_ZVAL(zfilter);
+       ZEND_REGISTER_RESOURCE(zfilter, filter, le_userfilters);
+       
+       /* create the object */
+       ALLOC_ZVAL(obj);
+       object_init_ex(obj, fdat->ce);
+       ZVAL_REFCOUNT(obj) = 1;
+       PZVAL_IS_REF(obj) = 1;
+
+       /* set the filter property */
+       filter->abstract = obj;
+       
+       zend_hash_index_update(Z_OBJPROP_P(obj), 0, &zfilter, sizeof(zfilter), NULL);
+
+       /* filtername */
+       add_property_string(obj, "filtername", (char*)filtername, 1);
+       
+       /* and the parameters, if any */
+       if (filterparams) {
+               add_property_stringl(obj, "params", (char*)filterparams, filterparamslen, 1);
+       } else {
+               add_property_null(obj, "params");
+       }
+
+       /* invoke the constructor */
+       ZVAL_STRINGL(&func_name, "oncreate", sizeof("oncreate")-1, 0);
+
+       call_user_function_ex(NULL,
+                       &obj,
+                       &func_name,
+                       &retval,
+                       0, NULL,
+                       0, NULL TSRMLS_CC);
+
+       if (retval)
+               zval_ptr_dtor(&retval);
+
+       return filter;
+}
+
+static php_stream_filter_factory user_filter_factory = {
+       user_filter_factory_create
+};
+
+static void filter_item_dtor(struct php_user_filter_data *fdat)
+{
+}
+
+/* {{{ proto bool stream_register_filter(string filtername, string classname)
+   Registers a custom filter handler class */
+PHP_FUNCTION(stream_register_filter)
+{
+       char *filtername, *classname;
+       int filtername_len, classname_len;
+       struct php_user_filter_data *fdat;
+       
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &filtername, &filtername_len,
+                               &classname, &classname_len) == FAILURE) {
+               RETURN_FALSE;
+       }
+
+       RETVAL_FALSE;
+
+       if (!BG(user_filter_map)) {
+               BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
+               zend_hash_init(BG(user_filter_map), 5, NULL, (dtor_func_t) filter_item_dtor, 0);
+       }
+
+       fdat = ecalloc(1, sizeof(*fdat) + classname_len);
+       memcpy(fdat->classname, classname, classname_len);
+       zend_str_tolower(fdat->classname, classname_len);
+
+       if (zend_hash_add(BG(user_filter_map), filtername, filtername_len, (void*)fdat,
+                               sizeof(*fdat) + classname_len, NULL) == SUCCESS &&
+                       php_stream_filter_register_factory(filtername, &user_filter_factory TSRMLS_CC) == SUCCESS) {
+               RETVAL_TRUE;
+       }
+
+       efree(fdat);
+}
+/* }}} */