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