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