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