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