root/standard/filters.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. strfilter_rot13_filter
  2. strfilter_rot13_create
  3. strfilter_toupper_filter
  4. strfilter_tolower_filter
  5. strfilter_toupper_create
  6. strfilter_tolower_create
  7. php_strip_tags_filter_ctor
  8. php_strip_tags_filter_dtor
  9. strfilter_strip_tags_filter
  10. strfilter_strip_tags_dtor
  11. strfilter_strip_tags_create
  12. php_conv_base64_encode_ctor
  13. php_conv_base64_encode_dtor
  14. php_conv_base64_encode_flush
  15. php_conv_base64_encode_convert
  16. php_conv_base64_decode_ctor
  17. php_conv_base64_decode_dtor
  18. php_conv_base64_decode_convert
  19. php_conv_qprint_encode_dtor
  20. php_conv_qprint_encode_convert
  21. php_conv_qprint_encode_ctor
  22. php_conv_qprint_decode_dtor
  23. php_conv_qprint_decode_convert
  24. php_conv_qprint_decode_ctor
  25. php_conv_get_string_prop_ex
  26. php_conv_get_long_prop_ex
  27. php_conv_get_ulong_prop_ex
  28. php_conv_get_bool_prop_ex
  29. php_conv_get_int_prop_ex
  30. php_conv_get_uint_prop_ex
  31. php_conv_open
  32. php_convert_filter_ctor
  33. php_convert_filter_dtor
  34. strfilter_convert_append_bucket
  35. strfilter_convert_filter
  36. strfilter_convert_dtor
  37. strfilter_convert_create
  38. consumed_filter_filter
  39. consumed_filter_dtor
  40. consumed_filter_create
  41. php_dechunk
  42. php_chunked_filter
  43. php_chunked_dtor
  44. chunked_filter_create
  45. PHP_MINIT_FUNCTION
  46. PHP_MSHUTDOWN_FUNCTION

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2013 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 "ext/standard/php_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
        TSRMLS_DC)
{
        php_stream_bucket *bucket;
        size_t consumed = 0;

        while (buckets_in->head) {
                bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
                
                php_strtr(bucket->buf, bucket->buflen, rot13_from, rot13_to, 52);
                consumed += bucket->buflen;
                
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }

        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 TSRMLS_DC)
{
        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
        TSRMLS_DC)
{
        php_stream_bucket *bucket;
        size_t consumed = 0;

        while (buckets_in->head) {
                bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
                
                php_strtr(bucket->buf, bucket->buflen, lowercase, uppercase, 26);
                consumed += bucket->buflen;
                
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }

        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
        TSRMLS_DC)
{
        php_stream_bucket *bucket;
        size_t consumed = 0;

        while (buckets_in->head) {
                bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
                
                php_strtr(bucket->buf, bucket->buflen, uppercase, lowercase, 26);
                consumed += bucket->buflen;
                
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }

        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 TSRMLS_DC)
{
        return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent);
}

static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
{
        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, const char *allowed_tags, int allowed_tags_len, int persistent)
{
        if (allowed_tags != NULL) {
                if (NULL == (inst->allowed_tags = pemalloc(allowed_tags_len, persistent))) {
                        return FAILURE;
                }
                memcpy((char *)inst->allowed_tags, allowed_tags, allowed_tags_len);
                inst->allowed_tags_len = allowed_tags_len; 
        } 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
        TSRMLS_DC)
{
        php_stream_bucket *bucket;
        size_t consumed = 0;
        php_strip_tags_filter *inst = (php_strip_tags_filter *) thisfilter->abstract;

        while (buckets_in->head) {
                bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
                consumed = bucket->buflen;
                
                bucket->buflen = php_strip_tags(bucket->buf, bucket->buflen, &(inst->state), (char *)inst->allowed_tags, inst->allowed_tags_len);
        
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }

        if (bytes_consumed) {
                *bytes_consumed = consumed;
        }
        
        return PSFS_PASS_ON;
}

static void strfilter_strip_tags_dtor(php_stream_filter *thisfilter TSRMLS_DC)
{
        assert(thisfilter->abstract != NULL);

        php_strip_tags_filter_dtor((php_strip_tags_filter *)thisfilter->abstract);

        pefree(thisfilter->abstract, ((php_strip_tags_filter *)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 TSRMLS_DC)
{
        php_strip_tags_filter *inst;
        smart_str tags_ss = { 0, 0, 0 };
        
        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) {
                        HashPosition pos;
                        zval **tmp;

                        zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(filterparams), &pos);
                        while (zend_hash_get_current_data_ex(Z_ARRVAL_P(filterparams), (void **) &tmp, &pos) == SUCCESS) {
                                convert_to_string_ex(tmp);
                                smart_str_appendc(&tags_ss, '<');
                                smart_str_appendl(&tags_ss, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
                                smart_str_appendc(&tags_ss, '>');
                                zend_hash_move_forward_ex(Z_ARRVAL_P(filterparams), &pos);
                        }
                        smart_str_0(&tags_ss);
                } else {
                        /* FIXME: convert_to_* may clutter zvals and lead it into segfault ? */
                        convert_to_string_ex(&filterparams);

                        tags_ss.c = Z_STRVAL_P(filterparams);
                        tags_ss.len = Z_STRLEN_P(filterparams);
                        tags_ss.a = 0;
                }
        }

        if (php_strip_tags_filter_ctor(inst, tags_ss.c, tags_ss.len, persistent) != SUCCESS) {
                if (tags_ss.a != 0) {
                        STR_FREE(tags_ss.c);
                }
                pefree(inst, persistent);
                return NULL;
        }

        if (tags_ss.a != 0) {
                STR_FREE(tags_ss.c);
        }

        return php_stream_filter_alloc(&strfilter_strip_tags_ops, inst, persistent);
}

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;

        unsigned char erem[3];
        size_t erem_len;
        unsigned int line_ccnt;
        unsigned int line_len;
        const char *lbchars;
        int lbchars_dup;
        size_t lbchars_len;
        int persistent;
} 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;

        int opts;
        unsigned int line_ccnt;
        unsigned int line_len;
        const char *lbchars;
        int lbchars_dup;
        size_t lbchars_len;
        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;
        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;

        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) && (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;
                                }
                                *(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) {
                                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;
                        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;

        int scan_stat;
        unsigned int next_char;
        const char *lbchars;
        int lbchars_dup;
        size_t lbchars_len;
        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 (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
                if (Z_TYPE_PP(tmpval) != IS_STRING) {
                        zval zt = **tmpval;

                        convert_to_string(&zt);

                        if (NULL == (*pretval = pemalloc(Z_STRLEN(zt) + 1, persistent))) {
                                return PHP_CONV_ERR_ALLOC;
                        }

                        *pretval_len = Z_STRLEN(zt);
                        memcpy(*pretval, Z_STRVAL(zt), Z_STRLEN(zt) + 1);
                        zval_dtor(&zt);
                } else {
                        if (NULL == (*pretval = pemalloc(Z_STRLEN_PP(tmpval) + 1, persistent))) {
                                return PHP_CONV_ERR_ALLOC;
                        }
                        *pretval_len = Z_STRLEN_PP(tmpval);
                        memcpy(*pretval, Z_STRVAL_PP(tmpval), Z_STRLEN_PP(tmpval) + 1);
                }
        } else {
                return PHP_CONV_ERR_NOT_FOUND;
        }
        return PHP_CONV_ERR_SUCCESS;
}

#if IT_WAS_USED
static php_conv_err_t php_conv_get_long_prop_ex(const HashTable *ht, long *pretval, char *field_name, size_t field_name_len)
{
        zval **tmpval;

        *pretval = 0;

        if (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
                zval tmp, *ztval = *tmpval;

                if (Z_TYPE_PP(tmpval) != IS_LONG) {
                        tmp = *ztval;
                        zval_copy_ctor(&tmp);
                        convert_to_long(&tmp);
                        ztval = &tmp;
                }
                *pretval = Z_LVAL_P(ztval);
        } else {
                return PHP_CONV_ERR_NOT_FOUND;
        } 
        return PHP_CONV_ERR_SUCCESS;
}
#endif

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)
{
        zval **tmpval;

        *pretval = 0;

        if (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
                zval tmp, *ztval = *tmpval;

                if (Z_TYPE_PP(tmpval) != IS_LONG) {
                        tmp = *ztval;
                        zval_copy_ctor(&tmp);
                        convert_to_long(&tmp);
                        ztval = &tmp;
                }
                if (Z_LVAL_P(ztval) < 0) {
                        *pretval = 0;
                } else {
                        *pretval = Z_LVAL_P(ztval);
                }
        } else {
                return PHP_CONV_ERR_NOT_FOUND;
        } 
        return PHP_CONV_ERR_SUCCESS;
}

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;

        *pretval = 0;

        if (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
                zval tmp, *ztval = *tmpval;

                if (Z_TYPE_PP(tmpval) != IS_BOOL) {
                        tmp = *ztval;
                        zval_copy_ctor(&tmp);
                        convert_to_boolean(&tmp);
                        ztval = &tmp;
                }
                *pretval = Z_BVAL_P(ztval);
        } else {
                return PHP_CONV_ERR_NOT_FOUND;
        } 
        return PHP_CONV_ERR_SUCCESS;
}


#if IT_WAS_USED
static int php_conv_get_int_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
{
        long l;
        php_conv_err_t err;

        *pretval = 0;

        if ((err = php_conv_get_long_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
                *pretval = l;
        }
        return err;
}
#endif

static int php_conv_get_uint_prop_ex(const HashTable *ht, unsigned int *pretval, char *field_name, size_t field_name_len)
{
        long 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 = 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 TSRMLS_DC)
{
        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 TSRMLS_CC, 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 TSRMLS_CC, 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 TSRMLS_CC, 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 TSRMLS_CC))) {
                                                        goto out_failure;
                                                }

                                                php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);

                                                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 TSRMLS_CC))) {
                                                                goto out_failure;
                                                        }

                                                        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
                                                        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 TSRMLS_CC, 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 TSRMLS_CC, 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 TSRMLS_CC, 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 TSRMLS_CC, 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 TSRMLS_CC))) {
                                                goto out_failure;
                                        }

                                        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);

                                        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 TSRMLS_CC))) {
                                                        goto out_failure;
                                                }

                                                php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
                                                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 TSRMLS_CC, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
                                goto out_failure;

                        default:
                                if (ps == NULL) {
                                        icnt = 0;
                                }
                                break;
                }
        }

        if (out_buf_size - ocnt > 0) {
                if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
                        goto out_failure;
                }
                php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
        } 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
        TSRMLS_DC)
{
        php_stream_bucket *bucket = NULL;
        size_t consumed = 0;
        php_convert_filter *inst = (php_convert_filter *)thisfilter->abstract;

        while (buckets_in->head != NULL) {
                bucket = buckets_in->head;

                php_stream_bucket_unlink(bucket TSRMLS_CC);

                if (strfilter_convert_append_bucket(inst, stream, thisfilter,
                                buckets_out, bucket->buf, bucket->buflen, &consumed,
                                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
                        goto out_failure;
                }

                php_stream_bucket_delref(bucket TSRMLS_CC);
        }

        if (flags != PSFS_FLAG_NORMAL) {
                if (strfilter_convert_append_bucket(inst, stream, thisfilter,
                                buckets_out, NULL, 0, &consumed,
                                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
                        goto out_failure;
                }
        }

        if (bytes_consumed) {
                *bytes_consumed = consumed;
        }

        return PSFS_PASS_ON;

out_failure:
        if (bucket != NULL) {
                php_stream_bucket_delref(bucket TSRMLS_CC);
        }
        return PSFS_ERR_FATAL;
}

static void strfilter_convert_dtor(php_stream_filter *thisfilter TSRMLS_DC)
{
        assert(thisfilter->abstract != NULL);

        php_convert_filter_dtor((php_convert_filter *)thisfilter->abstract);
        pefree(thisfilter->abstract, ((php_convert_filter *)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 TSRMLS_DC)
{
        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 TSRMLS_CC, 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 {
        int persistent;
        size_t consumed;
        off_t offset;
} 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
        TSRMLS_DC)
{
        php_consumed_filter_data *data = (php_consumed_filter_data *)(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 TSRMLS_CC);
                consumed += bucket->buflen;
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }
        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 TSRMLS_DC)
{
        if (thisfilter && thisfilter->abstract) {
                php_consumed_filter_data *data = (php_consumed_filter_data*)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 TSRMLS_DC)
{
        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 TSRMLS_CC, 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 {
        php_chunked_filter_state state;
        size_t chunk_size;
        int persistent;
} php_chunked_filter_data;

static int php_dechunk(char *buf, int len, php_chunked_filter_data *data)
{
        char *p = buf;
        char *end = p + len;
        char *out = buf;
        int 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
        TSRMLS_DC)
{
        php_stream_bucket *bucket;
        size_t consumed = 0;
        php_chunked_filter_data *data = (php_chunked_filter_data *) thisfilter->abstract;

        while (buckets_in->head) {
                bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
                consumed += bucket->buflen;
                bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);        
                php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
        }

        if (bytes_consumed) {
                *bytes_consumed = consumed;
        }
        
        return PSFS_PASS_ON;
}

static void php_chunked_dtor(php_stream_filter *thisfilter TSRMLS_DC)
{
        if (thisfilter && thisfilter->abstract) {
                php_chunked_filter_data *data = (php_chunked_filter_data *) 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 TSRMLS_DC)
{
        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 TSRMLS_CC, 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
                                        TSRMLS_CC)) {
                        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 TSRMLS_CC);
        }
        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
 */

/* [<][>][^][v][top][bottom][index][help] */