php-src/ext/standard/filters.c

1947 lines
50 KiB
C

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