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