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