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