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