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