xref: /PHP-7.0/main/streams/filter.c (revision ee25eb0e)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Wez Furlong <wez@thebrainroom.com>                          |
16    +----------------------------------------------------------------------+
17  */
18 
19 /* $Id$ */
20 
21 #include "php.h"
22 #include "php_globals.h"
23 #include "php_network.h"
24 #include "php_open_temporary_file.h"
25 #include "ext/standard/file.h"
26 #include <stddef.h>
27 #include <fcntl.h>
28 
29 #include "php_streams_int.h"
30 
31 /* Global filter hash, copied to FG(stream_filters) on registration of volatile filter */
32 static HashTable stream_filters_hash;
33 
34 /* Should only be used during core initialization */
php_get_stream_filters_hash_global(void)35 PHPAPI HashTable *php_get_stream_filters_hash_global(void)
36 {
37 	return &stream_filters_hash;
38 }
39 
40 /* Normal hash selection/retrieval call */
_php_get_stream_filters_hash(void)41 PHPAPI HashTable *_php_get_stream_filters_hash(void)
42 {
43 	return (FG(stream_filters) ? FG(stream_filters) : &stream_filters_hash);
44 }
45 
46 /* API for registering GLOBAL filters */
php_stream_filter_register_factory(const char * filterpattern,php_stream_filter_factory * factory)47 PHPAPI int php_stream_filter_register_factory(const char *filterpattern, php_stream_filter_factory *factory)
48 {
49 	return zend_hash_str_add_ptr(&stream_filters_hash, filterpattern, strlen(filterpattern), factory) ? SUCCESS : FAILURE;
50 }
51 
php_stream_filter_unregister_factory(const char * filterpattern)52 PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern)
53 {
54 	return zend_hash_str_del(&stream_filters_hash, filterpattern, strlen(filterpattern));
55 }
56 
57 /* API for registering VOLATILE wrappers */
php_stream_filter_register_factory_volatile(const char * filterpattern,php_stream_filter_factory * factory)58 PHPAPI int php_stream_filter_register_factory_volatile(const char *filterpattern, php_stream_filter_factory *factory)
59 {
60 	if (!FG(stream_filters)) {
61 		ALLOC_HASHTABLE(FG(stream_filters));
62 		zend_hash_init(FG(stream_filters), zend_hash_num_elements(&stream_filters_hash), NULL, NULL, 1);
63 		zend_hash_copy(FG(stream_filters), &stream_filters_hash, NULL);
64 	}
65 
66 	return zend_hash_str_add_ptr(FG(stream_filters), (char*)filterpattern, strlen(filterpattern), factory) ? SUCCESS : FAILURE;
67 }
68 
69 /* Buckets */
70 
php_stream_bucket_new(php_stream * stream,char * buf,size_t buflen,int own_buf,int buf_persistent)71 PHPAPI php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, int own_buf, int buf_persistent)
72 {
73 	int is_persistent = php_stream_is_persistent(stream);
74 	php_stream_bucket *bucket;
75 
76 	bucket = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), is_persistent);
77 
78 	if (bucket == NULL) {
79 		return NULL;
80 	}
81 
82 	bucket->next = bucket->prev = NULL;
83 
84 	if (is_persistent && !buf_persistent) {
85 		/* all data in a persistent bucket must also be persistent */
86 		bucket->buf = pemalloc(buflen, 1);
87 
88 		if (bucket->buf == NULL) {
89 			pefree(bucket, 1);
90 			return NULL;
91 		}
92 
93 		memcpy(bucket->buf, buf, buflen);
94 		bucket->buflen = buflen;
95 		bucket->own_buf = 1;
96 	} else {
97 		bucket->buf = buf;
98 		bucket->buflen = buflen;
99 		bucket->own_buf = own_buf;
100 	}
101 	bucket->is_persistent = is_persistent;
102 	bucket->refcount = 1;
103 	bucket->brigade = NULL;
104 
105 	return bucket;
106 }
107 
108 /* Given a bucket, returns a version of that bucket with a writeable buffer.
109  * If the original bucket has a refcount of 1 and owns its buffer, then it
110  * is returned unchanged.
111  * Otherwise, a copy of the buffer is made.
112  * In both cases, the original bucket is unlinked from its brigade.
113  * If a copy is made, the original bucket is delref'd.
114  * */
php_stream_bucket_make_writeable(php_stream_bucket * bucket)115 PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bucket)
116 {
117 	php_stream_bucket *retval;
118 
119 	php_stream_bucket_unlink(bucket);
120 
121 	if (bucket->refcount == 1 && bucket->own_buf) {
122 		return bucket;
123 	}
124 
125 	retval = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), bucket->is_persistent);
126 	memcpy(retval, bucket, sizeof(*retval));
127 
128 	retval->buf = pemalloc(retval->buflen, retval->is_persistent);
129 	memcpy(retval->buf, bucket->buf, retval->buflen);
130 
131 	retval->refcount = 1;
132 	retval->own_buf = 1;
133 
134 	php_stream_bucket_delref(bucket);
135 
136 	return retval;
137 }
138 
php_stream_bucket_split(php_stream_bucket * in,php_stream_bucket ** left,php_stream_bucket ** right,size_t length)139 PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length)
140 {
141 	*left = (php_stream_bucket*)pecalloc(1, sizeof(php_stream_bucket), in->is_persistent);
142 	*right = (php_stream_bucket*)pecalloc(1, sizeof(php_stream_bucket), in->is_persistent);
143 
144 	if (*left == NULL || *right == NULL) {
145 		goto exit_fail;
146 	}
147 
148 	(*left)->buf = pemalloc(length, in->is_persistent);
149 	(*left)->buflen = length;
150 	memcpy((*left)->buf, in->buf, length);
151 	(*left)->refcount = 1;
152 	(*left)->own_buf = 1;
153 	(*left)->is_persistent = in->is_persistent;
154 
155 	(*right)->buflen = in->buflen - length;
156 	(*right)->buf = pemalloc((*right)->buflen, in->is_persistent);
157 	memcpy((*right)->buf, in->buf + length, (*right)->buflen);
158 	(*right)->refcount = 1;
159 	(*right)->own_buf = 1;
160 	(*right)->is_persistent = in->is_persistent;
161 
162 	return SUCCESS;
163 
164 exit_fail:
165 	if (*right) {
166 		if ((*right)->buf) {
167 			pefree((*right)->buf, in->is_persistent);
168 		}
169 		pefree(*right, in->is_persistent);
170 	}
171 	if (*left) {
172 		if ((*left)->buf) {
173 			pefree((*left)->buf, in->is_persistent);
174 		}
175 		pefree(*left, in->is_persistent);
176 	}
177 	return FAILURE;
178 }
179 
php_stream_bucket_delref(php_stream_bucket * bucket)180 PHPAPI void php_stream_bucket_delref(php_stream_bucket *bucket)
181 {
182 	if (--bucket->refcount == 0) {
183 		if (bucket->own_buf) {
184 			pefree(bucket->buf, bucket->is_persistent);
185 		}
186 		pefree(bucket, bucket->is_persistent);
187 	}
188 }
189 
php_stream_bucket_prepend(php_stream_bucket_brigade * brigade,php_stream_bucket * bucket)190 PHPAPI void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket)
191 {
192 	bucket->next = brigade->head;
193 	bucket->prev = NULL;
194 
195 	if (brigade->head) {
196 		brigade->head->prev = bucket;
197 	} else {
198 		brigade->tail = bucket;
199 	}
200 	brigade->head = bucket;
201 	bucket->brigade = brigade;
202 }
203 
php_stream_bucket_append(php_stream_bucket_brigade * brigade,php_stream_bucket * bucket)204 PHPAPI void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket)
205 {
206 	if (brigade->tail == bucket) {
207 		return;
208 	}
209 
210 	bucket->prev = brigade->tail;
211 	bucket->next = NULL;
212 
213 	if (brigade->tail) {
214 		brigade->tail->next = bucket;
215 	} else {
216 		brigade->head = bucket;
217 	}
218 	brigade->tail = bucket;
219 	bucket->brigade = brigade;
220 }
221 
php_stream_bucket_unlink(php_stream_bucket * bucket)222 PHPAPI void php_stream_bucket_unlink(php_stream_bucket *bucket)
223 {
224 	if (bucket->prev) {
225 		bucket->prev->next = bucket->next;
226 	} else if (bucket->brigade) {
227 		bucket->brigade->head = bucket->next;
228 	}
229 	if (bucket->next) {
230 		bucket->next->prev = bucket->prev;
231 	} else if (bucket->brigade) {
232 		bucket->brigade->tail = bucket->prev;
233 	}
234 	bucket->brigade = NULL;
235 	bucket->next = bucket->prev = NULL;
236 }
237 
238 
239 
240 
241 
242 
243 
244 
245 /* We allow very simple pattern matching for filter factories:
246  * if "convert.charset.utf-8/sjis" is requested, we search first for an exact
247  * match. If that fails, we try "convert.charset.*", then "convert.*"
248  * This means that we don't need to clog up the hashtable with a zillion
249  * charsets (for example) but still be able to provide them all as filters */
php_stream_filter_create(const char * filtername,zval * filterparams,int persistent)250 PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, int persistent)
251 {
252 	HashTable *filter_hash = (FG(stream_filters) ? FG(stream_filters) : &stream_filters_hash);
253 	php_stream_filter_factory *factory = NULL;
254 	php_stream_filter *filter = NULL;
255 	size_t n;
256 	char *period;
257 
258 	n = strlen(filtername);
259 
260 	if (NULL != (factory = zend_hash_str_find_ptr(filter_hash, filtername, n))) {
261 		filter = factory->create_filter(filtername, filterparams, persistent);
262 	} else if ((period = strrchr(filtername, '.'))) {
263 		/* try a wildcard */
264 		char *wildname;
265 
266 		wildname = safe_emalloc(1, n, 3);
267 		memcpy(wildname, filtername, n+1);
268 		period = wildname + (period - filtername);
269 		while (period && !filter) {
270 			*period = '\0';
271 			strncat(wildname, ".*", 2);
272 			if (NULL != (factory = zend_hash_str_find_ptr(filter_hash, wildname, strlen(wildname)))) {
273 				filter = factory->create_filter(filtername, filterparams, persistent);
274 			}
275 
276 			*period = '\0';
277 			period = strrchr(wildname, '.');
278 		}
279 		efree(wildname);
280 	}
281 
282 	if (filter == NULL) {
283 		/* TODO: these need correct docrefs */
284 		if (factory == NULL)
285 			php_error_docref(NULL, E_WARNING, "unable to locate filter \"%s\"", filtername);
286 		else
287 			php_error_docref(NULL, E_WARNING, "unable to create or locate filter \"%s\"", filtername);
288 	}
289 
290 	return filter;
291 }
292 
_php_stream_filter_alloc(php_stream_filter_ops * fops,void * abstract,int persistent STREAMS_DC)293 PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, int persistent STREAMS_DC)
294 {
295 	php_stream_filter *filter;
296 
297 	filter = (php_stream_filter*) pemalloc_rel_orig(sizeof(php_stream_filter), persistent);
298 	memset(filter, 0, sizeof(php_stream_filter));
299 
300 	filter->fops = fops;
301 	Z_PTR(filter->abstract) = abstract;
302 	filter->is_persistent = persistent;
303 
304 	return filter;
305 }
306 
php_stream_filter_free(php_stream_filter * filter)307 PHPAPI void php_stream_filter_free(php_stream_filter *filter)
308 {
309 	if (filter->fops->dtor)
310 		filter->fops->dtor(filter);
311 	pefree(filter, filter->is_persistent);
312 }
313 
php_stream_filter_prepend_ex(php_stream_filter_chain * chain,php_stream_filter * filter)314 PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
315 {
316 	filter->next = chain->head;
317 	filter->prev = NULL;
318 
319 	if (chain->head) {
320 		chain->head->prev = filter;
321 	} else {
322 		chain->tail = filter;
323 	}
324 	chain->head = filter;
325 	filter->chain = chain;
326 
327 	return SUCCESS;
328 }
329 
_php_stream_filter_prepend(php_stream_filter_chain * chain,php_stream_filter * filter)330 PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter)
331 {
332 	php_stream_filter_prepend_ex(chain, filter);
333 }
334 
php_stream_filter_append_ex(php_stream_filter_chain * chain,php_stream_filter * filter)335 PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
336 {
337 	php_stream *stream = chain->stream;
338 
339 	filter->prev = chain->tail;
340 	filter->next = NULL;
341 	if (chain->tail) {
342 		chain->tail->next = filter;
343 	} else {
344 		chain->head = filter;
345 	}
346 	chain->tail = filter;
347 	filter->chain = chain;
348 
349 	if (&(stream->readfilters) == chain && (stream->writepos - stream->readpos) > 0) {
350 		/* Let's going ahead and wind anything in the buffer through this filter */
351 		php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
352 		php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out;
353 		php_stream_filter_status_t status;
354 		php_stream_bucket *bucket;
355 		size_t consumed = 0;
356 
357 		bucket = php_stream_bucket_new(stream, (char*) stream->readbuf + stream->readpos, stream->writepos - stream->readpos, 0, 0);
358 		php_stream_bucket_append(brig_inp, bucket);
359 		status = filter->fops->filter(stream, filter, brig_inp, brig_outp, &consumed, PSFS_FLAG_NORMAL);
360 
361 		if (stream->readpos + consumed > (uint)stream->writepos) {
362 			/* No behaving filter should cause this. */
363 			status = PSFS_ERR_FATAL;
364 		}
365 
366 		switch (status) {
367 			case PSFS_ERR_FATAL:
368 				while (brig_in.head) {
369 					bucket = brig_in.head;
370 					php_stream_bucket_unlink(bucket);
371 					php_stream_bucket_delref(bucket);
372 				}
373 				while (brig_out.head) {
374 					bucket = brig_out.head;
375 					php_stream_bucket_unlink(bucket);
376 					php_stream_bucket_delref(bucket);
377 				}
378 				php_error_docref(NULL, E_WARNING, "Filter failed to process pre-buffered data");
379 				return FAILURE;
380 			case PSFS_FEED_ME:
381 				/* We don't actually need data yet,
382 				   leave this filter in a feed me state until data is needed.
383 				   Reset stream's internal read buffer since the filter is "holding" it. */
384 				stream->readpos = 0;
385 				stream->writepos = 0;
386 				break;
387 			case PSFS_PASS_ON:
388 				/* If any data is consumed, we cannot rely upon the existing read buffer,
389 				   as the filtered data must replace the existing data, so invalidate the cache */
390 				/* note that changes here should be reflected in
391 				   main/streams/streams.c::php_stream_fill_read_buffer */
392 				stream->writepos = 0;
393 				stream->readpos = 0;
394 
395 				while (brig_outp->head) {
396 					bucket = brig_outp->head;
397 					/* Grow buffer to hold this bucket if need be.
398 					   TODO: See warning in main/stream/streams.c::php_stream_fill_read_buffer */
399 					if (stream->readbuflen - stream->writepos < bucket->buflen) {
400 						stream->readbuflen += bucket->buflen;
401 						stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, stream->is_persistent);
402 					}
403 					memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
404 					stream->writepos += bucket->buflen;
405 
406 					php_stream_bucket_unlink(bucket);
407 					php_stream_bucket_delref(bucket);
408 				}
409 				break;
410 		}
411 	}
412 
413 	return SUCCESS;
414 }
415 
_php_stream_filter_append(php_stream_filter_chain * chain,php_stream_filter * filter)416 PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter)
417 {
418 	if (php_stream_filter_append_ex(chain, filter) != SUCCESS) {
419 		if (chain->head == filter) {
420 			chain->head = NULL;
421 			chain->tail = NULL;
422 		} else {
423 			filter->prev->next = NULL;
424 			chain->tail = filter->prev;
425 		}
426 	}
427 }
428 
_php_stream_filter_flush(php_stream_filter * filter,int finish)429 PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
430 {
431 	php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;
432 	php_stream_bucket *bucket;
433 	php_stream_filter_chain *chain;
434 	php_stream_filter *current;
435 	php_stream *stream;
436 	size_t flushed_size = 0;
437 	long flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC);
438 
439 	if (!filter->chain || !filter->chain->stream) {
440 		/* Filter is not attached to a chain, or chain is somehow not part of a stream */
441 		return FAILURE;
442 	}
443 
444 	chain = filter->chain;
445 	stream = chain->stream;
446 
447 	for(current = filter; current; current = current->next) {
448 		php_stream_filter_status_t status;
449 
450 		status = filter->fops->filter(stream, current, inp, outp, NULL, flags);
451 		if (status == PSFS_FEED_ME) {
452 			/* We've flushed the data far enough */
453 			return SUCCESS;
454 		}
455 		if (status == PSFS_ERR_FATAL) {
456 			return FAILURE;
457 		}
458 		/* Otherwise we have data available to PASS_ON
459 			Swap the brigades and continue */
460 		brig_temp = inp;
461 		inp = outp;
462 		outp = brig_temp;
463 		outp->head = NULL;
464 		outp->tail = NULL;
465 
466 		flags = PSFS_FLAG_NORMAL;
467 	}
468 
469 	/* Last filter returned data via PSFS_PASS_ON
470 		Do something with it */
471 
472 	for(bucket = inp->head; bucket; bucket = bucket->next) {
473 		flushed_size += bucket->buflen;
474 	}
475 
476 	if (flushed_size == 0) {
477 		/* Unlikely, but possible */
478 		return SUCCESS;
479 	}
480 
481 	if (chain == &(stream->readfilters)) {
482 		/* Dump any newly flushed data to the read buffer */
483 		if (stream->readpos > 0) {
484 			/* Back the buffer up */
485 			memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
486 			stream->readpos = 0;
487 			stream->writepos -= stream->readpos;
488 		}
489 		if (flushed_size > (stream->readbuflen - stream->writepos)) {
490 			/* Grow the buffer */
491 			stream->readbuf = perealloc(stream->readbuf, stream->writepos + flushed_size + stream->chunk_size, stream->is_persistent);
492 		}
493 		while ((bucket = inp->head)) {
494 			memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
495 			stream->writepos += bucket->buflen;
496 			php_stream_bucket_unlink(bucket);
497 			php_stream_bucket_delref(bucket);
498 		}
499 	} else if (chain == &(stream->writefilters)) {
500 		/* Send flushed data to the stream */
501 		while ((bucket = inp->head)) {
502 			stream->ops->write(stream, bucket->buf, bucket->buflen);
503 			php_stream_bucket_unlink(bucket);
504 			php_stream_bucket_delref(bucket);
505 		}
506 	}
507 
508 	return SUCCESS;
509 }
510 
php_stream_filter_remove(php_stream_filter * filter,int call_dtor)511 PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor)
512 {
513 	if (filter->prev) {
514 		filter->prev->next = filter->next;
515 	} else {
516 		filter->chain->head = filter->next;
517 	}
518 	if (filter->next) {
519 		filter->next->prev = filter->prev;
520 	} else {
521 		filter->chain->tail = filter->prev;
522 	}
523 
524 	if (filter->res) {
525 		zend_list_delete(filter->res);
526 	}
527 
528 	if (call_dtor) {
529 		php_stream_filter_free(filter);
530 		return NULL;
531 	}
532 	return filter;
533 }
534 
535 /*
536  * Local variables:
537  * tab-width: 4
538  * c-basic-offset: 4
539  * End:
540  * vim600: noet sw=4 ts=4 fdm=marker
541  * vim<600: noet sw=4 ts=4
542  */
543