1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2013 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors:                                                             |
16   | Wez Furlong (wez@thebrainroom.com)                                   |
17   | Sara Golemon (pollita@php.net)                                       |
18   +----------------------------------------------------------------------+
19*/
20
21/* $Id$ */
22
23#include "php.h"
24#include "php_globals.h"
25#include "ext/standard/basic_functions.h"
26#include "ext/standard/file.h"
27
28#define PHP_STREAM_BRIGADE_RES_NAME "userfilter.bucket brigade"
29#define PHP_STREAM_BUCKET_RES_NAME "userfilter.bucket"
30#define PHP_STREAM_FILTER_RES_NAME "userfilter.filter"
31
32struct php_user_filter_data {
33    zend_class_entry *ce;
34    /* variable length; this *must* be last in the structure */
35    char classname[1];
36};
37
38/* to provide context for calling into the next filter from user-space */
39static int le_userfilters;
40static int le_bucket_brigade;
41static int le_bucket;
42
43#define GET_FILTER_FROM_OBJ()   { \
44    zval **tmp; \
45    if (FAILURE == zend_hash_index_find(Z_OBJPROP_P(this_ptr), 0, (void**)&tmp)) { \
46        php_error_docref(NULL TSRMLS_CC, E_WARNING, "filter property vanished"); \
47        RETURN_FALSE; \
48    } \
49    ZEND_FETCH_RESOURCE(filter, php_stream_filter*, tmp, -1, "filter", le_userfilters); \
50}
51
52/* define the base filter class */
53
54PHP_FUNCTION(user_filter_nop)
55{
56}
57ZEND_BEGIN_ARG_INFO(arginfo_php_user_filter_filter, 0)
58    ZEND_ARG_INFO(0, in)
59    ZEND_ARG_INFO(0, out)
60    ZEND_ARG_INFO(1, consumed)
61    ZEND_ARG_INFO(0, closing)
62ZEND_END_ARG_INFO()
63
64ZEND_BEGIN_ARG_INFO(arginfo_php_user_filter_onCreate, 0)
65ZEND_END_ARG_INFO()
66
67ZEND_BEGIN_ARG_INFO(arginfo_php_user_filter_onClose, 0)
68ZEND_END_ARG_INFO()
69
70static const zend_function_entry user_filter_class_funcs[] = {
71    PHP_NAMED_FE(filter,    PHP_FN(user_filter_nop),        arginfo_php_user_filter_filter)
72    PHP_NAMED_FE(onCreate,  PHP_FN(user_filter_nop),        arginfo_php_user_filter_onCreate)
73    PHP_NAMED_FE(onClose,   PHP_FN(user_filter_nop),        arginfo_php_user_filter_onClose)
74    PHP_FE_END
75};
76
77static zend_class_entry user_filter_class_entry;
78
79static ZEND_RSRC_DTOR_FUNC(php_bucket_dtor)
80{
81    php_stream_bucket *bucket = (php_stream_bucket *)rsrc->ptr;
82    if (bucket) {
83        php_stream_bucket_delref(bucket TSRMLS_CC);
84        bucket = NULL;
85    }
86}
87
88PHP_MINIT_FUNCTION(user_filters)
89{
90    zend_class_entry *php_user_filter;
91    /* init the filter class ancestor */
92    INIT_CLASS_ENTRY(user_filter_class_entry, "php_user_filter", user_filter_class_funcs);
93    if ((php_user_filter = zend_register_internal_class(&user_filter_class_entry TSRMLS_CC)) == NULL) {
94        return FAILURE;
95    }
96    zend_declare_property_string(php_user_filter, "filtername", sizeof("filtername")-1, "", ZEND_ACC_PUBLIC TSRMLS_CC);
97    zend_declare_property_string(php_user_filter, "params", sizeof("params")-1, "", ZEND_ACC_PUBLIC TSRMLS_CC);
98
99    /* init the filter resource; it has no dtor, as streams will always clean it up
100     * at the correct time */
101    le_userfilters = zend_register_list_destructors_ex(NULL, NULL, PHP_STREAM_FILTER_RES_NAME, 0);
102
103    if (le_userfilters == FAILURE) {
104        return FAILURE;
105    }
106
107    /* Filters will dispose of their brigades */
108    le_bucket_brigade = zend_register_list_destructors_ex(NULL, NULL, PHP_STREAM_BRIGADE_RES_NAME, module_number);
109    /* Brigades will dispose of their buckets */
110    le_bucket = zend_register_list_destructors_ex(php_bucket_dtor, NULL, PHP_STREAM_BUCKET_RES_NAME, module_number);
111
112    if (le_bucket_brigade == FAILURE) {
113        return FAILURE;
114    }
115
116    REGISTER_LONG_CONSTANT("PSFS_PASS_ON",          PSFS_PASS_ON,           CONST_CS | CONST_PERSISTENT);
117    REGISTER_LONG_CONSTANT("PSFS_FEED_ME",          PSFS_FEED_ME,           CONST_CS | CONST_PERSISTENT);
118    REGISTER_LONG_CONSTANT("PSFS_ERR_FATAL",        PSFS_ERR_FATAL,         CONST_CS | CONST_PERSISTENT);
119
120    REGISTER_LONG_CONSTANT("PSFS_FLAG_NORMAL",      PSFS_FLAG_NORMAL,       CONST_CS | CONST_PERSISTENT);
121    REGISTER_LONG_CONSTANT("PSFS_FLAG_FLUSH_INC",   PSFS_FLAG_FLUSH_INC,    CONST_CS | CONST_PERSISTENT);
122    REGISTER_LONG_CONSTANT("PSFS_FLAG_FLUSH_CLOSE", PSFS_FLAG_FLUSH_CLOSE,  CONST_CS | CONST_PERSISTENT);
123
124    return SUCCESS;
125}
126
127PHP_RSHUTDOWN_FUNCTION(user_filters)
128{
129    if (BG(user_filter_map)) {
130        zend_hash_destroy(BG(user_filter_map));
131        efree(BG(user_filter_map));
132        BG(user_filter_map) = NULL;
133    }
134
135    return SUCCESS;
136}
137
138static void userfilter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
139{
140    zval *obj = (zval*)thisfilter->abstract;
141    zval func_name;
142    zval *retval = NULL;
143
144    if (obj == NULL) {
145        /* If there's no object associated then there's nothing to dispose of */
146        return;
147    }
148
149    ZVAL_STRINGL(&func_name, "onclose", sizeof("onclose")-1, 0);
150
151    call_user_function_ex(NULL,
152            &obj,
153            &func_name,
154            &retval,
155            0, NULL,
156            0, NULL TSRMLS_CC);
157
158    if (retval)
159        zval_ptr_dtor(&retval);
160
161    /* kill the object */
162    zval_ptr_dtor(&obj);
163}
164
165php_stream_filter_status_t userfilter_filter(
166            php_stream *stream,
167            php_stream_filter *thisfilter,
168            php_stream_bucket_brigade *buckets_in,
169            php_stream_bucket_brigade *buckets_out,
170            size_t *bytes_consumed,
171            int flags
172            TSRMLS_DC)
173{
174    int ret = PSFS_ERR_FATAL;
175    zval *obj = (zval*)thisfilter->abstract;
176    zval func_name;
177    zval *retval = NULL;
178    zval **args[4];
179    zval *zclosing, *zconsumed, *zin, *zout, *zstream;
180    zval zpropname;
181    int call_result;
182
183    if (FAILURE == zend_hash_find(Z_OBJPROP_P(obj), "stream", sizeof("stream"), (void**)&zstream)) {
184        /* Give the userfilter class a hook back to the stream */
185        ALLOC_INIT_ZVAL(zstream);
186        php_stream_to_zval(stream, zstream);
187        zval_copy_ctor(zstream);
188        add_property_zval(obj, "stream", zstream);
189        /* add_property_zval increments the refcount which is unwanted here */
190        zval_ptr_dtor(&zstream);
191    }
192
193    ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1, 0);
194
195    /* Setup calling arguments */
196    ALLOC_INIT_ZVAL(zin);
197    ZEND_REGISTER_RESOURCE(zin, buckets_in, le_bucket_brigade);
198    args[0] = &zin;
199
200    ALLOC_INIT_ZVAL(zout);
201    ZEND_REGISTER_RESOURCE(zout, buckets_out, le_bucket_brigade);
202    args[1] = &zout;
203
204    ALLOC_INIT_ZVAL(zconsumed);
205    if (bytes_consumed) {
206        ZVAL_LONG(zconsumed, *bytes_consumed);
207    } else {
208        ZVAL_NULL(zconsumed);
209    }
210    args[2] = &zconsumed;
211
212    ALLOC_INIT_ZVAL(zclosing);
213    ZVAL_BOOL(zclosing, flags & PSFS_FLAG_FLUSH_CLOSE);
214    args[3] = &zclosing;
215
216    call_result = call_user_function_ex(NULL,
217            &obj,
218            &func_name,
219            &retval,
220            4, args,
221            0, NULL TSRMLS_CC);
222
223    if (call_result == SUCCESS && retval != NULL) {
224        convert_to_long(retval);
225        ret = Z_LVAL_P(retval);
226    } else if (call_result == FAILURE) {
227        php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to call filter function");
228    }
229
230    if (bytes_consumed) {
231        *bytes_consumed = Z_LVAL_P(zconsumed);
232    }
233
234    if (retval) {
235        zval_ptr_dtor(&retval);
236    }
237
238    if (buckets_in->head) {
239        php_stream_bucket *bucket = buckets_in->head;
240
241        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
242        while ((bucket = buckets_in->head)) {
243            /* Remove unconsumed buckets from the brigade */
244            php_stream_bucket_unlink(bucket TSRMLS_CC);
245            php_stream_bucket_delref(bucket TSRMLS_CC);
246        }
247    }
248    if (ret != PSFS_PASS_ON) {
249        php_stream_bucket *bucket = buckets_out->head;
250        while (bucket != NULL) {
251            php_stream_bucket_unlink(bucket TSRMLS_CC);
252            php_stream_bucket_delref(bucket TSRMLS_CC);
253            bucket = buckets_out->head;
254        }
255    }
256
257    /* filter resources are cleaned up by the stream destructor,
258     * keeping a reference to the stream resource here would prevent it
259     * from being destroyed properly */
260    INIT_ZVAL(zpropname);
261    ZVAL_STRINGL(&zpropname, "stream", sizeof("stream")-1, 0);
262    Z_OBJ_HANDLER_P(obj, unset_property)(obj, &zpropname TSRMLS_CC);
263
264    zval_ptr_dtor(&zclosing);
265    zval_ptr_dtor(&zconsumed);
266    zval_ptr_dtor(&zout);
267    zval_ptr_dtor(&zin);
268
269    return ret;
270}
271
272static php_stream_filter_ops userfilter_ops = {
273    userfilter_filter,
274    userfilter_dtor,
275    "user-filter"
276};
277
278static php_stream_filter *user_filter_factory_create(const char *filtername,
279        zval *filterparams, int persistent TSRMLS_DC)
280{
281    struct php_user_filter_data *fdat = NULL;
282    php_stream_filter *filter;
283    zval *obj, *zfilter;
284    zval func_name;
285    zval *retval = NULL;
286    int len;
287
288    /* some sanity checks */
289    if (persistent) {
290        php_error_docref(NULL TSRMLS_CC, E_WARNING,
291                "cannot use a user-space filter with a persistent stream");
292        return NULL;
293    }
294
295    len = strlen(filtername);
296
297    /* determine the classname/class entry */
298    if (FAILURE == zend_hash_find(BG(user_filter_map), (char*)filtername, len + 1, (void**)&fdat)) {
299        char *period;
300
301        /* Userspace Filters using ambiguous wildcards could cause problems.
302           i.e.: myfilter.foo.bar will always call into myfilter.foo.*
303                 never seeing myfilter.*
304           TODO: Allow failed userfilter creations to continue
305                 scanning through the list */
306        if ((period = strrchr(filtername, '.'))) {
307            char *wildcard = emalloc(len + 3);
308
309            /* Search for wildcard matches instead */
310            memcpy(wildcard, filtername, len + 1); /* copy \0 */
311            period = wildcard + (period - filtername);
312            while (period) {
313                *period = '\0';
314                strncat(wildcard, ".*", 2);
315                if (SUCCESS == zend_hash_find(BG(user_filter_map), wildcard, strlen(wildcard) + 1, (void**)&fdat)) {
316                    period = NULL;
317                } else {
318                    *period = '\0';
319                    period = strrchr(wildcard, '.');
320                }
321            }
322            efree(wildcard);
323        }
324        if (fdat == NULL) {
325            php_error_docref(NULL TSRMLS_CC, E_WARNING,
326                    "Err, filter \"%s\" is not in the user-filter map, but somehow the user-filter-factory was invoked for it!?", filtername);
327            return NULL;
328        }
329    }
330
331    /* bind the classname to the actual class */
332    if (fdat->ce == NULL) {
333        if (FAILURE == zend_lookup_class(fdat->classname, strlen(fdat->classname),
334                    (zend_class_entry ***)&fdat->ce TSRMLS_CC)) {
335            php_error_docref(NULL TSRMLS_CC, E_WARNING,
336                    "user-filter \"%s\" requires class \"%s\", but that class is not defined",
337                    filtername, fdat->classname);
338            return NULL;
339        }
340        fdat->ce = *(zend_class_entry**)fdat->ce;
341
342    }
343
344    filter = php_stream_filter_alloc(&userfilter_ops, NULL, 0);
345    if (filter == NULL) {
346        return NULL;
347    }
348
349    /* create the object */
350    ALLOC_ZVAL(obj);
351    object_init_ex(obj, fdat->ce);
352    Z_SET_REFCOUNT_P(obj, 1);
353    Z_SET_ISREF_P(obj);
354
355    /* filtername */
356    add_property_string(obj, "filtername", (char*)filtername, 1);
357
358    /* and the parameters, if any */
359    if (filterparams) {
360        add_property_zval(obj, "params", filterparams);
361    } else {
362        add_property_null(obj, "params");
363    }
364
365    /* invoke the constructor */
366    ZVAL_STRINGL(&func_name, "oncreate", sizeof("oncreate")-1, 0);
367
368    call_user_function_ex(NULL,
369            &obj,
370            &func_name,
371            &retval,
372            0, NULL,
373            0, NULL TSRMLS_CC);
374
375    if (retval) {
376        if (Z_TYPE_P(retval) == IS_BOOL && Z_LVAL_P(retval) == 0) {
377            /* User reported filter creation error "return false;" */
378            zval_ptr_dtor(&retval);
379
380            /* Kill the filter (safely) */
381            filter->abstract = NULL;
382            php_stream_filter_free(filter TSRMLS_CC);
383
384            /* Kill the object */
385            zval_ptr_dtor(&obj);
386
387            /* Report failure to filter_alloc */
388            return NULL;
389        }
390        zval_ptr_dtor(&retval);
391    }
392
393    /* set the filter property, this will be used during cleanup */
394    ALLOC_INIT_ZVAL(zfilter);
395    ZEND_REGISTER_RESOURCE(zfilter, filter, le_userfilters);
396    filter->abstract = obj;
397    add_property_zval(obj, "filter", zfilter);
398    /* add_property_zval increments the refcount which is unwanted here */
399    zval_ptr_dtor(&zfilter);
400
401    return filter;
402}
403
404static php_stream_filter_factory user_filter_factory = {
405    user_filter_factory_create
406};
407
408static void filter_item_dtor(struct php_user_filter_data *fdat)
409{
410}
411
412/* {{{ proto object stream_bucket_make_writeable(resource brigade)
413   Return a bucket object from the brigade for operating on */
414PHP_FUNCTION(stream_bucket_make_writeable)
415{
416    zval *zbrigade, *zbucket;
417    php_stream_bucket_brigade *brigade;
418    php_stream_bucket *bucket;
419
420    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zbrigade) == FAILURE) {
421        RETURN_FALSE;
422    }
423
424    ZEND_FETCH_RESOURCE(brigade, php_stream_bucket_brigade *, &zbrigade, -1, PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade);
425
426    ZVAL_NULL(return_value);
427
428    if (brigade->head && (bucket = php_stream_bucket_make_writeable(brigade->head TSRMLS_CC))) {
429        ALLOC_INIT_ZVAL(zbucket);
430        ZEND_REGISTER_RESOURCE(zbucket, bucket, le_bucket);
431        object_init(return_value);
432        add_property_zval(return_value, "bucket", zbucket);
433        /* add_property_zval increments the refcount which is unwanted here */
434        zval_ptr_dtor(&zbucket);
435        add_property_stringl(return_value, "data", bucket->buf, bucket->buflen, 1);
436        add_property_long(return_value, "datalen", bucket->buflen);
437    }
438}
439/* }}} */
440
441/* {{{ php_stream_bucket_attach */
442static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS)
443{
444    zval *zbrigade, *zobject;
445    zval **pzbucket, **pzdata;
446    php_stream_bucket_brigade *brigade;
447    php_stream_bucket *bucket;
448
449    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zo", &zbrigade, &zobject) == FAILURE) {
450        RETURN_FALSE;
451    }
452
453    if (FAILURE == zend_hash_find(Z_OBJPROP_P(zobject), "bucket", 7, (void**)&pzbucket)) {
454        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Object has no bucket property");
455        RETURN_FALSE;
456    }
457
458    ZEND_FETCH_RESOURCE(brigade, php_stream_bucket_brigade *, &zbrigade, -1, PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade);
459    ZEND_FETCH_RESOURCE(bucket, php_stream_bucket *, pzbucket, -1, PHP_STREAM_BUCKET_RES_NAME, le_bucket);
460
461    if (SUCCESS == zend_hash_find(Z_OBJPROP_P(zobject), "data", 5, (void**)&pzdata) && (*pzdata)->type == IS_STRING) {
462        if (!bucket->own_buf) {
463            bucket = php_stream_bucket_make_writeable(bucket TSRMLS_CC);
464        }
465        if ((int)bucket->buflen != Z_STRLEN_PP(pzdata)) {
466            bucket->buf = perealloc(bucket->buf, Z_STRLEN_PP(pzdata), bucket->is_persistent);
467            bucket->buflen = Z_STRLEN_PP(pzdata);
468        }
469        memcpy(bucket->buf, Z_STRVAL_PP(pzdata), bucket->buflen);
470    }
471
472    if (append) {
473        php_stream_bucket_append(brigade, bucket TSRMLS_CC);
474    } else {
475        php_stream_bucket_prepend(brigade, bucket TSRMLS_CC);
476    }
477    /* This is a hack necessary to accomodate situations where bucket is appended to the stream
478     * multiple times. See bug35916.phpt for reference.
479     */
480    if (bucket->refcount == 1) {
481        bucket->refcount++;
482    }
483}
484/* }}} */
485
486/* {{{ proto void stream_bucket_prepend(resource brigade, resource bucket)
487   Prepend bucket to brigade */
488PHP_FUNCTION(stream_bucket_prepend)
489{
490    php_stream_bucket_attach(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
491}
492/* }}} */
493
494/* {{{ proto void stream_bucket_append(resource brigade, resource bucket)
495   Append bucket to brigade */
496PHP_FUNCTION(stream_bucket_append)
497{
498    php_stream_bucket_attach(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
499}
500/* }}} */
501
502/* {{{ proto resource stream_bucket_new(resource stream, string buffer)
503   Create a new bucket for use on the current stream */
504PHP_FUNCTION(stream_bucket_new)
505{
506    zval *zstream, *zbucket;
507    php_stream *stream;
508    char *buffer;
509    char *pbuffer;
510    int buffer_len;
511    php_stream_bucket *bucket;
512
513    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs", &zstream, &buffer, &buffer_len) == FAILURE) {
514        RETURN_FALSE;
515    }
516
517    php_stream_from_zval(stream, &zstream);
518
519    if (!(pbuffer = pemalloc(buffer_len, php_stream_is_persistent(stream)))) {
520        RETURN_FALSE;
521    }
522
523    memcpy(pbuffer, buffer, buffer_len);
524
525    bucket = php_stream_bucket_new(stream, pbuffer, buffer_len, 1, php_stream_is_persistent(stream) TSRMLS_CC);
526
527    if (bucket == NULL) {
528        RETURN_FALSE;
529    }
530
531    ALLOC_INIT_ZVAL(zbucket);
532    ZEND_REGISTER_RESOURCE(zbucket, bucket, le_bucket);
533    object_init(return_value);
534    add_property_zval(return_value, "bucket", zbucket);
535    /* add_property_zval increments the refcount which is unwanted here */
536    zval_ptr_dtor(&zbucket);
537    add_property_stringl(return_value, "data", bucket->buf, bucket->buflen, 1);
538    add_property_long(return_value, "datalen", bucket->buflen);
539}
540/* }}} */
541
542/* {{{ proto array stream_get_filters(void)
543   Returns a list of registered filters */
544PHP_FUNCTION(stream_get_filters)
545{
546    char *filter_name;
547    int key_flags, filter_name_len = 0;
548    HashTable *filters_hash;
549    ulong num_key;
550
551    if (zend_parse_parameters_none() == FAILURE) {
552        return;
553    }
554
555    array_init(return_value);
556
557    filters_hash = php_get_stream_filters_hash();
558
559    if (filters_hash) {
560        for(zend_hash_internal_pointer_reset(filters_hash);
561            (key_flags = zend_hash_get_current_key_ex(filters_hash, &filter_name, &filter_name_len, &num_key, 0, NULL)) != HASH_KEY_NON_EXISTANT;
562            zend_hash_move_forward(filters_hash))
563                if (key_flags == HASH_KEY_IS_STRING) {
564                    add_next_index_stringl(return_value, filter_name, filter_name_len - 1, 1);
565                }
566    }
567    /* It's okay to return an empty array if no filters are registered */
568}
569/* }}} */
570
571/* {{{ proto bool stream_filter_register(string filtername, string classname)
572   Registers a custom filter handler class */
573PHP_FUNCTION(stream_filter_register)
574{
575    char *filtername, *classname;
576    int filtername_len, classname_len;
577    struct php_user_filter_data *fdat;
578
579    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &filtername, &filtername_len,
580                &classname, &classname_len) == FAILURE) {
581        RETURN_FALSE;
582    }
583
584    RETVAL_FALSE;
585
586    if (!filtername_len) {
587        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filter name cannot be empty");
588        return;
589    }
590
591    if (!classname_len) {
592        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class name cannot be empty");
593        return;
594    }
595
596    if (!BG(user_filter_map)) {
597        BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
598        zend_hash_init(BG(user_filter_map), 5, NULL, (dtor_func_t) filter_item_dtor, 0);
599    }
600
601    fdat = ecalloc(1, sizeof(struct php_user_filter_data) + classname_len);
602    memcpy(fdat->classname, classname, classname_len);
603
604    if (zend_hash_add(BG(user_filter_map), filtername, filtername_len + 1, (void*)fdat,
605                sizeof(*fdat) + classname_len, NULL) == SUCCESS &&
606            php_stream_filter_register_factory_volatile(filtername, &user_filter_factory TSRMLS_CC) == SUCCESS) {
607        RETVAL_TRUE;
608    }
609
610    efree(fdat);
611}
612/* }}} */
613
614
615/*
616 * Local variables:
617 * tab-width: 4
618 * c-basic-offset: 4
619 * End:
620 * vim600: sw=4 ts=4 fdm=marker
621 * vim<600: sw=4 ts=4
622 */
623