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 /* $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,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 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 *period = '\0';
241 strncat(wildname, ".*", 2);
242 if (NULL != (factory = zend_hash_str_find_ptr(filter_hash, wildname, strlen(wildname)))) {
243 filter = factory->create_filter(filtername, filterparams, persistent);
244 }
245
246 *period = '\0';
247 period = strrchr(wildname, '.');
248 }
249 efree(wildname);
250 }
251
252 if (filter == NULL) {
253 /* TODO: these need correct docrefs */
254 if (factory == NULL)
255 php_error_docref(NULL, E_WARNING, "unable to locate filter \"%s\"", filtername);
256 else
257 php_error_docref(NULL, E_WARNING, "unable to create or locate filter \"%s\"", filtername);
258 }
259
260 return filter;
261 }
262
_php_stream_filter_alloc(php_stream_filter_ops * fops,void * abstract,uint8_t persistent STREAMS_DC)263 PHPAPI php_stream_filter *_php_stream_filter_alloc(php_stream_filter_ops *fops, void *abstract, uint8_t persistent STREAMS_DC)
264 {
265 php_stream_filter *filter;
266
267 filter = (php_stream_filter*) pemalloc_rel_orig(sizeof(php_stream_filter), persistent);
268 memset(filter, 0, sizeof(php_stream_filter));
269
270 filter->fops = fops;
271 Z_PTR(filter->abstract) = abstract;
272 filter->is_persistent = persistent;
273
274 return filter;
275 }
276
php_stream_filter_free(php_stream_filter * filter)277 PHPAPI void php_stream_filter_free(php_stream_filter *filter)
278 {
279 if (filter->fops->dtor)
280 filter->fops->dtor(filter);
281 pefree(filter, filter->is_persistent);
282 }
283
php_stream_filter_prepend_ex(php_stream_filter_chain * chain,php_stream_filter * filter)284 PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
285 {
286 filter->next = chain->head;
287 filter->prev = NULL;
288
289 if (chain->head) {
290 chain->head->prev = filter;
291 } else {
292 chain->tail = filter;
293 }
294 chain->head = filter;
295 filter->chain = chain;
296
297 return SUCCESS;
298 }
299
_php_stream_filter_prepend(php_stream_filter_chain * chain,php_stream_filter * filter)300 PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter)
301 {
302 php_stream_filter_prepend_ex(chain, filter);
303 }
304
php_stream_filter_append_ex(php_stream_filter_chain * chain,php_stream_filter * filter)305 PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
306 {
307 php_stream *stream = chain->stream;
308
309 filter->prev = chain->tail;
310 filter->next = NULL;
311 if (chain->tail) {
312 chain->tail->next = filter;
313 } else {
314 chain->head = filter;
315 }
316 chain->tail = filter;
317 filter->chain = chain;
318
319 if (&(stream->readfilters) == chain && (stream->writepos - stream->readpos) > 0) {
320 /* Let's going ahead and wind anything in the buffer through this filter */
321 php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
322 php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out;
323 php_stream_filter_status_t status;
324 php_stream_bucket *bucket;
325 size_t consumed = 0;
326
327 bucket = php_stream_bucket_new(stream, (char*) stream->readbuf + stream->readpos, stream->writepos - stream->readpos, 0, 0);
328 php_stream_bucket_append(brig_inp, bucket);
329 status = filter->fops->filter(stream, filter, brig_inp, brig_outp, &consumed, PSFS_FLAG_NORMAL);
330
331 if (stream->readpos + consumed > (uint32_t)stream->writepos) {
332 /* No behaving filter should cause this. */
333 status = PSFS_ERR_FATAL;
334 }
335
336 switch (status) {
337 case PSFS_ERR_FATAL:
338 while (brig_in.head) {
339 bucket = brig_in.head;
340 php_stream_bucket_unlink(bucket);
341 php_stream_bucket_delref(bucket);
342 }
343 while (brig_out.head) {
344 bucket = brig_out.head;
345 php_stream_bucket_unlink(bucket);
346 php_stream_bucket_delref(bucket);
347 }
348 php_error_docref(NULL, E_WARNING, "Filter failed to process pre-buffered data");
349 return FAILURE;
350 case PSFS_FEED_ME:
351 /* We don't actually need data yet,
352 leave this filter in a feed me state until data is needed.
353 Reset stream's internal read buffer since the filter is "holding" it. */
354 stream->readpos = 0;
355 stream->writepos = 0;
356 break;
357 case PSFS_PASS_ON:
358 /* If any data is consumed, we cannot rely upon the existing read buffer,
359 as the filtered data must replace the existing data, so invalidate the cache */
360 stream->writepos = 0;
361 stream->readpos = 0;
362
363 while (brig_outp->head) {
364 bucket = brig_outp->head;
365 /* Grow buffer to hold this bucket if need be.
366 TODO: See warning in main/stream/streams.c::php_stream_fill_read_buffer */
367 if (stream->readbuflen - stream->writepos < bucket->buflen) {
368 stream->readbuflen += bucket->buflen;
369 stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, stream->is_persistent);
370 }
371 memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
372 stream->writepos += bucket->buflen;
373
374 php_stream_bucket_unlink(bucket);
375 php_stream_bucket_delref(bucket);
376 }
377 break;
378 }
379 }
380
381 return SUCCESS;
382 }
383
_php_stream_filter_append(php_stream_filter_chain * chain,php_stream_filter * filter)384 PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter)
385 {
386 if (php_stream_filter_append_ex(chain, filter) != SUCCESS) {
387 if (chain->head == filter) {
388 chain->head = NULL;
389 chain->tail = NULL;
390 } else {
391 filter->prev->next = NULL;
392 chain->tail = filter->prev;
393 }
394 }
395 }
396
_php_stream_filter_flush(php_stream_filter * filter,int finish)397 PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
398 {
399 php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;
400 php_stream_bucket *bucket;
401 php_stream_filter_chain *chain;
402 php_stream_filter *current;
403 php_stream *stream;
404 size_t flushed_size = 0;
405 long flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC);
406
407 if (!filter->chain || !filter->chain->stream) {
408 /* Filter is not attached to a chain, or chain is somehow not part of a stream */
409 return FAILURE;
410 }
411
412 chain = filter->chain;
413 stream = chain->stream;
414
415 for(current = filter; current; current = current->next) {
416 php_stream_filter_status_t status;
417
418 status = filter->fops->filter(stream, current, inp, outp, NULL, flags);
419 if (status == PSFS_FEED_ME) {
420 /* We've flushed the data far enough */
421 return SUCCESS;
422 }
423 if (status == PSFS_ERR_FATAL) {
424 return FAILURE;
425 }
426 /* Otherwise we have data available to PASS_ON
427 Swap the brigades and continue */
428 brig_temp = inp;
429 inp = outp;
430 outp = brig_temp;
431 outp->head = NULL;
432 outp->tail = NULL;
433
434 flags = PSFS_FLAG_NORMAL;
435 }
436
437 /* Last filter returned data via PSFS_PASS_ON
438 Do something with it */
439
440 for(bucket = inp->head; bucket; bucket = bucket->next) {
441 flushed_size += bucket->buflen;
442 }
443
444 if (flushed_size == 0) {
445 /* Unlikely, but possible */
446 return SUCCESS;
447 }
448
449 if (chain == &(stream->readfilters)) {
450 /* Dump any newly flushed data to the read buffer */
451 if (stream->readpos > 0) {
452 /* Back the buffer up */
453 memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
454 stream->readpos = 0;
455 stream->writepos -= stream->readpos;
456 }
457 if (flushed_size > (stream->readbuflen - stream->writepos)) {
458 /* Grow the buffer */
459 stream->readbuf = perealloc(stream->readbuf, stream->writepos + flushed_size + stream->chunk_size, stream->is_persistent);
460 }
461 while ((bucket = inp->head)) {
462 memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
463 stream->writepos += bucket->buflen;
464 php_stream_bucket_unlink(bucket);
465 php_stream_bucket_delref(bucket);
466 }
467 } else if (chain == &(stream->writefilters)) {
468 /* Send flushed data to the stream */
469 while ((bucket = inp->head)) {
470 stream->ops->write(stream, bucket->buf, bucket->buflen);
471 php_stream_bucket_unlink(bucket);
472 php_stream_bucket_delref(bucket);
473 }
474 }
475
476 return SUCCESS;
477 }
478
php_stream_filter_remove(php_stream_filter * filter,int call_dtor)479 PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor)
480 {
481 if (filter->prev) {
482 filter->prev->next = filter->next;
483 } else {
484 filter->chain->head = filter->next;
485 }
486 if (filter->next) {
487 filter->next->prev = filter->prev;
488 } else {
489 filter->chain->tail = filter->prev;
490 }
491
492 if (filter->res) {
493 zend_list_delete(filter->res);
494 }
495
496 if (call_dtor) {
497 php_stream_filter_free(filter);
498 return NULL;
499 }
500 return filter;
501 }
502
503 /*
504 * Local variables:
505 * tab-width: 4
506 * c-basic-offset: 4
507 * End:
508 * vim600: noet sw=4 ts=4 fdm=marker
509 * vim<600: noet sw=4 ts=4
510 */
511