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