From: Wez Furlong Date: Tue, 31 Dec 2002 18:39:36 +0000 (+0000) Subject: Implement user-space filters. X-Git-Tag: PHP_5_0_dev_before_13561_fix~545 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=350b0bbeac4a0c90e72f6ad5ceabab683d97f6f2;p=php Implement user-space filters. See ext/standard/tests/file/userfilters.phpt for an example of their use. --- diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 49b3adde87..ff7418c588 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -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)) { diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index c002ad5c98..f1b9f4b4fc 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -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 diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index cf0f0c2eeb..98964bce2e 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -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 index 0000000000..543e6a3db7 --- /dev/null +++ b/ext/standard/tests/file/userfilters.phpt @@ -0,0 +1,60 @@ +--TEST-- +User-space filters +--FILE-- +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 index 0000000000..0edca81425 --- /dev/null +++ b/ext/standard/user_filters.c @@ -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); +} +/* }}} */