1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 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), (char *)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, tags_ss.s->val, tags_ss.s->len, 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(str->len + 1, persistent))) {
1223            return PHP_CONV_ERR_ALLOC;
1224        }
1225
1226        *pretval_len = str->len;
1227        memcpy(*pretval, str->val, str->len + 1);
1228        zend_string_release(str);
1229    } else {
1230        return PHP_CONV_ERR_NOT_FOUND;
1231    }
1232    return PHP_CONV_ERR_SUCCESS;
1233}
1234
1235#if IT_WAS_USED
1236static php_conv_err_t php_conv_get_long_prop_ex(const HashTable *ht, zend_long *pretval, char *field_name, size_t field_name_len)
1237{
1238    zval **tmpval;
1239
1240    *pretval = 0;
1241
1242    if (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
1243        zval tmp, *ztval = *tmpval;
1244
1245        if (Z_TYPE_PP(tmpval) != IS_LONG) {
1246            tmp = *ztval;
1247            zval_copy_ctor(&tmp);
1248            convert_to_long(&tmp);
1249            ztval = &tmp;
1250        }
1251        *pretval = Z_LVAL_P(ztval);
1252    } else {
1253        return PHP_CONV_ERR_NOT_FOUND;
1254    }
1255    return PHP_CONV_ERR_SUCCESS;
1256}
1257#endif
1258
1259static php_conv_err_t php_conv_get_ulong_prop_ex(const HashTable *ht, zend_ulong *pretval, char *field_name, size_t field_name_len)
1260{
1261    zval *tmpval;
1262
1263    *pretval = 0;
1264
1265    if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1266        zval tmp;
1267
1268        if (Z_TYPE_P(tmpval) != IS_LONG) {
1269            ZVAL_DUP(&tmp, tmpval);
1270            convert_to_long(&tmp);
1271            tmpval = &tmp;
1272        }
1273        if (Z_LVAL_P(tmpval) < 0) {
1274            *pretval = 0;
1275        } else {
1276            *pretval = Z_LVAL_P(tmpval);
1277        }
1278    } else {
1279        return PHP_CONV_ERR_NOT_FOUND;
1280    }
1281    return PHP_CONV_ERR_SUCCESS;
1282}
1283
1284static php_conv_err_t php_conv_get_bool_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
1285{
1286    zval *tmpval;
1287
1288    *pretval = 0;
1289
1290    if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1291        zval tmp;
1292
1293        if (Z_TYPE_P(tmpval) != IS_FALSE || Z_TYPE_P(tmpval) != IS_TRUE) {
1294            ZVAL_DUP(&tmp, tmpval);
1295            zval_copy_ctor(&tmp);
1296            convert_to_boolean(&tmp);
1297            tmpval = &tmp;
1298        }
1299        *pretval = (Z_TYPE_P(tmpval) == IS_TRUE);
1300    } else {
1301        return PHP_CONV_ERR_NOT_FOUND;
1302    }
1303    return PHP_CONV_ERR_SUCCESS;
1304}
1305
1306
1307#if IT_WAS_USED
1308static int php_conv_get_int_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
1309{
1310    zend_long l;
1311    php_conv_err_t err;
1312
1313    *pretval = 0;
1314
1315    if ((err = php_conv_get_long_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
1316        *pretval = l;
1317    }
1318    return err;
1319}
1320#endif
1321
1322/* XXX this might need an additional fix so it uses size_t, whereby unsigned is quite big so leaving as is for now */
1323static int php_conv_get_uint_prop_ex(const HashTable *ht, unsigned int *pretval, char *field_name, size_t field_name_len)
1324{
1325    zend_ulong l;
1326    php_conv_err_t err;
1327
1328    *pretval = 0;
1329
1330    if ((err = php_conv_get_ulong_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
1331        *pretval = (unsigned int)l;
1332    }
1333    return err;
1334}
1335
1336#define GET_STR_PROP(ht, var, var_len, fldname, persistent) \
1337    php_conv_get_string_prop_ex(ht, &var, &var_len, fldname, sizeof(fldname), persistent)
1338
1339#define GET_INT_PROP(ht, var, fldname) \
1340    php_conv_get_int_prop_ex(ht, &var, fldname, sizeof(fldname))
1341
1342#define GET_UINT_PROP(ht, var, fldname) \
1343    php_conv_get_uint_prop_ex(ht, &var, fldname, sizeof(fldname))
1344
1345#define GET_BOOL_PROP(ht, var, fldname) \
1346    php_conv_get_bool_prop_ex(ht, &var, fldname, sizeof(fldname))
1347
1348static php_conv *php_conv_open(int conv_mode, const HashTable *options, int persistent)
1349{
1350    /* FIXME: I'll have to replace this ugly code by something neat
1351       (factories?) in the near future. */
1352    php_conv *retval = NULL;
1353
1354    switch (conv_mode) {
1355        case PHP_CONV_BASE64_ENCODE: {
1356            unsigned int line_len = 0;
1357            char *lbchars = NULL;
1358            size_t lbchars_len;
1359
1360            if (options != NULL) {
1361                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1362                GET_UINT_PROP(options, line_len, "line-length");
1363                if (line_len < 4) {
1364                    if (lbchars != NULL) {
1365                        pefree(lbchars, 0);
1366                    }
1367                    lbchars = NULL;
1368                } else {
1369                    if (lbchars == NULL) {
1370                        lbchars = pestrdup("\r\n", 0);
1371                        lbchars_len = 2;
1372                    }
1373                }
1374            }
1375            retval = pemalloc(sizeof(php_conv_base64_encode), persistent);
1376            if (lbchars != NULL) {
1377                if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, line_len, lbchars, lbchars_len, 1, persistent)) {
1378                    if (lbchars != NULL) {
1379                        pefree(lbchars, 0);
1380                    }
1381                    goto out_failure;
1382                }
1383                pefree(lbchars, 0);
1384            } else {
1385                if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, 0, NULL, 0, 0, persistent)) {
1386                    goto out_failure;
1387                }
1388            }
1389        } break;
1390
1391        case PHP_CONV_BASE64_DECODE:
1392            retval = pemalloc(sizeof(php_conv_base64_decode), persistent);
1393            if (php_conv_base64_decode_ctor((php_conv_base64_decode *)retval)) {
1394                goto out_failure;
1395            }
1396            break;
1397
1398        case PHP_CONV_QPRINT_ENCODE: {
1399            unsigned int line_len = 0;
1400            char *lbchars = NULL;
1401            size_t lbchars_len;
1402            int opts = 0;
1403
1404            if (options != NULL) {
1405                int opt_binary = 0;
1406                int opt_force_encode_first = 0;
1407
1408                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1409                GET_UINT_PROP(options, line_len, "line-length");
1410                GET_BOOL_PROP(options, opt_binary, "binary");
1411                GET_BOOL_PROP(options, opt_force_encode_first, "force-encode-first");
1412
1413                if (line_len < 4) {
1414                    if (lbchars != NULL) {
1415                        pefree(lbchars, 0);
1416                    }
1417                    lbchars = NULL;
1418                } else {
1419                    if (lbchars == NULL) {
1420                        lbchars = pestrdup("\r\n", 0);
1421                        lbchars_len = 2;
1422                    }
1423                }
1424                opts |= (opt_binary ? PHP_CONV_QPRINT_OPT_BINARY : 0);
1425                opts |= (opt_force_encode_first ? PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST : 0);
1426            }
1427            retval = pemalloc(sizeof(php_conv_qprint_encode), persistent);
1428            if (lbchars != NULL) {
1429                if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, line_len, lbchars, lbchars_len, 1, opts, persistent)) {
1430                    pefree(lbchars, 0);
1431                    goto out_failure;
1432                }
1433                pefree(lbchars, 0);
1434            } else {
1435                if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, 0, NULL, 0, 0, opts, persistent)) {
1436                    goto out_failure;
1437                }
1438            }
1439        } break;
1440
1441        case PHP_CONV_QPRINT_DECODE: {
1442            char *lbchars = NULL;
1443            size_t lbchars_len;
1444
1445            if (options != NULL) {
1446                /* If line-break-chars are not specified, filter will attempt to detect line endings (\r, \n, or \r\n) */
1447                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1448            }
1449
1450            retval = pemalloc(sizeof(php_conv_qprint_decode), persistent);
1451            if (lbchars != NULL) {
1452                if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, lbchars, lbchars_len, 1, persistent)) {
1453                    pefree(lbchars, 0);
1454                    goto out_failure;
1455                }
1456                pefree(lbchars, 0);
1457            } else {
1458                if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, NULL, 0, 0, persistent)) {
1459                    goto out_failure;
1460                }
1461            }
1462        } break;
1463
1464        default:
1465            retval = NULL;
1466            break;
1467    }
1468    return retval;
1469
1470out_failure:
1471    if (retval != NULL) {
1472        pefree(retval, persistent);
1473    }
1474    return NULL;
1475}
1476
1477#undef GET_STR_PROP
1478#undef GET_INT_PROP
1479#undef GET_UINT_PROP
1480#undef GET_BOOL_PROP
1481
1482static int php_convert_filter_ctor(php_convert_filter *inst,
1483    int conv_mode, HashTable *conv_opts,
1484    const char *filtername, int persistent)
1485{
1486    inst->persistent = persistent;
1487    inst->filtername = pestrdup(filtername, persistent);
1488    inst->stub_len = 0;
1489
1490    if ((inst->cd = php_conv_open(conv_mode, conv_opts, persistent)) == NULL) {
1491        goto out_failure;
1492    }
1493
1494    return SUCCESS;
1495
1496out_failure:
1497    if (inst->cd != NULL) {
1498        php_conv_dtor(inst->cd);
1499        pefree(inst->cd, persistent);
1500    }
1501    if (inst->filtername != NULL) {
1502        pefree(inst->filtername, persistent);
1503    }
1504    return FAILURE;
1505}
1506
1507static void php_convert_filter_dtor(php_convert_filter *inst)
1508{
1509    if (inst->cd != NULL) {
1510        php_conv_dtor(inst->cd);
1511        pefree(inst->cd, inst->persistent);
1512    }
1513
1514    if (inst->filtername != NULL) {
1515        pefree(inst->filtername, inst->persistent);
1516    }
1517}
1518
1519/* {{{ strfilter_convert_append_bucket */
1520static int strfilter_convert_append_bucket(
1521        php_convert_filter *inst,
1522        php_stream *stream, php_stream_filter *filter,
1523        php_stream_bucket_brigade *buckets_out,
1524        const char *ps, size_t buf_len, size_t *consumed,
1525        int persistent)
1526{
1527    php_conv_err_t err;
1528    php_stream_bucket *new_bucket;
1529    char *out_buf = NULL;
1530    size_t out_buf_size;
1531    char *pd;
1532    const char *pt;
1533    size_t ocnt, icnt, tcnt;
1534    size_t initial_out_buf_size;
1535
1536    if (ps == NULL) {
1537        initial_out_buf_size = 64;
1538        icnt = 1;
1539    } else {
1540        initial_out_buf_size = buf_len;
1541        icnt = buf_len;
1542    }
1543
1544    out_buf_size = ocnt = initial_out_buf_size;
1545    if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1546        return FAILURE;
1547    }
1548
1549    pd = out_buf;
1550
1551    if (inst->stub_len > 0) {
1552        pt = inst->stub;
1553        tcnt = inst->stub_len;
1554
1555        while (tcnt > 0) {
1556            err = php_conv_convert(inst->cd, &pt, &tcnt, &pd, &ocnt);
1557
1558            switch (err) {
1559                case PHP_CONV_ERR_INVALID_SEQ:
1560                    php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1561                    goto out_failure;
1562
1563                case PHP_CONV_ERR_MORE:
1564                    if (ps != NULL) {
1565                        if (icnt > 0) {
1566                            if (inst->stub_len >= sizeof(inst->stub)) {
1567                                php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1568                                goto out_failure;
1569                            }
1570                            inst->stub[inst->stub_len++] = *(ps++);
1571                            icnt--;
1572                            pt = inst->stub;
1573                            tcnt = inst->stub_len;
1574                        } else {
1575                            tcnt = 0;
1576                            break;
1577                        }
1578                    }
1579                    break;
1580
1581                case PHP_CONV_ERR_UNEXPECTED_EOS:
1582                    php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected end of stream", inst->filtername);
1583                    goto out_failure;
1584
1585                case PHP_CONV_ERR_TOO_BIG: {
1586                    char *new_out_buf;
1587                    size_t new_out_buf_size;
1588
1589                    new_out_buf_size = out_buf_size << 1;
1590
1591                    if (new_out_buf_size < out_buf_size) {
1592                        /* whoa! no bigger buckets are sold anywhere... */
1593                        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1594                            goto out_failure;
1595                        }
1596
1597                        php_stream_bucket_append(buckets_out, new_bucket);
1598
1599                        out_buf_size = ocnt = initial_out_buf_size;
1600                        if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1601                            return FAILURE;
1602                        }
1603                        pd = out_buf;
1604                    } else {
1605                        if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1606                            if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1607                                goto out_failure;
1608                            }
1609
1610                            php_stream_bucket_append(buckets_out, new_bucket);
1611                            return FAILURE;
1612                        }
1613
1614                        pd = new_out_buf + (pd - out_buf);
1615                        ocnt += (new_out_buf_size - out_buf_size);
1616                        out_buf = new_out_buf;
1617                        out_buf_size = new_out_buf_size;
1618                    }
1619                } break;
1620
1621                case PHP_CONV_ERR_UNKNOWN:
1622                    php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1623                    goto out_failure;
1624
1625                default:
1626                    break;
1627            }
1628        }
1629        memmove(inst->stub, pt, tcnt);
1630        inst->stub_len = tcnt;
1631    }
1632
1633    while (icnt > 0) {
1634        err = ((ps == NULL ? php_conv_convert(inst->cd, NULL, NULL, &pd, &ocnt):
1635                php_conv_convert(inst->cd, &ps, &icnt, &pd, &ocnt)));
1636        switch (err) {
1637            case PHP_CONV_ERR_INVALID_SEQ:
1638                php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1639                goto out_failure;
1640
1641            case PHP_CONV_ERR_MORE:
1642                if (ps != NULL) {
1643                    if (icnt > sizeof(inst->stub)) {
1644                        php_error_docref(NULL, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1645                        goto out_failure;
1646                    }
1647                    memcpy(inst->stub, ps, icnt);
1648                    inst->stub_len = icnt;
1649                    ps += icnt;
1650                    icnt = 0;
1651                } else {
1652                    php_error_docref(NULL, E_WARNING, "stream filter (%s): unexpected octet values", inst->filtername);
1653                    goto out_failure;
1654                }
1655                break;
1656
1657            case PHP_CONV_ERR_TOO_BIG: {
1658                char *new_out_buf;
1659                size_t new_out_buf_size;
1660
1661                new_out_buf_size = out_buf_size << 1;
1662
1663                if (new_out_buf_size < out_buf_size) {
1664                    /* whoa! no bigger buckets are sold anywhere... */
1665                    if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1666                        goto out_failure;
1667                    }
1668
1669                    php_stream_bucket_append(buckets_out, new_bucket);
1670
1671                    out_buf_size = ocnt = initial_out_buf_size;
1672                    if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1673                        return FAILURE;
1674                    }
1675                    pd = out_buf;
1676                } else {
1677                    if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1678                        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1679                            goto out_failure;
1680                        }
1681
1682                        php_stream_bucket_append(buckets_out, new_bucket);
1683                        return FAILURE;
1684                    }
1685                    pd = new_out_buf + (pd - out_buf);
1686                    ocnt += (new_out_buf_size - out_buf_size);
1687                    out_buf = new_out_buf;
1688                    out_buf_size = new_out_buf_size;
1689                }
1690            } break;
1691
1692            case PHP_CONV_ERR_UNKNOWN:
1693                php_error_docref(NULL, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1694                goto out_failure;
1695
1696            default:
1697                if (ps == NULL) {
1698                    icnt = 0;
1699                }
1700                break;
1701        }
1702    }
1703
1704    if (out_buf_size > ocnt) {
1705        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
1706            goto out_failure;
1707        }
1708        php_stream_bucket_append(buckets_out, new_bucket);
1709    } else {
1710        pefree(out_buf, persistent);
1711    }
1712    *consumed += buf_len - icnt;
1713
1714    return SUCCESS;
1715
1716out_failure:
1717    pefree(out_buf, persistent);
1718    return FAILURE;
1719}
1720/* }}} */
1721
1722static php_stream_filter_status_t strfilter_convert_filter(
1723    php_stream *stream,
1724    php_stream_filter *thisfilter,
1725    php_stream_bucket_brigade *buckets_in,
1726    php_stream_bucket_brigade *buckets_out,
1727    size_t *bytes_consumed,
1728    int flags
1729    )
1730{
1731    php_stream_bucket *bucket = NULL;
1732    size_t consumed = 0;
1733    php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
1734
1735    while (buckets_in->head != NULL) {
1736        bucket = buckets_in->head;
1737
1738        php_stream_bucket_unlink(bucket);
1739
1740        if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1741                buckets_out, bucket->buf, bucket->buflen, &consumed,
1742                php_stream_is_persistent(stream)) != SUCCESS) {
1743            goto out_failure;
1744        }
1745
1746        php_stream_bucket_delref(bucket);
1747    }
1748
1749    if (flags != PSFS_FLAG_NORMAL) {
1750        if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1751                buckets_out, NULL, 0, &consumed,
1752                php_stream_is_persistent(stream)) != SUCCESS) {
1753            goto out_failure;
1754        }
1755    }
1756
1757    if (bytes_consumed) {
1758        *bytes_consumed = consumed;
1759    }
1760
1761    return PSFS_PASS_ON;
1762
1763out_failure:
1764    if (bucket != NULL) {
1765        php_stream_bucket_delref(bucket);
1766    }
1767    return PSFS_ERR_FATAL;
1768}
1769
1770static void strfilter_convert_dtor(php_stream_filter *thisfilter)
1771{
1772    assert(Z_PTR(thisfilter->abstract) != NULL);
1773
1774    php_convert_filter_dtor((php_convert_filter *)Z_PTR(thisfilter->abstract));
1775    pefree(Z_PTR(thisfilter->abstract), ((php_convert_filter *)Z_PTR(thisfilter->abstract))->persistent);
1776}
1777
1778static php_stream_filter_ops strfilter_convert_ops = {
1779    strfilter_convert_filter,
1780    strfilter_convert_dtor,
1781    "convert.*"
1782};
1783
1784static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, int persistent)
1785{
1786    php_convert_filter *inst;
1787    php_stream_filter *retval = NULL;
1788
1789    char *dot;
1790    int conv_mode = 0;
1791
1792    if (filterparams != NULL && Z_TYPE_P(filterparams) != IS_ARRAY) {
1793        php_error_docref(NULL, E_WARNING, "stream filter (%s): invalid filter parameter", filtername);
1794        return NULL;
1795    }
1796
1797    if ((dot = strchr(filtername, '.')) == NULL) {
1798        return NULL;
1799    }
1800    ++dot;
1801
1802    inst = pemalloc(sizeof(php_convert_filter), persistent);
1803
1804    if (strcasecmp(dot, "base64-encode") == 0) {
1805        conv_mode = PHP_CONV_BASE64_ENCODE;
1806    } else if (strcasecmp(dot, "base64-decode") == 0) {
1807        conv_mode = PHP_CONV_BASE64_DECODE;
1808    } else if (strcasecmp(dot, "quoted-printable-encode") == 0) {
1809        conv_mode = PHP_CONV_QPRINT_ENCODE;
1810    } else if (strcasecmp(dot, "quoted-printable-decode") == 0) {
1811        conv_mode = PHP_CONV_QPRINT_DECODE;
1812    }
1813
1814    if (php_convert_filter_ctor(inst, conv_mode,
1815        (filterparams != NULL ? Z_ARRVAL_P(filterparams) : NULL),
1816        filtername, persistent) != SUCCESS) {
1817        goto out;
1818    }
1819
1820    retval = php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
1821out:
1822    if (retval == NULL) {
1823        pefree(inst, persistent);
1824    }
1825
1826    return retval;
1827}
1828
1829static php_stream_filter_factory strfilter_convert_factory = {
1830    strfilter_convert_create
1831};
1832/* }}} */
1833
1834/* {{{ consumed filter implementation */
1835typedef struct _php_consumed_filter_data {
1836    size_t consumed;
1837    zend_off_t offset;
1838    int persistent;
1839} php_consumed_filter_data;
1840
1841static php_stream_filter_status_t consumed_filter_filter(
1842    php_stream *stream,
1843    php_stream_filter *thisfilter,
1844    php_stream_bucket_brigade *buckets_in,
1845    php_stream_bucket_brigade *buckets_out,
1846    size_t *bytes_consumed,
1847    int flags
1848    )
1849{
1850    php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
1851    php_stream_bucket *bucket;
1852    size_t consumed = 0;
1853
1854    if (data->offset == ~0) {
1855        data->offset = php_stream_tell(stream);
1856    }
1857    while ((bucket = buckets_in->head) != NULL) {
1858        php_stream_bucket_unlink(bucket);
1859        consumed += bucket->buflen;
1860        php_stream_bucket_append(buckets_out, bucket);
1861    }
1862    if (bytes_consumed) {
1863        *bytes_consumed = consumed;
1864    }
1865    if (flags & PSFS_FLAG_FLUSH_CLOSE) {
1866        php_stream_seek(stream, data->offset + data->consumed, SEEK_SET);
1867    }
1868    data->consumed += consumed;
1869
1870    return PSFS_PASS_ON;
1871}
1872
1873static void consumed_filter_dtor(php_stream_filter *thisfilter)
1874{
1875    if (thisfilter && Z_PTR(thisfilter->abstract)) {
1876        php_consumed_filter_data *data = (php_consumed_filter_data*)Z_PTR(thisfilter->abstract);
1877        pefree(data, data->persistent);
1878    }
1879}
1880
1881static php_stream_filter_ops consumed_filter_ops = {
1882    consumed_filter_filter,
1883    consumed_filter_dtor,
1884    "consumed"
1885};
1886
1887static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, int persistent)
1888{
1889    php_stream_filter_ops *fops = NULL;
1890    php_consumed_filter_data *data;
1891
1892    if (strcasecmp(filtername, "consumed")) {
1893        return NULL;
1894    }
1895
1896    /* Create this filter */
1897    data = pecalloc(1, sizeof(php_consumed_filter_data), persistent);
1898    if (!data) {
1899        php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_consumed_filter_data));
1900        return NULL;
1901    }
1902    data->persistent = persistent;
1903    data->consumed = 0;
1904    data->offset = ~0;
1905    fops = &consumed_filter_ops;
1906
1907    return php_stream_filter_alloc(fops, data, persistent);
1908}
1909
1910php_stream_filter_factory consumed_filter_factory = {
1911    consumed_filter_create
1912};
1913
1914/* }}} */
1915
1916/* {{{ chunked filter implementation */
1917typedef enum _php_chunked_filter_state {
1918    CHUNK_SIZE_START,
1919    CHUNK_SIZE,
1920    CHUNK_SIZE_EXT,
1921    CHUNK_SIZE_CR,
1922    CHUNK_SIZE_LF,
1923    CHUNK_BODY,
1924    CHUNK_BODY_CR,
1925    CHUNK_BODY_LF,
1926    CHUNK_TRAILER,
1927    CHUNK_ERROR
1928} php_chunked_filter_state;
1929
1930typedef struct _php_chunked_filter_data {
1931    size_t chunk_size;
1932    php_chunked_filter_state state;
1933    int persistent;
1934} php_chunked_filter_data;
1935
1936static size_t php_dechunk(char *buf, size_t len, php_chunked_filter_data *data)
1937{
1938    char *p = buf;
1939    char *end = p + len;
1940    char *out = buf;
1941    size_t out_len = 0;
1942
1943    while (p < end) {
1944        switch (data->state) {
1945            case CHUNK_SIZE_START:
1946                data->chunk_size = 0;
1947            case CHUNK_SIZE:
1948                while (p < end) {
1949                    if (*p >= '0' && *p <= '9') {
1950                        data->chunk_size = (data->chunk_size * 16) + (*p - '0');
1951                    } else if (*p >= 'A' && *p <= 'F') {
1952                        data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
1953                    } else if (*p >= 'a' && *p <= 'f') {
1954                        data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
1955                    } else if (data->state == CHUNK_SIZE_START) {
1956                        data->state = CHUNK_ERROR;
1957                        break;
1958                    } else {
1959                        data->state = CHUNK_SIZE_EXT;
1960                        break;
1961                    }
1962                    data->state = CHUNK_SIZE;
1963                    p++;
1964                }
1965                if (data->state == CHUNK_ERROR) {
1966                    continue;
1967                } else if (p == end) {
1968                    return out_len;
1969                }
1970            case CHUNK_SIZE_EXT:
1971                /* skip extension */
1972                while (p < end && *p != '\r' && *p != '\n') {
1973                    p++;
1974                }
1975                if (p == end) {
1976                    return out_len;
1977                }
1978            case CHUNK_SIZE_CR:
1979                if (*p == '\r') {
1980                    p++;
1981                    if (p == end) {
1982                        data->state = CHUNK_SIZE_LF;
1983                        return out_len;
1984                    }
1985                }
1986            case CHUNK_SIZE_LF:
1987                if (*p == '\n') {
1988                    p++;
1989                    if (data->chunk_size == 0) {
1990                        /* last chunk */
1991                        data->state = CHUNK_TRAILER;
1992                        continue;
1993                    } else if (p == end) {
1994                        data->state = CHUNK_BODY;
1995                        return out_len;
1996                    }
1997                } else {
1998                    data->state = CHUNK_ERROR;
1999                    continue;
2000                }
2001            case CHUNK_BODY:
2002                if ((size_t) (end - p) >= data->chunk_size) {
2003                    if (p != out) {
2004                        memmove(out, p, data->chunk_size);
2005                    }
2006                    out += data->chunk_size;
2007                    out_len += data->chunk_size;
2008                    p += data->chunk_size;
2009                    if (p == end) {
2010                        data->state = CHUNK_BODY_CR;
2011                        return out_len;
2012                    }
2013                } else {
2014                    if (p != out) {
2015                        memmove(out, p, end - p);
2016                    }
2017                    data->chunk_size -= end - p;
2018                    data->state=CHUNK_BODY;
2019                    out_len += end - p;
2020                    return out_len;
2021                }
2022            case CHUNK_BODY_CR:
2023                if (*p == '\r') {
2024                    p++;
2025                    if (p == end) {
2026                        data->state = CHUNK_BODY_LF;
2027                        return out_len;
2028                    }
2029                }
2030            case CHUNK_BODY_LF:
2031                if (*p == '\n') {
2032                    p++;
2033                    data->state = CHUNK_SIZE_START;
2034                    continue;
2035                } else {
2036                    data->state = CHUNK_ERROR;
2037                    continue;
2038                }
2039            case CHUNK_TRAILER:
2040                /* ignore trailer */
2041                p = end;
2042                continue;
2043            case CHUNK_ERROR:
2044                if (p != out) {
2045                    memmove(out, p, end - p);
2046                }
2047                out_len += end - p;
2048                return out_len;
2049        }
2050    }
2051    return out_len;
2052}
2053
2054static php_stream_filter_status_t php_chunked_filter(
2055    php_stream *stream,
2056    php_stream_filter *thisfilter,
2057    php_stream_bucket_brigade *buckets_in,
2058    php_stream_bucket_brigade *buckets_out,
2059    size_t *bytes_consumed,
2060    int flags
2061    )
2062{
2063    php_stream_bucket *bucket;
2064    size_t consumed = 0;
2065    php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2066
2067    while (buckets_in->head) {
2068        bucket = php_stream_bucket_make_writeable(buckets_in->head);
2069        consumed += bucket->buflen;
2070        bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);
2071        php_stream_bucket_append(buckets_out, bucket);
2072    }
2073
2074    if (bytes_consumed) {
2075        *bytes_consumed = consumed;
2076    }
2077
2078    return PSFS_PASS_ON;
2079}
2080
2081static void php_chunked_dtor(php_stream_filter *thisfilter)
2082{
2083    if (thisfilter && Z_PTR(thisfilter->abstract)) {
2084        php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2085        pefree(data, data->persistent);
2086    }
2087}
2088
2089static php_stream_filter_ops chunked_filter_ops = {
2090    php_chunked_filter,
2091    php_chunked_dtor,
2092    "dechunk"
2093};
2094
2095static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, int persistent)
2096{
2097    php_stream_filter_ops *fops = NULL;
2098    php_chunked_filter_data *data;
2099
2100    if (strcasecmp(filtername, "dechunk")) {
2101        return NULL;
2102    }
2103
2104    /* Create this filter */
2105    data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent);
2106    if (!data) {
2107        php_error_docref(NULL, E_WARNING, "Failed allocating %zd bytes", sizeof(php_chunked_filter_data));
2108        return NULL;
2109    }
2110    data->state = CHUNK_SIZE_START;
2111    data->chunk_size = 0;
2112    data->persistent = persistent;
2113    fops = &chunked_filter_ops;
2114
2115    return php_stream_filter_alloc(fops, data, persistent);
2116}
2117
2118static php_stream_filter_factory chunked_filter_factory = {
2119    chunked_filter_create
2120};
2121/* }}} */
2122
2123static const struct {
2124    php_stream_filter_ops *ops;
2125    php_stream_filter_factory *factory;
2126} standard_filters[] = {
2127    { &strfilter_rot13_ops, &strfilter_rot13_factory },
2128    { &strfilter_toupper_ops, &strfilter_toupper_factory },
2129    { &strfilter_tolower_ops, &strfilter_tolower_factory },
2130    { &strfilter_strip_tags_ops, &strfilter_strip_tags_factory },
2131    { &strfilter_convert_ops, &strfilter_convert_factory },
2132    { &consumed_filter_ops, &consumed_filter_factory },
2133    { &chunked_filter_ops, &chunked_filter_factory },
2134    /* additional filters to go here */
2135    { NULL, NULL }
2136};
2137
2138/* {{{ filter MINIT and MSHUTDOWN */
2139PHP_MINIT_FUNCTION(standard_filters)
2140{
2141    int i;
2142
2143    for (i = 0; standard_filters[i].ops; i++) {
2144        if (FAILURE == php_stream_filter_register_factory(
2145                    standard_filters[i].ops->label,
2146                    standard_filters[i].factory
2147                    )) {
2148            return FAILURE;
2149        }
2150    }
2151    return SUCCESS;
2152}
2153
2154PHP_MSHUTDOWN_FUNCTION(standard_filters)
2155{
2156    int i;
2157
2158    for (i = 0; standard_filters[i].ops; i++) {
2159        php_stream_filter_unregister_factory(standard_filters[i].ops->label);
2160    }
2161    return SUCCESS;
2162}
2163/* }}} */
2164
2165/*
2166 * Local variables:
2167 * tab-width: 4
2168 * c-basic-offset: 4
2169 * End:
2170 * vim600: sw=4 ts=4 fdm=marker
2171 * vim<600: sw=4 ts=4
2172 */
2173