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