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