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