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