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