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    TSRMLS_DC)
44{
45    php_stream_bucket *bucket;
46    size_t consumed = 0;
47
48    while (buckets_in->head) {
49        bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
50
51        php_strtr(bucket->buf, bucket->buflen, rot13_from, rot13_to, 52);
52        consumed += bucket->buflen;
53
54        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
55    }
56
57    if (bytes_consumed) {
58        *bytes_consumed = consumed;
59    }
60
61    return PSFS_PASS_ON;
62}
63
64static php_stream_filter_ops strfilter_rot13_ops = {
65    strfilter_rot13_filter,
66    NULL,
67    "string.rot13"
68};
69
70static php_stream_filter *strfilter_rot13_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
71{
72    return php_stream_filter_alloc(&strfilter_rot13_ops, NULL, persistent);
73}
74
75static php_stream_filter_factory strfilter_rot13_factory = {
76    strfilter_rot13_create
77};
78/* }}} */
79
80/* {{{ string.toupper / string.tolower stream filter implementation */
81static char lowercase[] = "abcdefghijklmnopqrstuvwxyz";
82static char uppercase[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
83
84static php_stream_filter_status_t strfilter_toupper_filter(
85    php_stream *stream,
86    php_stream_filter *thisfilter,
87    php_stream_bucket_brigade *buckets_in,
88    php_stream_bucket_brigade *buckets_out,
89    size_t *bytes_consumed,
90    int flags
91    TSRMLS_DC)
92{
93    php_stream_bucket *bucket;
94    size_t consumed = 0;
95
96    while (buckets_in->head) {
97        bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
98
99        php_strtr(bucket->buf, bucket->buflen, lowercase, uppercase, 26);
100        consumed += bucket->buflen;
101
102        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
103    }
104
105    if (bytes_consumed) {
106        *bytes_consumed = consumed;
107    }
108
109    return PSFS_PASS_ON;
110}
111
112static php_stream_filter_status_t strfilter_tolower_filter(
113    php_stream *stream,
114    php_stream_filter *thisfilter,
115    php_stream_bucket_brigade *buckets_in,
116    php_stream_bucket_brigade *buckets_out,
117    size_t *bytes_consumed,
118    int flags
119    TSRMLS_DC)
120{
121    php_stream_bucket *bucket;
122    size_t consumed = 0;
123
124    while (buckets_in->head) {
125        bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
126
127        php_strtr(bucket->buf, bucket->buflen, uppercase, lowercase, 26);
128        consumed += bucket->buflen;
129
130        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
131    }
132
133    if (bytes_consumed) {
134        *bytes_consumed = consumed;
135    }
136
137    return PSFS_PASS_ON;
138}
139
140static php_stream_filter_ops strfilter_toupper_ops = {
141    strfilter_toupper_filter,
142    NULL,
143    "string.toupper"
144};
145
146static php_stream_filter_ops strfilter_tolower_ops = {
147    strfilter_tolower_filter,
148    NULL,
149    "string.tolower"
150};
151
152static php_stream_filter *strfilter_toupper_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
153{
154    return php_stream_filter_alloc(&strfilter_toupper_ops, NULL, persistent);
155}
156
157static php_stream_filter *strfilter_tolower_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
158{
159    return php_stream_filter_alloc(&strfilter_tolower_ops, NULL, persistent);
160}
161
162static php_stream_filter_factory strfilter_toupper_factory = {
163    strfilter_toupper_create
164};
165
166static php_stream_filter_factory strfilter_tolower_factory = {
167    strfilter_tolower_create
168};
169/* }}} */
170
171/* {{{ strip_tags filter implementation */
172typedef struct _php_strip_tags_filter {
173    const char *allowed_tags;
174    int allowed_tags_len;
175    int state;
176    int persistent;
177} php_strip_tags_filter;
178
179static int php_strip_tags_filter_ctor(php_strip_tags_filter *inst, const char *allowed_tags, int allowed_tags_len, int persistent)
180{
181    if (allowed_tags != NULL) {
182        if (NULL == (inst->allowed_tags = pemalloc(allowed_tags_len, persistent))) {
183            return FAILURE;
184        }
185        memcpy((char *)inst->allowed_tags, allowed_tags, allowed_tags_len);
186        inst->allowed_tags_len = allowed_tags_len;
187    } else {
188        inst->allowed_tags = NULL;
189    }
190    inst->state = 0;
191    inst->persistent = persistent;
192
193    return SUCCESS;
194}
195
196static void php_strip_tags_filter_dtor(php_strip_tags_filter *inst)
197{
198    if (inst->allowed_tags != NULL) {
199        pefree((void *)inst->allowed_tags, inst->persistent);
200    }
201}
202
203static php_stream_filter_status_t strfilter_strip_tags_filter(
204    php_stream *stream,
205    php_stream_filter *thisfilter,
206    php_stream_bucket_brigade *buckets_in,
207    php_stream_bucket_brigade *buckets_out,
208    size_t *bytes_consumed,
209    int flags
210    TSRMLS_DC)
211{
212    php_stream_bucket *bucket;
213    size_t consumed = 0;
214    php_strip_tags_filter *inst = (php_strip_tags_filter *) Z_PTR(thisfilter->abstract);
215
216    while (buckets_in->head) {
217        bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
218        consumed = bucket->buflen;
219
220        bucket->buflen = php_strip_tags(bucket->buf, bucket->buflen, &(inst->state), (char *)inst->allowed_tags, inst->allowed_tags_len);
221
222        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
223    }
224
225    if (bytes_consumed) {
226        *bytes_consumed = consumed;
227    }
228
229    return PSFS_PASS_ON;
230}
231
232static void strfilter_strip_tags_dtor(php_stream_filter *thisfilter TSRMLS_DC)
233{
234    assert(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 TSRMLS_DC)
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 j, lb_cnt2;
865
866                    lb_cnt2 = 0;
867                    ps2 = ps;
868                    trail_ws = 1;
869
870                    for (j = icnt - 1; j > 0; j--, ps2++) {
871                        if (*ps2 == inst->lbchars[lb_cnt2]) {
872                            lb_cnt2++;
873                            if (lb_cnt2 >= inst->lbchars_len) {
874                                /* Found trailing ws. Reset to top of main
875                                 * for loop to allow for code to do necessary
876                                 * wrapping/encoding. */
877                                break;
878                            }
879                        } else if (lb_cnt2 != 0 || (*ps2 != '\t' && *ps2 != ' ')) {
880                            /* At least one non-EOL character following, so
881                             * don't need to encode ws. */
882                            trail_ws = 0;
883                            break;
884                        } else {
885                            trail_ws++;
886                        }
887                    }
888                }
889
890                if (trail_ws == 0) {
891                    *(pd++) = c;
892                    ocnt--;
893                    line_ccnt--;
894                    CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
895                }
896            }
897        } else if ((!(opts & PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST) || line_ccnt < inst->line_len) && ((c >= 33 && c <= 60) || (c >= 62 && c <= 126))) {
898            if (line_ccnt < 2 && inst->lbchars != NULL) {
899                if (ocnt < inst->lbchars_len + 1) {
900                    err = PHP_CONV_ERR_TOO_BIG;
901                    break;
902                }
903                *(pd++) = '=';
904                ocnt--;
905                line_ccnt--;
906
907                memcpy(pd, inst->lbchars, inst->lbchars_len);
908                pd += inst->lbchars_len;
909                ocnt -= inst->lbchars_len;
910                line_ccnt = inst->line_len;
911            }
912            if (ocnt < 1) {
913                err = PHP_CONV_ERR_TOO_BIG;
914                break;
915            }
916            *(pd++) = c;
917            ocnt--;
918            line_ccnt--;
919            CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
920        } else {
921            if (line_ccnt < 4) {
922                if (ocnt < inst->lbchars_len + 1) {
923                    err = PHP_CONV_ERR_TOO_BIG;
924                    break;
925                }
926                *(pd++) = '=';
927                ocnt--;
928                line_ccnt--;
929
930                memcpy(pd, inst->lbchars, inst->lbchars_len);
931                pd += inst->lbchars_len;
932                ocnt -= inst->lbchars_len;
933                line_ccnt = inst->line_len;
934            }
935            if (ocnt < 3) {
936                err = PHP_CONV_ERR_TOO_BIG;
937                break;
938            }
939            *(pd++) = '=';
940            *(pd++) = qp_digits[(c >> 4)];
941            *(pd++) = qp_digits[(c & 0x0f)];
942            ocnt -= 3;
943            line_ccnt -= 3;
944            if (trail_ws > 0) {
945                trail_ws--;
946            }
947            CONSUME_CHAR(ps, icnt, lb_ptr, lb_cnt);
948        }
949    }
950
951    *in_pp = (const char *)ps;
952    *in_left_p = icnt;
953    *out_pp = (char *)pd;
954    *out_left_p = ocnt;
955    inst->line_ccnt = line_ccnt;
956    inst->lb_ptr = lb_ptr;
957    inst->lb_cnt = lb_cnt;
958    return err;
959}
960#undef NEXT_CHAR
961#undef CONSUME_CHAR
962
963static 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)
964{
965    if (line_len < 4 && lbchars != NULL) {
966        return PHP_CONV_ERR_TOO_BIG;
967    }
968    inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_encode_convert;
969    inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_encode_dtor;
970    inst->line_ccnt = line_len;
971    inst->line_len = line_len;
972    if (lbchars != NULL) {
973        inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
974        inst->lbchars_len = lbchars_len;
975    } else {
976        inst->lbchars = NULL;
977    }
978    inst->lbchars_dup = lbchars_dup;
979    inst->persistent = persistent;
980    inst->opts = opts;
981    inst->lb_cnt = inst->lb_ptr = 0;
982    return PHP_CONV_ERR_SUCCESS;
983}
984/* }}} */
985
986/* {{{ php_conv_qprint_decode */
987typedef struct _php_conv_qprint_decode {
988    php_conv _super;
989
990    const char *lbchars;
991    size_t lbchars_len;
992    int scan_stat;
993    unsigned int next_char;
994    int lbchars_dup;
995    int persistent;
996    unsigned int lb_ptr;
997    unsigned int lb_cnt;
998} php_conv_qprint_decode;
999
1000static void php_conv_qprint_decode_dtor(php_conv_qprint_decode *inst)
1001{
1002    assert(inst != NULL);
1003    if (inst->lbchars_dup && inst->lbchars != NULL) {
1004        pefree((void *)inst->lbchars, inst->persistent);
1005    }
1006}
1007
1008static 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)
1009{
1010    php_conv_err_t err = PHP_CONV_ERR_SUCCESS;
1011    size_t icnt, ocnt;
1012    unsigned char *ps, *pd;
1013    unsigned int scan_stat;
1014    unsigned int next_char;
1015    unsigned int lb_ptr, lb_cnt;
1016
1017    lb_ptr = inst->lb_ptr;
1018    lb_cnt = inst->lb_cnt;
1019
1020    if ((in_pp == NULL || in_left_p == NULL) && lb_cnt == lb_ptr) {
1021        if (inst->scan_stat != 0) {
1022            return PHP_CONV_ERR_UNEXPECTED_EOS;
1023        }
1024        return PHP_CONV_ERR_SUCCESS;
1025    }
1026
1027    ps = (unsigned char *)(*in_pp);
1028    icnt = *in_left_p;
1029    pd = (unsigned char *)(*out_pp);
1030    ocnt = *out_left_p;
1031    scan_stat = inst->scan_stat;
1032    next_char = inst->next_char;
1033
1034    for (;;) {
1035        switch (scan_stat) {
1036            case 0: {
1037                if (icnt <= 0) {
1038                    goto out;
1039                }
1040                if (*ps == '=') {
1041                    scan_stat = 1;
1042                } else {
1043                    if (ocnt < 1) {
1044                        err = PHP_CONV_ERR_TOO_BIG;
1045                        goto out;
1046                    }
1047                    *(pd++) = *ps;
1048                    ocnt--;
1049                }
1050                ps++, icnt--;
1051            } break;
1052
1053            case 1: {
1054                if (icnt <= 0) {
1055                    goto out;
1056                }
1057                if (*ps == ' ' || *ps == '\t') {
1058                    scan_stat = 4;
1059                    ps++, icnt--;
1060                    break;
1061                } else if (!inst->lbchars && lb_cnt == 0 && *ps == '\r') {
1062                    /* auto-detect line endings, looks like network line ending \r\n (could be mac \r) */
1063                    lb_cnt++;
1064                    scan_stat = 5;
1065                    ps++, icnt--;
1066                    break;
1067                } else if (!inst->lbchars && lb_cnt == 0 && *ps == '\n') {
1068                    /* auto-detect line endings, looks like unix-lineendings, not to spec, but it is seem in the wild, a lot */
1069                    lb_cnt = lb_ptr = 0;
1070                    scan_stat = 0;
1071                    ps++, icnt--;
1072                    break;
1073                } else if (lb_cnt < inst->lbchars_len &&
1074                            *ps == (unsigned char)inst->lbchars[lb_cnt]) {
1075                    lb_cnt++;
1076                    scan_stat = 5;
1077                    ps++, icnt--;
1078                    break;
1079                }
1080            } /* break is missing intentionally */
1081
1082            case 2: {
1083                if (icnt <= 0) {
1084                    goto out;
1085                }
1086
1087                if (!isxdigit((int) *ps)) {
1088                    err = PHP_CONV_ERR_INVALID_SEQ;
1089                    goto out;
1090                }
1091                next_char = (next_char << 4) | (*ps >= 'A' ? *ps - 0x37 : *ps - 0x30);
1092                scan_stat++;
1093                ps++, icnt--;
1094                if (scan_stat != 3) {
1095                    break;
1096                }
1097            } /* break is missing intentionally */
1098
1099            case 3: {
1100                if (ocnt < 1) {
1101                    err = PHP_CONV_ERR_TOO_BIG;
1102                    goto out;
1103                }
1104                *(pd++) = next_char;
1105                ocnt--;
1106                scan_stat = 0;
1107            } break;
1108
1109            case 4: {
1110                if (icnt <= 0) {
1111                    goto out;
1112                }
1113                if (lb_cnt < inst->lbchars_len &&
1114                    *ps == (unsigned char)inst->lbchars[lb_cnt]) {
1115                    lb_cnt++;
1116                    scan_stat = 5;
1117                }
1118                if (*ps != '\t' && *ps != ' ') {
1119                    err = PHP_CONV_ERR_INVALID_SEQ;
1120                    goto out;
1121                }
1122                ps++, icnt--;
1123            } break;
1124
1125            case 5: {
1126                if (!inst->lbchars && lb_cnt == 1 && *ps == '\n') {
1127                    /* auto-detect soft line breaks, found network line break */
1128                    lb_cnt = lb_ptr = 0;
1129                    scan_stat = 0;
1130                    ps++, icnt--; /* consume \n */
1131                } else if (!inst->lbchars && lb_cnt > 0) {
1132                    /* auto-detect soft line breaks, found mac line break */
1133                    lb_cnt = lb_ptr = 0;
1134                    scan_stat = 0;
1135                } else if (lb_cnt >= inst->lbchars_len) {
1136                    /* soft line break */
1137                    lb_cnt = lb_ptr = 0;
1138                    scan_stat = 0;
1139                } else if (icnt > 0) {
1140                    if (*ps == (unsigned char)inst->lbchars[lb_cnt]) {
1141                        lb_cnt++;
1142                        ps++, icnt--;
1143                    } else {
1144                        scan_stat = 6; /* no break for short-cut */
1145                    }
1146                } else {
1147                    goto out;
1148                }
1149            } break;
1150
1151            case 6: {
1152                if (lb_ptr < lb_cnt) {
1153                    if (ocnt < 1) {
1154                        err = PHP_CONV_ERR_TOO_BIG;
1155                        goto out;
1156                    }
1157                    *(pd++) = inst->lbchars[lb_ptr++];
1158                    ocnt--;
1159                } else {
1160                    scan_stat = 0;
1161                    lb_cnt = lb_ptr = 0;
1162                }
1163            } break;
1164        }
1165    }
1166out:
1167    *in_pp = (const char *)ps;
1168    *in_left_p = icnt;
1169    *out_pp = (char *)pd;
1170    *out_left_p = ocnt;
1171    inst->scan_stat = scan_stat;
1172    inst->lb_ptr = lb_ptr;
1173    inst->lb_cnt = lb_cnt;
1174    inst->next_char = next_char;
1175
1176    return err;
1177}
1178static 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)
1179{
1180    inst->_super.convert_op = (php_conv_convert_func) php_conv_qprint_decode_convert;
1181    inst->_super.dtor = (php_conv_dtor_func) php_conv_qprint_decode_dtor;
1182    inst->scan_stat = 0;
1183    inst->next_char = 0;
1184    inst->lb_ptr = inst->lb_cnt = 0;
1185    if (lbchars != NULL) {
1186        inst->lbchars = (lbchars_dup ? pestrdup(lbchars, persistent) : lbchars);
1187        inst->lbchars_len = lbchars_len;
1188    } else {
1189        inst->lbchars = NULL;
1190        inst->lbchars_len = 0;
1191    }
1192    inst->lbchars_dup = lbchars_dup;
1193    inst->persistent = persistent;
1194    return PHP_CONV_ERR_SUCCESS;
1195}
1196/* }}} */
1197
1198typedef struct _php_convert_filter {
1199    php_conv *cd;
1200    int persistent;
1201    char *filtername;
1202    char stub[128];
1203    size_t stub_len;
1204} php_convert_filter;
1205
1206#define PHP_CONV_BASE64_ENCODE 1
1207#define PHP_CONV_BASE64_DECODE 2
1208#define PHP_CONV_QPRINT_ENCODE 3
1209#define PHP_CONV_QPRINT_DECODE 4
1210
1211static 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 TSRMLS_DC)
1212{
1213    zval *tmpval;
1214
1215    *pretval = NULL;
1216    *pretval_len = 0;
1217
1218    if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1219        zend_string *str = zval_get_string(tmpval);
1220
1221        if (NULL == (*pretval = pemalloc(str->len + 1, persistent))) {
1222            return PHP_CONV_ERR_ALLOC;
1223        }
1224
1225        *pretval_len = str->len;
1226        memcpy(*pretval, str->val, str->len + 1);
1227        zend_string_release(str);
1228    } else {
1229        return PHP_CONV_ERR_NOT_FOUND;
1230    }
1231    return PHP_CONV_ERR_SUCCESS;
1232}
1233
1234#if IT_WAS_USED
1235static php_conv_err_t php_conv_get_long_prop_ex(const HashTable *ht, zend_long *pretval, char *field_name, size_t field_name_len)
1236{
1237    zval **tmpval;
1238
1239    *pretval = 0;
1240
1241    if (zend_hash_find((HashTable *)ht, field_name, field_name_len, (void **)&tmpval) == SUCCESS) {
1242        zval tmp, *ztval = *tmpval;
1243
1244        if (Z_TYPE_PP(tmpval) != IS_LONG) {
1245            tmp = *ztval;
1246            zval_copy_ctor(&tmp);
1247            convert_to_long(&tmp);
1248            ztval = &tmp;
1249        }
1250        *pretval = Z_LVAL_P(ztval);
1251    } else {
1252        return PHP_CONV_ERR_NOT_FOUND;
1253    }
1254    return PHP_CONV_ERR_SUCCESS;
1255}
1256#endif
1257
1258static php_conv_err_t php_conv_get_ulong_prop_ex(const HashTable *ht, zend_ulong *pretval, char *field_name, size_t field_name_len)
1259{
1260    zval *tmpval;
1261
1262    *pretval = 0;
1263
1264    if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1265        zval tmp;
1266
1267        if (Z_TYPE_P(tmpval) != IS_LONG) {
1268            ZVAL_DUP(&tmp, tmpval);
1269            convert_to_long(&tmp);
1270            tmpval = &tmp;
1271        }
1272        if (Z_LVAL_P(tmpval) < 0) {
1273            *pretval = 0;
1274        } else {
1275            *pretval = Z_LVAL_P(tmpval);
1276        }
1277    } else {
1278        return PHP_CONV_ERR_NOT_FOUND;
1279    }
1280    return PHP_CONV_ERR_SUCCESS;
1281}
1282
1283static php_conv_err_t php_conv_get_bool_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
1284{
1285    zval *tmpval;
1286
1287    *pretval = 0;
1288
1289    if ((tmpval = zend_hash_str_find((HashTable *)ht, field_name, field_name_len-1)) != NULL) {
1290        zval tmp;
1291
1292        if (Z_TYPE_P(tmpval) != IS_FALSE || Z_TYPE_P(tmpval) != IS_TRUE) {
1293            ZVAL_DUP(&tmp, tmpval);
1294            zval_copy_ctor(&tmp);
1295            convert_to_boolean(&tmp);
1296            tmpval = &tmp;
1297        }
1298        *pretval = (Z_TYPE_P(tmpval) == IS_TRUE);
1299    } else {
1300        return PHP_CONV_ERR_NOT_FOUND;
1301    }
1302    return PHP_CONV_ERR_SUCCESS;
1303}
1304
1305
1306#if IT_WAS_USED
1307static int php_conv_get_int_prop_ex(const HashTable *ht, int *pretval, char *field_name, size_t field_name_len)
1308{
1309    zend_long l;
1310    php_conv_err_t err;
1311
1312    *pretval = 0;
1313
1314    if ((err = php_conv_get_long_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
1315        *pretval = l;
1316    }
1317    return err;
1318}
1319#endif
1320
1321static int php_conv_get_uint_prop_ex(const HashTable *ht, unsigned int *pretval, char *field_name, size_t field_name_len)
1322{
1323    zend_ulong l;
1324    php_conv_err_t err;
1325
1326    *pretval = 0;
1327
1328    if ((err = php_conv_get_ulong_prop_ex(ht, &l, field_name, field_name_len)) == PHP_CONV_ERR_SUCCESS) {
1329        *pretval = l;
1330    }
1331    return err;
1332}
1333
1334#define GET_STR_PROP(ht, var, var_len, fldname, persistent) \
1335    php_conv_get_string_prop_ex(ht, &var, &var_len, fldname, sizeof(fldname), persistent TSRMLS_CC)
1336
1337#define GET_INT_PROP(ht, var, fldname) \
1338    php_conv_get_int_prop_ex(ht, &var, fldname, sizeof(fldname))
1339
1340#define GET_UINT_PROP(ht, var, fldname) \
1341    php_conv_get_uint_prop_ex(ht, &var, fldname, sizeof(fldname))
1342
1343#define GET_BOOL_PROP(ht, var, fldname) \
1344    php_conv_get_bool_prop_ex(ht, &var, fldname, sizeof(fldname))
1345
1346static php_conv *php_conv_open(int conv_mode, const HashTable *options, int persistent TSRMLS_DC)
1347{
1348    /* FIXME: I'll have to replace this ugly code by something neat
1349       (factories?) in the near future. */
1350    php_conv *retval = NULL;
1351
1352    switch (conv_mode) {
1353        case PHP_CONV_BASE64_ENCODE: {
1354            unsigned int line_len = 0;
1355            char *lbchars = NULL;
1356            size_t lbchars_len;
1357
1358            if (options != NULL) {
1359                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1360                GET_UINT_PROP(options, line_len, "line-length");
1361                if (line_len < 4) {
1362                    if (lbchars != NULL) {
1363                        pefree(lbchars, 0);
1364                    }
1365                    lbchars = NULL;
1366                } else {
1367                    if (lbchars == NULL) {
1368                        lbchars = pestrdup("\r\n", 0);
1369                        lbchars_len = 2;
1370                    }
1371                }
1372            }
1373            retval = pemalloc(sizeof(php_conv_base64_encode), persistent);
1374            if (lbchars != NULL) {
1375                if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, line_len, lbchars, lbchars_len, 1, persistent)) {
1376                    if (lbchars != NULL) {
1377                        pefree(lbchars, 0);
1378                    }
1379                    goto out_failure;
1380                }
1381                pefree(lbchars, 0);
1382            } else {
1383                if (php_conv_base64_encode_ctor((php_conv_base64_encode *)retval, 0, NULL, 0, 0, persistent)) {
1384                    goto out_failure;
1385                }
1386            }
1387        } break;
1388
1389        case PHP_CONV_BASE64_DECODE:
1390            retval = pemalloc(sizeof(php_conv_base64_decode), persistent);
1391            if (php_conv_base64_decode_ctor((php_conv_base64_decode *)retval)) {
1392                goto out_failure;
1393            }
1394            break;
1395
1396        case PHP_CONV_QPRINT_ENCODE: {
1397            unsigned int line_len = 0;
1398            char *lbchars = NULL;
1399            size_t lbchars_len;
1400            int opts = 0;
1401
1402            if (options != NULL) {
1403                int opt_binary = 0;
1404                int opt_force_encode_first = 0;
1405
1406                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1407                GET_UINT_PROP(options, line_len, "line-length");
1408                GET_BOOL_PROP(options, opt_binary, "binary");
1409                GET_BOOL_PROP(options, opt_force_encode_first, "force-encode-first");
1410
1411                if (line_len < 4) {
1412                    if (lbchars != NULL) {
1413                        pefree(lbchars, 0);
1414                    }
1415                    lbchars = NULL;
1416                } else {
1417                    if (lbchars == NULL) {
1418                        lbchars = pestrdup("\r\n", 0);
1419                        lbchars_len = 2;
1420                    }
1421                }
1422                opts |= (opt_binary ? PHP_CONV_QPRINT_OPT_BINARY : 0);
1423                opts |= (opt_force_encode_first ? PHP_CONV_QPRINT_OPT_FORCE_ENCODE_FIRST : 0);
1424            }
1425            retval = pemalloc(sizeof(php_conv_qprint_encode), persistent);
1426            if (lbchars != NULL) {
1427                if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, line_len, lbchars, lbchars_len, 1, opts, persistent)) {
1428                    pefree(lbchars, 0);
1429                    goto out_failure;
1430                }
1431                pefree(lbchars, 0);
1432            } else {
1433                if (php_conv_qprint_encode_ctor((php_conv_qprint_encode *)retval, 0, NULL, 0, 0, opts, persistent)) {
1434                    goto out_failure;
1435                }
1436            }
1437        } break;
1438
1439        case PHP_CONV_QPRINT_DECODE: {
1440            char *lbchars = NULL;
1441            size_t lbchars_len;
1442
1443            if (options != NULL) {
1444                /* If line-break-chars are not specified, filter will attempt to detect line endings (\r, \n, or \r\n) */
1445                GET_STR_PROP(options, lbchars, lbchars_len, "line-break-chars", 0);
1446            }
1447
1448            retval = pemalloc(sizeof(php_conv_qprint_decode), persistent);
1449            if (lbchars != NULL) {
1450                if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, lbchars, lbchars_len, 1, persistent)) {
1451                    pefree(lbchars, 0);
1452                    goto out_failure;
1453                }
1454                pefree(lbchars, 0);
1455            } else {
1456                if (php_conv_qprint_decode_ctor((php_conv_qprint_decode *)retval, NULL, 0, 0, persistent)) {
1457                    goto out_failure;
1458                }
1459            }
1460        } break;
1461
1462        default:
1463            retval = NULL;
1464            break;
1465    }
1466    return retval;
1467
1468out_failure:
1469    if (retval != NULL) {
1470        pefree(retval, persistent);
1471    }
1472    return NULL;
1473}
1474
1475#undef GET_STR_PROP
1476#undef GET_INT_PROP
1477#undef GET_UINT_PROP
1478#undef GET_BOOL_PROP
1479
1480static int php_convert_filter_ctor(php_convert_filter *inst,
1481    int conv_mode, HashTable *conv_opts,
1482    const char *filtername, int persistent TSRMLS_DC)
1483{
1484    inst->persistent = persistent;
1485    inst->filtername = pestrdup(filtername, persistent);
1486    inst->stub_len = 0;
1487
1488    if ((inst->cd = php_conv_open(conv_mode, conv_opts, persistent TSRMLS_CC)) == NULL) {
1489        goto out_failure;
1490    }
1491
1492    return SUCCESS;
1493
1494out_failure:
1495    if (inst->cd != NULL) {
1496        php_conv_dtor(inst->cd);
1497        pefree(inst->cd, persistent);
1498    }
1499    if (inst->filtername != NULL) {
1500        pefree(inst->filtername, persistent);
1501    }
1502    return FAILURE;
1503}
1504
1505static void php_convert_filter_dtor(php_convert_filter *inst)
1506{
1507    if (inst->cd != NULL) {
1508        php_conv_dtor(inst->cd);
1509        pefree(inst->cd, inst->persistent);
1510    }
1511
1512    if (inst->filtername != NULL) {
1513        pefree(inst->filtername, inst->persistent);
1514    }
1515}
1516
1517/* {{{ strfilter_convert_append_bucket */
1518static int strfilter_convert_append_bucket(
1519        php_convert_filter *inst,
1520        php_stream *stream, php_stream_filter *filter,
1521        php_stream_bucket_brigade *buckets_out,
1522        const char *ps, size_t buf_len, size_t *consumed,
1523        int persistent TSRMLS_DC)
1524{
1525    php_conv_err_t err;
1526    php_stream_bucket *new_bucket;
1527    char *out_buf = NULL;
1528    size_t out_buf_size;
1529    char *pd;
1530    const char *pt;
1531    size_t ocnt, icnt, tcnt;
1532    size_t initial_out_buf_size;
1533
1534    if (ps == NULL) {
1535        initial_out_buf_size = 64;
1536        icnt = 1;
1537    } else {
1538        initial_out_buf_size = buf_len;
1539        icnt = buf_len;
1540    }
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
1547    pd = out_buf;
1548
1549    if (inst->stub_len > 0) {
1550        pt = inst->stub;
1551        tcnt = inst->stub_len;
1552
1553        while (tcnt > 0) {
1554            err = php_conv_convert(inst->cd, &pt, &tcnt, &pd, &ocnt);
1555
1556            switch (err) {
1557                case PHP_CONV_ERR_INVALID_SEQ:
1558                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1559                    goto out_failure;
1560
1561                case PHP_CONV_ERR_MORE:
1562                    if (ps != NULL) {
1563                        if (icnt > 0) {
1564                            if (inst->stub_len >= sizeof(inst->stub)) {
1565                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1566                                goto out_failure;
1567                            }
1568                            inst->stub[inst->stub_len++] = *(ps++);
1569                            icnt--;
1570                            pt = inst->stub;
1571                            tcnt = inst->stub_len;
1572                        } else {
1573                            tcnt = 0;
1574                            break;
1575                        }
1576                    }
1577                    break;
1578
1579                case PHP_CONV_ERR_UNEXPECTED_EOS:
1580                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): unexpected end of stream", inst->filtername);
1581                    goto out_failure;
1582
1583                case PHP_CONV_ERR_TOO_BIG: {
1584                    char *new_out_buf;
1585                    size_t new_out_buf_size;
1586
1587                    new_out_buf_size = out_buf_size << 1;
1588
1589                    if (new_out_buf_size < out_buf_size) {
1590                        /* whoa! no bigger buckets are sold anywhere... */
1591                        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
1592                            goto out_failure;
1593                        }
1594
1595                        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
1596
1597                        out_buf_size = ocnt = initial_out_buf_size;
1598                        if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1599                            return FAILURE;
1600                        }
1601                        pd = out_buf;
1602                    } else {
1603                        if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1604                            if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
1605                                goto out_failure;
1606                            }
1607
1608                            php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
1609                            return FAILURE;
1610                        }
1611
1612                        pd = new_out_buf + (pd - out_buf);
1613                        ocnt += (new_out_buf_size - out_buf_size);
1614                        out_buf = new_out_buf;
1615                        out_buf_size = new_out_buf_size;
1616                    }
1617                } break;
1618
1619                case PHP_CONV_ERR_UNKNOWN:
1620                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1621                    goto out_failure;
1622
1623                default:
1624                    break;
1625            }
1626        }
1627        memmove(inst->stub, pt, tcnt);
1628        inst->stub_len = tcnt;
1629    }
1630
1631    while (icnt > 0) {
1632        err = ((ps == NULL ? php_conv_convert(inst->cd, NULL, NULL, &pd, &ocnt):
1633                php_conv_convert(inst->cd, &ps, &icnt, &pd, &ocnt)));
1634        switch (err) {
1635            case PHP_CONV_ERR_INVALID_SEQ:
1636                php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): invalid byte sequence", inst->filtername);
1637                goto out_failure;
1638
1639            case PHP_CONV_ERR_MORE:
1640                if (ps != NULL) {
1641                    if (icnt > sizeof(inst->stub)) {
1642                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): insufficient buffer", inst->filtername);
1643                        goto out_failure;
1644                    }
1645                    memcpy(inst->stub, ps, icnt);
1646                    inst->stub_len = icnt;
1647                    ps += icnt;
1648                    icnt = 0;
1649                } else {
1650                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): unexpected octet values", inst->filtername);
1651                    goto out_failure;
1652                }
1653                break;
1654
1655            case PHP_CONV_ERR_TOO_BIG: {
1656                char *new_out_buf;
1657                size_t new_out_buf_size;
1658
1659                new_out_buf_size = out_buf_size << 1;
1660
1661                if (new_out_buf_size < out_buf_size) {
1662                    /* whoa! no bigger buckets are sold anywhere... */
1663                    if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
1664                        goto out_failure;
1665                    }
1666
1667                    php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
1668
1669                    out_buf_size = ocnt = initial_out_buf_size;
1670                    if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
1671                        return FAILURE;
1672                    }
1673                    pd = out_buf;
1674                } else {
1675                    if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
1676                        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
1677                            goto out_failure;
1678                        }
1679
1680                        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
1681                        return FAILURE;
1682                    }
1683                    pd = new_out_buf + (pd - out_buf);
1684                    ocnt += (new_out_buf_size - out_buf_size);
1685                    out_buf = new_out_buf;
1686                    out_buf_size = new_out_buf_size;
1687                }
1688            } break;
1689
1690            case PHP_CONV_ERR_UNKNOWN:
1691                php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): unknown error", inst->filtername);
1692                goto out_failure;
1693
1694            default:
1695                if (ps == NULL) {
1696                    icnt = 0;
1697                }
1698                break;
1699        }
1700    }
1701
1702    if (out_buf_size > ocnt) {
1703        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
1704            goto out_failure;
1705        }
1706        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
1707    } else {
1708        pefree(out_buf, persistent);
1709    }
1710    *consumed += buf_len - icnt;
1711
1712    return SUCCESS;
1713
1714out_failure:
1715    pefree(out_buf, persistent);
1716    return FAILURE;
1717}
1718/* }}} */
1719
1720static php_stream_filter_status_t strfilter_convert_filter(
1721    php_stream *stream,
1722    php_stream_filter *thisfilter,
1723    php_stream_bucket_brigade *buckets_in,
1724    php_stream_bucket_brigade *buckets_out,
1725    size_t *bytes_consumed,
1726    int flags
1727    TSRMLS_DC)
1728{
1729    php_stream_bucket *bucket = NULL;
1730    size_t consumed = 0;
1731    php_convert_filter *inst = (php_convert_filter *)Z_PTR(thisfilter->abstract);
1732
1733    while (buckets_in->head != NULL) {
1734        bucket = buckets_in->head;
1735
1736        php_stream_bucket_unlink(bucket TSRMLS_CC);
1737
1738        if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1739                buckets_out, bucket->buf, bucket->buflen, &consumed,
1740                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
1741            goto out_failure;
1742        }
1743
1744        php_stream_bucket_delref(bucket TSRMLS_CC);
1745    }
1746
1747    if (flags != PSFS_FLAG_NORMAL) {
1748        if (strfilter_convert_append_bucket(inst, stream, thisfilter,
1749                buckets_out, NULL, 0, &consumed,
1750                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
1751            goto out_failure;
1752        }
1753    }
1754
1755    if (bytes_consumed) {
1756        *bytes_consumed = consumed;
1757    }
1758
1759    return PSFS_PASS_ON;
1760
1761out_failure:
1762    if (bucket != NULL) {
1763        php_stream_bucket_delref(bucket TSRMLS_CC);
1764    }
1765    return PSFS_ERR_FATAL;
1766}
1767
1768static void strfilter_convert_dtor(php_stream_filter *thisfilter TSRMLS_DC)
1769{
1770    assert(Z_PTR(thisfilter->abstract) != NULL);
1771
1772    php_convert_filter_dtor((php_convert_filter *)Z_PTR(thisfilter->abstract));
1773    pefree(Z_PTR(thisfilter->abstract), ((php_convert_filter *)Z_PTR(thisfilter->abstract))->persistent);
1774}
1775
1776static php_stream_filter_ops strfilter_convert_ops = {
1777    strfilter_convert_filter,
1778    strfilter_convert_dtor,
1779    "convert.*"
1780};
1781
1782static php_stream_filter *strfilter_convert_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
1783{
1784    php_convert_filter *inst;
1785    php_stream_filter *retval = NULL;
1786
1787    char *dot;
1788    int conv_mode = 0;
1789
1790    if (filterparams != NULL && Z_TYPE_P(filterparams) != IS_ARRAY) {
1791        php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream filter (%s): invalid filter parameter", filtername);
1792        return NULL;
1793    }
1794
1795    if ((dot = strchr(filtername, '.')) == NULL) {
1796        return NULL;
1797    }
1798    ++dot;
1799
1800    inst = pemalloc(sizeof(php_convert_filter), persistent);
1801
1802    if (strcasecmp(dot, "base64-encode") == 0) {
1803        conv_mode = PHP_CONV_BASE64_ENCODE;
1804    } else if (strcasecmp(dot, "base64-decode") == 0) {
1805        conv_mode = PHP_CONV_BASE64_DECODE;
1806    } else if (strcasecmp(dot, "quoted-printable-encode") == 0) {
1807        conv_mode = PHP_CONV_QPRINT_ENCODE;
1808    } else if (strcasecmp(dot, "quoted-printable-decode") == 0) {
1809        conv_mode = PHP_CONV_QPRINT_DECODE;
1810    }
1811
1812    if (php_convert_filter_ctor(inst, conv_mode,
1813        (filterparams != NULL ? Z_ARRVAL_P(filterparams) : NULL),
1814        filtername, persistent TSRMLS_CC) != SUCCESS) {
1815        goto out;
1816    }
1817
1818    retval = php_stream_filter_alloc(&strfilter_convert_ops, inst, persistent);
1819out:
1820    if (retval == NULL) {
1821        pefree(inst, persistent);
1822    }
1823
1824    return retval;
1825}
1826
1827static php_stream_filter_factory strfilter_convert_factory = {
1828    strfilter_convert_create
1829};
1830/* }}} */
1831
1832/* {{{ consumed filter implementation */
1833typedef struct _php_consumed_filter_data {
1834    size_t consumed;
1835    zend_off_t offset;
1836    int persistent;
1837} php_consumed_filter_data;
1838
1839static php_stream_filter_status_t consumed_filter_filter(
1840    php_stream *stream,
1841    php_stream_filter *thisfilter,
1842    php_stream_bucket_brigade *buckets_in,
1843    php_stream_bucket_brigade *buckets_out,
1844    size_t *bytes_consumed,
1845    int flags
1846    TSRMLS_DC)
1847{
1848    php_consumed_filter_data *data = (php_consumed_filter_data *)Z_PTR(thisfilter->abstract);
1849    php_stream_bucket *bucket;
1850    size_t consumed = 0;
1851
1852    if (data->offset == ~0) {
1853        data->offset = php_stream_tell(stream);
1854    }
1855    while ((bucket = buckets_in->head) != NULL) {
1856        php_stream_bucket_unlink(bucket TSRMLS_CC);
1857        consumed += bucket->buflen;
1858        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
1859    }
1860    if (bytes_consumed) {
1861        *bytes_consumed = consumed;
1862    }
1863    if (flags & PSFS_FLAG_FLUSH_CLOSE) {
1864        php_stream_seek(stream, data->offset + data->consumed, SEEK_SET);
1865    }
1866    data->consumed += consumed;
1867
1868    return PSFS_PASS_ON;
1869}
1870
1871static void consumed_filter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
1872{
1873    if (thisfilter && Z_PTR(thisfilter->abstract)) {
1874        php_consumed_filter_data *data = (php_consumed_filter_data*)Z_PTR(thisfilter->abstract);
1875        pefree(data, data->persistent);
1876    }
1877}
1878
1879static php_stream_filter_ops consumed_filter_ops = {
1880    consumed_filter_filter,
1881    consumed_filter_dtor,
1882    "consumed"
1883};
1884
1885static php_stream_filter *consumed_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
1886{
1887    php_stream_filter_ops *fops = NULL;
1888    php_consumed_filter_data *data;
1889
1890    if (strcasecmp(filtername, "consumed")) {
1891        return NULL;
1892    }
1893
1894    /* Create this filter */
1895    data = pecalloc(1, sizeof(php_consumed_filter_data), persistent);
1896    if (!data) {
1897        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", sizeof(php_consumed_filter_data));
1898        return NULL;
1899    }
1900    data->persistent = persistent;
1901    data->consumed = 0;
1902    data->offset = ~0;
1903    fops = &consumed_filter_ops;
1904
1905    return php_stream_filter_alloc(fops, data, persistent);
1906}
1907
1908php_stream_filter_factory consumed_filter_factory = {
1909    consumed_filter_create
1910};
1911
1912/* }}} */
1913
1914/* {{{ chunked filter implementation */
1915typedef enum _php_chunked_filter_state {
1916    CHUNK_SIZE_START,
1917    CHUNK_SIZE,
1918    CHUNK_SIZE_EXT,
1919    CHUNK_SIZE_CR,
1920    CHUNK_SIZE_LF,
1921    CHUNK_BODY,
1922    CHUNK_BODY_CR,
1923    CHUNK_BODY_LF,
1924    CHUNK_TRAILER,
1925    CHUNK_ERROR
1926} php_chunked_filter_state;
1927
1928typedef struct _php_chunked_filter_data {
1929    size_t chunk_size;
1930    php_chunked_filter_state state;
1931    int persistent;
1932} php_chunked_filter_data;
1933
1934static int php_dechunk(char *buf, int len, php_chunked_filter_data *data)
1935{
1936    char *p = buf;
1937    char *end = p + len;
1938    char *out = buf;
1939    int out_len = 0;
1940
1941    while (p < end) {
1942        switch (data->state) {
1943            case CHUNK_SIZE_START:
1944                data->chunk_size = 0;
1945            case CHUNK_SIZE:
1946                while (p < end) {
1947                    if (*p >= '0' && *p <= '9') {
1948                        data->chunk_size = (data->chunk_size * 16) + (*p - '0');
1949                    } else if (*p >= 'A' && *p <= 'F') {
1950                        data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10);
1951                    } else if (*p >= 'a' && *p <= 'f') {
1952                        data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10);
1953                    } else if (data->state == CHUNK_SIZE_START) {
1954                        data->state = CHUNK_ERROR;
1955                        break;
1956                    } else {
1957                        data->state = CHUNK_SIZE_EXT;
1958                        break;
1959                    }
1960                    data->state = CHUNK_SIZE;
1961                    p++;
1962                }
1963                if (data->state == CHUNK_ERROR) {
1964                    continue;
1965                } else if (p == end) {
1966                    return out_len;
1967                }
1968            case CHUNK_SIZE_EXT:
1969                /* skip extension */
1970                while (p < end && *p != '\r' && *p != '\n') {
1971                    p++;
1972                }
1973                if (p == end) {
1974                    return out_len;
1975                }
1976            case CHUNK_SIZE_CR:
1977                if (*p == '\r') {
1978                    p++;
1979                    if (p == end) {
1980                        data->state = CHUNK_SIZE_LF;
1981                        return out_len;
1982                    }
1983                }
1984            case CHUNK_SIZE_LF:
1985                if (*p == '\n') {
1986                    p++;
1987                    if (data->chunk_size == 0) {
1988                        /* last chunk */
1989                        data->state = CHUNK_TRAILER;
1990                        continue;
1991                    } else if (p == end) {
1992                        data->state = CHUNK_BODY;
1993                        return out_len;
1994                    }
1995                } else {
1996                    data->state = CHUNK_ERROR;
1997                    continue;
1998                }
1999            case CHUNK_BODY:
2000                if ((size_t) (end - p) >= data->chunk_size) {
2001                    if (p != out) {
2002                        memmove(out, p, data->chunk_size);
2003                    }
2004                    out += data->chunk_size;
2005                    out_len += data->chunk_size;
2006                    p += data->chunk_size;
2007                    if (p == end) {
2008                        data->state = CHUNK_BODY_CR;
2009                        return out_len;
2010                    }
2011                } else {
2012                    if (p != out) {
2013                        memmove(out, p, end - p);
2014                    }
2015                    data->chunk_size -= end - p;
2016                    data->state=CHUNK_BODY;
2017                    out_len += end - p;
2018                    return out_len;
2019                }
2020            case CHUNK_BODY_CR:
2021                if (*p == '\r') {
2022                    p++;
2023                    if (p == end) {
2024                        data->state = CHUNK_BODY_LF;
2025                        return out_len;
2026                    }
2027                }
2028            case CHUNK_BODY_LF:
2029                if (*p == '\n') {
2030                    p++;
2031                    data->state = CHUNK_SIZE_START;
2032                    continue;
2033                } else {
2034                    data->state = CHUNK_ERROR;
2035                    continue;
2036                }
2037            case CHUNK_TRAILER:
2038                /* ignore trailer */
2039                p = end;
2040                continue;
2041            case CHUNK_ERROR:
2042                if (p != out) {
2043                    memmove(out, p, end - p);
2044                }
2045                out_len += end - p;
2046                return out_len;
2047        }
2048    }
2049    return out_len;
2050}
2051
2052static php_stream_filter_status_t php_chunked_filter(
2053    php_stream *stream,
2054    php_stream_filter *thisfilter,
2055    php_stream_bucket_brigade *buckets_in,
2056    php_stream_bucket_brigade *buckets_out,
2057    size_t *bytes_consumed,
2058    int flags
2059    TSRMLS_DC)
2060{
2061    php_stream_bucket *bucket;
2062    size_t consumed = 0;
2063    php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2064
2065    while (buckets_in->head) {
2066        bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
2067        consumed += bucket->buflen;
2068        bucket->buflen = php_dechunk(bucket->buf, bucket->buflen, data);
2069        php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
2070    }
2071
2072    if (bytes_consumed) {
2073        *bytes_consumed = consumed;
2074    }
2075
2076    return PSFS_PASS_ON;
2077}
2078
2079static void php_chunked_dtor(php_stream_filter *thisfilter TSRMLS_DC)
2080{
2081    if (thisfilter && Z_PTR(thisfilter->abstract)) {
2082        php_chunked_filter_data *data = (php_chunked_filter_data *) Z_PTR(thisfilter->abstract);
2083        pefree(data, data->persistent);
2084    }
2085}
2086
2087static php_stream_filter_ops chunked_filter_ops = {
2088    php_chunked_filter,
2089    php_chunked_dtor,
2090    "dechunk"
2091};
2092
2093static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
2094{
2095    php_stream_filter_ops *fops = NULL;
2096    php_chunked_filter_data *data;
2097
2098    if (strcasecmp(filtername, "dechunk")) {
2099        return NULL;
2100    }
2101
2102    /* Create this filter */
2103    data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent);
2104    if (!data) {
2105        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", sizeof(php_chunked_filter_data));
2106        return NULL;
2107    }
2108    data->state = CHUNK_SIZE_START;
2109    data->chunk_size = 0;
2110    data->persistent = persistent;
2111    fops = &chunked_filter_ops;
2112
2113    return php_stream_filter_alloc(fops, data, persistent);
2114}
2115
2116static php_stream_filter_factory chunked_filter_factory = {
2117    chunked_filter_create
2118};
2119/* }}} */
2120
2121static const struct {
2122    php_stream_filter_ops *ops;
2123    php_stream_filter_factory *factory;
2124} standard_filters[] = {
2125    { &strfilter_rot13_ops, &strfilter_rot13_factory },
2126    { &strfilter_toupper_ops, &strfilter_toupper_factory },
2127    { &strfilter_tolower_ops, &strfilter_tolower_factory },
2128    { &strfilter_strip_tags_ops, &strfilter_strip_tags_factory },
2129    { &strfilter_convert_ops, &strfilter_convert_factory },
2130    { &consumed_filter_ops, &consumed_filter_factory },
2131    { &chunked_filter_ops, &chunked_filter_factory },
2132    /* additional filters to go here */
2133    { NULL, NULL }
2134};
2135
2136/* {{{ filter MINIT and MSHUTDOWN */
2137PHP_MINIT_FUNCTION(standard_filters)
2138{
2139    int i;
2140
2141    for (i = 0; standard_filters[i].ops; i++) {
2142        if (FAILURE == php_stream_filter_register_factory(
2143                    standard_filters[i].ops->label,
2144                    standard_filters[i].factory
2145                    TSRMLS_CC)) {
2146            return FAILURE;
2147        }
2148    }
2149    return SUCCESS;
2150}
2151
2152PHP_MSHUTDOWN_FUNCTION(standard_filters)
2153{
2154    int i;
2155
2156    for (i = 0; standard_filters[i].ops; i++) {
2157        php_stream_filter_unregister_factory(standard_filters[i].ops->label TSRMLS_CC);
2158    }
2159    return SUCCESS;
2160}
2161/* }}} */
2162
2163/*
2164 * Local variables:
2165 * tab-width: 4
2166 * c-basic-offset: 4
2167 * End:
2168 * vim600: sw=4 ts=4 fdm=marker
2169 * vim<600: sw=4 ts=4
2170 */
2171