xref: /PHP-8.4/ext/standard/user_filters.c (revision 8e62e2b8)
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    | https://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 
28 struct php_user_filter_data {
29 	zend_class_entry *ce;
30 	/* variable length; this *must* be last in the structure */
31 	zend_string *classname;
32 };
33 
34 /* to provide context for calling into the next filter from user-space */
35 static int le_bucket_brigade;
36 static int le_bucket;
37 
38 /* define the base filter class */
39 
PHP_METHOD(php_user_filter,filter)40 PHP_METHOD(php_user_filter, filter)
41 {
42 	zval *in, *out, *consumed;
43 	bool closing;
44 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrzb", &in, &out, &consumed, &closing) == FAILURE) {
45 		RETURN_THROWS();
46 	}
47 
48 	RETURN_LONG(PSFS_ERR_FATAL);
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 static zend_class_entry *stream_bucket_class_entry;
65 
ZEND_RSRC_DTOR_FUNC(php_bucket_dtor)66 static ZEND_RSRC_DTOR_FUNC(php_bucket_dtor)
67 {
68 	php_stream_bucket *bucket = (php_stream_bucket *)res->ptr;
69 	if (bucket) {
70 		php_stream_bucket_delref(bucket);
71 		bucket = NULL;
72 	}
73 }
74 
PHP_MINIT_FUNCTION(user_filters)75 PHP_MINIT_FUNCTION(user_filters)
76 {
77 	/* init the filter class ancestor */
78 	user_filter_class_entry = register_class_php_user_filter();
79 	stream_bucket_class_entry = register_class_StreamBucket();
80 
81 	/* Filters will dispose of their brigades */
82 	le_bucket_brigade = zend_register_list_destructors_ex(NULL, NULL, PHP_STREAM_BRIGADE_RES_NAME, module_number);
83 	/* Brigades will dispose of their buckets */
84 	le_bucket = zend_register_list_destructors_ex(php_bucket_dtor, NULL, PHP_STREAM_BUCKET_RES_NAME, module_number);
85 
86 	if (le_bucket_brigade == FAILURE) {
87 		return FAILURE;
88 	}
89 
90 	register_user_filters_symbols(module_number);
91 
92 	return SUCCESS;
93 }
94 
PHP_RSHUTDOWN_FUNCTION(user_filters)95 PHP_RSHUTDOWN_FUNCTION(user_filters)
96 {
97 	if (BG(user_filter_map)) {
98 		zend_hash_destroy(BG(user_filter_map));
99 		efree(BG(user_filter_map));
100 		BG(user_filter_map) = NULL;
101 	}
102 
103 	return SUCCESS;
104 }
105 
userfilter_dtor(php_stream_filter * thisfilter)106 static void userfilter_dtor(php_stream_filter *thisfilter)
107 {
108 	zval *obj = &thisfilter->abstract;
109 	zval retval;
110 
111 	if (Z_ISUNDEF_P(obj)) {
112 		/* If there's no object associated then there's nothing to dispose of */
113 		return;
114 	}
115 
116 	zend_string *func_name = ZSTR_INIT_LITERAL("onclose", 0);
117 	zend_call_method_if_exists(Z_OBJ_P(obj), func_name, &retval, 0, NULL);
118 	zend_string_release(func_name);
119 
120 	zval_ptr_dtor(&retval);
121 
122 	/* kill the object */
123 	zval_ptr_dtor(obj);
124 }
125 
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)126 static php_stream_filter_status_t userfilter_filter(
127 			php_stream *stream,
128 			php_stream_filter *thisfilter,
129 			php_stream_bucket_brigade *buckets_in,
130 			php_stream_bucket_brigade *buckets_out,
131 			size_t *bytes_consumed,
132 			int flags
133 			)
134 {
135 	int ret = PSFS_ERR_FATAL;
136 	zval *obj = &thisfilter->abstract;
137 	zval func_name;
138 	zval retval;
139 	zval args[4];
140 	int call_result;
141 
142 	/* the userfilter object probably doesn't exist anymore */
143 	if (CG(unclean_shutdown)) {
144 		return ret;
145 	}
146 
147 	/* Make sure the stream is not closed while the filter callback executes. */
148 	uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
149 	stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
150 
151 	zval *stream_prop = zend_hash_str_find_ind(Z_OBJPROP_P(obj), "stream", sizeof("stream")-1);
152 	if (stream_prop) {
153 		/* Give the userfilter class a hook back to the stream */
154 		zval_ptr_dtor(stream_prop);
155 		php_stream_to_zval(stream, stream_prop);
156 		Z_ADDREF_P(stream_prop);
157 	}
158 
159 	ZVAL_STRINGL(&func_name, "filter", sizeof("filter")-1);
160 
161 	/* Setup calling arguments */
162 	ZVAL_RES(&args[0], zend_register_resource(buckets_in, le_bucket_brigade));
163 	ZVAL_RES(&args[1], zend_register_resource(buckets_out, le_bucket_brigade));
164 
165 	if (bytes_consumed) {
166 		ZVAL_LONG(&args[2], *bytes_consumed);
167 	} else {
168 		ZVAL_NULL(&args[2]);
169 	}
170 	ZVAL_MAKE_REF(&args[2]);
171 
172 	ZVAL_BOOL(&args[3], flags & PSFS_FLAG_FLUSH_CLOSE);
173 
174 	call_result = call_user_function(NULL,
175 			obj,
176 			&func_name,
177 			&retval,
178 			4, args);
179 
180 	zval_ptr_dtor(&func_name);
181 
182 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
183 		convert_to_long(&retval);
184 		ret = (int)Z_LVAL(retval);
185 	} else if (call_result == FAILURE) {
186 		php_error_docref(NULL, E_WARNING, "Failed to call filter function");
187 	}
188 
189 	if (bytes_consumed) {
190 		*bytes_consumed = zval_get_long(&args[2]);
191 	}
192 
193 	if (buckets_in->head) {
194 		php_error_docref(NULL, E_WARNING, "Unprocessed filter buckets remaining on input brigade");
195 	}
196 
197 	/* filter resources are cleaned up by the stream destructor,
198 	 * keeping a reference to the stream resource here would prevent it
199 	 * from being destroyed properly */
200 	if (stream_prop) {
201 		convert_to_null(stream_prop);
202 	}
203 
204 	zval_ptr_dtor(&args[3]);
205 	zval_ptr_dtor(&args[2]);
206 	zval_ptr_dtor(&args[1]);
207 	zval_ptr_dtor(&args[0]);
208 
209 	stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
210 	stream->flags |= orig_no_fclose;
211 
212 	return ret;
213 }
214 
215 static const php_stream_filter_ops userfilter_ops = {
216 	userfilter_filter,
217 	userfilter_dtor,
218 	"user-filter"
219 };
220 
user_filter_factory_create(const char * filtername,zval * filterparams,uint8_t persistent)221 static php_stream_filter *user_filter_factory_create(const char *filtername,
222 		zval *filterparams, uint8_t persistent)
223 {
224 	struct php_user_filter_data *fdat = NULL;
225 	php_stream_filter *filter;
226 	zval obj;
227 	zval retval;
228 	size_t len;
229 
230 	/* some sanity checks */
231 	if (persistent) {
232 		php_error_docref(NULL, E_WARNING,
233 				"Cannot use a user-space filter with a persistent stream");
234 		return NULL;
235 	}
236 
237 	len = strlen(filtername);
238 
239 	/* determine the classname/class entry */
240 	if (NULL == (fdat = zend_hash_str_find_ptr(BG(user_filter_map), (char*)filtername, len))) {
241 		char *period;
242 
243 		/* Userspace Filters using ambiguous wildcards could cause problems.
244            i.e.: myfilter.foo.bar will always call into myfilter.foo.*
245                  never seeing myfilter.*
246            TODO: Allow failed userfilter creations to continue
247                  scanning through the list */
248 		if ((period = strrchr(filtername, '.'))) {
249 			char *wildcard = safe_emalloc(len, 1, 3);
250 
251 			/* Search for wildcard matches instead */
252 			memcpy(wildcard, filtername, len + 1); /* copy \0 */
253 			period = wildcard + (period - filtername);
254 			while (period) {
255 				ZEND_ASSERT(period[0] == '.');
256 				period[1] = '*';
257 				period[2] = '\0';
258 				if (NULL != (fdat = zend_hash_str_find_ptr(BG(user_filter_map), wildcard, strlen(wildcard)))) {
259 					period = NULL;
260 				} else {
261 					*period = '\0';
262 					period = strrchr(wildcard, '.');
263 				}
264 			}
265 			efree(wildcard);
266 		}
267 		ZEND_ASSERT(fdat);
268 	}
269 
270 	/* bind the classname to the actual class */
271 	if (fdat->ce == NULL) {
272 		if (NULL == (fdat->ce = zend_lookup_class(fdat->classname))) {
273 			php_error_docref(NULL, E_WARNING,
274 					"User-filter \"%s\" requires class \"%s\", but that class is not defined",
275 					filtername, ZSTR_VAL(fdat->classname));
276 			return NULL;
277 		}
278 	}
279 
280 	/* create the object */
281 	if (object_init_ex(&obj, fdat->ce) == FAILURE) {
282 		return NULL;
283 	}
284 
285 	filter = php_stream_filter_alloc(&userfilter_ops, NULL, 0);
286 	if (filter == NULL) {
287 		zval_ptr_dtor(&obj);
288 		return NULL;
289 	}
290 
291 	/* filtername */
292 	add_property_string(&obj, "filtername", (char*)filtername);
293 
294 	/* and the parameters, if any */
295 	if (filterparams) {
296 		add_property_zval(&obj, "params", filterparams);
297 	} else {
298 		add_property_null(&obj, "params");
299 	}
300 
301 	/* invoke the constructor */
302 	zend_string *func_name = ZSTR_INIT_LITERAL("oncreate", 0);
303 	zend_call_method_if_exists(Z_OBJ(obj), func_name, &retval, 0, NULL);
304 	zend_string_release(func_name);
305 
306 	if (Z_TYPE(retval) != IS_UNDEF) {
307 		if (Z_TYPE(retval) == IS_FALSE) {
308 			/* User reported filter creation error "return false;" */
309 			zval_ptr_dtor(&retval);
310 
311 			/* Kill the filter (safely) */
312 			ZVAL_UNDEF(&filter->abstract);
313 			php_stream_filter_free(filter);
314 
315 			/* Kill the object */
316 			zval_ptr_dtor(&obj);
317 
318 			/* Report failure to filter_alloc */
319 			return NULL;
320 		}
321 		zval_ptr_dtor(&retval);
322 	}
323 
324 	ZVAL_OBJ(&filter->abstract, Z_OBJ(obj));
325 
326 	return filter;
327 }
328 
329 static const php_stream_filter_factory user_filter_factory = {
330 	user_filter_factory_create
331 };
332 
filter_item_dtor(zval * zv)333 static void filter_item_dtor(zval *zv)
334 {
335 	struct php_user_filter_data *fdat = Z_PTR_P(zv);
336 	zend_string_release_ex(fdat->classname, 0);
337 	efree(fdat);
338 }
339 
340 /* {{{ Return a bucket object from the brigade for operating on */
PHP_FUNCTION(stream_bucket_make_writeable)341 PHP_FUNCTION(stream_bucket_make_writeable)
342 {
343 	zval *zbrigade, zbucket;
344 	php_stream_bucket_brigade *brigade;
345 	php_stream_bucket *bucket;
346 
347 	ZEND_PARSE_PARAMETERS_START(1, 1)
348 		Z_PARAM_RESOURCE(zbrigade)
349 	ZEND_PARSE_PARAMETERS_END();
350 
351 	if ((brigade = (php_stream_bucket_brigade*)zend_fetch_resource(
352 					Z_RES_P(zbrigade), PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade)) == NULL) {
353 		RETURN_THROWS();
354 	}
355 
356 	if (brigade->head && (bucket = php_stream_bucket_make_writeable(brigade->head))) {
357 		ZVAL_RES(&zbucket, zend_register_resource(bucket, le_bucket));
358 		object_init_ex(return_value, stream_bucket_class_entry);
359 		zend_update_property(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("bucket"), &zbucket);
360 		/* add_property_zval increments the refcount which is unwanted here */
361 		zval_ptr_dtor(&zbucket);
362 		zend_update_property_stringl(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("data"), bucket->buf, bucket->buflen);
363 		zend_update_property_long(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("datalen"), bucket->buflen);
364 		zend_update_property_long(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("dataLength"), bucket->buflen);
365 	} else {
366 		ZVAL_NULL(return_value);
367 	}
368 }
369 /* }}} */
370 
371 /* {{{ php_stream_bucket_attach */
php_stream_bucket_attach(int append,INTERNAL_FUNCTION_PARAMETERS)372 static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS)
373 {
374 	zval *zbrigade, *zobject;
375 	zval *pzbucket, *pzdata, rv;
376 	php_stream_bucket_brigade *brigade;
377 	php_stream_bucket *bucket;
378 
379 	ZEND_PARSE_PARAMETERS_START(2, 2)
380 		Z_PARAM_RESOURCE(zbrigade)
381 		Z_PARAM_OBJECT_OF_CLASS(zobject, stream_bucket_class_entry)
382 	ZEND_PARSE_PARAMETERS_END();
383 
384 	if ((brigade = (php_stream_bucket_brigade*)zend_fetch_resource(
385 		Z_RES_P(zbrigade), PHP_STREAM_BRIGADE_RES_NAME, le_bucket_brigade)) == NULL) {
386 		RETURN_THROWS();
387 	}
388 
389 	if (NULL == (pzbucket = zend_read_property(NULL, Z_OBJ_P(zobject), "bucket", sizeof("bucket")-1, false, &rv))) {
390 		zend_argument_value_error(2, "must be an object that has a \"bucket\" property");
391 		RETURN_THROWS();
392 	}
393 	ZVAL_DEREF(pzbucket);
394 
395 	if ((bucket = (php_stream_bucket *)zend_fetch_resource_ex(pzbucket, PHP_STREAM_BUCKET_RES_NAME, le_bucket)) == NULL) {
396 		RETURN_THROWS();
397 	}
398 
399 	if (NULL != (pzdata = zend_read_property(NULL, Z_OBJ_P(zobject), "data", sizeof("data")-1, false, &rv))) {
400 		ZVAL_DEREF(pzdata);
401 		if (!bucket->own_buf) {
402 			bucket = php_stream_bucket_make_writeable(bucket);
403 		}
404 		if (bucket->buflen != Z_STRLEN_P(pzdata)) {
405 			bucket->buf = perealloc(bucket->buf, Z_STRLEN_P(pzdata), bucket->is_persistent);
406 			bucket->buflen = Z_STRLEN_P(pzdata);
407 		}
408 		memcpy(bucket->buf, Z_STRVAL_P(pzdata), bucket->buflen);
409 	}
410 
411 	if (append) {
412 		php_stream_bucket_append(brigade, bucket);
413 	} else {
414 		php_stream_bucket_prepend(brigade, bucket);
415 	}
416 	/* This is a hack necessary to accommodate situations where bucket is appended to the stream
417  	 * multiple times. See bug35916.phpt for reference.
418 	 */
419 	if (bucket->refcount == 1) {
420 		bucket->refcount++;
421 	}
422 }
423 /* }}} */
424 
425 /* {{{ Prepend bucket to brigade */
PHP_FUNCTION(stream_bucket_prepend)426 PHP_FUNCTION(stream_bucket_prepend)
427 {
428 	php_stream_bucket_attach(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
429 }
430 /* }}} */
431 
432 /* {{{ Append bucket to brigade */
PHP_FUNCTION(stream_bucket_append)433 PHP_FUNCTION(stream_bucket_append)
434 {
435 	php_stream_bucket_attach(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
436 }
437 /* }}} */
438 
439 /* {{{ Create a new bucket for use on the current stream */
PHP_FUNCTION(stream_bucket_new)440 PHP_FUNCTION(stream_bucket_new)
441 {
442 	zval *zstream, zbucket;
443 	php_stream *stream;
444 	char *buffer;
445 	char *pbuffer;
446 	size_t buffer_len;
447 	php_stream_bucket *bucket;
448 
449 	ZEND_PARSE_PARAMETERS_START(2, 2)
450 		Z_PARAM_ZVAL(zstream)
451 		Z_PARAM_STRING(buffer, buffer_len)
452 	ZEND_PARSE_PARAMETERS_END();
453 
454 	php_stream_from_zval(stream, zstream);
455 	pbuffer = pemalloc(buffer_len, php_stream_is_persistent(stream));
456 
457 	memcpy(pbuffer, buffer, buffer_len);
458 
459 	bucket = php_stream_bucket_new(stream, pbuffer, buffer_len, 1, php_stream_is_persistent(stream));
460 
461 	ZVAL_RES(&zbucket, zend_register_resource(bucket, le_bucket));
462 	object_init_ex(return_value, stream_bucket_class_entry);
463 	zend_update_property(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("bucket"), &zbucket);
464 	/* add_property_zval increments the refcount which is unwanted here */
465 	zval_ptr_dtor(&zbucket);
466 	zend_update_property_stringl(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("data"), bucket->buf, bucket->buflen);
467 	zend_update_property_long(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("datalen"), bucket->buflen);
468 	zend_update_property_long(Z_OBJCE_P(return_value), Z_OBJ_P(return_value), ZEND_STRL("dataLength"), bucket->buflen);
469 }
470 /* }}} */
471 
472 /* {{{ Returns a list of registered filters */
PHP_FUNCTION(stream_get_filters)473 PHP_FUNCTION(stream_get_filters)
474 {
475 	zend_string *filter_name;
476 	HashTable *filters_hash;
477 
478 	ZEND_PARSE_PARAMETERS_NONE();
479 
480 	array_init(return_value);
481 
482 	filters_hash = php_get_stream_filters_hash();
483 
484 	if (filters_hash && !HT_IS_PACKED(filters_hash)) {
485 		ZEND_HASH_MAP_FOREACH_STR_KEY(filters_hash, filter_name) {
486 			if (filter_name) {
487 				add_next_index_str(return_value, zend_string_copy(filter_name));
488 			}
489 		} ZEND_HASH_FOREACH_END();
490 	}
491 	/* It's okay to return an empty array if no filters are registered */
492 }
493 /* }}} */
494 
495 /* {{{ Registers a custom filter handler class */
PHP_FUNCTION(stream_filter_register)496 PHP_FUNCTION(stream_filter_register)
497 {
498 	zend_string *filtername, *classname;
499 	struct php_user_filter_data *fdat;
500 
501 	ZEND_PARSE_PARAMETERS_START(2, 2)
502 		Z_PARAM_STR(filtername)
503 		Z_PARAM_STR(classname)
504 	ZEND_PARSE_PARAMETERS_END();
505 
506 	if (!ZSTR_LEN(filtername)) {
507 		zend_argument_value_error(1, "must be a non-empty string");
508 		RETURN_THROWS();
509 	}
510 
511 	if (!ZSTR_LEN(classname)) {
512 		zend_argument_value_error(2, "must be a non-empty string");
513 		RETURN_THROWS();
514 	}
515 
516 	if (!BG(user_filter_map)) {
517 		BG(user_filter_map) = (HashTable*) emalloc(sizeof(HashTable));
518 		zend_hash_init(BG(user_filter_map), 8, NULL, (dtor_func_t) filter_item_dtor, 0);
519 	}
520 
521 	fdat = ecalloc(1, sizeof(struct php_user_filter_data));
522 	fdat->classname = zend_string_copy(classname);
523 
524 	if (zend_hash_add_ptr(BG(user_filter_map), filtername, fdat) != NULL &&
525 			php_stream_filter_register_factory_volatile(filtername, &user_filter_factory) == SUCCESS) {
526 		RETVAL_TRUE;
527 	} else {
528 		zend_string_release_ex(classname, 0);
529 		efree(fdat);
530 		RETVAL_FALSE;
531 	}
532 }
533 /* }}} */
534