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