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