1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 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:                                                             |
16   | Wez Furlong (wez@thebrainroom.com)                                   |
17   | Sara Golemon (pollita@php.net)                                       |
18   | Moriyoshi Koizumi (moriyoshi@php.net)                                |
19   | Marcus Boerger (helly@php.net)                                       |
20   +----------------------------------------------------------------------+
21*/
22
23/* $Id$ */
24
25#include "php.h"
26#include "php_globals.h"
27#include "ext/standard/basic_functions.h"
28#include "ext/standard/file.h"
29#include "ext/standard/php_string.h"
30#include "zend_smart_str.h"
31
32/* {{{ rot13 stream filter implementation */
33static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
34static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
35
36static php_stream_filter_status_t strfilter_rot13_filter(
37	php_stream *stream,
38	php_stream_filter *thisfilter,
39	php_stream_bucket_brigade *buckets_in,
40	php_stream_bucket_brigade *buckets_out,
41	size_t *bytes_consumed,
42	int flags
43	)
44{
45	php_stream_bucket *bucket;
46	size_t consumed = 0;
47
48	while (buckets_in->head) {
49		bucket = php_stream_bucket_make_writeable(buckets_in->head);
50
51		php_strtr(bucket->buf, bucket->buflen, rot13_from, rot13_to, 52);
52		consumed += bucket->buflen;
53
54		php_stream_bucket_append(buckets_out, bucket);
55	}
56
57	if (bytes_consumed) {
58		*bytes_consumed = consumed;
59	}
60
61	return PSFS_PASS_ON;
62}
63
64static php_stream_filter_ops strfilter_rot13_ops = {
65	strfilter_rot13_filter,
66	NULL,
67	"string.rot13"
68};
69
70static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, int persistent)
71{
72	return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent);
73}
74
75static php_stream_filter_factory strfilter_rot13_factory = {
76	strfilter_rot13_create
77};
78/* }}} */
79
80/* {{{ string.toupper / string.tolower stream filter implementation */
81static char lowercase[] = "abcdefghijklmnopqrstuvwxyz";
82static char uppercase[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
83
84static php_stream_filter_status_t strfilter_toupper_filter(
85	php_stream *stream,
86	php_stream_filter *thisfilter,
87	php_stream_bucket_brigade *buckets_in,
88	php_stream_bucket_brigade *buckets_out,
89	size_t *bytes_consumed,
90	int flags
91	)
92{
93	php_stream_bucket *bucket;
94	size_t consumed = 0;
95
96	while (buckets_in->head) {
97		bucket = php_stream_bucket_make_writeable(buckets_in->head);
98
99		php_strtr(bucket->buf, bucket->buflen, lowercase, uppercase, 26);
100		consumed += bucket->buflen;
101
102		php_stream_bucket_append(buckets_out, bucket);
103	}
104
105	if (bytes_consumed) {
106		*bytes_consumed = consumed;
107	}
108
109	return PSFS_PASS_ON;
110}
111
112static php_stream_filter_status_t strfilter_tolower_filter(
113	php_stream *stream,
114	php_stream_filter *thisfilter,
115	php_stream_bucket_brigade *buckets_in,
116	php_stream_bucket_brigade *buckets_out,
117	size_t *bytes_consumed,
118	int flags
119	)
120{
121	php_stream_bucket *bucket;
122	size_t consumed = 0;
123
124	while (buckets_in->head) {
125		bucket = php_stream_bucket_make_writeable(buckets_in->head);
126
127		php_strtr(bucket->buf, bucket->buflen, uppercase, lowercase, 26);
128		consumed += bucket->buflen;
129
130		php_stream_bucket_append(buckets_out, bucket);
131	}
132
133	if (bytes_consumed) {
134		*bytes_consumed = consumed;
135	}
136
137	return PSFS_PASS_ON;
138}
139
140static php_stream_filter_ops strfilter_toupper_ops = {
141	strfilter_toupper_filter,
142	NULL,
143	"string.toupper"
144};
145
146static php_stream_filter_ops strfilter_tolower_ops = {
147	strfilter_tolower_filter,
148	NULL,
149	"string.tolower"
150};
151
152static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, int persistent)
153{
154	return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent);
155}
156
157static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, int persistent)
158{
159	return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent);
160}
161
162static php_stream_filter_factory strfilter_toupper_factory = {
163	strfilter_toupper_create
164};
165
166static php_stream_filter_factory strfilter_tolower_factory = {
167	strfilter_tolower_create
168};
169/* }}} */
170
171/* {{{ strip_tags filter implementation */
172typedef struct _php_strip_tags_filter {
173	const char *allowed_tags;
174	int allowed_tags_len;
175	int state;
176	int persistent;
177} php_strip_tags_filter;
178
179static int php_strip_tags_filter_ctor(php_strip_tags_filter *inst, const char *allowed_tags, size_t allowed_tags_len, int persistent)
180{
181	if (allowed_tags != NULL) {
182		if (NULL == (inst->allowed_tags = pemalloc(allowed_tags_len, persistent))) {
183			return FAILURE;
184		}
185		memcpy((char *)inst->allowed_tags, allowed_tags, allowed_tags_len);
186		inst->allowed_tags_len = (int)allowed_tags_len;
187	} else {
188		inst->allowed_tags = NULL;
189	}
190	inst->state = 0;
191	inst->persistent = persistent;
192
193	return SUCCESS;
194}
195
196static void php_strip_tags_filter_dtor(php_strip_tags_filter *inst)
197{
198	if (inst->allowed_tags != NULL) {
199		pefree((void *)inst->allowed_tags, inst->persistent);
200	}
201}
202
203static php_stream_filter_status_t strfilter_strip_tags_filter(
204	php_stream *stream,
205	php_stream_filter *thisfilter,
206	php_stream_bucket_brigade *buckets_in,
207	php_stream_bucket_brigade *buckets_out,
208	size_t *bytes_consumed,
209	int flags
210	)
211{
212	php_stream_bucket *bucket;
213	size_t consumed = 0;
214	php_strip_tags_filter *inst = (php_strip_tags_filter *) Z_PTR(thisfilter->abstract);
215
216	while (buckets_in->head) {
217		bucket = php_stream_bucket_make_writeable(buckets_in->head);
218		consumed = bucket->buflen;
219
220		bucket->buflen = php_strip_tags(bucket->buf, bucket->buflen, &(inst->state), inst->allowed_tags, inst->allowed_tags_len);
221
222		php_stream_bucket_append(buckets_out, bucket);
223	}
224
225	if (bytes_consumed) {
226		*bytes_consumed = consumed;
227	}
228
229	return PSFS_PASS_ON;
230}
231
232static void strfilter_strip_tags_dtor(php_stream_filter *thisfilter)
233{
234	assert(Z_PTR(thisfilter->abstract) != NULL);
235
236	php_strip_tags_filter_dtor((php_strip_tags_filter *)Z_PTR(thisfilter->abstract));
237
238	pefree(Z_PTR(thisfilter->abstract), ((php_strip_tags_filter *)Z_PTR(thisfilter->abstract))->persistent);
239}
240
241static php_stream_filter_ops strfilter_strip_tags_ops = {
242	strfilter_strip_tags_filter,
243	strfilter_strip_tags_dtor,
244	"string.strip_tags"
245};
246
247static php_stream_filter *strfilter_strip_tags_create(const char *filtername, zval *filterparams, int persistent)
248{
249	php_strip_tags_filter *inst;
250	smart_str tags_ss = {0};
251
252	inst = pemalloc(sizeof(php_strip_tags_filter), persistent);
253
254	if (inst == NULL) { /* it's possible pemalloc returns NULL
255						   instead of causing it to bail out */
256		return NULL;
257	}
258
259	if (filterparams != NULL) {
260		if (Z_TYPE_P(filterparams) == IS_ARRAY) {
261			zval *tmp;
262
263			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(filterparams), tmp) {
264				convert_to_string_ex(tmp);
265				smart_str_appendc(&tags_ss, '<');
266				smart_str_append(&tags_ss, Z_STR_P(tmp));
267				smart_str_appendc(&tags_ss, '>');
268			} ZEND_HASH_FOREACH_END();
269			smart_str_0(&tags_ss);
270		} else {
271			/* FIXME: convert_to_* may clutter zvals and lead it into segfault ? */
272			convert_to_string_ex(filterparams);
273			smart_str_setl(&tags_ss, Z_STRVAL_P(filterparams), Z_STRLEN_P(filterparams));
274		}
275	}
276
277	if (php_strip_tags_filter_ctor(inst, ZSTR_VAL(tags_ss.s), ZSTR_LEN(tags_ss.s), persistent) != SUCCESS) {
278		smart_str_free(&tags_ss);
279		pefree(inst, persistent);
280		return NULL;
281	}
282
283	smart_str_free(&tags_ss);
284
285	return php_stream_filter_alloc(&strfilter_strip_tags_ops, inst, persistent);
286}
287
288static php_stream_filter_factory strfilter_strip_tags_factory = {
289	strfilter_strip_tags_create
290};
291
292/* }}} */
293
294/* {{{ base64 / quoted_printable stream filter implementation */
295
296typedef enum _php_conv_err_t {
297	PHP_CONV_ERR_SUCCESS = SUCCESS,
298	PHP_CONV_ERR_UNKNOWN,
299	PHP_CONV_ERR_TOO_BIG,
300	PHP_CONV_ERR_INVALID_SEQ,
301	PHP_CONV_ERR_UNEXPECTED_EOS,
302	PHP_CONV_ERR_EXISTS,
303	PHP_CONV_ERR_MORE,
304	PHP_CONV_ERR_ALLOC,
305	PHP_CONV_ERR_NOT_FOUND
306} php_conv_err_t;
307
308typedef struct _php_conv php_conv;
309
310typedef php_conv_err_t (*php_conv_convert_func)(php_conv *, const char **, size_t *, char **, size_t *);
311typedef void (*php_conv_dtor_func)(php_conv *);
312
313struct _php_conv {
314	php_conv_convert_func convert_op;
315	php_conv_dtor_func dtor;
316};
317
318#define php_conv_convert(a, b, c, d, e) ((php_conv *)(a))->convert_op((php_conv *)(a), (b), (c), (d), (e))
319#define php_conv_dtor(a) ((php_conv *)a)->dtor((a))
320
321/* {{{ php_conv_base64_encode */
322typedef struct _php_conv_base64_encode {
323	php_conv _super;
324
325	const char *lbchars;
326	size_t lbchars_len;
327	size_t erem_len;
328	unsigned int line_ccnt;
329	unsigned int line_len;
330	int lbchars_dup;
331	int persistent;
332	unsigned char erem[3];
333} php_conv_base64_encode;
334
335static php_conv_err_t php_conv_base64_encode_convert(php_conv_base64_encode *inst, const char **in_p, size_t *in_left, char **out_p, size_t *out_left);
336static void php_conv_base64_encode_dtor(php_conv_base64_encode *inst);
337
338static unsigned char b64_tbl_enc[256] = {
339	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
340	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
341	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
342	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
343	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
344	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
345	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
346	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
347	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
348	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
349	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
350	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
351	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
352	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
353	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
354	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
355};
356
357static php_conv_err_t php_conv_base64_encode_ctor(php_conv_base64_encode *inst, unsigned int line_len, const char *lbchars, size_t lbchars_len, int lbchars_dup, int persistent)
358{
359	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_encode_convert;
360	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_encode_dtor;
361	inst->erem_len = 0;
362	inst->line_ccnt = line_len;
363	inst->line_len = line_len;
364	if (lbchars != NULL) {
365		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
366		inst->lbchars_len = lbchars_len;
367	} else {
368		inst->lbchars = NULL;
369	}
370	inst->lbchars_dup = lbchars_dup;
371	inst->persistent = persistent;
372	return PHP_CONV_ERR_SUCCESS;
373}
374
375static void php_conv_base64_encode_dtor(php_conv_base64_encode *inst)
376{
377	assert(inst != NULL);
378	if (inst->lbchars_dup && inst->lbchars != NULL) {
379		pefree((void *)inst->lbchars, inst->persistent);
380	}
381}
382
383static php_conv_err_t php_conv_base64_encode_flush(php_conv_base64_encode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
384{
385	volatile php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
386	register unsigned char *pd;
387	register size_t ocnt;
388	unsigned int line_ccnt;
389
390	pd = (unsigned char *)(*out_pp);
391	ocnt = *out_left_p;
392	line_ccnt = inst->line_ccnt;
393
394	switch (inst->erem_len) {
395		case 0:
396			/* do nothing */
397			break;
398
399		case 1:
400			if (line_ccnt < 4 && inst->lbchars != NULL) {
401				if (ocnt < inst->lbchars_len) {
402					return PHP_CONV_ERR_TOO_BIG;
403				}
404				memcpy(pd, inst->lbchars, inst->lbchars_len);
405				pd += inst->lbchars_len;
406				ocnt -= inst->lbchars_len;
407				line_ccnt = inst->line_len;
408			}
409			if (ocnt < 4) {
410				err = PHP_CONV_ERR_TOO_BIG;
411				goto out;
412			}
413			*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
414			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4)];
415			*(pd++) = '=';
416			*(pd++) = '=';
417			inst->erem_len = 0;
418			ocnt -= 4;
419			line_ccnt -= 4;
420			break;
421
422		case 2:
423			if (line_ccnt < 4 && inst->lbchars != NULL) {
424				if (ocnt < inst->lbchars_len) {
425					return PHP_CONV_ERR_TOO_BIG;
426				}
427				memcpy(pd, inst->lbchars, inst->lbchars_len);
428				pd += inst->lbchars_len;
429				ocnt -= inst->lbchars_len;
430				line_ccnt = inst->line_len;
431			}
432			if (ocnt < 4) {
433				err = PHP_CONV_ERR_TOO_BIG;
434				goto out;
435			}
436			*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
437			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (inst->erem[1] >> 4)];
438			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[1] << 2)];
439			*(pd++) = '=';
440			inst->erem_len = 0;
441			ocnt -=4;
442			line_ccnt -= 4;
443			break;
444
445		default:
446			/* should not happen... */
447			err = PHP_CONV_ERR_UNKNOWN;
448			break;
449	}
450out:
451	*out_pp = (char *)pd;
452	*out_left_p = ocnt;
453	inst->line_ccnt = line_ccnt;
454	return err;
455}
456
457static php_conv_err_t php_conv_base64_encode_convert(php_conv_base64_encode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
458{
459	volatile php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
460	register size_t ocnt, icnt;
461	register unsigned char *ps, *pd;
462	register unsigned int line_ccnt;
463
464	if (in_pp == NULL || in_left_p == NULL) {
465		return php_conv_base64_encode_flush(inst, in_pp, in_left_p, out_pp, out_left_p);
466	}
467
468	pd = (unsigned char *)(*out_pp);
469	ocnt = *out_left_p;
470	ps = (unsigned char *)(*in_pp);
471	icnt = *in_left_p;
472	line_ccnt = inst->line_ccnt;
473
474	/* consume the remainder first */
475	switch (inst->erem_len) {
476		case 1:
477			if (icnt >= 2) {
478				if (line_ccnt < 4 && inst->lbchars != NULL) {
479					if (ocnt < inst->lbchars_len) {
480						return PHP_CONV_ERR_TOO_BIG;
481					}
482					memcpy(pd, inst->lbchars, inst->lbchars_len);
483					pd += inst->lbchars_len;
484					ocnt -= inst->lbchars_len;
485					line_ccnt = inst->line_len;
486				}
487				if (ocnt < 4) {
488					err = PHP_CONV_ERR_TOO_BIG;
489					goto out;
490				}
491				*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
492				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (ps[0] >> 4)];
493				*(pd++) = b64_tbl_enc[(unsigned char)(ps[0] << 2) | (ps[1] >> 6)];
494				*(pd++) = b64_tbl_enc[ps[1]];
495				ocnt -= 4;
496				ps += 2;
497				icnt -= 2;
498				inst->erem_len = 0;
499				line_ccnt -= 4;
500			}
501			break;
502
503		case 2:
504			if (icnt >= 1) {
505				if (inst->line_ccnt < 4 && inst->lbchars != NULL) {
506					if (ocnt < inst->lbchars_len) {
507						return PHP_CONV_ERR_TOO_BIG;
508					}
509					memcpy(pd, inst->lbchars, inst->lbchars_len);
510					pd += inst->lbchars_len;
511					ocnt -= inst->lbchars_len;
512					line_ccnt = inst->line_len;
513				}
514				if (ocnt < 4) {
515					err = PHP_CONV_ERR_TOO_BIG;
516					goto out;
517				}
518				*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
519				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (inst->erem[1] >> 4)];
520				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[1] << 2) | (ps[0] >> 6)];
521				*(pd++) = b64_tbl_enc[ps[0]];
522				ocnt -= 4;
523				ps += 1;
524				icnt -= 1;
525				inst->erem_len = 0;
526				line_ccnt -= 4;
527			}
528			break;
529	}
530
531	while (icnt >= 3) {
532		if (line_ccnt < 4 && inst->lbchars != NULL) {
533			if (ocnt < inst->lbchars_len) {
534				err = PHP_CONV_ERR_TOO_BIG;
535				goto out;
536			}
537			memcpy(pd, inst->lbchars, inst->lbchars_len);
538			pd += inst->lbchars_len;
539			ocnt -= inst->lbchars_len;
540			line_ccnt = inst->line_len;
541		}
542		if (ocnt < 4) {
543			err = PHP_CONV_ERR_TOO_BIG;
544			goto out;
545		}
546		*(pd++) = b64_tbl_enc[ps[0] >> 2];
547		*(pd++) = b64_tbl_enc[(unsigned char)(ps[0] << 4) | (ps[1] >> 4)];
548		*(pd++) = b64_tbl_enc[(unsigned char)(ps[1] << 2) | (ps[2] >> 6)];
549		*(pd++) = b64_tbl_enc[ps[2]];
550
551		ps += 3;
552		icnt -=3;
553		ocnt -= 4;
554		line_ccnt -= 4;
555	}
556	for (;icnt > 0; icnt--) {
557		inst->erem[inst->erem_len++] = *(ps++);
558	}
559
560out:
561	*in_pp = (const char *)ps;
562	*in_left_p = icnt;
563	*out_pp = (char *)pd;
564	*out_left_p = ocnt;
565	inst->line_ccnt = line_ccnt;
566
567	return err;
568}
569
570/* }}} */
571
572/* {{{ php_conv_base64_decode */
573typedef struct _php_conv_base64_decode {
574	php_conv _super;
575
576	unsigned int urem;
577	unsigned int urem_nbits;
578	unsigned int ustat;
579	int eos;
580} php_conv_base64_decode;
581
582static php_conv_err_t php_conv_base64_decode_convert(php_conv_base64_decode *inst, const char **in_p, size_t *in_left, char **out_p, size_t *out_left);
583static void php_conv_base64_decode_dtor(php_conv_base64_decode *inst);
584
585static unsigned int b64_tbl_dec[256] = {
586	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
587	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
588	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
589	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64,128, 64, 64,
590	64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
591	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
592	64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
593	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
594	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
595	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
596	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
597	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
598	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
599	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
600	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
601	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
602};
603
604static int php_conv_base64_decode_ctor(php_conv_base64_decode *inst)
605{
606	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_decode_convert;
607	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_decode_dtor;
608
609	inst->urem = 0;
610	inst->urem_nbits = 0;
611	inst->ustat = 0;
612	inst->eos = 0;
613	return SUCCESS;
614}
615
616static void php_conv_base64_decode_dtor(php_conv_base64_decode *inst)
617{
618	/* do nothing */
619}
620
621#define bmask(a) (0xffff >> (16 - a))
622static php_conv_err_t php_conv_base64_decode_convert(php_conv_base64_decode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
623{
624	php_conv_err_t err;
625
626	unsigned int urem, urem_nbits;
627	unsigned int pack, pack_bcnt;
628	unsigned char *ps, *pd;
629	size_t icnt, ocnt;
630	unsigned int ustat;
631
632	static const unsigned int nbitsof_pack = 8;
633
634	if (in_pp == NULL || in_left_p == NULL) {
635		if (inst->eos || inst->urem_nbits == 0) {
636			return PHP_CONV_ERR_SUCCESS;
637		}
638		return PHP_CONV_ERR_UNEXPECTED_EOS;
639	}
640
641	err = PHP_CONV_ERR_SUCCESS;
642
643	ps = (unsigned char *)*in_pp;
644	pd = (unsigned char *)*out_pp;
645	icnt = *in_left_p;
646	ocnt = *out_left_p;
647
648	urem = inst->urem;
649	urem_nbits = inst->urem_nbits;
650	ustat = inst->ustat;
651
652	pack = 0;
653	pack_bcnt = nbitsof_pack;
654
655	for (;;) {
656		if (pack_bcnt >= urem_nbits) {
657			pack_bcnt -= urem_nbits;
658			pack |= (urem << pack_bcnt);
659			urem_nbits = 0;
660		} else {
661			urem_nbits -= pack_bcnt;
662			pack |= (urem >> urem_nbits);
663			urem &= bmask(urem_nbits);
664			pack_bcnt = 0;
665		}
666		if (pack_bcnt > 0) {
667			unsigned int i;
668
669			if (icnt < 1) {
670				break;
671			}
672
673			i = b64_tbl_dec[(unsigned int)*(ps++)];
674			icnt--;
675			ustat |= i & 0x80;
676
677			if (!(i & 0xc0)) {
678				if (ustat) {
679					err = PHP_CONV_ERR_INVALID_SEQ;
680					break;
681				}
682				if (6 <= pack_bcnt) {
683					pack_bcnt -= 6;
684					pack |= (i << pack_bcnt);
685					urem = 0;
686				} else {
687					urem_nbits = 6 - pack_bcnt;
688					pack |= (i >> urem_nbits);
689					urem = i & bmask(urem_nbits);
690					pack_bcnt = 0;
691				}
692			} else if (ustat) {
693				if (pack_bcnt == 8 || pack_bcnt == 2) {
694					err = PHP_CONV_ERR_INVALID_SEQ;
695					break;
696				}
697				inst->eos = 1;
698			}
699		}
700		if ((pack_bcnt | ustat) == 0) {
701			if (ocnt < 1) {
702				err = PHP_CONV_ERR_TOO_BIG;
703				break;
704			}
705			*(pd++) = pack;
706			ocnt--;
707			pack = 0;
708			pack_bcnt = nbitsof_pack;
709		}
710	}
711
712	if (urem_nbits >= pack_bcnt) {
713		urem |= (pack << (urem_nbits - pack_bcnt));
714		urem_nbits += (nbitsof_pack - pack_bcnt);
715	} else {
716		urem |= (pack >> (pack_bcnt - urem_nbits));
717		urem_nbits += (nbitsof_pack - pack_bcnt);
718	}
719
720	inst->urem = urem;
721	inst->urem_nbits = urem_nbits;
722	inst->ustat = ustat;
723
724	*in_pp = (const char *)ps;
725	*in_left_p = icnt;
726	*out_pp = (char *)pd;
727	*out_left_p = ocnt;
728
729	return err;
730}
731#undef bmask
732/* }}} */
733
734/* {{{ php_conv_qprint_encode */
735typedef struct _php_conv_qprint_encode {
736	php_conv _super;
737
738	const char *lbchars;
739	size_t lbchars_len;
740	int opts;
741	unsigned int line_ccnt;
742	unsigned int line_len;
743	int lbchars_dup;
744	int persistent;
745	unsigned int lb_ptr;
746	unsigned int lb_cnt;
747} php_conv_qprint_encode;
748
749#define PHP_CONV_QPRINT_OPT_BINARY             0x00000001
750#define PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST 0x00000002
751
752static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst);
753static php_conv_err_t php_conv_qprint_encode_convert(php_conv_qprint_encode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p);
754
755static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst)
756{
757	assert(inst != NULL);
758	if (inst->lbchars_dup && inst->lbchars != NULL) {
759		pefree((void *)inst->lbchars, inst->persistent);
760	}
761}
762
763#define NEXT_CHAR(ps, icnt, lb_ptr, lb_cnt, lbchars) \
764	((lb_ptr) < (lb_cnt) ? (lbchars)[(lb_ptr)] : *(ps))
765
766#define CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt) \
767	if ((lb_ptr) < (lb_cnt)) { \
768		(lb_ptr)++; \
769	} else { \
770		(lb_cnt) = (lb_ptr) = 0; \
771		--(icnt); \
772		(ps)++; \
773	}
774
775static php_conv_err_t php_conv_qprint_encode_convert(php_conv_qprint_encode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
776{
777	php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
778	unsigned char *ps, *pd;
779	size_t icnt, ocnt;
780	unsigned int c;
781	unsigned int line_ccnt;
782	unsigned int lb_ptr;
783	unsigned int lb_cnt;
784	unsigned int trail_ws;
785	int opts;
786	static char qp_digits[] = "0123456789ABCDEF";
787
788	line_ccnt = inst->line_ccnt;
789	opts = inst->opts;
790	lb_ptr = inst->lb_ptr;
791	lb_cnt = inst->lb_cnt;
792
793	if ((in_pp == NULL || in_left_p == NULL) && (lb_ptr >=lb_cnt)) {
794		return PHP_CONV_ERR_SUCCESS;
795	}
796
797	ps = (unsigned char *)(*in_pp);
798	icnt = *in_left_p;
799	pd = (unsigned char *)(*out_pp);
800	ocnt = *out_left_p;
801	trail_ws = 0;
802
803	for (;;) {
804		if (!(opts & PHP_CONV_QPRINT_OPT_BINARY) && inst->lbchars != NULL && inst->lbchars_len > 0) {
805			/* look ahead for the line break chars to make a right decision
806			 * how to consume incoming characters */
807
808			if (icnt > 0 && *ps == inst->lbchars[lb_cnt]) {
809				lb_cnt++;
810
811				if (lb_cnt >= inst->lbchars_len) {
812					unsigned int i;
813
814					if (ocnt < lb_cnt) {
815						lb_cnt--;
816						err = PHP_CONV_ERR_TOO_BIG;
817						break;
818					}
819
820					for (i = 0; i < lb_cnt; i++) {
821						*(pd++) = inst->lbchars[i];
822						ocnt--;
823					}
824					line_ccnt = inst->line_len;
825					lb_ptr = lb_cnt = 0;
826				}
827				ps++, icnt--;
828				continue;
829			}
830		}
831
832		if (lb_ptr >= lb_cnt && icnt <= 0) {
833			break;
834		}
835
836		c = NEXT_CHAR(ps, icnt, lb_ptr, lb_cnt, inst->lbchars);
837
838		if (!(opts & PHP_CONV_QPRINT_OPT_BINARY) &&
839			(trail_ws == 0) &&
840			(c == '\t' || c == ' ')) {
841			if (line_ccnt < 2 && inst->lbchars != NULL) {
842				if (ocnt < inst->lbchars_len + 1) {
843					err = PHP_CONV_ERR_TOO_BIG;
844					break;
845				}
846
847				*(pd++) = '=';
848				ocnt--;
849				line_ccnt--;
850
851				memcpy(pd, inst->lbchars, inst->lbchars_len);
852				pd += inst->lbchars_len;
853				ocnt -= inst->lbchars_len;
854				line_ccnt = inst->line_len;
855			} else {
856				if (ocnt < 1) {
857					err = PHP_CONV_ERR_TOO_BIG;
858					break;
859				}
860
861				/* Check to see if this is EOL whitespace. */
862				if (inst->lbchars != NULL) {
863					unsigned char *ps2;
864					unsigned int lb_cnt2;
865					size_t j;
866
867					lb_cnt2 = 0;
868					ps2 = ps;
869					trail_ws = 1;
870
871					for (j = icnt - 1; j > 0; j--, ps2++) {
872						if (*ps2 == inst->lbchars[lb_cnt2]) {
873							lb_cnt2++;
874							if (lb_cnt2 >= inst->lbchars_len) {
875								/* Found trailing ws. Reset to top of main
876								 * for loop to allow for code to do necessary
877								 * wrapping/encoding. */
878								break;
879							}
880						} else if (lb_cnt2 != 0 || (*ps2 != '\t' && *ps2 != ' ')) {
881							/* At least one non-EOL character following, so
882							 * don't need to encode ws. */
883							trail_ws = 0;
884							break;
885						} else {
886							trail_ws++;
887						}
888					}
889				}
890
891				if (trail_ws == 0) {
892					*(pd++) = c;
893					ocnt--;
894					line_ccnt--;
895					CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
896				}
897			}
898		} else if ((!(opts & PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST) || line_ccnt < inst->line_len) && ((c >= 33 && c <= 60) || (c >= 62 && c <= 126))) {
899			if (line_ccnt < 2 && inst->lbchars != NULL) {
900				if (ocnt < inst->lbchars_len + 1) {
901					err = PHP_CONV_ERR_TOO_BIG;
902					break;
903				}
904				*(pd++) = '=';
905				ocnt--;
906				line_ccnt--;
907
908				memcpy(pd, inst->lbchars, inst->lbchars_len);
909				pd += inst->lbchars_len;
910				ocnt -= inst->lbchars_len;
911				line_ccnt = inst->line_len;
912			}
913			if (ocnt < 1) {
914				err = PHP_CONV_ERR_TOO_BIG;
915				break;
916			}
917			*(pd++) = c;
918			ocnt--;
919			line_ccnt--;
920			CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
921		} else {
922			if (line_ccnt < 4) {
923				if (ocnt < inst->lbchars_len + 1) {
924					err = PHP_CONV_ERR_TOO_BIG;
925					break;
926				}
927				*(pd++) = '=';
928				ocnt--;
929				line_ccnt--;
930
931				memcpy(pd, inst->lbchars, inst->lbchars_len);
932				pd += inst->lbchars_len;
933				ocnt -= inst->lbchars_len;
934				line_ccnt = inst->line_len;
935			}
936			if (ocnt < 3) {
937				err = PHP_CONV_ERR_TOO_BIG;
938				break;
939			}
940			*(pd++) = '=';
941			*(pd++) = qp_digits[(c >> 4)];
942			*(pd++) = qp_digits[(c & 0x0f)];
943			ocnt -= 3;
944			line_ccnt -= 3;
945			if (trail_ws > 0) {
946				trail_ws--;
947			}
948			CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
949		}
950	}
951
952	*in_pp = (const char *)ps;
953	*in_left_p = icnt;
954	*out_pp = (char *)pd;
955	*out_left_p = ocnt;
956	inst->line_ccnt = line_ccnt;
957	inst->lb_ptr = lb_ptr;
958	inst->lb_cnt = lb_cnt;
959	return err;
960}
961#undef NEXT_CHAR
962#undef CONSUME_CHAR
963
964static php_conv_err_t php_conv_qprint_encode_ctor(php_conv_qprint_encode *inst, unsigned int line_len, const char *lbchars, size_t lbchars_len, int lbchars_dup, int opts, int persistent)
965{
966	if (line_len < 4 && lbchars != NULL) {
967		return PHP_CONV_ERR_TOO_BIG;
968	}
969	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_encode_convert;
970	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_encode_dtor;
971	inst->line_ccnt = line_len;
972	inst->line_len = line_len;
973	if (lbchars != NULL) {
974		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
975		inst->lbchars_len = lbchars_len;
976	} else {
977		inst->lbchars = NULL;
978	}
979	inst->lbchars_dup = lbchars_dup;
980	inst->persistent = persistent;
981	inst->opts = opts;
982	inst->lb_cnt = inst->lb_ptr = 0;
983	return PHP_CONV_ERR_SUCCESS;
984}
985/* }}} */
986
987/* {{{ php_conv_qprint_decode */
988typedef struct _php_conv_qprint_decode {
989	php_conv _super;
990
991	const char *lbchars;
992	size_t lbchars_len;
993	int scan_stat;
994	unsigned int next_char;
995	int lbchars_dup;
996	int persistent;
997	unsigned int lb_ptr;
998	unsigned int lb_cnt;
999} php_conv_qprint_decode;
1000
1001static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst)
1002{
1003	assert(inst != NULL);
1004	if (inst->lbchars_dup && inst->lbchars != NULL) {
1005		pefree((void *)inst->lbchars, inst->persistent);
1006	}
1007}
1008
1009static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *inst, const char **in_pp, size_t *in_left_p, char **out_pp, size_t *out_left_p)
1010{
1011	php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
1012	size_t icnt, ocnt;
1013	unsigned char *ps, *pd;
1014	unsigned int scan_stat;
1015	unsigned int next_char;
1016	unsigned int lb_ptr, lb_cnt;
1017
1018	lb_ptr = inst->lb_ptr;
1019	lb_cnt = inst->lb_cnt;
1020
1021	if ((in_pp == NULL || in_left_p == NULL) && lb_cnt == lb_ptr) {
1022		if (inst->scan_stat != 0) {
1023			return PHP_CONV_ERR_UNEXPECTED_EOS;
1024		}
1025		return PHP_CONV_ERR_SUCCESS;
1026	}
1027
1028	ps = (unsigned char *)(*in_pp);
1029	icnt = *in_left_p;
1030	pd = (unsigned char *)(*out_pp);
1031	ocnt = *out_left_p;
1032	scan_stat = inst->scan_stat;
1033	next_char = inst->next_char;
1034
1035	for (;;) {
1036		switch (scan_stat) {
1037			case 0: {
1038				if (icnt <= 0) {
1039					goto out;
1040				}
1041				if (*ps == '=') {
1042					scan_stat = 1;
1043				} else {
1044					if (ocnt < 1) {
1045						err = PHP_CONV_ERR_TOO_BIG;
1046						goto out;
1047					}
1048					*(pd++) = *ps;
1049					ocnt--;
1050				}
1051				ps++, icnt--;
1052			} break;
1053
1054			case 1: {
1055				if (icnt <= 0) {
1056					goto out;
1057				}
1058				if (*ps == ' ' || *ps == '\t') {
1059					scan_stat = 4;
1060					ps++, icnt--;
1061					break;
1062				} else if (!inst->lbchars && lb_cnt == 0 && *ps == '\r') {
1063					/* auto-detect line endings, looks like network line ending \r\n (could be mac \r) */
1064					lb_cnt++;
1065					scan_stat = 5;
1066					ps++, icnt--;
1067					break;
1068				} else if (!inst->lbchars && lb_cnt == 0 && *ps == '\n') {
1069					/* auto-detect line endings, looks like unix-lineendings, not to spec, but it is seem in the wild, a lot */
1070					lb_cnt = lb_ptr = 0;
1071					scan_stat = 0;
1072					ps++, icnt--;
1073					break;
1074				} else if (lb_cnt < inst->lbchars_len &&
1075							*ps == (unsigned char)inst->lbchars[lb_cnt]) {
1076					lb_cnt++;
1077					scan_stat = 5;
1078					ps++, icnt--;
1079					break;
1080				}
1081			} /* break is missing intentionally */
1082
1083			case 2: {
1084				if (icnt <= 0) {
1085					goto out;
1086				}
1087
1088				if (!isxdigit((int) *ps)) {
1089					err = PHP_CONV_ERR_INVALID_SEQ;
1090					goto out;
1091				}
1092				next_char = (next_char << 4) | (*ps >= 'A' ? *ps - 0x37 : *ps - 0x30);
1093				scan_stat++;
1094				ps++, icnt--;
1095				if (scan_stat != 3) {
1096					break;
1097				}
1098			} /* break is missing intentionally */
1099
1100			case 3: {
1101				if (ocnt < 1) {
1102					err = PHP_CONV_ERR_TOO_BIG;
1103					goto out;
1104				}
1105				*(pd++) = next_char;
1106				ocnt--;
1107				scan_stat = 0;
1108			} break;
1109
1110			case 4: {
1111				if (icnt <= 0) {
1112					goto out;
1113				}
1114				if (lb_cnt < inst->lbchars_len &&
1115					*ps == (unsigned char)inst->lbchars[lb_cnt]) {
1116					lb_cnt++;
1117					scan_stat = 5;
1118				}
1119				if (*ps != '\t' && *ps != ' ') {
1120					err = PHP_CONV_ERR_INVALID_SEQ;
1121					goto out;
1122				}
1123				ps++, icnt--;
1124			} break;
1125
1126			case 5: {
1127				if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') {
1128					/* auto-detect soft line breaks, found network line break */
1129					lb_cnt = lb_ptr = 0;
1130					scan_stat = 0;
1131					ps++, icnt--; /* consume \n */
1132				} else if (!inst->lbchars && lb_cnt > 0) {
1133					/* auto-detect soft line breaks, found mac line break */
1134					lb_cnt = lb_ptr = 0;
1135					scan_stat = 0;
1136				} else if (lb_cnt >= inst->lbchars_len) {
1137					/* soft line break */
1138					lb_cnt = lb_ptr = 0;
1139					scan_stat = 0;
1140				} else if (icnt > 0) {
1141					if (*ps == (unsigned char)inst->lbchars[lb_cnt]) {
1142						lb_cnt++;
1143						ps++, icnt--;
1144					} else {
1145						scan_stat = 6; /* no break for short-cut */
1146					}
1147				} else {
1148					goto out;
1149				}
1150			} break;
1151
1152			case 6: {
1153				if (lb_ptr < lb_cnt) {
1154					if (ocnt < 1) {
1155						err = PHP_CONV_ERR_TOO_BIG;
1156						goto out;
1157					}
1158					*(pd++) = inst->lbchars[lb_ptr++];
1159					ocnt--;
1160				} else {
1161					scan_stat = 0;
1162					lb_cnt = lb_ptr = 0;
1163				}
1164			} break;
1165		}
1166	}
1167out:
1168	*in_pp = (const char *)ps;
1169	*in_left_p = icnt;
1170	*out_pp = (char *)pd;
1171	*out_left_p = ocnt;
1172	inst->scan_stat = scan_stat;
1173	inst->lb_ptr = lb_ptr;
1174	inst->lb_cnt = lb_cnt;
1175	inst->next_char = next_char;
1176
1177	return err;
1178}
1179static php_conv_err_t php_conv_qprint_decode_ctor(php_conv_qprint_decode *inst, const char *lbchars, size_t lbchars_len, int lbchars_dup, int persistent)
1180{
1181	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_decode_convert;
1182	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_decode_dtor;
1183	inst->scan_stat = 0;
1184	inst->next_char = 0;
1185	inst->lb_ptr = inst->lb_cnt = 0;
1186	if (lbchars != NULL) {
1187		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
1188		inst->lbchars_len = lbchars_len;
1189	} else {
1190		inst->lbchars = NULL;
1191		inst->lbchars_len = 0;
1192	}
1193	inst->lbchars_dup = lbchars_dup;
1194	inst->persistent = persistent;
1195	return PHP_CONV_ERR_SUCCESS;
1196}
1197/* }}} */
1198
1199typedef struct _php_convert_filter {
1200	php_conv *cd;
1201	int persistent;
1202	char *filtername;
1203	char stub[128];
1204	size_t stub_len;
1205} php_convert_filter;
1206
1207#define PHP_CONV_BASE64_ENCODE 1
1208#define PHP_CONV_BASE64_DECODE 2
1209#define PHP_CONV_QPRINT_ENCODE 3
1210#define PHP_CONV_QPRINT_DECODE 4
1211
1212static php_conv_err_t php_conv_get_string_prop_ex(const HashTable *ht, char **pretval, size_t *pretval_len, char *field_name, size_t field_name_len, int persistent)
1213{
1214	zval *tmpval;
1215
1216	*pretval = NULL;
1217	*pretval_len = 0;
1218
1219	if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1220		zend_string *str = zval_get_string(tmpval);
1221
1222		if (NULL == (*pretval = pemalloc(ZSTR_LEN(str) + 1, persistent))) {
1223			return PHP_CONV_ERR_ALLOC;
1224		}
1225
1226		*pretval_len = ZSTR_LEN(str);
1227		memcpy(*pretval, ZSTR_VAL(str), ZSTR_LEN(str) + 1);
1228		zend_string_release(str);
1229	} else {
1230		return PHP_CONV_ERR_NOT_FOUND;
1231	}
1232	return PHP_CONV_ERR_SUCCESS;
1233}
1234
1235static php_conv_err_t php_conv_get_ulong_prop_ex(const HashTable *ht, zend_ulong *pretval, char *field_name, size_t field_name_len)
1236{
1237	zval *tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1);
1238	if (tmpval != NULL) {
1239		zend_long lval = zval_get_long(tmpval);
1240
1241		if (lval < 0) {
1242			*pretval = 0;
1243		} else {
1244			*pretval = lval;
1245		}
1246		return PHP_CONV_ERR_SUCCESS;
1247	} else {
1248		*pretval = 0;
1249		return PHP_CONV_ERR_NOT_FOUND;
1250	}
1251}
1252
1253static php_conv_err_t php_conv_get_bool_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
1254{
1255	zval *tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1);
1256	if (tmpval != NULL) {
1257		*pretval = zend_is_true(tmpval);
1258		return PHP_CONV_ERR_SUCCESS;
1259	} else {
1260		*pretval = 0;
1261		return PHP_CONV_ERR_NOT_FOUND;
1262	}
1263}
1264
1265/* XXX this might need an additional fix so it uses size_t, whereby unsigned is quite big so leaving as is for now */
1266static int php_conv_get_uint_prop_ex(const HashTable *ht, unsigned int *pretval, char *field_name, size_t field_name_len)
1267{
1268	zend_ulong l;
1269	php_conv_err_t err;
1270
1271	*pretval = 0;
1272
1273	if ((err = php_conv_get_ulong_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
1274		*pretval = (unsigned int)l;
1275	}
1276	return err;
1277}
1278
1279#define GET_STR_PROP(ht, var, var_len, fldname, persistent) \
1280	php_conv_get_string_prop_ex(ht, &var, &var_len, fldname, sizeof(fldname), persistent)
1281
1282#define GET_INT_PROP(ht, var, fldname) \
1283	php_conv_get_int_prop_ex(ht, &var, fldname, sizeof(fldname))
1284
1285#define GET_UINT_PROP(ht, var, fldname) \
1286	php_conv_get_uint_prop_ex(ht, &var, fldname, sizeof(fldname))
1287
1288#define GET_BOOL_PROP(ht, var, fldname) \
1289	php_conv_get_bool_prop_ex(ht, &var, fldname, sizeof(fldname))
1290
1291static php_conv *php_conv_open(int conv_mode, const HashTable *options, int persistent)
1292{
1293	/* FIXME: I'll have to replace this ugly code by something neat
1294	   (factories?) in the near future. */
1295	php_conv *retval = NULL;
1296
1297	switch (conv_mode) {
1298		case PHP_CONV_BASE64_ENCODE: {
1299			unsigned int line_len = 0;
1300			char *lbchars = NULL;
1301			size_t lbchars_len;
1302
1303			if (options != NULL) {
1304				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1305				GET_UINT_PROP(options, line_len, "line-length");
1306				if (line_len < 4) {
1307					if (lbchars != NULL) {
1308						pefree(lbchars, 0);
1309					}
1310					lbchars = NULL;
1311				} else {
1312					if (lbchars == NULL) {
1313						lbchars = pestrdup("\r\n", 0);
1314						lbchars_len = 2;
1315					}
1316				}
1317			}
1318			retval = pemalloc(sizeof(php_conv_base64_encode), persistent);
1319			if (lbchars != NULL) {
1320				if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, line_len, lbchars, lbchars_len, 1, persistent)) {
1321					if (lbchars != NULL) {
1322						pefree(lbchars, 0);
1323					}
1324					goto out_failure;
1325				}
1326				pefree(lbchars, 0);
1327			} else {
1328				if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, 0, NULL, 0, 0, persistent)) {
1329					goto out_failure;
1330				}
1331			}
1332		} break;
1333
1334		case PHP_CONV_BASE64_DECODE:
1335			retval = pemalloc(sizeof(php_conv_base64_decode), persistent);
1336			if (php_conv_base64_decode_ctor((php_conv_base64_decode *)retval)) {
1337				goto out_failure;
1338			}
1339			break;
1340
1341		case PHP_CONV_QPRINT_ENCODE: {
1342			unsigned int line_len = 0;
1343			char *lbchars = NULL;
1344			size_t lbchars_len;
1345			int opts = 0;
1346
1347			if (options != NULL) {
1348				int opt_binary = 0;
1349				int opt_force_encode_first = 0;
1350
1351				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1352				GET_UINT_PROP(options, line_len, "line-length");
1353				GET_BOOL_PROP(options, opt_binary, "binary");
1354				GET_BOOL_PROP(options, opt_force_encode_first, "force-encode-first");
1355
1356				if (line_len < 4) {
1357					if (lbchars != NULL) {
1358						pefree(lbchars, 0);
1359					}
1360					lbchars = NULL;
1361				} else {
1362					if (lbchars == NULL) {
1363						lbchars = pestrdup("\r\n", 0);
1364						lbchars_len = 2;
1365					}
1366				}
1367				opts |= (opt_binary ? PHP_CONV_QPRINT_OPT_BINARY : 0);
1368				opts |= (opt_force_encode_first ? PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST : 0);
1369			}
1370			retval = pemalloc(sizeof(php_conv_qprint_encode), persistent);
1371			if (lbchars != NULL) {
1372				if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, line_len, lbchars, lbchars_len, 1, opts, persistent)) {
1373					pefree(lbchars, 0);
1374					goto out_failure;
1375				}
1376				pefree(lbchars, 0);
1377			} else {
1378				if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, 0, NULL, 0, 0, opts, persistent)) {
1379					goto out_failure;
1380				}
1381			}
1382		} break;
1383
1384		case PHP_CONV_QPRINT_DECODE: {
1385			char *lbchars = NULL;
1386			size_t lbchars_len;
1387
1388			if (options != NULL) {
1389				/* If line-break-chars are not specified, filter will attempt to detect line endings (\r, \n, or \r\n) */
1390				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1391			}
1392
1393			retval = pemalloc(sizeof(php_conv_qprint_decode), persistent);
1394			if (lbchars != NULL) {
1395				if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, lbchars, lbchars_len, 1, persistent)) {
1396					pefree(lbchars, 0);
1397					goto out_failure;
1398				}
1399				pefree(lbchars, 0);
1400			} else {
1401				if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, NULL, 0, 0, persistent)) {
1402					goto out_failure;
1403				}
1404			}
1405		} break;
1406
1407		default:
1408			retval = NULL;
1409			break;
1410	}
1411	return retval;
1412
1413out_failure:
1414	if (retval != NULL) {
1415		pefree(retval, persistent);
1416	}
1417	return NULL;
1418}
1419
1420#undef GET_STR_PROP
1421#undef GET_INT_PROP
1422#undef GET_UINT_PROP
1423#undef GET_BOOL_PROP
1424
1425static int php_convert_filter_ctor(php_convert_filter *inst,
1426	int conv_mode, HashTable *conv_opts,
1427	const char *filtername, int persistent)
1428{
1429	inst->persistent = persistent;
1430	inst->filtername = pestrdup(filtername, persistent);
1431	inst->stub_len = 0;
1432
1433	if ((inst->cd = php_conv_open(conv_mode, conv_opts, persistent)) == NULL) {
1434		goto out_failure;
1435	}
1436
1437	return SUCCESS;
1438
1439out_failure:
1440	if (inst->cd != NULL) {
1441		php_conv_dtor(inst->cd);
1442		pefree(inst->cd, persistent);
1443	}
1444	if (inst->filtername != NULL) {
1445		pefree(inst->filtername, persistent);
1446	}
1447	return FAILURE;
1448}
1449
1450static void php_convert_filter_dtor(php_convert_filter *inst)
1451{
1452	if (inst->cd != NULL) {
1453		php_conv_dtor(inst->cd);
1454		pefree(inst->cd, inst->persistent);
1455	}
1456
1457	if (inst->filtername != NULL) {
1458		pefree(inst->filtername, inst->persistent);
1459	}
1460}
1461
1462/* {{{ strfilter_convert_append_bucket */
1463static int strfilter_convert_append_bucket(
1464		php_convert_filter *inst,
1465		php_stream *stream, php_stream_filter *filter,
1466		php_stream_bucket_brigade *buckets_out,
1467		const char *ps, size_t buf_len, size_t *consumed,
1468		int persistent)
1469{
1470	php_conv_err_t err;
1471	php_stream_bucket *new_bucket;
1472	char *out_buf = NULL;
1473	size_t out_buf_size;
1474	char *pd;
1475	const char *pt;
1476	size_t ocnt, icnt, tcnt;
1477	size_t initial_out_buf_size;
1478
1479	if (ps == NULL) {
1480		initial_out_buf_size = 64;
1481		icnt = 1;
1482	} else {
1483		initial_out_buf_size = buf_len;
1484		icnt = buf_len;
1485	}
1486
1487	out_buf_size = ocnt = initial_out_buf_size;
1488	if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1489		return FAILURE;
1490	}
1491
1492	pd = out_buf;
1493
1494	if (inst->stub_len > 0) {
1495		pt = inst->stub;
1496		tcnt = inst->stub_len;
1497
1498		while (tcnt > 0) {
1499			err = php_conv_convert(inst->cd, &pt, &tcnt, &pd, &ocnt);
1500
1501			switch (err) {
1502				case PHP_CONV_ERR_INVALID_SEQ:
1503					php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1504					goto out_failure;
1505
1506				case PHP_CONV_ERR_MORE:
1507					if (ps != NULL) {
1508						if (icnt > 0) {
1509							if (inst->stub_len >= sizeof(inst->stub)) {
1510								php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1511								goto out_failure;
1512							}
1513							inst->stub[inst->stub_len++] = *(ps++);
1514							icnt--;
1515							pt = inst->stub;
1516							tcnt = inst->stub_len;
1517						} else {
1518							tcnt = 0;
1519							break;
1520						}
1521					}
1522					break;
1523
1524				case PHP_CONV_ERR_UNEXPECTED_EOS:
1525					php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected end of stream", inst->filtername);
1526					goto out_failure;
1527
1528				case PHP_CONV_ERR_TOO_BIG: {
1529					char *new_out_buf;
1530					size_t new_out_buf_size;
1531
1532					new_out_buf_size = out_buf_size << 1;
1533
1534					if (new_out_buf_size < out_buf_size) {
1535						/* whoa! no bigger buckets are sold anywhere... */
1536						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1537							goto out_failure;
1538						}
1539
1540						php_stream_bucket_append(buckets_out, new_bucket);
1541
1542						out_buf_size = ocnt = initial_out_buf_size;
1543						if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1544							return FAILURE;
1545						}
1546						pd = out_buf;
1547					} else {
1548						if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1549							if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1550								goto out_failure;
1551							}
1552
1553							php_stream_bucket_append(buckets_out, new_bucket);
1554							return FAILURE;
1555						}
1556
1557						pd = new_out_buf + (pd - out_buf);
1558						ocnt += (new_out_buf_size - out_buf_size);
1559						out_buf = new_out_buf;
1560						out_buf_size = new_out_buf_size;
1561					}
1562				} break;
1563
1564				case PHP_CONV_ERR_UNKNOWN:
1565					php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1566					goto out_failure;
1567
1568				default:
1569					break;
1570			}
1571		}
1572		memmove(inst->stub, pt, tcnt);
1573		inst->stub_len = tcnt;
1574	}
1575
1576	while (icnt > 0) {
1577		err = ((ps == NULL ? php_conv_convert(inst->cd, NULL, NULL, &pd, &ocnt):
1578				php_conv_convert(inst->cd, &ps, &icnt, &pd, &ocnt)));
1579		switch (err) {
1580			case PHP_CONV_ERR_INVALID_SEQ:
1581				php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1582				goto out_failure;
1583
1584			case PHP_CONV_ERR_MORE:
1585				if (ps != NULL) {
1586					if (icnt > sizeof(inst->stub)) {
1587						php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1588						goto out_failure;
1589					}
1590					memcpy(inst->stub, ps, icnt);
1591					inst->stub_len = icnt;
1592					ps += icnt;
1593					icnt = 0;
1594				} else {
1595					php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected octet values", inst->filtername);
1596					goto out_failure;
1597				}
1598				break;
1599
1600			case PHP_CONV_ERR_TOO_BIG: {
1601				char *new_out_buf;
1602				size_t new_out_buf_size;
1603
1604				new_out_buf_size = out_buf_size << 1;
1605
1606				if (new_out_buf_size < out_buf_size) {
1607					/* whoa! no bigger buckets are sold anywhere... */
1608					if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1609						goto out_failure;
1610					}
1611
1612					php_stream_bucket_append(buckets_out, new_bucket);
1613
1614					out_buf_size = ocnt = initial_out_buf_size;
1615					if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1616						return FAILURE;
1617					}
1618					pd = out_buf;
1619				} else {
1620					if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1621						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1622							goto out_failure;
1623						}
1624
1625						php_stream_bucket_append(buckets_out, new_bucket);
1626						return FAILURE;
1627					}
1628					pd = new_out_buf + (pd - out_buf);
1629					ocnt += (new_out_buf_size - out_buf_size);
1630					out_buf = new_out_buf;
1631					out_buf_size = new_out_buf_size;
1632				}
1633			} break;
1634
1635			case PHP_CONV_ERR_UNKNOWN:
1636				php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1637				goto out_failure;
1638
1639			default:
1640				if (ps == NULL) {
1641					icnt = 0;
1642				}
1643				break;
1644		}
1645	}
1646
1647	if (out_buf_size > ocnt) {
1648		if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1649			goto out_failure;
1650		}
1651		php_stream_bucket_append(buckets_out, new_bucket);
1652	} else {
1653		pefree(out_buf, persistent);
1654	}
1655	*consumed += buf_len - icnt;
1656
1657	return SUCCESS;
1658
1659out_failure:
1660	pefree(out_buf, persistent);
1661	return FAILURE;
1662}
1663/* }}} */
1664
1665static php_stream_filter_status_t strfilter_convert_filter(
1666	php_stream *stream,
1667	php_stream_filter *thisfilter,
1668	php_stream_bucket_brigade *buckets_in,
1669	php_stream_bucket_brigade *buckets_out,
1670	size_t *bytes_consumed,
1671	int flags
1672	)
1673{
1674	php_stream_bucket *bucket = NULL;
1675	size_t consumed = 0;
1676	php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
1677
1678	while (buckets_in->head != NULL) {
1679		bucket = buckets_in->head;
1680
1681		php_stream_bucket_unlink(bucket);
1682
1683		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1684				buckets_out, bucket->buf, bucket->buflen, &consumed,
1685				php_stream_is_persistent(stream)) != SUCCESS) {
1686			goto out_failure;
1687		}
1688
1689		php_stream_bucket_delref(bucket);
1690	}
1691
1692	if (flags != PSFS_FLAG_NORMAL) {
1693		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1694				buckets_out, NULL, 0, &consumed,
1695				php_stream_is_persistent(stream)) != SUCCESS) {
1696			goto out_failure;
1697		}
1698	}
1699
1700	if (bytes_consumed) {
1701		*bytes_consumed = consumed;
1702	}
1703
1704	return PSFS_PASS_ON;
1705
1706out_failure:
1707	if (bucket != NULL) {
1708		php_stream_bucket_delref(bucket);
1709	}
1710	return PSFS_ERR_FATAL;
1711}
1712
1713static void strfilter_convert_dtor(php_stream_filter *thisfilter)
1714{
1715	assert(Z_PTR(thisfilter->abstract) != NULL);
1716
1717	php_convert_filter_dtor((php_convert_filter *)Z_PTR(thisfilter->abstract));
1718	pefree(Z_PTR(thisfilter->abstract), ((php_convert_filter *)Z_PTR(thisfilter->abstract))->persistent);
1719}
1720
1721static php_stream_filter_ops strfilter_convert_ops = {
1722	strfilter_convert_filter,
1723	strfilter_convert_dtor,
1724	"convert.*"
1725};
1726
1727static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, int persistent)
1728{
1729	php_convert_filter *inst;
1730	php_stream_filter *retval = NULL;
1731
1732	char *dot;
1733	int conv_mode = 0;
1734
1735	if (filterparams != NULL && Z_TYPE_P(filterparams) != IS_ARRAY) {
1736		php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid filter parameter", filtername);
1737		return NULL;
1738	}
1739
1740	if ((dot = strchr(filtername, '.')) == NULL) {
1741		return NULL;
1742	}
1743	++dot;
1744
1745	inst = pemalloc(sizeof(php_convert_filter), persistent);
1746
1747	if (strcasecmp(dot, "base64-encode") == 0) {
1748		conv_mode = PHP_CONV_BASE64_ENCODE;
1749	} else if (strcasecmp(dot, "base64-decode") == 0) {
1750		conv_mode = PHP_CONV_BASE64_DECODE;
1751	} else if (strcasecmp(dot, "quoted-printable-encode") == 0) {
1752		conv_mode = PHP_CONV_QPRINT_ENCODE;
1753	} else if (strcasecmp(dot, "quoted-printable-decode") == 0) {
1754		conv_mode = PHP_CONV_QPRINT_DECODE;
1755	}
1756
1757	if (php_convert_filter_ctor(inst, conv_mode,
1758		(filterparams != NULL ? Z_ARRVAL_P(filterparams) : NULL),
1759		filtername, persistent) != SUCCESS) {
1760		goto out;
1761	}
1762
1763	retval = php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
1764out:
1765	if (retval == NULL) {
1766		pefree(inst, persistent);
1767	}
1768
1769	return retval;
1770}
1771
1772static php_stream_filter_factory strfilter_convert_factory = {
1773	strfilter_convert_create
1774};
1775/* }}} */
1776
1777/* {{{ consumed filter implementation */
1778typedef struct _php_consumed_filter_data {
1779	size_t consumed;
1780	zend_off_t offset;
1781	int persistent;
1782} php_consumed_filter_data;
1783
1784static php_stream_filter_status_t consumed_filter_filter(
1785	php_stream *stream,
1786	php_stream_filter *thisfilter,
1787	php_stream_bucket_brigade *buckets_in,
1788	php_stream_bucket_brigade *buckets_out,
1789	size_t *bytes_consumed,
1790	int flags
1791	)
1792{
1793	php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
1794	php_stream_bucket *bucket;
1795	size_t consumed = 0;
1796
1797	if (data->offset == ~0) {
1798		data->offset = php_stream_tell(stream);
1799	}
1800	while ((bucket = buckets_in->head) != NULL) {
1801		php_stream_bucket_unlink(bucket);
1802		consumed += bucket->buflen;
1803		php_stream_bucket_append(buckets_out, bucket);
1804	}
1805	if (bytes_consumed) {
1806		*bytes_consumed = consumed;
1807	}
1808	if (flags & PSFS_FLAG_FLUSH_CLOSE) {
1809		php_stream_seek(stream, data->offset + data->consumed, SEEK_SET);
1810	}
1811	data->consumed += consumed;
1812
1813	return PSFS_PASS_ON;
1814}
1815
1816static void consumed_filter_dtor(php_stream_filter *thisfilter)
1817{
1818	if (thisfilter && Z_PTR(thisfilter->abstract)) {
1819		php_consumed_filter_data *data = (php_consumed_filter_data*)Z_PTR(thisfilter->abstract);
1820		pefree(data, data->persistent);
1821	}
1822}
1823
1824static php_stream_filter_ops consumed_filter_ops = {
1825	consumed_filter_filter,
1826	consumed_filter_dtor,
1827	"consumed"
1828};
1829
1830static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, int persistent)
1831{
1832	php_stream_filter_ops *fops = NULL;
1833	php_consumed_filter_data *data;
1834
1835	if (strcasecmp(filtername, "consumed")) {
1836		return NULL;
1837	}
1838
1839	/* Create this filter */
1840	data = pecalloc(1, sizeof(php_consumed_filter_data), persistent);
1841	if (!data) {
1842		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_consumed_filter_data));
1843		return NULL;
1844	}
1845	data->persistent = persistent;
1846	data->consumed = 0;
1847	data->offset = ~0;
1848	fops = &consumed_filter_ops;
1849
1850	return php_stream_filter_alloc(fops, data, persistent);
1851}
1852
1853php_stream_filter_factory consumed_filter_factory = {
1854	consumed_filter_create
1855};
1856
1857/* }}} */
1858
1859/* {{{ chunked filter implementation */
1860typedef enum _php_chunked_filter_state {
1861	CHUNK_SIZE_START,
1862	CHUNK_SIZE,
1863	CHUNK_SIZE_EXT,
1864	CHUNK_SIZE_CR,
1865	CHUNK_SIZE_LF,
1866	CHUNK_BODY,
1867	CHUNK_BODY_CR,
1868	CHUNK_BODY_LF,
1869	CHUNK_TRAILER,
1870	CHUNK_ERROR
1871} php_chunked_filter_state;
1872
1873typedef struct _php_chunked_filter_data {
1874	size_t chunk_size;
1875	php_chunked_filter_state state;
1876	int persistent;
1877} php_chunked_filter_data;
1878
1879static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
1880{
1881	char *p = buf;
1882	char *end = p + len;
1883	char *out = buf;
1884	size_t out_len = 0;
1885
1886	while (p < end) {
1887		switch (data->state) {
1888			case CHUNK_SIZE_START:
1889				data->chunk_size = 0;
1890			case CHUNK_SIZE:
1891				while (p < end) {
1892					if (*p >= '0' && *p <= '9') {
1893						data->chunk_size = (data->chunk_size * 16) + (*p - '0');
1894					} else if (*p >= 'A' && *p <= 'F') {
1895						data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
1896					} else if (*p >= 'a' && *p <= 'f') {
1897						data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
1898					} else if (data->state == CHUNK_SIZE_START) {
1899						data->state = CHUNK_ERROR;
1900						break;
1901					} else {
1902						data->state = CHUNK_SIZE_EXT;
1903						break;
1904					}
1905					data->state = CHUNK_SIZE;
1906					p++;
1907				}
1908				if (data->state == CHUNK_ERROR) {
1909					continue;
1910				} else if (p == end) {
1911					return out_len;
1912				}
1913			case CHUNK_SIZE_EXT:
1914				/* skip extension */
1915				while (p < end && *p != '\r' && *p != '\n') {
1916					p++;
1917				}
1918				if (p == end) {
1919					return out_len;
1920				}
1921			case CHUNK_SIZE_CR:
1922				if (*p == '\r') {
1923					p++;
1924					if (p == end) {
1925						data->state = CHUNK_SIZE_LF;
1926						return out_len;
1927					}
1928				}
1929			case CHUNK_SIZE_LF:
1930				if (*p == '\n') {
1931					p++;
1932					if (data->chunk_size == 0) {
1933						/* last chunk */
1934						data->state = CHUNK_TRAILER;
1935						continue;
1936					} else if (p == end) {
1937						data->state = CHUNK_BODY;
1938						return out_len;
1939					}
1940				} else {
1941					data->state = CHUNK_ERROR;
1942					continue;
1943				}
1944			case CHUNK_BODY:
1945				if ((size_t) (end - p) >= data->chunk_size) {
1946					if (p != out) {
1947						memmove(out, p, data->chunk_size);
1948					}
1949					out += data->chunk_size;
1950					out_len += data->chunk_size;
1951					p += data->chunk_size;
1952					if (p == end) {
1953						data->state = CHUNK_BODY_CR;
1954						return out_len;
1955					}
1956				} else {
1957					if (p != out) {
1958						memmove(out, p, end - p);
1959					}
1960					data->chunk_size -= end - p;
1961					data->state=CHUNK_BODY;
1962					out_len += end - p;
1963					return out_len;
1964				}
1965			case CHUNK_BODY_CR:
1966				if (*p == '\r') {
1967					p++;
1968					if (p == end) {
1969						data->state = CHUNK_BODY_LF;
1970						return out_len;
1971					}
1972				}
1973			case CHUNK_BODY_LF:
1974				if (*p == '\n') {
1975					p++;
1976					data->state = CHUNK_SIZE_START;
1977					continue;
1978				} else {
1979					data->state = CHUNK_ERROR;
1980					continue;
1981				}
1982			case CHUNK_TRAILER:
1983				/* ignore trailer */
1984				p = end;
1985				continue;
1986			case CHUNK_ERROR:
1987				if (p != out) {
1988					memmove(out, p, end - p);
1989				}
1990				out_len += end - p;
1991				return out_len;
1992		}
1993	}
1994	return out_len;
1995}
1996
1997static php_stream_filter_status_t php_chunked_filter(
1998	php_stream *stream,
1999	php_stream_filter *thisfilter,
2000	php_stream_bucket_brigade *buckets_in,
2001	php_stream_bucket_brigade *buckets_out,
2002	size_t *bytes_consumed,
2003	int flags
2004	)
2005{
2006	php_stream_bucket *bucket;
2007	size_t consumed = 0;
2008	php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2009
2010	while (buckets_in->head) {
2011		bucket = php_stream_bucket_make_writeable(buckets_in->head);
2012		consumed += bucket->buflen;
2013		bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);
2014		php_stream_bucket_append(buckets_out, bucket);
2015	}
2016
2017	if (bytes_consumed) {
2018		*bytes_consumed = consumed;
2019	}
2020
2021	return PSFS_PASS_ON;
2022}
2023
2024static void php_chunked_dtor(php_stream_filter *thisfilter)
2025{
2026	if (thisfilter && Z_PTR(thisfilter->abstract)) {
2027		php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2028		pefree(data, data->persistent);
2029	}
2030}
2031
2032static php_stream_filter_ops chunked_filter_ops = {
2033	php_chunked_filter,
2034	php_chunked_dtor,
2035	"dechunk"
2036};
2037
2038static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, int persistent)
2039{
2040	php_stream_filter_ops *fops = NULL;
2041	php_chunked_filter_data *data;
2042
2043	if (strcasecmp(filtername, "dechunk")) {
2044		return NULL;
2045	}
2046
2047	/* Create this filter */
2048	data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent);
2049	if (!data) {
2050		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_chunked_filter_data));
2051		return NULL;
2052	}
2053	data->state = CHUNK_SIZE_START;
2054	data->chunk_size = 0;
2055	data->persistent = persistent;
2056	fops = &chunked_filter_ops;
2057
2058	return php_stream_filter_alloc(fops, data, persistent);
2059}
2060
2061static php_stream_filter_factory chunked_filter_factory = {
2062	chunked_filter_create
2063};
2064/* }}} */
2065
2066static const struct {
2067	php_stream_filter_ops *ops;
2068	php_stream_filter_factory *factory;
2069} standard_filters[] = {
2070	{ &strfilter_rot13_ops, &strfilter_rot13_factory },
2071	{ &strfilter_toupper_ops, &strfilter_toupper_factory },
2072	{ &strfilter_tolower_ops, &strfilter_tolower_factory },
2073	{ &strfilter_strip_tags_ops, &strfilter_strip_tags_factory },
2074	{ &strfilter_convert_ops, &strfilter_convert_factory },
2075	{ &consumed_filter_ops, &consumed_filter_factory },
2076	{ &chunked_filter_ops, &chunked_filter_factory },
2077	/* additional filters to go here */
2078	{ NULL, NULL }
2079};
2080
2081/* {{{ filter MINIT and MSHUTDOWN */
2082PHP_MINIT_FUNCTION(standard_filters)
2083{
2084	int i;
2085
2086	for (i = 0; standard_filters[i].ops; i++) {
2087		if (FAILURE == php_stream_filter_register_factory(
2088					standard_filters[i].ops->label,
2089					standard_filters[i].factory
2090					)) {
2091			return FAILURE;
2092		}
2093	}
2094	return SUCCESS;
2095}
2096
2097PHP_MSHUTDOWN_FUNCTION(standard_filters)
2098{
2099	int i;
2100
2101	for (i = 0; standard_filters[i].ops; i++) {
2102		php_stream_filter_unregister_factory(standard_filters[i].ops->label);
2103	}
2104	return SUCCESS;
2105}
2106/* }}} */
2107
2108/*
2109 * Local variables:
2110 * tab-width: 4
2111 * c-basic-offset: 4
2112 * End:
2113 * vim600: sw=4 ts=4 fdm=marker
2114 * vim<600: sw=4 ts=4
2115 */
2116