1/*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2015 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 /* the userfilter object probably doesn't exist anymore */
184 if (CG(unclean_shutdown)) {
185 return ret;
186 }
187
188 if (FAILURE == zend_hash_find(Z_OBJPROP_P(obj), "stream", sizeof("stream"), (void**)&zstream)) {
189 /* Give the userfilter class a hook back to the stream */
190 ALLOC_INIT_ZVAL(zstream);
191 php_stream_to_zval(stream, zstream);
192 zval_copy_ctor(zstream);
193 add_property_zval(obj, "stream", zstream);
194 /* add_property_zval increments the refcount which is unwanted here */
195 zval_ptr_dtor(&zstream);
196 }
197
198 ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1, 0);
199
200 /* Setup calling arguments */
201 ALLOC_INIT_ZVAL(zin);
202 ZEND_REGISTER_RESOURCE(zin, buckets_in, le_bucket_brigade);
203 args[0] = &zin;
204
205 ALLOC_INIT_ZVAL(zout);
206 ZEND_REGISTER_RESOURCE(zout, buckets_out, le_bucket_brigade);
207 args[1] = &zout;
208
209 ALLOC_INIT_ZVAL(zconsumed);
210 if (bytes_consumed) {
211 ZVAL_LONG(zconsumed, *bytes_consumed);
212 } else {
213 ZVAL_NULL(zconsumed);
214 }
215 args[2] = &zconsumed;
216
217 ALLOC_INIT_ZVAL(zclosing);
218 ZVAL_BOOL(zclosing, flags & PSFS_FLAG_FLUSH_CLOSE);
219 args[3] = &zclosing;
220
221 call_result = call_user_function_ex(NULL,
222 &obj,
223 &func_name,
224 &retval,
225 4, args,
226 0, NULL TSRMLS_CC);
227
228 if (call_result == SUCCESS && retval != NULL) {
229 convert_to_long(retval);
230 ret = Z_LVAL_P(retval);
231 } else if (call_result == FAILURE) {
232 php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to call filter function");
233 }
234
235 if (bytes_consumed) {
236 *bytes_consumed = Z_LVAL_P(zconsumed);
237 }
238
239 if (retval) {
240 zval_ptr_dtor(&retval);
241 }
242
243 if (buckets_in->head) {
244 php_stream_bucket *bucket = buckets_in->head;
245
246 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
247 while ((bucket = buckets_in->head)) {
248 /* Remove unconsumed buckets from the brigade */
249 php_stream_bucket_unlink(bucket TSRMLS_CC);
250 php_stream_bucket_delref(bucket TSRMLS_CC);
251 }
252 }
253 if (ret != PSFS_PASS_ON) {
254 php_stream_bucket *bucket = buckets_out->head;
255 while (bucket != NULL) {
256 php_stream_bucket_unlink(bucket TSRMLS_CC);
257 php_stream_bucket_delref(bucket TSRMLS_CC);
258 bucket = buckets_out->head;
259 }
260 }
261
262 /* filter resources are cleaned up by the stream destructor,
263 * keeping a reference to the stream resource here would prevent it
264 * from being destroyed properly */
265 INIT_ZVAL(zpropname);
266 ZVAL_STRINGL(&zpropname, "stream", sizeof("stream")-1, 0);
267 Z_OBJ_HANDLER_P(obj, unset_property)(obj, &zpropname, 0 TSRMLS_CC);
268
269 zval_ptr_dtor(&zclosing);
270 zval_ptr_dtor(&zconsumed);
271 zval_ptr_dtor(&zout);
272 zval_ptr_dtor(&zin);
273
274 return ret;
275}
276
277static php_stream_filter_ops userfilter_ops = {
278 userfilter_filter,
279 userfilter_dtor,
280 "user-filter"
281};
282
283static php_stream_filter *user_filter_factory_create(const char *filtername,
284 zval *filterparams, int persistent TSRMLS_DC)
285{
286 struct php_user_filter_data *fdat = NULL;
287 php_stream_filter *filter;
288 zval *obj, *zfilter;
289 zval func_name;
290 zval *retval = NULL;
291 int len;
292
293 /* some sanity checks */
294 if (persistent) {
295 php_error_docref(NULL TSRMLS_CC, E_WARNING,
296 "cannot use a user-space filter with a persistent stream");
297 return NULL;
298 }
299
300 len = strlen(filtername);
301
302 /* determine the classname/class entry */
303 if (FAILURE == zend_hash_find(BG(user_filter_map), (char*)filtername, len + 1, (void**)&fdat)) {
304 char *period;
305
306 /* Userspace Filters using ambiguous wildcards could cause problems.
307 i.e.: myfilter.foo.bar will always call into myfilter.foo.*
308 never seeing myfilter.*
309 TODO: Allow failed userfilter creations to continue
310 scanning through the list */
311 if ((period = strrchr(filtername, '.'))) {
312 char *wildcard = emalloc(len + 3);
313
314 /* Search for wildcard matches instead */
315 memcpy(wildcard, filtername, len + 1); /* copy \0 */
316 period = wildcard + (period - filtername);
317 while (period) {
318 *period = '\0';
319 strncat(wildcard, ".*", 2);
320 if (SUCCESS == zend_hash_find(BG(user_filter_map), wildcard, strlen(wildcard) + 1, (void**)&fdat)) {
321 period = NULL;
322 } else {
323 *period = '\0';
324 period = strrchr(wildcard, '.');
325 }
326 }
327 efree(wildcard);
328 }
329 if (fdat == NULL) {
330 php_error_docref(NULL TSRMLS_CC, E_WARNING,
331 "Err, filter \"%s\" is not in the user-filter map, but somehow the user-filter-factory was invoked for it!?", filtername);
332 return NULL;
333 }
334 }
335
336 /* bind the classname to the actual class */
337 if (fdat->ce == NULL) {
338 if (FAILURE == zend_lookup_class(fdat->classname, strlen(fdat->classname),
339 (zend_class_entry ***)&fdat->ce TSRMLS_CC)) {
340 php_error_docref(NULL TSRMLS_CC, E_WARNING,
341 "user-filter \"%s\" requires class \"%s\", but that class is not defined",
342 filtername, fdat->classname);
343 return NULL;
344 }
345 fdat->ce = *(zend_class_entry**)fdat->ce;
346
347 }
348
349 filter = php_stream_filter_alloc(&userfilter_ops, NULL, 0);
350 if (filter == NULL) {
351 return NULL;
352 }
353
354 /* create the object */
355 ALLOC_ZVAL(obj);
356 object_init_ex(obj, fdat->ce);
357 Z_SET_REFCOUNT_P(obj, 1);
358 Z_SET_ISREF_P(obj);
359
360 /* filtername */
361 add_property_string(obj, "filtername", (char*)filtername, 1);
362
363 /* and the parameters, if any */
364 if (filterparams) {
365 add_property_zval(obj, "params", filterparams);
366 } else {
367 add_property_null(obj, "params");
368 }
369
370 /* invoke the constructor */
371 ZVAL_STRINGL(&func_name, "oncreate", sizeof("oncreate")-1, 0);
372
373 call_user_function_ex(NULL,
374 &obj,
375 &func_name,
376 &retval,
377 0, NULL,
378 0, NULL TSRMLS_CC);
379
380 if (retval) {
381 if (Z_TYPE_P(retval) == IS_BOOL && Z_LVAL_P(retval) == 0) {
382 /* User reported filter creation error "return false;" */
383 zval_ptr_dtor(&retval);
384
385 /* Kill the filter (safely) */
386 filter->abstract = NULL;
387 php_stream_filter_free(filter TSRMLS_CC);
388
389 /* Kill the object */
390 zval_ptr_dtor(&obj);
391
392 /* Report failure to filter_alloc */
393 return NULL;
394 }
395 zval_ptr_dtor(&retval);
396 }
397
398 /* set the filter property, this will be used during cleanup */
399 ALLOC_INIT_ZVAL(zfilter);
400 ZEND_REGISTER_RESOURCE(zfilter, filter, le_userfilters);
401 filter->abstract = obj;
402 add_property_zval(obj, "filter", zfilter);
403 /* add_property_zval increments the refcount which is unwanted here */
404 zval_ptr_dtor(&zfilter);
405
406 return filter;
407}
408
409static php_stream_filter_factory user_filter_factory = {
410 user_filter_factory_create
411};
412
413static void filter_item_dtor(struct php_user_filter_data *fdat)
414{
415}
416
417/* {{{ proto object stream_bucket_make_writeable(resource brigade)
418 Return a bucket object from the brigade for operating on */
419PHP_FUNCTION(stream_bucket_make_writeable)
420{
421 zval *zbrigade, *zbucket;
422 php_stream_bucket_brigade *brigade;
423 php_stream_bucket *bucket;
424
425 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zbrigade) == FAILURE) {
426 RETURN_FALSE;
427 }
428
429 ZEND_FETCH_RESOURCE(brigade, php_stream_bucket_brigade *, &zbrigade, -1, PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade);
430
431 ZVAL_NULL(return_value);
432
433 if (brigade->head && (bucket = php_stream_bucket_make_writeable(brigade->head TSRMLS_CC))) {
434 ALLOC_INIT_ZVAL(zbucket);
435 ZEND_REGISTER_RESOURCE(zbucket, bucket, le_bucket);
436 object_init(return_value);
437 add_property_zval(return_value, "bucket", zbucket);
438 /* add_property_zval increments the refcount which is unwanted here */
439 zval_ptr_dtor(&zbucket);
440 add_property_stringl(return_value, "data", bucket->buf, bucket->buflen, 1);
441 add_property_long(return_value, "datalen", bucket->buflen);
442 }
443}
444/* }}} */
445
446/* {{{ php_stream_bucket_attach */
447static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS)
448{
449 zval *zbrigade, *zobject;
450 zval **pzbucket, **pzdata;
451 php_stream_bucket_brigade *brigade;
452 php_stream_bucket *bucket;
453
454 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zo", &zbrigade, &zobject) == FAILURE) {
455 RETURN_FALSE;
456 }
457
458 if (FAILURE == zend_hash_find(Z_OBJPROP_P(zobject), "bucket", 7, (void**)&pzbucket)) {
459 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Object has no bucket property");
460 RETURN_FALSE;
461 }
462
463 ZEND_FETCH_RESOURCE(brigade, php_stream_bucket_brigade *, &zbrigade, -1, PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade);
464 ZEND_FETCH_RESOURCE(bucket, php_stream_bucket *, pzbucket, -1, PHP_STREAM_BUCKET_RES_NAME, le_bucket);
465
466 if (SUCCESS == zend_hash_find(Z_OBJPROP_P(zobject), "data", 5, (void**)&pzdata) && (*pzdata)->type == IS_STRING) {
467 if (!bucket->own_buf) {
468 bucket = php_stream_bucket_make_writeable(bucket TSRMLS_CC);
469 }
470 if ((int)bucket->buflen != Z_STRLEN_PP(pzdata)) {
471 bucket->buf = perealloc(bucket->buf, Z_STRLEN_PP(pzdata), bucket->is_persistent);
472 bucket->buflen = Z_STRLEN_PP(pzdata);
473 }
474 memcpy(bucket->buf, Z_STRVAL_PP(pzdata), bucket->buflen);
475 }
476
477 if (append) {
478 php_stream_bucket_append(brigade, bucket TSRMLS_CC);
479 } else {
480 php_stream_bucket_prepend(brigade, bucket TSRMLS_CC);
481 }
482 /* This is a hack necessary to accommodate situations where bucket is appended to the stream
483 * multiple times. See bug35916.phpt for reference.
484 */
485 if (bucket->refcount == 1) {
486 bucket->refcount++;
487 }
488}
489/* }}} */
490
491/* {{{ proto void stream_bucket_prepend(resource brigade, resource bucket)
492 Prepend bucket to brigade */
493PHP_FUNCTION(stream_bucket_prepend)
494{
495 php_stream_bucket_attach(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
496}
497/* }}} */
498
499/* {{{ proto void stream_bucket_append(resource brigade, resource bucket)
500 Append bucket to brigade */
501PHP_FUNCTION(stream_bucket_append)
502{
503 php_stream_bucket_attach(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
504}
505/* }}} */
506
507/* {{{ proto resource stream_bucket_new(resource stream, string buffer)
508 Create a new bucket for use on the current stream */
509PHP_FUNCTION(stream_bucket_new)
510{
511 zval *zstream, *zbucket;
512 php_stream *stream;
513 char *buffer;
514 char *pbuffer;
515 int buffer_len;
516 php_stream_bucket *bucket;
517
518 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs", &zstream, &buffer, &buffer_len) == FAILURE) {
519 RETURN_FALSE;
520 }
521
522 php_stream_from_zval(stream, &zstream);
523
524 if (!(pbuffer = pemalloc(buffer_len, php_stream_is_persistent(stream)))) {
525 RETURN_FALSE;
526 }
527
528 memcpy(pbuffer, buffer, buffer_len);
529
530 bucket = php_stream_bucket_new(stream, pbuffer, buffer_len, 1, php_stream_is_persistent(stream) TSRMLS_CC);
531
532 if (bucket == NULL) {
533 RETURN_FALSE;
534 }
535
536 ALLOC_INIT_ZVAL(zbucket);
537 ZEND_REGISTER_RESOURCE(zbucket, bucket, le_bucket);
538 object_init(return_value);
539 add_property_zval(return_value, "bucket", zbucket);
540 /* add_property_zval increments the refcount which is unwanted here */
541 zval_ptr_dtor(&zbucket);
542 add_property_stringl(return_value, "data", bucket->buf, bucket->buflen, 1);
543 add_property_long(return_value, "datalen", bucket->buflen);
544}
545/* }}} */
546
547/* {{{ proto array stream_get_filters(void)
548 Returns a list of registered filters */
549PHP_FUNCTION(stream_get_filters)
550{
551 char *filter_name;
552 int key_flags;
553 uint filter_name_len = 0;
554 HashTable *filters_hash;
555 ulong num_key;
556
557 if (zend_parse_parameters_none() == FAILURE) {
558 return;
559 }
560
561 array_init(return_value);
562
563 filters_hash = php_get_stream_filters_hash();
564
565 if (filters_hash) {
566 for(zend_hash_internal_pointer_reset(filters_hash);
567 (key_flags = zend_hash_get_current_key_ex(filters_hash, &filter_name, &filter_name_len, &num_key, 0, NULL)) != HASH_KEY_NON_EXISTENT;
568 zend_hash_move_forward(filters_hash))
569 if (key_flags == HASH_KEY_IS_STRING) {
570 add_next_index_stringl(return_value, filter_name, filter_name_len - 1, 1);
571 }
572 }
573 /* It's okay to return an empty array if no filters are registered */
574}
575/* }}} */
576
577/* {{{ proto bool stream_filter_register(string filtername, string classname)
578 Registers a custom filter handler class */
579PHP_FUNCTION(stream_filter_register)
580{
581 char *filtername, *classname;
582 int filtername_len, classname_len;
583 struct php_user_filter_data *fdat;
584
585 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &filtername, &filtername_len,
586 &classname, &classname_len) == FAILURE) {
587 RETURN_FALSE;
588 }
589
590 RETVAL_FALSE;
591
592 if (!filtername_len) {
593 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filter name cannot be empty");
594 return;
595 }
596
597 if (!classname_len) {
598 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class name cannot be empty");
599 return;
600 }
601
602 if (!BG(user_filter_map)) {
603 BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
604 zend_hash_init(BG(user_filter_map), 5, NULL, (dtor_func_t) filter_item_dtor, 0);
605 }
606
607 fdat = ecalloc(1, sizeof(struct php_user_filter_data) + classname_len);
608 memcpy(fdat->classname, classname, classname_len);
609
610 if (zend_hash_add(BG(user_filter_map), filtername, filtername_len + 1, (void*)fdat,
611 sizeof(*fdat) + classname_len, NULL) == SUCCESS &&
612 php_stream_filter_register_factory_volatile(filtername, &user_filter_factory TSRMLS_CC) == SUCCESS) {
613 RETVAL_TRUE;
614 }
615
616 efree(fdat);
617}
618/* }}} */
619
620
621/*
622 * Local variables:
623 * tab-width: 4
624 * c-basic-offset: 4
625 * End:
626 * vim600: sw=4 ts=4 fdm=marker
627 * vim<600: sw=4 ts=4
628 */
629