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