text: /*
     +----------------------------------------------------------------------+
     | PHP Version 7                                                        |
     +----------------------------------------------------------------------+
     | Copyright (c) 1997-2018 The PHP Group                                |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
     | Authors:                                                             |
     | Wez Furlong (wez@thebrainroom.com)                                   |
     | Sara Golemon (pollita@php.net)                                       |
     | Moriyoshi Koizumi (moriyoshi@php.net)                                |
     | Marcus Boerger (helly@php.net)                                       |
     +----------------------------------------------------------------------+
  */
  
  /* $Id$ */
  
  #include "php.h"
  #include "php_globals.h"
  #include "ext/standard/basic_functions.h"
  #include "ext/standard/file.h"
  #include "ext/standard/php_string.h"
  #include "zend_smart_str.h"
  
  /* {{{ rot13 stream filter implementation */
  static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  
  	while (buckets_in->head) {
  		bucket = php_stream_bucket_make_writeable(buckets_in->head);
  
  		php_strtr(bucket->buf, bucket->buflen, rot13_from, rot13_to, 52);
  		consumed += bucket->buflen;
  
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  }
  
  static php_stream_filter_ops strfilter_rot13_ops = {
  	strfilter_rot13_filter,
  	NULL,
  	"string.rot13"
  };
  
  static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, int persistent)
  {
  	return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent);
  }
  
  static php_stream_filter_factory strfilter_rot13_factory = {
  	strfilter_rot13_create
  };
  /* }}} */
  
  /* {{{ string.toupper / string.tolower stream filter implementation */
  static char lowercase[] = "abcdefghijklmnopqrstuvwxyz";
  static char uppercase[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  
  	while (buckets_in->head) {
  		bucket = php_stream_bucket_make_writeable(buckets_in->head);
  
  		php_strtr(bucket->buf, bucket->buflen, lowercase, uppercase, 26);
  		consumed += bucket->buflen;
  
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  }
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  
  	while (buckets_in->head) {
  		bucket = php_stream_bucket_make_writeable(buckets_in->head);
  
  		php_strtr(bucket->buf, bucket->buflen, uppercase, lowercase, 26);
  		consumed += bucket->buflen;
  
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  }
  
  static php_stream_filter_ops strfilter_toupper_ops = {
  	strfilter_toupper_filter,
  	NULL,
  	"string.toupper"
  };
  
  static php_stream_filter_ops strfilter_tolower_ops = {
  	strfilter_tolower_filter,
  	NULL,
  	"string.tolower"
  };
  
  static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, int persistent)
  {
  	return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent);
  }
  
  static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, int persistent)
  {
  	return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent);
  }
  
  static php_stream_filter_factory strfilter_toupper_factory = {
  	strfilter_toupper_create
  };
  
  static php_stream_filter_factory strfilter_tolower_factory = {
  	strfilter_tolower_create
  };
  /* }}} */
  
  /* {{{ strip_tags filter implementation */
  typedef struct _php_strip_tags_filter {
  	const char *allowed_tags;
  	int allowed_tags_len;
  	int state;
  	int persistent;
  } php_strip_tags_filter;
  
  static int php_strip_tags_filter_ctor(php_strip_tags_filter *inst, zend_string *allowed_tags, int persistent)
  {
  	if (allowed_tags != NULL) {
  		if (NULL == (inst->allowed_tags = pemalloc(ZSTR_LEN(allowed_tags) + 1, persistent))) {
  			return FAILURE;
  		}
  		memcpy((char *)inst->allowed_tags, ZSTR_VAL(allowed_tags), ZSTR_LEN(allowed_tags) + 1);
  		inst->allowed_tags_len = (int)ZSTR_LEN(allowed_tags);
  	} else {
  		inst->allowed_tags = NULL;
  	}
  	inst->state = 0;
  	inst->persistent = persistent;
  
  	return SUCCESS;
  }
  
  static void php_strip_tags_filter_dtor(php_strip_tags_filter *inst)
  {
  	if (inst->allowed_tags != NULL) {
  		pefree((void *)inst->allowed_tags, inst->persistent);
  	}
  }
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  	php_strip_tags_filter *inst = (php_strip_tags_filter *) Z_PTR(thisfilter->abstract);
  
  	while (buckets_in->head) {
  		bucket = php_stream_bucket_make_writeable(buckets_in->head);
  		consumed = bucket->buflen;
  
  		bucket->buflen = php_strip_tags(bucket->buf, bucket->buflen, &(inst->state), inst->allowed_tags, inst->allowed_tags_len);
  
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  }
  
  static void strfilter_strip_tags_dtor(php_stream_filter *thisfilter)
  {
  	assert(Z_PTR(thisfilter->abstract) != NULL);
  
  	php_strip_tags_filter_dtor((php_strip_tags_filter *)Z_PTR(thisfilter->abstract));
  
  	pefree(Z_PTR(thisfilter->abstract), ((php_strip_tags_filter *)Z_PTR(thisfilter->abstract))->persistent);
  }
  
  static php_stream_filter_ops strfilter_strip_tags_ops = {
  	strfilter_strip_tags_filter,
  	strfilter_strip_tags_dtor,
  	"string.strip_tags"
  };
  
  static php_stream_filter *strfilter_strip_tags_create(const char *filtername, zval *filterparams, int persistent)
  {
  	php_strip_tags_filter *inst;
  	php_stream_filter *filter = NULL;
  	zend_string *allowed_tags = NULL;;
  
  	inst = pemalloc(sizeof(php_strip_tags_filter), persistent);
  
  	if (inst == NULL) { /* it's possible pemalloc returns NULL
  						   instead of causing it to bail out */
  		return NULL;
  	}
  
  	if (filterparams != NULL) {
  		if (Z_TYPE_P(filterparams) == IS_ARRAY) {
  			smart_str tags_ss = {0};
  			zval *tmp;
  
  			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(filterparams), tmp) {
  				convert_to_string_ex(tmp);
  				smart_str_appendc(&tags_ss, '<');
  				smart_str_append(&tags_ss, Z_STR_P(tmp));
  				smart_str_appendc(&tags_ss, '>');
  			} ZEND_HASH_FOREACH_END();
  			smart_str_0(&tags_ss);
  			allowed_tags = tags_ss.s;
  		} else {
  			allowed_tags = zval_get_string(filterparams);
  		}
  	}
  
  	if (php_strip_tags_filter_ctor(inst, allowed_tags, persistent) == SUCCESS) {
  		filter = php_stream_filter_alloc(&strfilter_strip_tags_ops, inst, persistent);
  	} else {
  		pefree(inst, persistent);
  	}
  
  	if (allowed_tags) {
  		zend_string_release(allowed_tags);
  	}
  
  	return filter;
  }
  
  static php_stream_filter_factory strfilter_strip_tags_factory = {
  	strfilter_strip_tags_create
  };
  
  /* }}} */
  
  /* {{{ base64 / quoted_printable stream filter implementation */
  
  typedef enum _php_conv_err_t {
  	PHP_CONV_ERR_SUCCESS = SUCCESS,
  	PHP_CONV_ERR_UNKNOWN,
  	PHP_CONV_ERR_TOO_BIG,
  	PHP_CONV_ERR_INVALID_SEQ,
  	PHP_CONV_ERR_UNEXPECTED_EOS,
  	PHP_CONV_ERR_EXISTS,
  	PHP_CONV_ERR_MORE,
  	PHP_CONV_ERR_ALLOC,
  	PHP_CONV_ERR_NOT_FOUND
  } php_conv_err_t;
  
  typedef struct _php_conv php_conv;
  
  typedef php_conv_err_t (*php_conv_convert_func)(php_conv *, const char **, size_t *, char **, size_t *);
  typedef void (*php_conv_dtor_func)(php_conv *);
  
  struct _php_conv {
  	php_conv_convert_func convert_op;
  	php_conv_dtor_func dtor;
  };
  
  #define php_conv_convert(a, b, c, d, e) ((php_conv *)(a))->convert_op((php_conv *)(a), (b), (c), (d), (e))
  #define php_conv_dtor(a) ((php_conv *)a)->dtor((a))
  
  /* {{{ php_conv_base64_encode */
  typedef struct _php_conv_base64_encode {
  	php_conv _super;
  
  	const char *lbchars;
  	size_t lbchars_len;
  	size_t erem_len;
  	unsigned int line_ccnt;
  	unsigned int line_len;
  	int lbchars_dup;
  	int persistent;
  	unsigned char erem[3];
  } php_conv_base64_encode;
  
  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);
  static void php_conv_base64_encode_dtor(php_conv_base64_encode *inst);
  
  static unsigned char b64_tbl_enc[256] = {
  	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
  	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
  	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
  	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
  };
  
  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)
  {
  	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_encode_convert;
  	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_encode_dtor;
  	inst->erem_len = 0;
  	inst->line_ccnt = line_len;
  	inst->line_len = line_len;
  	if (lbchars != NULL) {
  		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
  		inst->lbchars_len = lbchars_len;
  	} else {
  		inst->lbchars = NULL;
  	}
  	inst->lbchars_dup = lbchars_dup;
  	inst->persistent = persistent;
  	return PHP_CONV_ERR_SUCCESS;
  }
  
  static void php_conv_base64_encode_dtor(php_conv_base64_encode *inst)
  {
  	assert(inst != NULL);
  	if (inst->lbchars_dup && inst->lbchars != NULL) {
  		pefree((void *)inst->lbchars, inst->persistent);
  	}
  }
  
  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)
  {
  	volatile php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
  	register unsigned char *pd;
  	register size_t ocnt;
  	unsigned int line_ccnt;
  
  	pd = (unsigned char *)(*out_pp);
  	ocnt = *out_left_p;
  	line_ccnt = inst->line_ccnt;
  
  	switch (inst->erem_len) {
  		case 0:
  			/* do nothing */
  			break;
  
  		case 1:
  			if (line_ccnt < 4 && inst->lbchars != NULL) {
  				if (ocnt < inst->lbchars_len) {
  					return PHP_CONV_ERR_TOO_BIG;
  				}
  				memcpy(pd, inst->lbchars, inst->lbchars_len);
  				pd += inst->lbchars_len;
  				ocnt -= inst->lbchars_len;
  				line_ccnt = inst->line_len;
  			}
  			if (ocnt < 4) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				goto out;
  			}
  			*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
  			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4)];
  			*(pd++) = '=';
  			*(pd++) = '=';
  			inst->erem_len = 0;
  			ocnt -= 4;
  			line_ccnt -= 4;
  			break;
  
  		case 2:
  			if (line_ccnt < 4 && inst->lbchars != NULL) {
  				if (ocnt < inst->lbchars_len) {
  					return PHP_CONV_ERR_TOO_BIG;
  				}
  				memcpy(pd, inst->lbchars, inst->lbchars_len);
  				pd += inst->lbchars_len;
  				ocnt -= inst->lbchars_len;
  				line_ccnt = inst->line_len;
  			}
  			if (ocnt < 4) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				goto out;
  			}
  			*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
  			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (inst->erem[1] >> 4)];
  			*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[1] << 2)];
  			*(pd++) = '=';
  			inst->erem_len = 0;
  			ocnt -=4;
  			line_ccnt -= 4;
  			break;
  
  		default:
  			/* should not happen... */
  			err = PHP_CONV_ERR_UNKNOWN;
  			break;
  	}
  out:
  	*out_pp = (char *)pd;
  	*out_left_p = ocnt;
  	inst->line_ccnt = line_ccnt;
  	return err;
  }
  
  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)
  {
  	volatile php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
  	register size_t ocnt, icnt;
  	register unsigned char *ps, *pd;
  	register unsigned int line_ccnt;
  
  	if (in_pp == NULL || in_left_p == NULL) {
  		return php_conv_base64_encode_flush(inst, in_pp, in_left_p, out_pp, out_left_p);
  	}
  
  	pd = (unsigned char *)(*out_pp);
  	ocnt = *out_left_p;
  	ps = (unsigned char *)(*in_pp);
  	icnt = *in_left_p;
  	line_ccnt = inst->line_ccnt;
  
  	/* consume the remainder first */
  	switch (inst->erem_len) {
  		case 1:
  			if (icnt >= 2) {
  				if (line_ccnt < 4 && inst->lbchars != NULL) {
  					if (ocnt < inst->lbchars_len) {
  						return PHP_CONV_ERR_TOO_BIG;
  					}
  					memcpy(pd, inst->lbchars, inst->lbchars_len);
  					pd += inst->lbchars_len;
  					ocnt -= inst->lbchars_len;
  					line_ccnt = inst->line_len;
  				}
  				if (ocnt < 4) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					goto out;
  				}
  				*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
  				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (ps[0] >> 4)];
  				*(pd++) = b64_tbl_enc[(unsigned char)(ps[0] << 2) | (ps[1] >> 6)];
  				*(pd++) = b64_tbl_enc[ps[1]];
  				ocnt -= 4;
  				ps += 2;
  				icnt -= 2;
  				inst->erem_len = 0;
  				line_ccnt -= 4;
  			}
  			break;
  
  		case 2:
  			if (icnt >= 1) {
  				if (inst->line_ccnt < 4 && inst->lbchars != NULL) {
  					if (ocnt < inst->lbchars_len) {
  						return PHP_CONV_ERR_TOO_BIG;
  					}
  					memcpy(pd, inst->lbchars, inst->lbchars_len);
  					pd += inst->lbchars_len;
  					ocnt -= inst->lbchars_len;
  					line_ccnt = inst->line_len;
  				}
  				if (ocnt < 4) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					goto out;
  				}
  				*(pd++) = b64_tbl_enc[(inst->erem[0] >> 2)];
  				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[0] << 4) | (inst->erem[1] >> 4)];
  				*(pd++) = b64_tbl_enc[(unsigned char)(inst->erem[1] << 2) | (ps[0] >> 6)];
  				*(pd++) = b64_tbl_enc[ps[0]];
  				ocnt -= 4;
  				ps += 1;
  				icnt -= 1;
  				inst->erem_len = 0;
  				line_ccnt -= 4;
  			}
  			break;
  	}
  
  	while (icnt >= 3) {
  		if (line_ccnt < 4 && inst->lbchars != NULL) {
  			if (ocnt < inst->lbchars_len) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				goto out;
  			}
  			memcpy(pd, inst->lbchars, inst->lbchars_len);
  			pd += inst->lbchars_len;
  			ocnt -= inst->lbchars_len;
  			line_ccnt = inst->line_len;
  		}
  		if (ocnt < 4) {
  			err = PHP_CONV_ERR_TOO_BIG;
  			goto out;
  		}
  		*(pd++) = b64_tbl_enc[ps[0] >> 2];
  		*(pd++) = b64_tbl_enc[(unsigned char)(ps[0] << 4) | (ps[1] >> 4)];
  		*(pd++) = b64_tbl_enc[(unsigned char)(ps[1] << 2) | (ps[2] >> 6)];
  		*(pd++) = b64_tbl_enc[ps[2]];
  
  		ps += 3;
  		icnt -=3;
  		ocnt -= 4;
  		line_ccnt -= 4;
  	}
  	for (;icnt > 0; icnt--) {
  		inst->erem[inst->erem_len++] = *(ps++);
  	}
  
  out:
  	*in_pp = (const char *)ps;
  	*in_left_p = icnt;
  	*out_pp = (char *)pd;
  	*out_left_p = ocnt;
  	inst->line_ccnt = line_ccnt;
  
  	return err;
  }
  
  /* }}} */
  
  /* {{{ php_conv_base64_decode */
  typedef struct _php_conv_base64_decode {
  	php_conv _super;
  
  	unsigned int urem;
  	unsigned int urem_nbits;
  	unsigned int ustat;
  	int eos;
  } php_conv_base64_decode;
  
  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);
  static void php_conv_base64_decode_dtor(php_conv_base64_decode *inst);
  
  static unsigned int b64_tbl_dec[256] = {
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
  	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64,128, 64, 64,
  	64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
  	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
  	64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
  	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
  };
  
  static int php_conv_base64_decode_ctor(php_conv_base64_decode *inst)
  {
  	inst->_super.convert_op = (php_conv_convert_func) php_conv_base64_decode_convert;
  	inst->_super.dtor = (php_conv_dtor_func) php_conv_base64_decode_dtor;
  
  	inst->urem = 0;
  	inst->urem_nbits = 0;
  	inst->ustat = 0;
  	inst->eos = 0;
  	return SUCCESS;
  }
  
  static void php_conv_base64_decode_dtor(php_conv_base64_decode *inst)
  {
  	/* do nothing */
  }
  
  #define bmask(a) (0xffff >> (16 - a))
  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)
  {
  	php_conv_err_t err;
  
  	unsigned int urem, urem_nbits;
  	unsigned int pack, pack_bcnt;
  	unsigned char *ps, *pd;
  	size_t icnt, ocnt;
  	unsigned int ustat;
  
  	static const unsigned int nbitsof_pack = 8;
  
  	if (in_pp == NULL || in_left_p == NULL) {
  		if (inst->eos || inst->urem_nbits == 0) {
  			return PHP_CONV_ERR_SUCCESS;
  		}
  		return PHP_CONV_ERR_UNEXPECTED_EOS;
  	}
  
  	err = PHP_CONV_ERR_SUCCESS;
  
  	ps = (unsigned char *)*in_pp;
  	pd = (unsigned char *)*out_pp;
  	icnt = *in_left_p;
  	ocnt = *out_left_p;
  
  	urem = inst->urem;
  	urem_nbits = inst->urem_nbits;
  	ustat = inst->ustat;
  
  	pack = 0;
  	pack_bcnt = nbitsof_pack;
  
  	for (;;) {
  		if (pack_bcnt >= urem_nbits) {
  			pack_bcnt -= urem_nbits;
  			pack |= (urem << pack_bcnt);
  			urem_nbits = 0;
  		} else {
  			urem_nbits -= pack_bcnt;
  			pack |= (urem >> urem_nbits);
  			urem &= bmask(urem_nbits);
  			pack_bcnt = 0;
  		}
  		if (pack_bcnt > 0) {
  			unsigned int i;
  
  			if (icnt < 1) {
  				break;
  			}
  
  			i = b64_tbl_dec[(unsigned int)*(ps++)];
  			icnt--;
  			ustat |= i & 0x80;
  
  			if (!(i & 0xc0)) {
  				if (ustat) {
  					err = PHP_CONV_ERR_INVALID_SEQ;
  					break;
  				}
  				if (6 <= pack_bcnt) {
  					pack_bcnt -= 6;
  					pack |= (i << pack_bcnt);
  					urem = 0;
  				} else {
  					urem_nbits = 6 - pack_bcnt;
  					pack |= (i >> urem_nbits);
  					urem = i & bmask(urem_nbits);
  					pack_bcnt = 0;
  				}
  			} else if (ustat) {
  				if (pack_bcnt == 8 || pack_bcnt == 2) {
  					err = PHP_CONV_ERR_INVALID_SEQ;
  					break;
  				}
  				inst->eos = 1;
  			}
  		}
  		if ((pack_bcnt | ustat) == 0) {
  			if (ocnt < 1) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				break;
  			}
  			*(pd++) = pack;
  			ocnt--;
  			pack = 0;
  			pack_bcnt = nbitsof_pack;
  		}
  	}
  
  	if (urem_nbits >= pack_bcnt) {
  		urem |= (pack << (urem_nbits - pack_bcnt));
  		urem_nbits += (nbitsof_pack - pack_bcnt);
  	} else {
  		urem |= (pack >> (pack_bcnt - urem_nbits));
  		urem_nbits += (nbitsof_pack - pack_bcnt);
  	}
  
  	inst->urem = urem;
  	inst->urem_nbits = urem_nbits;
  	inst->ustat = ustat;
  
  	*in_pp = (const char *)ps;
  	*in_left_p = icnt;
  	*out_pp = (char *)pd;
  	*out_left_p = ocnt;
  
  	return err;
  }
  #undef bmask
  /* }}} */
  
  /* {{{ php_conv_qprint_encode */
  typedef struct _php_conv_qprint_encode {
  	php_conv _super;
  
  	const char *lbchars;
  	size_t lbchars_len;
  	int opts;
  	unsigned int line_ccnt;
  	unsigned int line_len;
  	int lbchars_dup;
  	int persistent;
  	unsigned int lb_ptr;
  	unsigned int lb_cnt;
  } php_conv_qprint_encode;
  
  #define PHP_CONV_QPRINT_OPT_BINARY             0x00000001
  #define PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST 0x00000002
  
  static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst);
  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);
  
  static void php_conv_qprint_encode_dtor(php_conv_qprint_encode *inst)
  {
  	assert(inst != NULL);
  	if (inst->lbchars_dup && inst->lbchars != NULL) {
  		pefree((void *)inst->lbchars, inst->persistent);
  	}
  }
  
  #define NEXT_CHAR(ps, icnt, lb_ptr, lb_cnt, lbchars) \
  	((lb_ptr) < (lb_cnt) ? (lbchars)[(lb_ptr)] : *(ps))
  
  #define CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt) \
  	if ((lb_ptr) < (lb_cnt)) { \
  		(lb_ptr)++; \
  	} else { \
  		(lb_cnt) = (lb_ptr) = 0; \
  		--(icnt); \
  		(ps)++; \
  	}
  
  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)
  {
  	php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
  	unsigned char *ps, *pd;
  	size_t icnt, ocnt;
  	unsigned int c;
  	unsigned int line_ccnt;
  	unsigned int lb_ptr;
  	unsigned int lb_cnt;
  	unsigned int trail_ws;
  	int opts;
  	static char qp_digits[] = "0123456789ABCDEF";
  
  	line_ccnt = inst->line_ccnt;
  	opts = inst->opts;
  	lb_ptr = inst->lb_ptr;
  	lb_cnt = inst->lb_cnt;
  
  	if ((in_pp == NULL || in_left_p == NULL) && (lb_ptr >=lb_cnt)) {
  		return PHP_CONV_ERR_SUCCESS;
  	}
  
  	ps = (unsigned char *)(*in_pp);
  	icnt = *in_left_p;
  	pd = (unsigned char *)(*out_pp);
  	ocnt = *out_left_p;
  	trail_ws = 0;
  
  	for (;;) {
  		if (!(opts & PHP_CONV_QPRINT_OPT_BINARY) && inst->lbchars != NULL && inst->lbchars_len > 0) {
  			/* look ahead for the line break chars to make a right decision
  			 * how to consume incoming characters */
  
  			if (icnt > 0 && *ps == inst->lbchars[lb_cnt]) {
  				lb_cnt++;
  
  				if (lb_cnt >= inst->lbchars_len) {
  					unsigned int i;
  
  					if (ocnt < lb_cnt) {
  						lb_cnt--;
  						err = PHP_CONV_ERR_TOO_BIG;
  						break;
  					}
  
  					for (i = 0; i < lb_cnt; i++) {
  						*(pd++) = inst->lbchars[i];
  						ocnt--;
  					}
  					line_ccnt = inst->line_len;
  					lb_ptr = lb_cnt = 0;
  				}
  				ps++, icnt--;
  				continue;
  			}
  		}
  
  		if (lb_ptr >= lb_cnt && icnt <= 0) {
  			break;
  		}
  
  		c = NEXT_CHAR(ps, icnt, lb_ptr, lb_cnt, inst->lbchars);
  
  		if (!(opts & PHP_CONV_QPRINT_OPT_BINARY) &&
  			(trail_ws == 0) &&
  			(c == '\t' || c == ' ')) {
  			if (line_ccnt < 2 && inst->lbchars != NULL) {
  				if (ocnt < inst->lbchars_len + 1) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					break;
  				}
  
  				*(pd++) = '=';
  				ocnt--;
  				line_ccnt--;
  
  				memcpy(pd, inst->lbchars, inst->lbchars_len);
  				pd += inst->lbchars_len;
  				ocnt -= inst->lbchars_len;
  				line_ccnt = inst->line_len;
  			} else {
  				if (ocnt < 1) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					break;
  				}
  
  				/* Check to see if this is EOL whitespace. */
  				if (inst->lbchars != NULL) {
  					unsigned char *ps2;
  					unsigned int lb_cnt2;
  					size_t j;
  
  					lb_cnt2 = 0;
  					ps2 = ps;
  					trail_ws = 1;
  
  					for (j = icnt - 1; j > 0; j--, ps2++) {
  						if (*ps2 == inst->lbchars[lb_cnt2]) {
  							lb_cnt2++;
  							if (lb_cnt2 >= inst->lbchars_len) {
  								/* Found trailing ws. Reset to top of main
  								 * for loop to allow for code to do necessary
  								 * wrapping/encoding. */
  								break;
  							}
  						} else if (lb_cnt2 != 0 || (*ps2 != '\t' && *ps2 != ' ')) {
  							/* At least one non-EOL character following, so
  							 * don't need to encode ws. */
  							trail_ws = 0;
  							break;
  						} else {
  							trail_ws++;
  						}
  					}
  				}
  
  				if (trail_ws == 0) {
  					*(pd++) = c;
  					ocnt--;
  					line_ccnt--;
  					CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
  				}
  			}
  		} else if ((!(opts & PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST) || line_ccnt < inst->line_len) && ((c >= 33 && c <= 60) || (c >= 62 && c <= 126))) {
  			if (line_ccnt < 2 && inst->lbchars != NULL) {
  				if (ocnt < inst->lbchars_len + 1) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					break;
  				}
  				*(pd++) = '=';
  				ocnt--;
  				line_ccnt--;
  
  				memcpy(pd, inst->lbchars, inst->lbchars_len);
  				pd += inst->lbchars_len;
  				ocnt -= inst->lbchars_len;
  				line_ccnt = inst->line_len;
  			}
  			if (ocnt < 1) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				break;
  			}
  			*(pd++) = c;
  			ocnt--;
  			line_ccnt--;
  			CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
  		} else {
  			if (line_ccnt < 4 && inst->lbchars != NULL) {
  				if (ocnt < inst->lbchars_len + 1) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					break;
  				}
  				*(pd++) = '=';
  				ocnt--;
  				line_ccnt--;
  
  				memcpy(pd, inst->lbchars, inst->lbchars_len);
  				pd += inst->lbchars_len;
  				ocnt -= inst->lbchars_len;
  				line_ccnt = inst->line_len;
  			}
  			if (ocnt < 3) {
  				err = PHP_CONV_ERR_TOO_BIG;
  				break;
  			}
  			*(pd++) = '=';
  			*(pd++) = qp_digits[(c >> 4)];
  			*(pd++) = qp_digits[(c & 0x0f)];
  			ocnt -= 3;
  			line_ccnt -= 3;
  			if (trail_ws > 0) {
  				trail_ws--;
  			}
  			CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
  		}
  	}
  
  	*in_pp = (const char *)ps;
  	*in_left_p = icnt;
  	*out_pp = (char *)pd;
  	*out_left_p = ocnt;
  	inst->line_ccnt = line_ccnt;
  	inst->lb_ptr = lb_ptr;
  	inst->lb_cnt = lb_cnt;
  	return err;
  }
  #undef NEXT_CHAR
  #undef CONSUME_CHAR
  
  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)
  {
  	if (line_len < 4 && lbchars != NULL) {
  		return PHP_CONV_ERR_TOO_BIG;
  	}
  	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_encode_convert;
  	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_encode_dtor;
  	inst->line_ccnt = line_len;
  	inst->line_len = line_len;
  	if (lbchars != NULL) {
  		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
  		inst->lbchars_len = lbchars_len;
  	} else {
  		inst->lbchars = NULL;
  	}
  	inst->lbchars_dup = lbchars_dup;
  	inst->persistent = persistent;
  	inst->opts = opts;
  	inst->lb_cnt = inst->lb_ptr = 0;
  	return PHP_CONV_ERR_SUCCESS;
  }
  /* }}} */
  
  /* {{{ php_conv_qprint_decode */
  typedef struct _php_conv_qprint_decode {
  	php_conv _super;
  
  	const char *lbchars;
  	size_t lbchars_len;
  	int scan_stat;
  	unsigned int next_char;
  	int lbchars_dup;
  	int persistent;
  	unsigned int lb_ptr;
  	unsigned int lb_cnt;
  } php_conv_qprint_decode;
  
  static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst)
  {
  	assert(inst != NULL);
  	if (inst->lbchars_dup && inst->lbchars != NULL) {
  		pefree((void *)inst->lbchars, inst->persistent);
  	}
  }
  
  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)
  {
  	php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
  	size_t icnt, ocnt;
  	unsigned char *ps, *pd;
  	unsigned int scan_stat;
  	unsigned int next_char;
  	unsigned int lb_ptr, lb_cnt;
  
  	lb_ptr = inst->lb_ptr;
  	lb_cnt = inst->lb_cnt;
  
  	if ((in_pp == NULL || in_left_p == NULL) && lb_cnt == lb_ptr) {
  		if (inst->scan_stat != 0) {
  			return PHP_CONV_ERR_UNEXPECTED_EOS;
  		}
  		return PHP_CONV_ERR_SUCCESS;
  	}
  
  	ps = (unsigned char *)(*in_pp);
  	icnt = *in_left_p;
  	pd = (unsigned char *)(*out_pp);
  	ocnt = *out_left_p;
  	scan_stat = inst->scan_stat;
  	next_char = inst->next_char;
  
  	for (;;) {
  		switch (scan_stat) {
  			case 0: {
  				if (icnt <= 0) {
  					goto out;
  				}
  				if (*ps == '=') {
  					scan_stat = 1;
  				} else {
  					if (ocnt < 1) {
  						err = PHP_CONV_ERR_TOO_BIG;
  						goto out;
  					}
  					*(pd++) = *ps;
  					ocnt--;
  				}
  				ps++, icnt--;
  			} break;
  
  			case 1: {
  				if (icnt <= 0) {
  					goto out;
  				}
  				if (*ps == ' ' || *ps == '\t') {
  					scan_stat = 4;
  					ps++, icnt--;
  					break;
  				} else if (!inst->lbchars && lb_cnt == 0 && *ps == '\r') {
  					/* auto-detect line endings, looks like network line ending \r\n (could be mac \r) */
  					lb_cnt++;
  					scan_stat = 5;
  					ps++, icnt--;
  					break;
  				} else if (!inst->lbchars && lb_cnt == 0 && *ps == '\n') {
  					/* auto-detect line endings, looks like unix-lineendings, not to spec, but it is seem in the wild, a lot */
  					lb_cnt = lb_ptr = 0;
  					scan_stat = 0;
  					ps++, icnt--;
  					break;
  				} else if (lb_cnt < inst->lbchars_len &&
  							*ps == (unsigned char)inst->lbchars[lb_cnt]) {
  					lb_cnt++;
  					scan_stat = 5;
  					ps++, icnt--;
  					break;
  				}
  			} /* break is missing intentionally */
  
  			case 2: {
  				if (icnt <= 0) {
  					goto out;
  				}
  
  				if (!isxdigit((int) *ps)) {
  					err = PHP_CONV_ERR_INVALID_SEQ;
  					goto out;
  				}
  				next_char = (next_char << 4) | (*ps >= 'A' ? *ps - 0x37 : *ps - 0x30);
  				scan_stat++;
  				ps++, icnt--;
  				if (scan_stat != 3) {
  					break;
  				}
  			} /* break is missing intentionally */
  
  			case 3: {
  				if (ocnt < 1) {
  					err = PHP_CONV_ERR_TOO_BIG;
  					goto out;
  				}
  				*(pd++) = next_char;
  				ocnt--;
  				scan_stat = 0;
  			} break;
  
  			case 4: {
  				if (icnt <= 0) {
  					goto out;
  				}
  				if (lb_cnt < inst->lbchars_len &&
  					*ps == (unsigned char)inst->lbchars[lb_cnt]) {
  					lb_cnt++;
  					scan_stat = 5;
  				}
  				if (*ps != '\t' && *ps != ' ') {
  					err = PHP_CONV_ERR_INVALID_SEQ;
  					goto out;
  				}
  				ps++, icnt--;
  			} break;
  
  			case 5: {
  				if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') {
  					/* auto-detect soft line breaks, found network line break */
  					lb_cnt = lb_ptr = 0;
  					scan_stat = 0;
  					ps++, icnt--; /* consume \n */
  				} else if (!inst->lbchars && lb_cnt > 0) {
  					/* auto-detect soft line breaks, found mac line break */
  					lb_cnt = lb_ptr = 0;
  					scan_stat = 0;
  				} else if (lb_cnt >= inst->lbchars_len) {
  					/* soft line break */
  					lb_cnt = lb_ptr = 0;
  					scan_stat = 0;
  				} else if (icnt > 0) {
  					if (*ps == (unsigned char)inst->lbchars[lb_cnt]) {
  						lb_cnt++;
  						ps++, icnt--;
  					} else {
  						scan_stat = 6; /* no break for short-cut */
  					}
  				} else {
  					goto out;
  				}
  			} break;
  
  			case 6: {
  				if (lb_ptr < lb_cnt) {
  					if (ocnt < 1) {
  						err = PHP_CONV_ERR_TOO_BIG;
  						goto out;
  					}
  					*(pd++) = inst->lbchars[lb_ptr++];
  					ocnt--;
  				} else {
  					scan_stat = 0;
  					lb_cnt = lb_ptr = 0;
  				}
  			} break;
  		}
  	}
  out:
  	*in_pp = (const char *)ps;
  	*in_left_p = icnt;
  	*out_pp = (char *)pd;
  	*out_left_p = ocnt;
  	inst->scan_stat = scan_stat;
  	inst->lb_ptr = lb_ptr;
  	inst->lb_cnt = lb_cnt;
  	inst->next_char = next_char;
  
  	return err;
  }
  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)
  {
  	inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_decode_convert;
  	inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_decode_dtor;
  	inst->scan_stat = 0;
  	inst->next_char = 0;
  	inst->lb_ptr = inst->lb_cnt = 0;
  	if (lbchars != NULL) {
  		inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
  		inst->lbchars_len = lbchars_len;
  	} else {
  		inst->lbchars = NULL;
  		inst->lbchars_len = 0;
  	}
  	inst->lbchars_dup = lbchars_dup;
  	inst->persistent = persistent;
  	return PHP_CONV_ERR_SUCCESS;
  }
  /* }}} */
  
  typedef struct _php_convert_filter {
  	php_conv *cd;
  	int persistent;
  	char *filtername;
  	char stub[128];
  	size_t stub_len;
  } php_convert_filter;
  
  #define PHP_CONV_BASE64_ENCODE 1
  #define PHP_CONV_BASE64_DECODE 2
  #define PHP_CONV_QPRINT_ENCODE 3
  #define PHP_CONV_QPRINT_DECODE 4
  
  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)
  {
  	zval *tmpval;
  
  	*pretval = NULL;
  	*pretval_len = 0;
  
  	if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
  		zend_string *str = zval_get_string(tmpval);
  
  		if (NULL == (*pretval = pemalloc(ZSTR_LEN(str) + 1, persistent))) {
  			return PHP_CONV_ERR_ALLOC;
  		}
  
  		*pretval_len = ZSTR_LEN(str);
  		memcpy(*pretval, ZSTR_VAL(str), ZSTR_LEN(str) + 1);
  		zend_string_release(str);
  	} else {
  		return PHP_CONV_ERR_NOT_FOUND;
  	}
  	return PHP_CONV_ERR_SUCCESS;
  }
  
  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)
  {
  	zval *tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1);
  	if (tmpval != NULL) {
  		zend_long lval = zval_get_long(tmpval);
  
  		if (lval < 0) {
  			*pretval = 0;
  		} else {
  			*pretval = lval;
  		}
  		return PHP_CONV_ERR_SUCCESS;
  	} else {
  		*pretval = 0;
  		return PHP_CONV_ERR_NOT_FOUND;
  	}
  }
  
  static php_conv_err_t php_conv_get_bool_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
  {
  	zval *tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1);
  	if (tmpval != NULL) {
  		*pretval = zend_is_true(tmpval);
  		return PHP_CONV_ERR_SUCCESS;
  	} else {
  		*pretval = 0;
  		return PHP_CONV_ERR_NOT_FOUND;
  	}
  }
  
  /* XXX this might need an additional fix so it uses size_t, whereby unsigned is quite big so leaving as is for now */
  static int php_conv_get_uint_prop_ex(const HashTable *ht, unsigned int *pretval, char *field_name, size_t field_name_len)
  {
  	zend_ulong l;
  	php_conv_err_t err;
  
  	*pretval = 0;
  
  	if ((err = php_conv_get_ulong_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
  		*pretval = (unsigned int)l;
  	}
  	return err;
  }
  
  #define GET_STR_PROP(ht, var, var_len, fldname, persistent) \
  	php_conv_get_string_prop_ex(ht, &var, &var_len, fldname, sizeof(fldname), persistent)
  
  #define GET_INT_PROP(ht, var, fldname) \
  	php_conv_get_int_prop_ex(ht, &var, fldname, sizeof(fldname))
  
  #define GET_UINT_PROP(ht, var, fldname) \
  	php_conv_get_uint_prop_ex(ht, &var, fldname, sizeof(fldname))
  
  #define GET_BOOL_PROP(ht, var, fldname) \
  	php_conv_get_bool_prop_ex(ht, &var, fldname, sizeof(fldname))
  
  static php_conv *php_conv_open(int conv_mode, const HashTable *options, int persistent)
  {
  	/* FIXME: I'll have to replace this ugly code by something neat
  	   (factories?) in the near future. */
  	php_conv *retval = NULL;
  
  	switch (conv_mode) {
  		case PHP_CONV_BASE64_ENCODE: {
  			unsigned int line_len = 0;
  			char *lbchars = NULL;
  			size_t lbchars_len;
  
  			if (options != NULL) {
  				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
  				GET_UINT_PROP(options, line_len, "line-length");
  				if (line_len < 4) {
  					if (lbchars != NULL) {
  						pefree(lbchars, 0);
  					}
  					lbchars = NULL;
  				} else {
  					if (lbchars == NULL) {
  						lbchars = pestrdup("\r\n", 0);
  						lbchars_len = 2;
  					}
  				}
  			}
  			retval = pemalloc(sizeof(php_conv_base64_encode), persistent);
  			if (lbchars != NULL) {
  				if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, line_len, lbchars, lbchars_len, 1, persistent)) {
  					if (lbchars != NULL) {
  						pefree(lbchars, 0);
  					}
  					goto out_failure;
  				}
  				pefree(lbchars, 0);
  			} else {
  				if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, 0, NULL, 0, 0, persistent)) {
  					goto out_failure;
  				}
  			}
  		} break;
  
  		case PHP_CONV_BASE64_DECODE:
  			retval = pemalloc(sizeof(php_conv_base64_decode), persistent);
  			if (php_conv_base64_decode_ctor((php_conv_base64_decode *)retval)) {
  				goto out_failure;
  			}
  			break;
  
  		case PHP_CONV_QPRINT_ENCODE: {
  			unsigned int line_len = 0;
  			char *lbchars = NULL;
  			size_t lbchars_len;
  			int opts = 0;
  
  			if (options != NULL) {
  				int opt_binary = 0;
  				int opt_force_encode_first = 0;
  
  				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
  				GET_UINT_PROP(options, line_len, "line-length");
  				GET_BOOL_PROP(options, opt_binary, "binary");
  				GET_BOOL_PROP(options, opt_force_encode_first, "force-encode-first");
  
  				if (line_len < 4) {
  					if (lbchars != NULL) {
  						pefree(lbchars, 0);
  					}
  					lbchars = NULL;
  				} else {
  					if (lbchars == NULL) {
  						lbchars = pestrdup("\r\n", 0);
  						lbchars_len = 2;
  					}
  				}
  				opts |= (opt_binary ? PHP_CONV_QPRINT_OPT_BINARY : 0);
  				opts |= (opt_force_encode_first ? PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST : 0);
  			}
  			retval = pemalloc(sizeof(php_conv_qprint_encode), persistent);
  			if (lbchars != NULL) {
  				if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, line_len, lbchars, lbchars_len, 1, opts, persistent)) {
  					pefree(lbchars, 0);
  					goto out_failure;
  				}
  				pefree(lbchars, 0);
  			} else {
  				if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, 0, NULL, 0, 0, opts, persistent)) {
  					goto out_failure;
  				}
  			}
  		} break;
  
  		case PHP_CONV_QPRINT_DECODE: {
  			char *lbchars = NULL;
  			size_t lbchars_len;
  
  			if (options != NULL) {
  				/* If line-break-chars are not specified, filter will attempt to detect line endings (\r, \n, or \r\n) */
  				GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
  			}
  
  			retval = pemalloc(sizeof(php_conv_qprint_decode), persistent);
  			if (lbchars != NULL) {
  				if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, lbchars, lbchars_len, 1, persistent)) {
  					pefree(lbchars, 0);
  					goto out_failure;
  				}
  				pefree(lbchars, 0);
  			} else {
  				if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, NULL, 0, 0, persistent)) {
  					goto out_failure;
  				}
  			}
  		} break;
  
  		default:
  			retval = NULL;
  			break;
  	}
  	return retval;
  
  out_failure:
  	if (retval != NULL) {
  		pefree(retval, persistent);
  	}
  	return NULL;
  }
  
  #undef GET_STR_PROP
  #undef GET_INT_PROP
  #undef GET_UINT_PROP
  #undef GET_BOOL_PROP
  
  static int php_convert_filter_ctor(php_convert_filter *inst,
  	int conv_mode, HashTable *conv_opts,
  	const char *filtername, int persistent)
  {
  	inst->persistent = persistent;
  	inst->filtername = pestrdup(filtername, persistent);
  	inst->stub_len = 0;
  
  	if ((inst->cd = php_conv_open(conv_mode, conv_opts, persistent)) == NULL) {
  		goto out_failure;
  	}
  
  	return SUCCESS;
  
  out_failure:
  	if (inst->cd != NULL) {
  		php_conv_dtor(inst->cd);
  		pefree(inst->cd, persistent);
  	}
  	if (inst->filtername != NULL) {
  		pefree(inst->filtername, persistent);
  	}
  	return FAILURE;
  }
  
  static void php_convert_filter_dtor(php_convert_filter *inst)
  {
  	if (inst->cd != NULL) {
  		php_conv_dtor(inst->cd);
  		pefree(inst->cd, inst->persistent);
  	}
  
  	if (inst->filtername != NULL) {
  		pefree(inst->filtername, inst->persistent);
  	}
  }
  
  /* {{{ strfilter_convert_append_bucket */
  static int 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)
  {
  	php_conv_err_t err;
  	php_stream_bucket *new_bucket;
  	char *out_buf = NULL;
  	size_t out_buf_size;
  	char *pd;
  	const char *pt;
  	size_t ocnt, icnt, tcnt;
  	size_t initial_out_buf_size;
  
  	if (ps == NULL) {
  		initial_out_buf_size = 64;
  		icnt = 1;
  	} else {
  		initial_out_buf_size = buf_len;
  		icnt = buf_len;
  	}
  
  	out_buf_size = ocnt = initial_out_buf_size;
  	if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
  		return FAILURE;
  	}
  
  	pd = out_buf;
  
  	if (inst->stub_len > 0) {
  		pt = inst->stub;
  		tcnt = inst->stub_len;
  
  		while (tcnt > 0) {
  			err = php_conv_convert(inst->cd, &pt, &tcnt, &pd, &ocnt);
  
  			switch (err) {
  				case PHP_CONV_ERR_INVALID_SEQ:
  					php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
  					goto out_failure;
  
  				case PHP_CONV_ERR_MORE:
  					if (ps != NULL) {
  						if (icnt > 0) {
  							if (inst->stub_len >= sizeof(inst->stub)) {
  								php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
  								goto out_failure;
  							}
  							inst->stub[inst->stub_len++] = *(ps++);
  							icnt--;
  							pt = inst->stub;
  							tcnt = inst->stub_len;
  						} else {
  							tcnt = 0;
  							break;
  						}
  					}
  					break;
  
  				case PHP_CONV_ERR_UNEXPECTED_EOS:
  					php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected end of stream", inst->filtername);
  					goto out_failure;
  
  				case PHP_CONV_ERR_TOO_BIG: {
  					char *new_out_buf;
  					size_t new_out_buf_size;
  
  					new_out_buf_size = out_buf_size << 1;
  
  					if (new_out_buf_size < out_buf_size) {
  						/* whoa! no bigger buckets are sold anywhere... */
  						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
  							goto out_failure;
  						}
  
  						php_stream_bucket_append(buckets_out, new_bucket);
  
  						out_buf_size = ocnt = initial_out_buf_size;
  						if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
  							return FAILURE;
  						}
  						pd = out_buf;
  					} else {
  						if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
  							if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
  								goto out_failure;
  							}
  
  							php_stream_bucket_append(buckets_out, new_bucket);
  							return FAILURE;
  						}
  
  						pd = new_out_buf + (pd - out_buf);
  						ocnt += (new_out_buf_size - out_buf_size);
  						out_buf = new_out_buf;
  						out_buf_size = new_out_buf_size;
  					}
  				} break;
  
  				case PHP_CONV_ERR_UNKNOWN:
  					php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
  					goto out_failure;
  
  				default:
  					break;
  			}
  		}
  		memmove(inst->stub, pt, tcnt);
  		inst->stub_len = tcnt;
  	}
  
  	while (icnt > 0) {
  		err = ((ps == NULL ? php_conv_convert(inst->cd, NULL, NULL, &pd, &ocnt):
  				php_conv_convert(inst->cd, &ps, &icnt, &pd, &ocnt)));
  		switch (err) {
  			case PHP_CONV_ERR_INVALID_SEQ:
  				php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
  				goto out_failure;
  
  			case PHP_CONV_ERR_MORE:
  				if (ps != NULL) {
  					if (icnt > sizeof(inst->stub)) {
  						php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
  						goto out_failure;
  					}
  					memcpy(inst->stub, ps, icnt);
  					inst->stub_len = icnt;
  					ps += icnt;
  					icnt = 0;
  				} else {
  					php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected octet values", inst->filtername);
  					goto out_failure;
  				}
  				break;
  
  			case PHP_CONV_ERR_TOO_BIG: {
  				char *new_out_buf;
  				size_t new_out_buf_size;
  
  				new_out_buf_size = out_buf_size << 1;
  
  				if (new_out_buf_size < out_buf_size) {
  					/* whoa! no bigger buckets are sold anywhere... */
  					if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
  						goto out_failure;
  					}
  
  					php_stream_bucket_append(buckets_out, new_bucket);
  
  					out_buf_size = ocnt = initial_out_buf_size;
  					if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
  						return FAILURE;
  					}
  					pd = out_buf;
  				} else {
  					if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
  						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
  							goto out_failure;
  						}
  
  						php_stream_bucket_append(buckets_out, new_bucket);
  						return FAILURE;
  					}
  					pd = new_out_buf + (pd - out_buf);
  					ocnt += (new_out_buf_size - out_buf_size);
  					out_buf = new_out_buf;
  					out_buf_size = new_out_buf_size;
  				}
  			} break;
  
  			case PHP_CONV_ERR_UNKNOWN:
  				php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
  				goto out_failure;
  
  			default:
  				if (ps == NULL) {
  					icnt = 0;
  				}
  				break;
  		}
  	}
  
  	if (out_buf_size > ocnt) {
  		if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
  			goto out_failure;
  		}
  		php_stream_bucket_append(buckets_out, new_bucket);
  	} else {
  		pefree(out_buf, persistent);
  	}
  	*consumed += buf_len - icnt;
  
  	return SUCCESS;
  
  out_failure:
  	pefree(out_buf, persistent);
  	return FAILURE;
  }
  /* }}} */
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket = NULL;
  	size_t consumed = 0;
  	php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
  
  	while (buckets_in->head != NULL) {
  		bucket = buckets_in->head;
  
  		php_stream_bucket_unlink(bucket);
  
  		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
  				buckets_out, bucket->buf, bucket->buflen, &consumed,
  				php_stream_is_persistent(stream)) != SUCCESS) {
  			goto out_failure;
  		}
  
  		php_stream_bucket_delref(bucket);
  	}
  
  	if (flags != PSFS_FLAG_NORMAL) {
  		if (strfilter_convert_append_bucket(inst, stream, thisfilter,
  				buckets_out, NULL, 0, &consumed,
  				php_stream_is_persistent(stream)) != SUCCESS) {
  			goto out_failure;
  		}
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  
  out_failure:
  	if (bucket != NULL) {
  		php_stream_bucket_delref(bucket);
  	}
  	return PSFS_ERR_FATAL;
  }
  
  static void strfilter_convert_dtor(php_stream_filter *thisfilter)
  {
  	assert(Z_PTR(thisfilter->abstract) != NULL);
  
  	php_convert_filter_dtor((php_convert_filter *)Z_PTR(thisfilter->abstract));
  	pefree(Z_PTR(thisfilter->abstract), ((php_convert_filter *)Z_PTR(thisfilter->abstract))->persistent);
  }
  
  static php_stream_filter_ops strfilter_convert_ops = {
  	strfilter_convert_filter,
  	strfilter_convert_dtor,
  	"convert.*"
  };
  
  static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, int persistent)
  {
  	php_convert_filter *inst;
  	php_stream_filter *retval = NULL;
  
  	char *dot;
  	int conv_mode = 0;
  
  	if (filterparams != NULL && Z_TYPE_P(filterparams) != IS_ARRAY) {
  		php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid filter parameter", filtername);
  		return NULL;
  	}
  
  	if ((dot = strchr(filtername, '.')) == NULL) {
  		return NULL;
  	}
  	++dot;
  
  	inst = pemalloc(sizeof(php_convert_filter), persistent);
  
  	if (strcasecmp(dot, "base64-encode") == 0) {
  		conv_mode = PHP_CONV_BASE64_ENCODE;
  	} else if (strcasecmp(dot, "base64-decode") == 0) {
  		conv_mode = PHP_CONV_BASE64_DECODE;
  	} else if (strcasecmp(dot, "quoted-printable-encode") == 0) {
  		conv_mode = PHP_CONV_QPRINT_ENCODE;
  	} else if (strcasecmp(dot, "quoted-printable-decode") == 0) {
  		conv_mode = PHP_CONV_QPRINT_DECODE;
  	}
  
  	if (php_convert_filter_ctor(inst, conv_mode,
  		(filterparams != NULL ? Z_ARRVAL_P(filterparams) : NULL),
  		filtername, persistent) != SUCCESS) {
  		goto out;
  	}
  
  	retval = php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
  out:
  	if (retval == NULL) {
  		pefree(inst, persistent);
  	}
  
  	return retval;
  }
  
  static php_stream_filter_factory strfilter_convert_factory = {
  	strfilter_convert_create
  };
  /* }}} */
  
  /* {{{ consumed filter implementation */
  typedef struct _php_consumed_filter_data {
  	size_t consumed;
  	zend_off_t offset;
  	int persistent;
  } php_consumed_filter_data;
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  
  	if (data->offset == ~0) {
  		data->offset = php_stream_tell(stream);
  	}
  	while ((bucket = buckets_in->head) != NULL) {
  		php_stream_bucket_unlink(bucket);
  		consumed += bucket->buflen;
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  	if (flags & PSFS_FLAG_FLUSH_CLOSE) {
  		php_stream_seek(stream, data->offset + data->consumed, SEEK_SET);
  	}
  	data->consumed += consumed;
  
  	return PSFS_PASS_ON;
  }
  
  static void consumed_filter_dtor(php_stream_filter *thisfilter)
  {
  	if (thisfilter && Z_PTR(thisfilter->abstract)) {
  		php_consumed_filter_data *data = (php_consumed_filter_data*)Z_PTR(thisfilter->abstract);
  		pefree(data, data->persistent);
  	}
  }
  
  static php_stream_filter_ops consumed_filter_ops = {
  	consumed_filter_filter,
  	consumed_filter_dtor,
  	"consumed"
  };
  
  static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, int persistent)
  {
  	php_stream_filter_ops *fops = NULL;
  	php_consumed_filter_data *data;
  
  	if (strcasecmp(filtername, "consumed")) {
  		return NULL;
  	}
  
  	/* Create this filter */
  	data = pecalloc(1, sizeof(php_consumed_filter_data), persistent);
  	if (!data) {
  		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_consumed_filter_data));
  		return NULL;
  	}
  	data->persistent = persistent;
  	data->consumed = 0;
  	data->offset = ~0;
  	fops = &consumed_filter_ops;
  
  	return php_stream_filter_alloc(fops, data, persistent);
  }
  
  php_stream_filter_factory consumed_filter_factory = {
  	consumed_filter_create
  };
  
  /* }}} */
  
  /* {{{ chunked filter implementation */
  typedef enum _php_chunked_filter_state {
  	CHUNK_SIZE_START,
  	CHUNK_SIZE,
  	CHUNK_SIZE_EXT,
  	CHUNK_SIZE_CR,
  	CHUNK_SIZE_LF,
  	CHUNK_BODY,
  	CHUNK_BODY_CR,
  	CHUNK_BODY_LF,
  	CHUNK_TRAILER,
  	CHUNK_ERROR
  } php_chunked_filter_state;
  
  typedef struct _php_chunked_filter_data {
  	size_t chunk_size;
  	php_chunked_filter_state state;
  	int persistent;
  } php_chunked_filter_data;
  
  static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
  {
  	char *p = buf;
  	char *end = p + len;
  	char *out = buf;
  	size_t out_len = 0;
  
  	while (p < end) {
  		switch (data->state) {
  			case CHUNK_SIZE_START:
  				data->chunk_size = 0;
  			case CHUNK_SIZE:
  				while (p < end) {
  					if (*p >= '0' && *p <= '9') {
  						data->chunk_size = (data->chunk_size * 16) + (*p - '0');
  					} else if (*p >= 'A' && *p <= 'F') {
  						data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
  					} else if (*p >= 'a' && *p <= 'f') {
  						data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
  					} else if (data->state == CHUNK_SIZE_START) {
  						data->state = CHUNK_ERROR;
  						break;
  					} else {
  						data->state = CHUNK_SIZE_EXT;
  						break;
  					}
  					data->state = CHUNK_SIZE;
  					p++;
  				}
  				if (data->state == CHUNK_ERROR) {
  					continue;
  				} else if (p == end) {
  					return out_len;
  				}
  			case CHUNK_SIZE_EXT:
  				/* skip extension */
  				while (p < end && *p != '\r' && *p != '\n') {
  					p++;
  				}
  				if (p == end) {
  					return out_len;
  				}
  			case CHUNK_SIZE_CR:
  				if (*p == '\r') {
  					p++;
  					if (p == end) {
  						data->state = CHUNK_SIZE_LF;
  						return out_len;
  					}
  				}
  			case CHUNK_SIZE_LF:
  				if (*p == '\n') {
  					p++;
  					if (data->chunk_size == 0) {
  						/* last chunk */
  						data->state = CHUNK_TRAILER;
  						continue;
  					} else if (p == end) {
  						data->state = CHUNK_BODY;
  						return out_len;
  					}
  				} else {
  					data->state = CHUNK_ERROR;
  					continue;
  				}
  			case CHUNK_BODY:
  				if ((size_t) (end - p) >= data->chunk_size) {
  					if (p != out) {
  						memmove(out, p, data->chunk_size);
  					}
  					out += data->chunk_size;
  					out_len += data->chunk_size;
  					p += data->chunk_size;
  					if (p == end) {
  						data->state = CHUNK_BODY_CR;
  						return out_len;
  					}
  				} else {
  					if (p != out) {
  						memmove(out, p, end - p);
  					}
  					data->chunk_size -= end - p;
  					data->state=CHUNK_BODY;
  					out_len += end - p;
  					return out_len;
  				}
  			case CHUNK_BODY_CR:
  				if (*p == '\r') {
  					p++;
  					if (p == end) {
  						data->state = CHUNK_BODY_LF;
  						return out_len;
  					}
  				}
  			case CHUNK_BODY_LF:
  				if (*p == '\n') {
  					p++;
  					data->state = CHUNK_SIZE_START;
  					continue;
  				} else {
  					data->state = CHUNK_ERROR;
  					continue;
  				}
  			case CHUNK_TRAILER:
  				/* ignore trailer */
  				p = end;
  				continue;
  			case CHUNK_ERROR:
  				if (p != out) {
  					memmove(out, p, end - p);
  				}
  				out_len += end - p;
  				return out_len;
  		}
  	}
  	return out_len;
  }
  
  static php_stream_filter_status_t 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
  	)
  {
  	php_stream_bucket *bucket;
  	size_t consumed = 0;
  	php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
  
  	while (buckets_in->head) {
  		bucket = php_stream_bucket_make_writeable(buckets_in->head);
  		consumed += bucket->buflen;
  		bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);
  		php_stream_bucket_append(buckets_out, bucket);
  	}
  
  	if (bytes_consumed) {
  		*bytes_consumed = consumed;
  	}
  
  	return PSFS_PASS_ON;
  }
  
  static void php_chunked_dtor(php_stream_filter *thisfilter)
  {
  	if (thisfilter && Z_PTR(thisfilter->abstract)) {
  		php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
  		pefree(data, data->persistent);
  	}
  }
  
  static php_stream_filter_ops chunked_filter_ops = {
  	php_chunked_filter,
  	php_chunked_dtor,
  	"dechunk"
  };
  
  static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, int persistent)
  {
  	php_stream_filter_ops *fops = NULL;
  	php_chunked_filter_data *data;
  
  	if (strcasecmp(filtername, "dechunk")) {
  		return NULL;
  	}
  
  	/* Create this filter */
  	data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent);
  	if (!data) {
  		php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_chunked_filter_data));
  		return NULL;
  	}
  	data->state = CHUNK_SIZE_START;
  	data->chunk_size = 0;
  	data->persistent = persistent;
  	fops = &chunked_filter_ops;
  
  	return php_stream_filter_alloc(fops, data, persistent);
  }
  
  static php_stream_filter_factory chunked_filter_factory = {
  	chunked_filter_create
  };
  /* }}} */
  
  static const struct {
  	php_stream_filter_ops *ops;
  	php_stream_filter_factory *factory;
  } standard_filters[] = {
  	{ &strfilter_rot13_ops, &strfilter_rot13_factory },
  	{ &strfilter_toupper_ops, &strfilter_toupper_factory },
  	{ &strfilter_tolower_ops, &strfilter_tolower_factory },
  	{ &strfilter_strip_tags_ops, &strfilter_strip_tags_factory },
  	{ &strfilter_convert_ops, &strfilter_convert_factory },
  	{ &consumed_filter_ops, &consumed_filter_factory },
  	{ &chunked_filter_ops, &chunked_filter_factory },
  	/* additional filters to go here */
  	{ NULL, NULL }
  };
  
  /* {{{ filter MINIT and MSHUTDOWN */
  PHP_MINIT_FUNCTION(standard_filters)
  {
  	int i;
  
  	for (i = 0; standard_filters[i].ops; i++) {
  		if (FAILURE == php_stream_filter_register_factory(
  					standard_filters[i].ops->label,
  					standard_filters[i].factory
  					)) {
  			return FAILURE;
  		}
  	}
  	return SUCCESS;
  }
  
  PHP_MSHUTDOWN_FUNCTION(standard_filters)
  {
  	int i;
  
  	for (i = 0; standard_filters[i].ops; i++) {
  		php_stream_filter_unregister_factory(standard_filters[i].ops->label);
  	}
  	return SUCCESS;
  }
  /* }}} */
  
  /*
   * Local variables:
   * tab-width: 4
   * c-basic-offset: 4
   * End:
   * vim600: sw=4 ts=4 fdm=marker
   * vim<600: sw=4 ts=4
   */
  