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