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