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