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