/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2015 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; 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 j, lb_cnt2; 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) { 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; 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 */