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