xref: /PHP-8.2/ext/standard/filters.c (revision 01b3fc03)
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 seem 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)) {
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)) {
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)) {
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)) {
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)) {
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)) {
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 		goto out_failure;
1305 	}
1306 
1307 	return SUCCESS;
1308 
1309 out_failure:
1310 	if (inst->cd != NULL) {
1311 		php_conv_dtor(inst->cd);
1312 		pefree(inst->cd, persistent);
1313 	}
1314 	if (inst->filtername != NULL) {
1315 		pefree(inst->filtername, persistent);
1316 	}
1317 	return FAILURE;
1318 }
1319 
php_convert_filter_dtor(php_convert_filter * inst)1320 static void php_convert_filter_dtor(php_convert_filter *inst)
1321 {
1322 	if (inst->cd != NULL) {
1323 		php_conv_dtor(inst->cd);
1324 		pefree(inst->cd, inst->persistent);
1325 	}
1326 
1327 	if (inst->filtername != NULL) {
1328 		pefree(inst->filtername, inst->persistent);
1329 	}
1330 }
1331 
1332 /* {{{ 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)1333 static int strfilter_convert_append_bucket(
1334 		php_convert_filter *inst,
1335 		php_stream *stream, php_stream_filter *filter,
1336 		php_stream_bucket_brigade *buckets_out,
1337 		const char *ps, size_t buf_len, size_t *consumed,
1338 		int persistent)
1339 {
1340 	php_conv_err_t err;
1341 	php_stream_bucket *new_bucket;
1342 	char *out_buf = NULL;
1343 	size_t out_buf_size;
1344 	char *pd;
1345 	const char *pt;
1346 	size_t ocnt, icnt, tcnt;
1347 	size_t initial_out_buf_size;
1348 
1349 	if (ps == NULL) {
1350 		initial_out_buf_size = 64;
1351 		icnt = 1;
1352 	} else {
1353 		initial_out_buf_size = buf_len;
1354 		icnt = buf_len;
1355 	}
1356 
1357 	out_buf_size = ocnt = initial_out_buf_size;
1358 	out_buf = pemalloc(out_buf_size, persistent);
1359 
1360 	pd = out_buf;
1361 
1362 	if (inst->stub_len > 0) {
1363 		pt = inst->stub;
1364 		tcnt = inst->stub_len;
1365 
1366 		while (tcnt > 0) {
1367 			err = php_conv_convert(inst->cd, &pt, &tcnt, &pd, &ocnt);
1368 
1369 			switch (err) {
1370 				case PHP_CONV_ERR_INVALID_SEQ:
1371 					php_error_docref(NULL, E_WARNING, "Stream filter (%s): invalid byte sequence", inst->filtername);
1372 					goto out_failure;
1373 
1374 				case PHP_CONV_ERR_MORE:
1375 					if (ps != NULL) {
1376 						if (icnt > 0) {
1377 							if (inst->stub_len >= sizeof(inst->stub)) {
1378 								php_error_docref(NULL, E_WARNING, "Stream filter (%s): insufficient buffer", inst->filtername);
1379 								goto out_failure;
1380 							}
1381 							inst->stub[inst->stub_len++] = *(ps++);
1382 							icnt--;
1383 							pt = inst->stub;
1384 							tcnt = inst->stub_len;
1385 						} else {
1386 							tcnt = 0;
1387 							break;
1388 						}
1389 					}
1390 					break;
1391 
1392 				case PHP_CONV_ERR_UNEXPECTED_EOS:
1393 					php_error_docref(NULL, E_WARNING, "Stream filter (%s): unexpected end of stream", inst->filtername);
1394 					goto out_failure;
1395 
1396 				case PHP_CONV_ERR_TOO_BIG: {
1397 					char *new_out_buf;
1398 					size_t new_out_buf_size;
1399 
1400 					new_out_buf_size = out_buf_size << 1;
1401 
1402 					if (new_out_buf_size < out_buf_size) {
1403 						/* whoa! no bigger buckets are sold anywhere... */
1404 						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1405 							goto out_failure;
1406 						}
1407 
1408 						php_stream_bucket_append(buckets_out, new_bucket);
1409 
1410 						out_buf_size = ocnt = initial_out_buf_size;
1411 						out_buf = pemalloc(out_buf_size, persistent);
1412 						pd = out_buf;
1413 					} else {
1414 						new_out_buf = perealloc(out_buf, new_out_buf_size, persistent);
1415 						pd = new_out_buf + (pd - out_buf);
1416 						ocnt += (new_out_buf_size - out_buf_size);
1417 						out_buf = new_out_buf;
1418 						out_buf_size = new_out_buf_size;
1419 					}
1420 				} break;
1421 
1422 				case PHP_CONV_ERR_UNKNOWN:
1423 					php_error_docref(NULL, E_WARNING, "Stream filter (%s): unknown error", inst->filtername);
1424 					goto out_failure;
1425 
1426 				default:
1427 					break;
1428 			}
1429 		}
1430 		memmove(inst->stub, pt, tcnt);
1431 		inst->stub_len = tcnt;
1432 	}
1433 
1434 	while (icnt > 0) {
1435 		err = ((ps == NULL ? php_conv_convert(inst->cd, NULL, NULL, &pd, &ocnt):
1436 				php_conv_convert(inst->cd, &ps, &icnt, &pd, &ocnt)));
1437 		switch (err) {
1438 			case PHP_CONV_ERR_INVALID_SEQ:
1439 				php_error_docref(NULL, E_WARNING, "Stream filter (%s): invalid byte sequence", inst->filtername);
1440 				goto out_failure;
1441 
1442 			case PHP_CONV_ERR_MORE:
1443 				if (ps != NULL) {
1444 					if (icnt > sizeof(inst->stub)) {
1445 						php_error_docref(NULL, E_WARNING, "Stream filter (%s): insufficient buffer", inst->filtername);
1446 						goto out_failure;
1447 					}
1448 					memcpy(inst->stub, ps, icnt);
1449 					inst->stub_len = icnt;
1450 					ps += icnt;
1451 					icnt = 0;
1452 				} else {
1453 					php_error_docref(NULL, E_WARNING, "Stream filter (%s): unexpected octet values", inst->filtername);
1454 					goto out_failure;
1455 				}
1456 				break;
1457 
1458 			case PHP_CONV_ERR_TOO_BIG: {
1459 				char *new_out_buf;
1460 				size_t new_out_buf_size;
1461 
1462 				new_out_buf_size = out_buf_size << 1;
1463 
1464 				if (new_out_buf_size < out_buf_size) {
1465 					/* whoa! no bigger buckets are sold anywhere... */
1466 					if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1467 						goto out_failure;
1468 					}
1469 
1470 					php_stream_bucket_append(buckets_out, new_bucket);
1471 
1472 					out_buf_size = ocnt = initial_out_buf_size;
1473 					out_buf = pemalloc(out_buf_size, persistent);
1474 					pd = out_buf;
1475 				} else {
1476 					new_out_buf = perealloc(out_buf, new_out_buf_size, persistent);
1477 					pd = new_out_buf + (pd - out_buf);
1478 					ocnt += (new_out_buf_size - out_buf_size);
1479 					out_buf = new_out_buf;
1480 					out_buf_size = new_out_buf_size;
1481 				}
1482 			} break;
1483 
1484 			case PHP_CONV_ERR_UNKNOWN:
1485 				php_error_docref(NULL, E_WARNING, "Stream filter (%s): unknown error", inst->filtername);
1486 				goto out_failure;
1487 
1488 			default:
1489 				if (ps == NULL) {
1490 					icnt = 0;
1491 				}
1492 				break;
1493 		}
1494 	}
1495 
1496 	if (out_buf_size > ocnt) {
1497 		if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1498 			goto out_failure;
1499 		}
1500 		php_stream_bucket_append(buckets_out, new_bucket);
1501 	} else {
1502 		pefree(out_buf, persistent);
1503 	}
1504 	*consumed += buf_len - icnt;
1505 
1506 	return SUCCESS;
1507 
1508 out_failure:
1509 	pefree(out_buf, persistent);
1510 	return FAILURE;
1511 }
1512 /* }}} */
1513 
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)1514 static php_stream_filter_status_t strfilter_convert_filter(
1515 	php_stream *stream,
1516 	php_stream_filter *thisfilter,
1517 	php_stream_bucket_brigade *buckets_in,
1518 	php_stream_bucket_brigade *buckets_out,
1519 	size_t *bytes_consumed,
1520 	int flags
1521 	)
1522 {
1523 	php_stream_bucket *bucket = NULL;
1524 	size_t consumed = 0;
1525 	php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
1526 
1527 	while (buckets_in->head != NULL) {
1528 		bucket = buckets_in->head;
1529 
1530 		php_stream_bucket_unlink(bucket);
1531 
1532 		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1533 				buckets_out, bucket->buf, bucket->buflen, &consumed,
1534 				php_stream_is_persistent(stream)) != SUCCESS) {
1535 			goto out_failure;
1536 		}
1537 
1538 		php_stream_bucket_delref(bucket);
1539 	}
1540 
1541 	if (flags != PSFS_FLAG_NORMAL) {
1542 		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1543 				buckets_out, NULL, 0, &consumed,
1544 				php_stream_is_persistent(stream)) != SUCCESS) {
1545 			goto out_failure;
1546 		}
1547 	}
1548 
1549 	if (bytes_consumed) {
1550 		*bytes_consumed = consumed;
1551 	}
1552 
1553 	return PSFS_PASS_ON;
1554 
1555 out_failure:
1556 	if (bucket != NULL) {
1557 		php_stream_bucket_delref(bucket);
1558 	}
1559 	return PSFS_ERR_FATAL;
1560 }
1561 
strfilter_convert_dtor(php_stream_filter * thisfilter)1562 static void strfilter_convert_dtor(php_stream_filter *thisfilter)
1563 {
1564 	assert(Z_PTR(thisfilter->abstract) != NULL);
1565 
1566 	php_convert_filter_dtor((php_convert_filter *)Z_PTR(thisfilter->abstract));
1567 	pefree(Z_PTR(thisfilter->abstract), ((php_convert_filter *)Z_PTR(thisfilter->abstract))->persistent);
1568 }
1569 
1570 static const php_stream_filter_ops strfilter_convert_ops = {
1571 	strfilter_convert_filter,
1572 	strfilter_convert_dtor,
1573 	"convert.*"
1574 };
1575 
strfilter_convert_create(const char * filtername,zval * filterparams,uint8_t persistent)1576 static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, uint8_t persistent)
1577 {
1578 	php_convert_filter *inst;
1579 	php_stream_filter *retval = NULL;
1580 
1581 	char *dot;
1582 	int conv_mode = 0;
1583 
1584 	if (filterparams != NULL && Z_TYPE_P(filterparams) != IS_ARRAY) {
1585 		php_error_docref(NULL, E_WARNING, "Stream filter (%s): invalid filter parameter", filtername);
1586 		return NULL;
1587 	}
1588 
1589 	if ((dot = strchr(filtername, '.')) == NULL) {
1590 		return NULL;
1591 	}
1592 	++dot;
1593 
1594 	inst = pemalloc(sizeof(php_convert_filter), persistent);
1595 
1596 	if (strcasecmp(dot, "base64-encode") == 0) {
1597 		conv_mode = PHP_CONV_BASE64_ENCODE;
1598 	} else if (strcasecmp(dot, "base64-decode") == 0) {
1599 		conv_mode = PHP_CONV_BASE64_DECODE;
1600 	} else if (strcasecmp(dot, "quoted-printable-encode") == 0) {
1601 		conv_mode = PHP_CONV_QPRINT_ENCODE;
1602 	} else if (strcasecmp(dot, "quoted-printable-decode") == 0) {
1603 		conv_mode = PHP_CONV_QPRINT_DECODE;
1604 	}
1605 
1606 	if (php_convert_filter_ctor(inst, conv_mode,
1607 		(filterparams != NULL ? Z_ARRVAL_P(filterparams) : NULL),
1608 		filtername, persistent) != SUCCESS) {
1609 		goto out;
1610 	}
1611 
1612 	retval = php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
1613 out:
1614 	if (retval == NULL) {
1615 		pefree(inst, persistent);
1616 	}
1617 
1618 	return retval;
1619 }
1620 
1621 static const php_stream_filter_factory strfilter_convert_factory = {
1622 	strfilter_convert_create
1623 };
1624 /* }}} */
1625 
1626 /* {{{ consumed filter implementation */
1627 typedef struct _php_consumed_filter_data {
1628 	size_t consumed;
1629 	zend_off_t offset;
1630 	uint8_t persistent;
1631 } php_consumed_filter_data;
1632 
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)1633 static php_stream_filter_status_t consumed_filter_filter(
1634 	php_stream *stream,
1635 	php_stream_filter *thisfilter,
1636 	php_stream_bucket_brigade *buckets_in,
1637 	php_stream_bucket_brigade *buckets_out,
1638 	size_t *bytes_consumed,
1639 	int flags
1640 	)
1641 {
1642 	php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
1643 	php_stream_bucket *bucket;
1644 	size_t consumed = 0;
1645 
1646 	if (data->offset == ~0) {
1647 		data->offset = php_stream_tell(stream);
1648 	}
1649 	while ((bucket = buckets_in->head) != NULL) {
1650 		php_stream_bucket_unlink(bucket);
1651 		consumed += bucket->buflen;
1652 		php_stream_bucket_append(buckets_out, bucket);
1653 	}
1654 	if (bytes_consumed) {
1655 		*bytes_consumed = consumed;
1656 	}
1657 	if (flags & PSFS_FLAG_FLUSH_CLOSE) {
1658 		php_stream_seek(stream, data->offset + data->consumed, SEEK_SET);
1659 	}
1660 	data->consumed += consumed;
1661 
1662 	return PSFS_PASS_ON;
1663 }
1664 
consumed_filter_dtor(php_stream_filter * thisfilter)1665 static void consumed_filter_dtor(php_stream_filter *thisfilter)
1666 {
1667 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
1668 		php_consumed_filter_data *data = (php_consumed_filter_data*)Z_PTR(thisfilter->abstract);
1669 		pefree(data, data->persistent);
1670 	}
1671 }
1672 
1673 static const php_stream_filter_ops consumed_filter_ops = {
1674 	consumed_filter_filter,
1675 	consumed_filter_dtor,
1676 	"consumed"
1677 };
1678 
consumed_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)1679 static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
1680 {
1681 	const php_stream_filter_ops *fops = NULL;
1682 	php_consumed_filter_data *data;
1683 
1684 	if (strcasecmp(filtername, "consumed")) {
1685 		return NULL;
1686 	}
1687 
1688 	/* Create this filter */
1689 	data = pecalloc(1, sizeof(php_consumed_filter_data), persistent);
1690 	data->persistent = persistent;
1691 	data->consumed = 0;
1692 	data->offset = ~0;
1693 	fops = &consumed_filter_ops;
1694 
1695 	return php_stream_filter_alloc(fops, data, persistent);
1696 }
1697 
1698 static const php_stream_filter_factory consumed_filter_factory = {
1699 	consumed_filter_create
1700 };
1701 
1702 /* }}} */
1703 
1704 /* {{{ chunked filter implementation */
1705 typedef enum _php_chunked_filter_state {
1706 	CHUNK_SIZE_START,
1707 	CHUNK_SIZE,
1708 	CHUNK_SIZE_EXT,
1709 	CHUNK_SIZE_CR,
1710 	CHUNK_SIZE_LF,
1711 	CHUNK_BODY,
1712 	CHUNK_BODY_CR,
1713 	CHUNK_BODY_LF,
1714 	CHUNK_TRAILER,
1715 	CHUNK_ERROR
1716 } php_chunked_filter_state;
1717 
1718 typedef struct _php_chunked_filter_data {
1719 	size_t chunk_size;
1720 	php_chunked_filter_state state;
1721 	int persistent;
1722 } php_chunked_filter_data;
1723 
php_dechunk(char * buf,size_t len,php_chunked_filter_data * data)1724 static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
1725 {
1726 	char *p = buf;
1727 	char *end = p + len;
1728 	char *out = buf;
1729 	size_t out_len = 0;
1730 
1731 	while (p < end) {
1732 		switch (data->state) {
1733 			case CHUNK_SIZE_START:
1734 				data->chunk_size = 0;
1735 			case CHUNK_SIZE:
1736 				while (p < end) {
1737 					if (*p >= '0' && *p <= '9') {
1738 						data->chunk_size = (data->chunk_size * 16) + (*p - '0');
1739 					} else if (*p >= 'A' && *p <= 'F') {
1740 						data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
1741 					} else if (*p >= 'a' && *p <= 'f') {
1742 						data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
1743 					} else if (data->state == CHUNK_SIZE_START) {
1744 						data->state = CHUNK_ERROR;
1745 						break;
1746 					} else {
1747 						data->state = CHUNK_SIZE_EXT;
1748 						break;
1749 					}
1750 					data->state = CHUNK_SIZE;
1751 					p++;
1752 				}
1753 				if (data->state == CHUNK_ERROR) {
1754 					continue;
1755 				} else if (p == end) {
1756 					return out_len;
1757 				}
1758 			case CHUNK_SIZE_EXT:
1759 				/* skip extension */
1760 				while (p < end && *p != '\r' && *p != '\n') {
1761 					p++;
1762 				}
1763 				if (p == end) {
1764 					return out_len;
1765 				}
1766 				/* TODO: Check if Intentional? */
1767 				ZEND_FALLTHROUGH;
1768 			case CHUNK_SIZE_CR:
1769 				if (*p == '\r') {
1770 					p++;
1771 					if (p == end) {
1772 						data->state = CHUNK_SIZE_LF;
1773 						return out_len;
1774 					}
1775 				}
1776 				/* TODO: Check if Intentional? */
1777 				ZEND_FALLTHROUGH;
1778 			case CHUNK_SIZE_LF:
1779 				if (*p == '\n') {
1780 					p++;
1781 					if (data->chunk_size == 0) {
1782 						/* last chunk */
1783 						data->state = CHUNK_TRAILER;
1784 						continue;
1785 					} else if (p == end) {
1786 						data->state = CHUNK_BODY;
1787 						return out_len;
1788 					}
1789 				} else {
1790 					data->state = CHUNK_ERROR;
1791 					continue;
1792 				}
1793 				/* TODO: Check if Intentional? */
1794 				ZEND_FALLTHROUGH;
1795 			case CHUNK_BODY:
1796 				if ((size_t) (end - p) >= data->chunk_size) {
1797 					if (p != out) {
1798 						memmove(out, p, data->chunk_size);
1799 					}
1800 					out += data->chunk_size;
1801 					out_len += data->chunk_size;
1802 					p += data->chunk_size;
1803 					if (p == end) {
1804 						data->state = CHUNK_BODY_CR;
1805 						return out_len;
1806 					}
1807 				} else {
1808 					if (p != out) {
1809 						memmove(out, p, end - p);
1810 					}
1811 					data->chunk_size -= end - p;
1812 					data->state=CHUNK_BODY;
1813 					out_len += end - p;
1814 					return out_len;
1815 				}
1816 				/* TODO: Check if Intentional? */
1817 				ZEND_FALLTHROUGH;
1818 			case CHUNK_BODY_CR:
1819 				if (*p == '\r') {
1820 					p++;
1821 					if (p == end) {
1822 						data->state = CHUNK_BODY_LF;
1823 						return out_len;
1824 					}
1825 				}
1826 				/* TODO: Check if Intentional? */
1827 				ZEND_FALLTHROUGH;
1828 			case CHUNK_BODY_LF:
1829 				if (*p == '\n') {
1830 					p++;
1831 					data->state = CHUNK_SIZE_START;
1832 					continue;
1833 				} else {
1834 					data->state = CHUNK_ERROR;
1835 					continue;
1836 				}
1837 			case CHUNK_TRAILER:
1838 				/* ignore trailer */
1839 				p = end;
1840 				continue;
1841 			case CHUNK_ERROR:
1842 				if (p != out) {
1843 					memmove(out, p, end - p);
1844 				}
1845 				out_len += end - p;
1846 				return out_len;
1847 		}
1848 	}
1849 	return out_len;
1850 }
1851 
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)1852 static php_stream_filter_status_t php_chunked_filter(
1853 	php_stream *stream,
1854 	php_stream_filter *thisfilter,
1855 	php_stream_bucket_brigade *buckets_in,
1856 	php_stream_bucket_brigade *buckets_out,
1857 	size_t *bytes_consumed,
1858 	int flags
1859 	)
1860 {
1861 	php_stream_bucket *bucket;
1862 	size_t consumed = 0;
1863 	php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
1864 
1865 	while (buckets_in->head) {
1866 		bucket = php_stream_bucket_make_writeable(buckets_in->head);
1867 		consumed += bucket->buflen;
1868 		bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);
1869 		php_stream_bucket_append(buckets_out, bucket);
1870 	}
1871 
1872 	if (bytes_consumed) {
1873 		*bytes_consumed = consumed;
1874 	}
1875 
1876 	return PSFS_PASS_ON;
1877 }
1878 
php_chunked_dtor(php_stream_filter * thisfilter)1879 static void php_chunked_dtor(php_stream_filter *thisfilter)
1880 {
1881 	if (thisfilter && Z_PTR(thisfilter->abstract)) {
1882 		php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
1883 		pefree(data, data->persistent);
1884 	}
1885 }
1886 
1887 static const php_stream_filter_ops chunked_filter_ops = {
1888 	php_chunked_filter,
1889 	php_chunked_dtor,
1890 	"dechunk"
1891 };
1892 
chunked_filter_create(const char * filtername,zval * filterparams,uint8_t persistent)1893 static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, uint8_t persistent)
1894 {
1895 	const php_stream_filter_ops *fops = NULL;
1896 	php_chunked_filter_data *data;
1897 
1898 	if (strcasecmp(filtername, "dechunk")) {
1899 		return NULL;
1900 	}
1901 
1902 	/* Create this filter */
1903 	data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent);
1904 	data->state = CHUNK_SIZE_START;
1905 	data->chunk_size = 0;
1906 	data->persistent = persistent;
1907 	fops = &chunked_filter_ops;
1908 
1909 	return php_stream_filter_alloc(fops, data, persistent);
1910 }
1911 
1912 static const php_stream_filter_factory chunked_filter_factory = {
1913 	chunked_filter_create
1914 };
1915 /* }}} */
1916 
1917 static const struct {
1918 	const php_stream_filter_ops *ops;
1919 	const php_stream_filter_factory *factory;
1920 } standard_filters[] = {
1921 	{ &strfilter_rot13_ops, &strfilter_rot13_factory },
1922 	{ &strfilter_toupper_ops, &strfilter_toupper_factory },
1923 	{ &strfilter_tolower_ops, &strfilter_tolower_factory },
1924 	{ &strfilter_convert_ops, &strfilter_convert_factory },
1925 	{ &consumed_filter_ops, &consumed_filter_factory },
1926 	{ &chunked_filter_ops, &chunked_filter_factory },
1927 	/* additional filters to go here */
1928 	{ NULL, NULL }
1929 };
1930 
1931 /* {{{ filter MINIT and MSHUTDOWN */
PHP_MINIT_FUNCTION(standard_filters)1932 PHP_MINIT_FUNCTION(standard_filters)
1933 {
1934 	int i;
1935 
1936 	for (i = 0; standard_filters[i].ops; i++) {
1937 		if (FAILURE == php_stream_filter_register_factory(
1938 					standard_filters[i].ops->label,
1939 					standard_filters[i].factory
1940 					)) {
1941 			return FAILURE;
1942 		}
1943 	}
1944 	return SUCCESS;
1945 }
1946 
PHP_MSHUTDOWN_FUNCTION(standard_filters)1947 PHP_MSHUTDOWN_FUNCTION(standard_filters)
1948 {
1949 	int i;
1950 
1951 	for (i = 0; standard_filters[i].ops; i++) {
1952 		php_stream_filter_unregister_factory(standard_filters[i].ops->label);
1953 	}
1954 	return SUCCESS;
1955 }
1956 /* }}} */
1957