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: Rui Hirokawa <rui_hirokawa@ybb.ne.jp>                       |
16   |          Stig Bakken <ssb@php.net>                                   |
17   |          Moriyoshi Koizumi <moriyoshi@php.net>                       |
18   +----------------------------------------------------------------------+
19 */
20
21/* $Id$ */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include "php.h"
28#include "php_globals.h"
29#include "ext/standard/info.h"
30#include "main/php_output.h"
31#include "SAPI.h"
32#include "php_ini.h"
33
34#ifdef HAVE_STDLIB_H
35# include <stdlib.h>
36#endif
37
38#include <errno.h>
39
40#include "php_iconv.h"
41
42#ifdef HAVE_ICONV
43
44#ifdef PHP_ICONV_H_PATH
45#include PHP_ICONV_H_PATH
46#else
47#include <iconv.h>
48#endif
49
50#ifdef HAVE_GLIBC_ICONV
51#include <gnu/libc-version.h>
52#endif
53
54#ifdef HAVE_LIBICONV
55#undef iconv
56#endif
57
58#include "ext/standard/php_smart_str.h"
59#include "ext/standard/base64.h"
60#include "ext/standard/quot_print.h"
61
62#define _php_iconv_memequal(a, b, c) \
63  ((c) == sizeof(unsigned long) ? *((unsigned long *)(a)) == *((unsigned long *)(b)) : ((c) == sizeof(unsigned int) ? *((unsigned int *)(a)) == *((unsigned int *)(b)) : memcmp(a, b, c) == 0))
64
65/* {{{ arginfo */
66ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strlen, 0, 0, 1)
67    ZEND_ARG_INFO(0, str)
68    ZEND_ARG_INFO(0, charset)
69ZEND_END_ARG_INFO()
70
71ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_substr, 0, 0, 2)
72    ZEND_ARG_INFO(0, str)
73    ZEND_ARG_INFO(0, offset)
74    ZEND_ARG_INFO(0, length)
75    ZEND_ARG_INFO(0, charset)
76ZEND_END_ARG_INFO()
77
78ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strpos, 0, 0, 2)
79    ZEND_ARG_INFO(0, haystack)
80    ZEND_ARG_INFO(0, needle)
81    ZEND_ARG_INFO(0, offset)
82    ZEND_ARG_INFO(0, charset)
83ZEND_END_ARG_INFO()
84
85ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strrpos, 0, 0, 2)
86    ZEND_ARG_INFO(0, haystack)
87    ZEND_ARG_INFO(0, needle)
88    ZEND_ARG_INFO(0, charset)
89ZEND_END_ARG_INFO()
90
91ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_encode, 0, 0, 2)
92    ZEND_ARG_INFO(0, field_name)
93    ZEND_ARG_INFO(0, field_value)
94    ZEND_ARG_INFO(0, preference) /* ZEND_ARG_ARRAY_INFO(0, preference, 1) */
95ZEND_END_ARG_INFO()
96
97ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode, 0, 0, 1)
98    ZEND_ARG_INFO(0, encoded_string)
99    ZEND_ARG_INFO(0, mode)
100    ZEND_ARG_INFO(0, charset)
101ZEND_END_ARG_INFO()
102
103ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode_headers, 0, 0, 1)
104    ZEND_ARG_INFO(0, headers)
105    ZEND_ARG_INFO(0, mode)
106    ZEND_ARG_INFO(0, charset)
107ZEND_END_ARG_INFO()
108
109ZEND_BEGIN_ARG_INFO(arginfo_iconv, 0)
110    ZEND_ARG_INFO(0, in_charset)
111    ZEND_ARG_INFO(0, out_charset)
112    ZEND_ARG_INFO(0, str)
113ZEND_END_ARG_INFO()
114
115ZEND_BEGIN_ARG_INFO(arginfo_ob_iconv_handler, 0)
116    ZEND_ARG_INFO(0, contents)
117    ZEND_ARG_INFO(0, status)
118ZEND_END_ARG_INFO()
119
120ZEND_BEGIN_ARG_INFO(arginfo_iconv_set_encoding, 0)
121    ZEND_ARG_INFO(0, type)
122    ZEND_ARG_INFO(0, charset)
123ZEND_END_ARG_INFO()
124
125ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_get_encoding, 0, 0, 0)
126    ZEND_ARG_INFO(0, type)
127ZEND_END_ARG_INFO()
128
129/* }}} */
130
131/* {{{ iconv_functions[]
132 */
133const zend_function_entry iconv_functions[] = {
134    PHP_RAW_NAMED_FE(iconv,php_if_iconv,                arginfo_iconv)
135    PHP_FE(ob_iconv_handler,                        arginfo_ob_iconv_handler)
136    PHP_FE(iconv_get_encoding,                      arginfo_iconv_get_encoding)
137    PHP_FE(iconv_set_encoding,                      arginfo_iconv_set_encoding)
138    PHP_FE(iconv_strlen,                            arginfo_iconv_strlen)
139    PHP_FE(iconv_substr,                            arginfo_iconv_substr)
140    PHP_FE(iconv_strpos,                            arginfo_iconv_strpos)
141    PHP_FE(iconv_strrpos,                           arginfo_iconv_strrpos)
142    PHP_FE(iconv_mime_encode,                       arginfo_iconv_mime_encode)
143    PHP_FE(iconv_mime_decode,                       arginfo_iconv_mime_decode)
144    PHP_FE(iconv_mime_decode_headers,               arginfo_iconv_mime_decode_headers)
145    PHP_FE_END
146};
147/* }}} */
148
149ZEND_DECLARE_MODULE_GLOBALS(iconv)
150static PHP_GINIT_FUNCTION(iconv);
151
152/* {{{ iconv_module_entry
153 */
154zend_module_entry iconv_module_entry = {
155    STANDARD_MODULE_HEADER,
156    "iconv",
157    iconv_functions,
158    PHP_MINIT(miconv),
159    PHP_MSHUTDOWN(miconv),
160    NULL,
161    NULL,
162    PHP_MINFO(miconv),
163    NO_VERSION_YET,
164    PHP_MODULE_GLOBALS(iconv),
165    PHP_GINIT(iconv),
166    NULL,
167    NULL,
168    STANDARD_MODULE_PROPERTIES_EX
169};
170/* }}} */
171
172#ifdef COMPILE_DL_ICONV
173ZEND_GET_MODULE(iconv)
174#endif
175
176/* {{{ PHP_GINIT_FUNCTION */
177static PHP_GINIT_FUNCTION(iconv)
178{
179    iconv_globals->input_encoding = NULL;
180    iconv_globals->output_encoding = NULL;
181    iconv_globals->internal_encoding = NULL;
182}
183/* }}} */
184
185#if defined(HAVE_LIBICONV) && defined(ICONV_ALIASED_LIBICONV)
186#define iconv libiconv
187#endif
188
189/* {{{ typedef enum php_iconv_enc_scheme_t */
190typedef enum _php_iconv_enc_scheme_t {
191    PHP_ICONV_ENC_SCHEME_BASE64,
192    PHP_ICONV_ENC_SCHEME_QPRINT
193} php_iconv_enc_scheme_t;
194/* }}} */
195
196#define PHP_ICONV_MIME_DECODE_STRICT            (1<<0)
197#define PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR (1<<1)
198
199/* {{{ prototypes */
200static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd);
201static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd);
202
203static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset TSRMLS_DC);
204
205static php_iconv_err_t _php_iconv_strlen(unsigned int *pretval, const char *str, size_t nbytes, const char *enc);
206
207static php_iconv_err_t _php_iconv_substr(smart_str *pretval, const char *str, size_t nbytes, int offset, int len, const char *enc);
208
209static php_iconv_err_t _php_iconv_strpos(unsigned int *pretval, const char *haystk, size_t haystk_nbytes, const char *ndl, size_t ndl_nbytes, int offset, const char *enc);
210
211static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, unsigned int max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc);
212
213static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode);
214
215static php_iconv_err_t php_iconv_stream_filter_register_factory(TSRMLS_D);
216static php_iconv_err_t php_iconv_stream_filter_unregister_factory(TSRMLS_D);
217/* }}} */
218
219/* {{{ static globals */
220static char _generic_superset_name[] = ICONV_UCS4_ENCODING;
221#define GENERIC_SUPERSET_NAME _generic_superset_name
222#define GENERIC_SUPERSET_NBYTES 4
223/* }}} */
224
225static PHP_INI_MH(OnUpdateStringIconvCharset)
226{
227    if(new_value_length >= ICONV_CSNMAXLEN) {
228        return FAILURE;
229    }
230    OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
231    return SUCCESS;
232}
233
234/* {{{ PHP_INI
235 */
236PHP_INI_BEGIN()
237    STD_PHP_INI_ENTRY("iconv.input_encoding",    ICONV_INPUT_ENCODING,    PHP_INI_ALL, OnUpdateStringIconvCharset, input_encoding,    zend_iconv_globals, iconv_globals)
238    STD_PHP_INI_ENTRY("iconv.output_encoding",   ICONV_OUTPUT_ENCODING,   PHP_INI_ALL, OnUpdateStringIconvCharset, output_encoding,   zend_iconv_globals, iconv_globals)
239    STD_PHP_INI_ENTRY("iconv.internal_encoding", ICONV_INTERNAL_ENCODING, PHP_INI_ALL, OnUpdateStringIconvCharset, internal_encoding, zend_iconv_globals, iconv_globals)
240PHP_INI_END()
241/* }}} */
242
243/* {{{ PHP_MINIT_FUNCTION */
244PHP_MINIT_FUNCTION(miconv)
245{
246    char *version = "unknown";
247
248    REGISTER_INI_ENTRIES();
249
250#if HAVE_LIBICONV
251    {
252        static char buf[16];
253        snprintf(buf, sizeof(buf), "%d.%d",
254            ((_libiconv_version >> 8) & 0x0f), (_libiconv_version & 0x0f));
255        version = buf;
256    }
257#elif HAVE_GLIBC_ICONV
258    version = (char *)gnu_get_libc_version();
259#elif defined(NETWARE)
260    version = "OS built-in";
261#endif
262
263#ifdef PHP_ICONV_IMPL
264    REGISTER_STRING_CONSTANT("ICONV_IMPL", PHP_ICONV_IMPL, CONST_CS | CONST_PERSISTENT);
265#elif HAVE_LIBICONV
266    REGISTER_STRING_CONSTANT("ICONV_IMPL", "libiconv", CONST_CS | CONST_PERSISTENT);
267#elif defined(NETWARE)
268    REGISTER_STRING_CONSTANT("ICONV_IMPL", "Novell", CONST_CS | CONST_PERSISTENT);
269#else
270    REGISTER_STRING_CONSTANT("ICONV_IMPL", "unknown", CONST_CS | CONST_PERSISTENT);
271#endif
272    REGISTER_STRING_CONSTANT("ICONV_VERSION", version, CONST_CS | CONST_PERSISTENT);
273
274    REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_STRICT", PHP_ICONV_MIME_DECODE_STRICT, CONST_CS | CONST_PERSISTENT);
275    REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_CONTINUE_ON_ERROR", PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR, CONST_CS | CONST_PERSISTENT);
276
277    if (php_iconv_stream_filter_register_factory(TSRMLS_C) != PHP_ICONV_ERR_SUCCESS) {
278        return FAILURE;
279    }
280
281    return SUCCESS;
282}
283/* }}} */
284
285/* {{{ PHP_MSHUTDOWN_FUNCTION */
286PHP_MSHUTDOWN_FUNCTION(miconv)
287{
288    php_iconv_stream_filter_unregister_factory(TSRMLS_C);
289    UNREGISTER_INI_ENTRIES();
290    return SUCCESS;
291}
292/* }}} */
293
294/* {{{ PHP_MINFO_FUNCTION */
295PHP_MINFO_FUNCTION(miconv)
296{
297    zval iconv_impl, iconv_ver;
298
299    zend_get_constant("ICONV_IMPL", sizeof("ICONV_IMPL")-1, &iconv_impl TSRMLS_CC);
300    zend_get_constant("ICONV_VERSION", sizeof("ICONV_VERSION")-1, &iconv_ver TSRMLS_CC);
301
302    php_info_print_table_start();
303    php_info_print_table_row(2, "iconv support", "enabled");
304    php_info_print_table_row(2, "iconv implementation", Z_STRVAL(iconv_impl));
305    php_info_print_table_row(2, "iconv library version", Z_STRVAL(iconv_ver));
306    php_info_print_table_end();
307
308    DISPLAY_INI_ENTRIES();
309
310    zval_dtor(&iconv_impl);
311    zval_dtor(&iconv_ver);
312}
313/* }}} */
314
315/* {{{ _php_iconv_appendl() */
316static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd)
317{
318    const char *in_p = s;
319    size_t in_left = l;
320    char *out_p;
321    size_t out_left = 0;
322    size_t buf_growth = 128;
323#if !ICONV_SUPPORTS_ERRNO
324    size_t prev_in_left = in_left;
325#endif
326
327    if (in_p != NULL) {
328        while (in_left > 0) {
329            out_left = buf_growth - out_left;
330            {
331                size_t newlen;
332                smart_str_alloc((d), out_left, 0);
333            }
334
335            out_p = (d)->c + (d)->len;
336
337            if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
338#if ICONV_SUPPORTS_ERRNO
339                switch (errno) {
340                    case EINVAL:
341                        return PHP_ICONV_ERR_ILLEGAL_CHAR;
342
343                    case EILSEQ:
344                        return PHP_ICONV_ERR_ILLEGAL_SEQ;
345
346                    case E2BIG:
347                        break;
348
349                    default:
350                        return PHP_ICONV_ERR_UNKNOWN;
351                }
352#else
353                if (prev_in_left == in_left) {
354                    return PHP_ICONV_ERR_UNKNOWN;
355                }
356#endif
357            }
358#if !ICONV_SUPPORTS_ERRNO
359            prev_in_left = in_left;
360#endif
361            (d)->len += (buf_growth - out_left);
362            buf_growth <<= 1;
363        }
364    } else {
365        for (;;) {
366            out_left = buf_growth - out_left;
367            {
368                size_t newlen;
369                smart_str_alloc((d), out_left, 0);
370            }
371
372            out_p = (d)->c + (d)->len;
373
374            if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)0) {
375                (d)->len += (buf_growth - out_left);
376                break;
377            } else {
378#if ICONV_SUPPORTS_ERRNO
379                if (errno != E2BIG) {
380                    return PHP_ICONV_ERR_UNKNOWN;
381                }
382#else
383                if (out_left != 0) {
384                    return PHP_ICONV_ERR_UNKNOWN;
385                }
386#endif
387            }
388            (d)->len += (buf_growth - out_left);
389            buf_growth <<= 1;
390        }
391    }
392    return PHP_ICONV_ERR_SUCCESS;
393}
394/* }}} */
395
396/* {{{ _php_iconv_appendc() */
397static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd)
398{
399    return _php_iconv_appendl(d, &c, 1, cd);
400}
401/* }}} */
402
403/* {{{ php_iconv_string()
404 */
405PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len,
406                            char **out, size_t *out_len,
407                            const char *out_charset, const char *in_charset)
408{
409#if !ICONV_SUPPORTS_ERRNO
410    size_t in_size, out_size, out_left;
411    char *out_buffer, *out_p;
412    iconv_t cd;
413    size_t result;
414
415    *out = NULL;
416    *out_len = 0;
417
418    /*
419      This is not the right way to get output size...
420      This is not space efficient for large text.
421      This is also problem for encoding like UTF-7/UTF-8/ISO-2022 which
422      a single char can be more than 4 bytes.
423      I added 15 extra bytes for safety. <yohgaki@php.net>
424    */
425    out_size = in_len * sizeof(int) + 15;
426    out_left = out_size;
427
428    in_size = in_len;
429
430    cd = iconv_open(out_charset, in_charset);
431
432    if (cd == (iconv_t)(-1)) {
433        return PHP_ICONV_ERR_UNKNOWN;
434    }
435
436    out_buffer = (char *) emalloc(out_size + 1);
437    out_p = out_buffer;
438
439#ifdef NETWARE
440    result = iconv(cd, (char **) &in_p, &in_size, (char **)
441#else
442    result = iconv(cd, (const char **) &in_p, &in_size, (char **)
443#endif
444                &out_p, &out_left);
445
446    if (result == (size_t)(-1)) {
447        efree(out_buffer);
448        return PHP_ICONV_ERR_UNKNOWN;
449    }
450
451    if (out_left < 8) {
452        out_buffer = (char *) erealloc(out_buffer, out_size + 8);
453    }
454
455    /* flush the shift-out sequences */
456    result = iconv(cd, NULL, NULL, &out_p, &out_left);
457
458    if (result == (size_t)(-1)) {
459        efree(out_buffer);
460        return PHP_ICONV_ERR_UNKNOWN;
461    }
462
463    *out_len = out_size - out_left;
464    out_buffer[*out_len] = '\0';
465    *out = out_buffer;
466
467    iconv_close(cd);
468
469    return PHP_ICONV_ERR_SUCCESS;
470
471#else
472    /*
473      iconv supports errno. Handle it better way.
474    */
475    iconv_t cd;
476    size_t in_left, out_size, out_left;
477    char *out_p, *out_buf, *tmp_buf;
478    size_t bsz, result = 0;
479    php_iconv_err_t retval = PHP_ICONV_ERR_SUCCESS;
480
481    *out = NULL;
482    *out_len = 0;
483
484    cd = iconv_open(out_charset, in_charset);
485
486    if (cd == (iconv_t)(-1)) {
487        if (errno == EINVAL) {
488            return PHP_ICONV_ERR_WRONG_CHARSET;
489        } else {
490            return PHP_ICONV_ERR_CONVERTER;
491        }
492    }
493    in_left= in_len;
494    out_left = in_len + 32; /* Avoid realloc() most cases */
495    out_size = 0;
496    bsz = out_left;
497    out_buf = (char *) emalloc(bsz+1);
498    out_p = out_buf;
499
500    while (in_left > 0) {
501        result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left);
502        out_size = bsz - out_left;
503        if (result == (size_t)(-1)) {
504            if (errno == E2BIG && in_left > 0) {
505                /* converted string is longer than out buffer */
506                bsz += in_len;
507
508                tmp_buf = (char*) erealloc(out_buf, bsz+1);
509                out_p = out_buf = tmp_buf;
510                out_p += out_size;
511                out_left = bsz - out_size;
512                continue;
513            }
514        }
515        break;
516    }
517
518    if (result != (size_t)(-1)) {
519        /* flush the shift-out sequences */
520        for (;;) {
521            result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
522            out_size = bsz - out_left;
523
524            if (result != (size_t)(-1)) {
525                break;
526            }
527
528            if (errno == E2BIG) {
529                bsz += 16;
530                tmp_buf = (char *) erealloc(out_buf, bsz);
531
532                out_p = out_buf = tmp_buf;
533                out_p += out_size;
534                out_left = bsz - out_size;
535            } else {
536                break;
537            }
538        }
539    }
540
541    iconv_close(cd);
542
543    if (result == (size_t)(-1)) {
544        switch (errno) {
545            case EINVAL:
546                retval = PHP_ICONV_ERR_ILLEGAL_CHAR;
547                break;
548
549            case EILSEQ:
550                retval = PHP_ICONV_ERR_ILLEGAL_SEQ;
551                break;
552
553            case E2BIG:
554                /* should not happen */
555                retval = PHP_ICONV_ERR_TOO_BIG;
556                break;
557
558            default:
559                /* other error */
560                retval = PHP_ICONV_ERR_UNKNOWN;
561                efree(out_buf);
562                return PHP_ICONV_ERR_UNKNOWN;
563        }
564    }
565    *out_p = '\0';
566    *out = out_buf;
567    *out_len = out_size;
568    return retval;
569#endif
570}
571/* }}} */
572
573/* {{{ _php_iconv_strlen() */
574static php_iconv_err_t _php_iconv_strlen(unsigned int *pretval, const char *str, size_t nbytes, const char *enc)
575{
576    char buf[GENERIC_SUPERSET_NBYTES*2];
577
578    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
579
580    iconv_t cd;
581
582    const char *in_p;
583    size_t in_left;
584
585    char *out_p;
586    size_t out_left;
587
588    unsigned int cnt;
589
590    *pretval = (unsigned int)-1;
591
592    cd = iconv_open(GENERIC_SUPERSET_NAME, enc);
593
594    if (cd == (iconv_t)(-1)) {
595#if ICONV_SUPPORTS_ERRNO
596        if (errno == EINVAL) {
597            return PHP_ICONV_ERR_WRONG_CHARSET;
598        } else {
599            return PHP_ICONV_ERR_CONVERTER;
600        }
601#else
602        return PHP_ICONV_ERR_UNKNOWN;
603#endif
604    }
605
606    errno = out_left = 0;
607
608    for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0; cnt+=2) {
609        size_t prev_in_left;
610        out_p = buf;
611        out_left = sizeof(buf);
612
613        prev_in_left = in_left;
614
615        if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
616            if (prev_in_left == in_left) {
617                break;
618            }
619        }
620    }
621
622    if (out_left > 0) {
623        cnt -= out_left / GENERIC_SUPERSET_NBYTES;
624    }
625
626#if ICONV_SUPPORTS_ERRNO
627    switch (errno) {
628        case EINVAL:
629            err = PHP_ICONV_ERR_ILLEGAL_CHAR;
630            break;
631
632        case EILSEQ:
633            err = PHP_ICONV_ERR_ILLEGAL_SEQ;
634            break;
635
636        case E2BIG:
637        case 0:
638            *pretval = cnt;
639            break;
640
641        default:
642            err = PHP_ICONV_ERR_UNKNOWN;
643            break;
644    }
645#else
646    *pretval = cnt;
647#endif
648
649    iconv_close(cd);
650
651    return err;
652}
653
654/* }}} */
655
656/* {{{ _php_iconv_substr() */
657static php_iconv_err_t _php_iconv_substr(smart_str *pretval,
658    const char *str, size_t nbytes, int offset, int len, const char *enc)
659{
660    char buf[GENERIC_SUPERSET_NBYTES];
661
662    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
663
664    iconv_t cd1, cd2;
665
666    const char *in_p;
667    size_t in_left;
668
669    char *out_p;
670    size_t out_left;
671
672    unsigned int cnt;
673    int total_len;
674
675    err = _php_iconv_strlen(&total_len, str, nbytes, enc);
676    if (err != PHP_ICONV_ERR_SUCCESS) {
677        return err;
678    }
679
680    if (len < 0) {
681        if ((len += (total_len - offset)) < 0) {
682            return PHP_ICONV_ERR_SUCCESS;
683        }
684    }
685
686    if (offset < 0) {
687        if ((offset += total_len) < 0) {
688            return PHP_ICONV_ERR_SUCCESS;
689        }
690    }
691
692    if(len > total_len) {
693        len = total_len;
694    }
695
696
697    if (offset >= total_len) {
698        return PHP_ICONV_ERR_SUCCESS;
699    }
700
701    if ((offset + len) > total_len ) {
702        /* trying to compute the length */
703        len = total_len - offset;
704    }
705
706    if (len == 0) {
707        smart_str_appendl(pretval, "", 0);
708        smart_str_0(pretval);
709        return PHP_ICONV_ERR_SUCCESS;
710    }
711
712    cd1 = iconv_open(GENERIC_SUPERSET_NAME, enc);
713
714    if (cd1 == (iconv_t)(-1)) {
715#if ICONV_SUPPORTS_ERRNO
716        if (errno == EINVAL) {
717            return PHP_ICONV_ERR_WRONG_CHARSET;
718        } else {
719            return PHP_ICONV_ERR_CONVERTER;
720        }
721#else
722        return PHP_ICONV_ERR_UNKNOWN;
723#endif
724    }
725
726    cd2 = (iconv_t)NULL;
727    errno = 0;
728
729    for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0 && len > 0; ++cnt) {
730        size_t prev_in_left;
731        out_p = buf;
732        out_left = sizeof(buf);
733
734        prev_in_left = in_left;
735
736        if (iconv(cd1, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
737            if (prev_in_left == in_left) {
738                break;
739            }
740        }
741
742        if (cnt >= (unsigned int)offset) {
743            if (cd2 == (iconv_t)NULL) {
744                cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME);
745
746                if (cd2 == (iconv_t)(-1)) {
747                    cd2 = (iconv_t)NULL;
748#if ICONV_SUPPORTS_ERRNO
749                    if (errno == EINVAL) {
750                        err = PHP_ICONV_ERR_WRONG_CHARSET;
751                    } else {
752                        err = PHP_ICONV_ERR_CONVERTER;
753                    }
754#else
755                    err = PHP_ICONV_ERR_UNKNOWN;
756#endif
757                    break;
758                }
759            }
760
761            if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) {
762                break;
763            }
764            --len;
765        }
766
767    }
768
769#if ICONV_SUPPORTS_ERRNO
770    switch (errno) {
771        case EINVAL:
772            err = PHP_ICONV_ERR_ILLEGAL_CHAR;
773            break;
774
775        case EILSEQ:
776            err = PHP_ICONV_ERR_ILLEGAL_SEQ;
777            break;
778
779        case E2BIG:
780            break;
781    }
782#endif
783    if (err == PHP_ICONV_ERR_SUCCESS) {
784        if (cd2 != (iconv_t)NULL) {
785            _php_iconv_appendl(pretval, NULL, 0, cd2);
786        }
787        smart_str_0(pretval);
788    }
789
790    if (cd1 != (iconv_t)NULL) {
791        iconv_close(cd1);
792    }
793
794    if (cd2 != (iconv_t)NULL) {
795        iconv_close(cd2);
796    }
797    return err;
798}
799
800/* }}} */
801
802/* {{{ _php_iconv_strpos() */
803static php_iconv_err_t _php_iconv_strpos(unsigned int *pretval,
804    const char *haystk, size_t haystk_nbytes,
805    const char *ndl, size_t ndl_nbytes,
806    int offset, const char *enc)
807{
808    char buf[GENERIC_SUPERSET_NBYTES];
809
810    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
811
812    iconv_t cd;
813
814    const char *in_p;
815    size_t in_left;
816
817    char *out_p;
818    size_t out_left;
819
820    unsigned int cnt;
821
822    char *ndl_buf;
823    const char *ndl_buf_p;
824    size_t ndl_buf_len, ndl_buf_left;
825
826    unsigned int match_ofs;
827
828    *pretval = (unsigned int)-1;
829
830    err = php_iconv_string(ndl, ndl_nbytes,
831        &ndl_buf, &ndl_buf_len, GENERIC_SUPERSET_NAME, enc);
832
833    if (err != PHP_ICONV_ERR_SUCCESS) {
834        if (ndl_buf != NULL) {
835            efree(ndl_buf);
836        }
837        return err;
838    }
839
840    cd = iconv_open(GENERIC_SUPERSET_NAME, enc);
841
842    if (cd == (iconv_t)(-1)) {
843        if (ndl_buf != NULL) {
844            efree(ndl_buf);
845        }
846#if ICONV_SUPPORTS_ERRNO
847        if (errno == EINVAL) {
848            return PHP_ICONV_ERR_WRONG_CHARSET;
849        } else {
850            return PHP_ICONV_ERR_CONVERTER;
851        }
852#else
853        return PHP_ICONV_ERR_UNKNOWN;
854#endif
855    }
856
857    ndl_buf_p = ndl_buf;
858    ndl_buf_left = ndl_buf_len;
859    match_ofs = (unsigned int)-1;
860
861    for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; in_left > 0; ++cnt) {
862        size_t prev_in_left;
863        out_p = buf;
864        out_left = sizeof(buf);
865
866        prev_in_left = in_left;
867
868        if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
869            if (prev_in_left == in_left) {
870#if ICONV_SUPPORTS_ERRNO
871                switch (errno) {
872                    case EINVAL:
873                        err = PHP_ICONV_ERR_ILLEGAL_CHAR;
874                        break;
875
876                    case EILSEQ:
877                        err = PHP_ICONV_ERR_ILLEGAL_SEQ;
878                        break;
879
880                    case E2BIG:
881                        break;
882
883                    default:
884                        err = PHP_ICONV_ERR_UNKNOWN;
885                        break;
886                }
887#endif
888                break;
889            }
890        }
891        if (offset >= 0) {
892            if (cnt >= (unsigned int)offset) {
893                if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) {
894                    if (match_ofs == (unsigned int)-1) {
895                        match_ofs = cnt;
896                    }
897                    ndl_buf_p += GENERIC_SUPERSET_NBYTES;
898                    ndl_buf_left -= GENERIC_SUPERSET_NBYTES;
899                    if (ndl_buf_left == 0) {
900                        *pretval = match_ofs;
901                        break;
902                    }
903                } else {
904                    unsigned int i, j, lim;
905
906                    i = 0;
907                    j = GENERIC_SUPERSET_NBYTES;
908                    lim = (unsigned int)(ndl_buf_p - ndl_buf);
909
910                    while (j < lim) {
911                        if (_php_iconv_memequal(&ndl_buf[j], &ndl_buf[i],
912                                   GENERIC_SUPERSET_NBYTES)) {
913                            i += GENERIC_SUPERSET_NBYTES;
914                        } else {
915                            j -= i;
916                            i = 0;
917                        }
918                        j += GENERIC_SUPERSET_NBYTES;
919                    }
920
921                    if (_php_iconv_memequal(buf, &ndl_buf[i], sizeof(buf))) {
922                        match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES;
923                        i += GENERIC_SUPERSET_NBYTES;
924                        ndl_buf_p = &ndl_buf[i];
925                        ndl_buf_left = ndl_buf_len - i;
926                    } else {
927                        match_ofs = (unsigned int)-1;
928                        ndl_buf_p = ndl_buf;
929                        ndl_buf_left = ndl_buf_len;
930                    }
931                }
932            }
933        } else {
934            if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) {
935                if (match_ofs == (unsigned int)-1) {
936                    match_ofs = cnt;
937                }
938                ndl_buf_p += GENERIC_SUPERSET_NBYTES;
939                ndl_buf_left -= GENERIC_SUPERSET_NBYTES;
940                if (ndl_buf_left == 0) {
941                    *pretval = match_ofs;
942                    ndl_buf_p = ndl_buf;
943                    ndl_buf_left = ndl_buf_len;
944                    match_ofs = -1;
945                }
946            } else {
947                unsigned int i, j, lim;
948
949                i = 0;
950                j = GENERIC_SUPERSET_NBYTES;
951                lim = (unsigned int)(ndl_buf_p - ndl_buf);
952
953                while (j < lim) {
954                    if (_php_iconv_memequal(&ndl_buf[j], &ndl_buf[i],
955                               GENERIC_SUPERSET_NBYTES)) {
956                        i += GENERIC_SUPERSET_NBYTES;
957                    } else {
958                        j -= i;
959                        i = 0;
960                    }
961                    j += GENERIC_SUPERSET_NBYTES;
962                }
963
964                if (_php_iconv_memequal(buf, &ndl_buf[i], sizeof(buf))) {
965                    match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES;
966                    i += GENERIC_SUPERSET_NBYTES;
967                    ndl_buf_p = &ndl_buf[i];
968                    ndl_buf_left = ndl_buf_len - i;
969                } else {
970                    match_ofs = (unsigned int)-1;
971                    ndl_buf_p = ndl_buf;
972                    ndl_buf_left = ndl_buf_len;
973                }
974            }
975        }
976    }
977
978    if (ndl_buf) {
979        efree(ndl_buf);
980    }
981
982    iconv_close(cd);
983
984    return err;
985}
986/* }}} */
987
988/* {{{ _php_iconv_mime_encode() */
989static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, unsigned int max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc)
990{
991    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
992    iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1);
993    unsigned int char_cnt = 0;
994    size_t out_charset_len;
995    size_t lfchars_len;
996    char *buf = NULL;
997    char *encoded = NULL;
998    size_t encoded_len;
999    const char *in_p;
1000    size_t in_left;
1001    char *out_p;
1002    size_t out_left;
1003    static int qp_table[256] = {
1004        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x00 */
1005        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 */
1006        3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x20 */
1007        1, 1, 1, 1, 1, 1, 1 ,1, 1, 1, 1, 1, 1, 3, 1, 3, /* 0x30 */
1008        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 */
1009        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x50 */
1010        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 */
1011        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x70 */
1012        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x80 */
1013        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x90 */
1014        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xA0 */
1015        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xB0 */
1016        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xC0 */
1017        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xD0 */
1018        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xE0 */
1019        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3  /* 0xF0 */
1020    };
1021
1022    out_charset_len = strlen(out_charset);
1023    lfchars_len = strlen(lfchars);
1024
1025    if ((fname_nbytes + 2) >= max_line_len
1026        || (out_charset_len + 12) >= max_line_len) {
1027        /* field name is too long */
1028        err = PHP_ICONV_ERR_TOO_BIG;
1029        goto out;
1030    }
1031
1032    cd_pl = iconv_open(ICONV_ASCII_ENCODING, enc);
1033    if (cd_pl == (iconv_t)(-1)) {
1034#if ICONV_SUPPORTS_ERRNO
1035        if (errno == EINVAL) {
1036            err = PHP_ICONV_ERR_WRONG_CHARSET;
1037        } else {
1038            err = PHP_ICONV_ERR_CONVERTER;
1039        }
1040#else
1041        err = PHP_ICONV_ERR_UNKNOWN;
1042#endif
1043        goto out;
1044    }
1045
1046    cd = iconv_open(out_charset, enc);
1047    if (cd == (iconv_t)(-1)) {
1048#if ICONV_SUPPORTS_ERRNO
1049        if (errno == EINVAL) {
1050            err = PHP_ICONV_ERR_WRONG_CHARSET;
1051        } else {
1052            err = PHP_ICONV_ERR_CONVERTER;
1053        }
1054#else
1055        err = PHP_ICONV_ERR_UNKNOWN;
1056#endif
1057        goto out;
1058    }
1059
1060    buf = safe_emalloc(1, max_line_len, 5);
1061
1062    char_cnt = max_line_len;
1063
1064    _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl);
1065    char_cnt -= fname_nbytes;
1066    smart_str_appendl(pretval, ": ", sizeof(": ") - 1);
1067    char_cnt -= 2;
1068
1069    in_p = fval;
1070    in_left = fval_nbytes;
1071
1072    do {
1073        size_t prev_in_left;
1074        size_t out_size;
1075
1076        if (char_cnt < (out_charset_len + 12)) {
1077            /* lfchars must be encoded in ASCII here*/
1078            smart_str_appendl(pretval, lfchars, lfchars_len);
1079            smart_str_appendc(pretval, ' ');
1080            char_cnt = max_line_len - 1;
1081        }
1082
1083        smart_str_appendl(pretval, "=?", sizeof("=?") - 1);
1084        char_cnt -= 2;
1085        smart_str_appendl(pretval, out_charset, out_charset_len);
1086        char_cnt -= out_charset_len;
1087        smart_str_appendc(pretval, '?');
1088        char_cnt --;
1089
1090        switch (enc_scheme) {
1091            case PHP_ICONV_ENC_SCHEME_BASE64: {
1092                size_t ini_in_left;
1093                const char *ini_in_p;
1094                size_t out_reserved = 4;
1095                int dummy;
1096
1097                smart_str_appendc(pretval, 'B');
1098                char_cnt--;
1099                smart_str_appendc(pretval, '?');
1100                char_cnt--;
1101
1102                prev_in_left = ini_in_left = in_left;
1103                ini_in_p = in_p;
1104
1105                out_size = (char_cnt - 2) / 4 * 3;
1106
1107                for (;;) {
1108                    out_p = buf;
1109
1110                    if (out_size <= out_reserved) {
1111                        err = PHP_ICONV_ERR_TOO_BIG;
1112                        goto out;
1113                    }
1114
1115                    out_left = out_size - out_reserved;
1116
1117                    if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
1118#if ICONV_SUPPORTS_ERRNO
1119                        switch (errno) {
1120                            case EINVAL:
1121                                err = PHP_ICONV_ERR_ILLEGAL_CHAR;
1122                                goto out;
1123
1124                            case EILSEQ:
1125                                err = PHP_ICONV_ERR_ILLEGAL_SEQ;
1126                                goto out;
1127
1128                            case E2BIG:
1129                                if (prev_in_left == in_left) {
1130                                    err = PHP_ICONV_ERR_TOO_BIG;
1131                                    goto out;
1132                                }
1133                                break;
1134
1135                            default:
1136                                err = PHP_ICONV_ERR_UNKNOWN;
1137                                goto out;
1138                        }
1139#else
1140                        if (prev_in_left == in_left) {
1141                            err = PHP_ICONV_ERR_UNKNOWN;
1142                            goto out;
1143                        }
1144#endif
1145                    }
1146
1147                    out_left += out_reserved;
1148
1149                    if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
1150#if ICONV_SUPPORTS_ERRNO
1151                        if (errno != E2BIG) {
1152                            err = PHP_ICONV_ERR_UNKNOWN;
1153                            goto out;
1154                        }
1155#else
1156                        if (out_left != 0) {
1157                            err = PHP_ICONV_ERR_UNKNOWN;
1158                            goto out;
1159                        }
1160#endif
1161                    } else {
1162                        break;
1163                    }
1164
1165                    if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
1166                        err = PHP_ICONV_ERR_UNKNOWN;
1167                        goto out;
1168                    }
1169
1170                    out_reserved += 4;
1171                    in_left = ini_in_left;
1172                    in_p = ini_in_p;
1173                }
1174
1175                prev_in_left = in_left;
1176
1177                encoded = (char *) php_base64_encode((unsigned char *) buf, (int)(out_size - out_left), &dummy);
1178                encoded_len = (size_t)dummy;
1179
1180                if (char_cnt < encoded_len) {
1181                    /* something went wrong! */
1182                    err = PHP_ICONV_ERR_UNKNOWN;
1183                    goto out;
1184                }
1185
1186                smart_str_appendl(pretval, encoded, encoded_len);
1187                char_cnt -= encoded_len;
1188                smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
1189                char_cnt -= 2;
1190
1191                efree(encoded);
1192                encoded = NULL;
1193            } break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */
1194
1195            case PHP_ICONV_ENC_SCHEME_QPRINT: {
1196                size_t ini_in_left;
1197                const char *ini_in_p;
1198                const unsigned char *p;
1199                size_t nbytes_required;
1200
1201                smart_str_appendc(pretval, 'Q');
1202                char_cnt--;
1203                smart_str_appendc(pretval, '?');
1204                char_cnt--;
1205
1206                prev_in_left = ini_in_left = in_left;
1207                ini_in_p = in_p;
1208
1209                for (out_size = (char_cnt - 2) / 3; out_size > 0;) {
1210                    size_t prev_out_left;
1211
1212                    nbytes_required = 0;
1213
1214                    out_p = buf;
1215                    out_left = out_size;
1216
1217                    if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
1218#if ICONV_SUPPORTS_ERRNO
1219                        switch (errno) {
1220                            case EINVAL:
1221                                err = PHP_ICONV_ERR_ILLEGAL_CHAR;
1222                                goto out;
1223
1224                            case EILSEQ:
1225                                err = PHP_ICONV_ERR_ILLEGAL_SEQ;
1226                                goto out;
1227
1228                            case E2BIG:
1229                                if (prev_in_left == in_left) {
1230                                    err = PHP_ICONV_ERR_UNKNOWN;
1231                                    goto out;
1232                                }
1233                                break;
1234
1235                            default:
1236                                err = PHP_ICONV_ERR_UNKNOWN;
1237                                goto out;
1238                        }
1239#else
1240                        if (prev_in_left == in_left) {
1241                            err = PHP_ICONV_ERR_UNKNOWN;
1242                            goto out;
1243                        }
1244#endif
1245                    }
1246
1247                    prev_out_left = out_left;
1248                    if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
1249#if ICONV_SUPPORTS_ERRNO
1250                        if (errno != E2BIG) {
1251                            err = PHP_ICONV_ERR_UNKNOWN;
1252                            goto out;
1253                        }
1254#else
1255                        if (out_left == prev_out_left) {
1256                            err = PHP_ICONV_ERR_UNKNOWN;
1257                            goto out;
1258                        }
1259#endif
1260                    }
1261
1262                    for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
1263                        nbytes_required += qp_table[*p];
1264                    }
1265
1266                    if (nbytes_required <= char_cnt - 2) {
1267                        break;
1268                    }
1269
1270                    out_size -= ((nbytes_required - (char_cnt - 2)) + 1) / 3;
1271                    in_left = ini_in_left;
1272                    in_p = ini_in_p;
1273                }
1274
1275                for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
1276                    if (qp_table[*p] == 1) {
1277                        smart_str_appendc(pretval, *(char *)p);
1278                        char_cnt--;
1279                    } else {
1280                        static char qp_digits[] = "0123456789ABCDEF";
1281                        smart_str_appendc(pretval, '=');
1282                        smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]);
1283                        smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]);
1284                        char_cnt -= 3;
1285                    }
1286                }
1287
1288                smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
1289                char_cnt -= 2;
1290
1291                if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
1292                    err = PHP_ICONV_ERR_UNKNOWN;
1293                    goto out;
1294                }
1295
1296            } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */
1297        }
1298    } while (in_left > 0);
1299
1300    smart_str_0(pretval);
1301
1302out:
1303    if (cd != (iconv_t)(-1)) {
1304        iconv_close(cd);
1305    }
1306    if (cd_pl != (iconv_t)(-1)) {
1307        iconv_close(cd_pl);
1308    }
1309    if (encoded != NULL) {
1310        efree(encoded);
1311    }
1312    if (buf != NULL) {
1313        efree(buf);
1314    }
1315    return err;
1316}
1317/* }}} */
1318
1319/* {{{ _php_iconv_mime_decode() */
1320static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode)
1321{
1322    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
1323
1324    iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1);
1325
1326    const char *p1;
1327    size_t str_left;
1328    unsigned int scan_stat = 0;
1329    const char *csname = NULL;
1330    size_t csname_len;
1331    const char *encoded_text = NULL;
1332    size_t encoded_text_len = 0;
1333    const char *encoded_word = NULL;
1334    const char *spaces = NULL;
1335
1336    php_iconv_enc_scheme_t enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64;
1337
1338    if (next_pos != NULL) {
1339        *next_pos = NULL;
1340    }
1341
1342    cd_pl = iconv_open(enc, ICONV_ASCII_ENCODING);
1343
1344    if (cd_pl == (iconv_t)(-1)) {
1345#if ICONV_SUPPORTS_ERRNO
1346        if (errno == EINVAL) {
1347            err = PHP_ICONV_ERR_WRONG_CHARSET;
1348        } else {
1349            err = PHP_ICONV_ERR_CONVERTER;
1350        }
1351#else
1352        err = PHP_ICONV_ERR_UNKNOWN;
1353#endif
1354        goto out;
1355    }
1356
1357    p1 = str;
1358    for (str_left = str_nbytes; str_left > 0; str_left--, p1++) {
1359        int eos = 0;
1360
1361        switch (scan_stat) {
1362            case 0: /* expecting any character */
1363                switch (*p1) {
1364                    case '\r': /* part of an EOL sequence? */
1365                        scan_stat = 7;
1366                        break;
1367
1368                    case '\n':
1369                        scan_stat = 8;
1370                        break;
1371
1372                    case '=': /* first letter of an encoded chunk */
1373                        encoded_word = p1;
1374                        scan_stat = 1;
1375                        break;
1376
1377                    case ' ': case '\t': /* a chunk of whitespaces */
1378                        spaces = p1;
1379                        scan_stat = 11;
1380                        break;
1381
1382                    default: /* first letter of a non-encoded word */
1383                        _php_iconv_appendc(pretval, *p1, cd_pl);
1384                        encoded_word = NULL;
1385                        if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1386                            scan_stat = 12;
1387                        }
1388                        break;
1389                }
1390                break;
1391
1392            case 1: /* expecting a delimiter */
1393                if (*p1 != '?') {
1394                    err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1395                    if (err != PHP_ICONV_ERR_SUCCESS) {
1396                        goto out;
1397                    }
1398                    encoded_word = NULL;
1399                    if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1400                        scan_stat = 12;
1401                    } else {
1402                        scan_stat = 0;
1403                    }
1404                    break;
1405                }
1406                csname = p1 + 1;
1407                scan_stat = 2;
1408                break;
1409
1410            case 2: /* expecting a charset name */
1411                switch (*p1) {
1412                    case '?': /* normal delimiter: encoding scheme follows */
1413                        scan_stat = 3;
1414                        break;
1415
1416                    case '*': /* new style delimiter: locale id follows */
1417                        scan_stat = 10;
1418                        break;
1419                }
1420                if (scan_stat != 2) {
1421                    char tmpbuf[80];
1422
1423                    if (csname == NULL) {
1424                        err = PHP_ICONV_ERR_MALFORMED;
1425                        goto out;
1426                    }
1427
1428                    csname_len = (size_t)(p1 - csname);
1429
1430                    if (csname_len > sizeof(tmpbuf) - 1) {
1431                        if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1432                            err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1433                            if (err != PHP_ICONV_ERR_SUCCESS) {
1434                                goto out;
1435                            }
1436                            encoded_word = NULL;
1437                            if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1438                                scan_stat = 12;
1439                            } else {
1440                                scan_stat = 0;
1441                            }
1442                            break;
1443                        } else {
1444                            err = PHP_ICONV_ERR_MALFORMED;
1445                            goto out;
1446                        }
1447                    }
1448
1449                    memcpy(tmpbuf, csname, csname_len);
1450                    tmpbuf[csname_len] = '\0';
1451
1452                    if (cd != (iconv_t)(-1)) {
1453                        iconv_close(cd);
1454                    }
1455
1456                    cd = iconv_open(enc, tmpbuf);
1457
1458                    if (cd == (iconv_t)(-1)) {
1459                        if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1460                            /* Bad character set, but the user wants us to
1461                             * press on. In this case, we'll just insert the
1462                             * undecoded encoded word, since there isn't really
1463                             * a more sensible behaviour available; the only
1464                             * other options are to swallow the encoded word
1465                             * entirely or decode it with an arbitrarily chosen
1466                             * single byte encoding, both of which seem to have
1467                             * a higher WTF factor than leaving it undecoded.
1468                             *
1469                             * Given this approach, we need to skip ahead to
1470                             * the end of the encoded word. */
1471                            int qmarks = 2;
1472                            while (qmarks > 0 && str_left > 1) {
1473                                if (*(++p1) == '?') {
1474                                    --qmarks;
1475                                }
1476                                --str_left;
1477                            }
1478
1479                            /* Look ahead to check for the terminating = that
1480                             * should be there as well; if it's there, we'll
1481                             * also include that. If it's not, there isn't much
1482                             * we can do at this point. */
1483                            if (*(p1 + 1) == '=') {
1484                                ++p1;
1485                                --str_left;
1486                            }
1487
1488                            err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1489                            if (err != PHP_ICONV_ERR_SUCCESS) {
1490                                goto out;
1491                            }
1492
1493                            /* Let's go back and see if there are further
1494                             * encoded words or bare content, and hope they
1495                             * might actually have a valid character set. */
1496                            scan_stat = 12;
1497                            break;
1498                        } else {
1499#if ICONV_SUPPORTS_ERRNO
1500                            if (errno == EINVAL) {
1501                                err = PHP_ICONV_ERR_WRONG_CHARSET;
1502                            } else {
1503                                err = PHP_ICONV_ERR_CONVERTER;
1504                            }
1505#else
1506                            err = PHP_ICONV_ERR_UNKNOWN;
1507#endif
1508                            goto out;
1509                        }
1510                    }
1511                }
1512                break;
1513
1514            case 3: /* expecting a encoding scheme specifier */
1515                switch (*p1) {
1516                    case 'b':
1517                    case 'B':
1518                        enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64;
1519                        scan_stat = 4;
1520                        break;
1521
1522                    case 'q':
1523                    case 'Q':
1524                        enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT;
1525                        scan_stat = 4;
1526                        break;
1527
1528                    default:
1529                        if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1530                            err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1531                            if (err != PHP_ICONV_ERR_SUCCESS) {
1532                                goto out;
1533                            }
1534                            encoded_word = NULL;
1535                            if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1536                                scan_stat = 12;
1537                            } else {
1538                                scan_stat = 0;
1539                            }
1540                            break;
1541                        } else {
1542                            err = PHP_ICONV_ERR_MALFORMED;
1543                            goto out;
1544                        }
1545                }
1546                break;
1547
1548            case 4: /* expecting a delimiter */
1549                if (*p1 != '?') {
1550                    if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1551                        /* pass the entire chunk through the converter */
1552                        err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1553                        if (err != PHP_ICONV_ERR_SUCCESS) {
1554                            goto out;
1555                        }
1556                        encoded_word = NULL;
1557                        if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1558                            scan_stat = 12;
1559                        } else {
1560                            scan_stat = 0;
1561                        }
1562                        break;
1563                    } else {
1564                        err = PHP_ICONV_ERR_MALFORMED;
1565                        goto out;
1566                    }
1567                }
1568                encoded_text = p1 + 1;
1569                scan_stat = 5;
1570                break;
1571
1572            case 5: /* expecting an encoded portion */
1573                if (*p1 == '?') {
1574                    encoded_text_len = (size_t)(p1 - encoded_text);
1575                    scan_stat = 6;
1576                }
1577                break;
1578
1579            case 7: /* expecting a "\n" character */
1580                if (*p1 == '\n') {
1581                    scan_stat = 8;
1582                } else {
1583                    /* bare CR */
1584                    _php_iconv_appendc(pretval, '\r', cd_pl);
1585                    _php_iconv_appendc(pretval, *p1, cd_pl);
1586                    scan_stat = 0;
1587                }
1588                break;
1589
1590            case 8: /* checking whether the following line is part of a
1591                       folded header */
1592                if (*p1 != ' ' && *p1 != '\t') {
1593                    --p1;
1594                    str_left = 1; /* quit_loop */
1595                    break;
1596                }
1597                if (encoded_word == NULL) {
1598                    _php_iconv_appendc(pretval, ' ', cd_pl);
1599                }
1600                spaces = NULL;
1601                scan_stat = 11;
1602                break;
1603
1604            case 6: /* expecting a End-Of-Chunk character "=" */
1605                if (*p1 != '=') {
1606                    if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1607                        /* pass the entire chunk through the converter */
1608                        err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1609                        if (err != PHP_ICONV_ERR_SUCCESS) {
1610                            goto out;
1611                        }
1612                        encoded_word = NULL;
1613                        if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1614                            scan_stat = 12;
1615                        } else {
1616                            scan_stat = 0;
1617                        }
1618                        break;
1619                    } else {
1620                        err = PHP_ICONV_ERR_MALFORMED;
1621                        goto out;
1622                    }
1623                }
1624                scan_stat = 9;
1625                if (str_left == 1) {
1626                    eos = 1;
1627                } else {
1628                    break;
1629                }
1630
1631            case 9: /* choice point, seeing what to do next.*/
1632                switch (*p1) {
1633                    default:
1634                        /* Handle non-RFC-compliant formats
1635                         *
1636                         * RFC2047 requires the character that comes right
1637                         * after an encoded word (chunk) to be a whitespace,
1638                         * while there are lots of broken implementations that
1639                         * generate such malformed headers that don't fulfill
1640                         * that requirement.
1641                         */
1642                        if (!eos) {
1643                            if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1644                                /* pass the entire chunk through the converter */
1645                                err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1646                                if (err != PHP_ICONV_ERR_SUCCESS) {
1647                                    goto out;
1648                                }
1649                                scan_stat = 12;
1650                                break;
1651                            }
1652                        }
1653                        /* break is omitted intentionally */
1654
1655                    case '\r': case '\n': case ' ': case '\t': {
1656                        char *decoded_text;
1657                        size_t decoded_text_len;
1658                        int dummy;
1659
1660                        switch (enc_scheme) {
1661                            case PHP_ICONV_ENC_SCHEME_BASE64:
1662                                decoded_text = (char *)php_base64_decode((unsigned char*)encoded_text, (int)encoded_text_len, &dummy);
1663                                decoded_text_len = (size_t)dummy;
1664                                break;
1665
1666                            case PHP_ICONV_ENC_SCHEME_QPRINT:
1667                                decoded_text = (char *)php_quot_print_decode((unsigned char*)encoded_text, (int)encoded_text_len, &decoded_text_len, 1);
1668                                break;
1669                            default:
1670                                decoded_text = NULL;
1671                                break;
1672                        }
1673
1674                        if (decoded_text == NULL) {
1675                            if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1676                                /* pass the entire chunk through the converter */
1677                                err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
1678                                if (err != PHP_ICONV_ERR_SUCCESS) {
1679                                    goto out;
1680                                }
1681                                encoded_word = NULL;
1682                                if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1683                                    scan_stat = 12;
1684                                } else {
1685                                    scan_stat = 0;
1686                                }
1687                                break;
1688                            } else {
1689                                err = PHP_ICONV_ERR_UNKNOWN;
1690                                goto out;
1691                            }
1692                        }
1693
1694                        err = _php_iconv_appendl(pretval, decoded_text, decoded_text_len, cd);
1695                        efree(decoded_text);
1696
1697                        if (err != PHP_ICONV_ERR_SUCCESS) {
1698                            if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1699                                /* pass the entire chunk through the converter */
1700                                err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl);
1701                                encoded_word = NULL;
1702                                if (err != PHP_ICONV_ERR_SUCCESS) {
1703                                    break;
1704                                }
1705                            } else {
1706                                goto out;
1707                            }
1708                        }
1709
1710                        if (eos) { /* reached end-of-string. done. */
1711                            scan_stat = 0;
1712                            break;
1713                        }
1714
1715                        switch (*p1) {
1716                            case '\r': /* part of an EOL sequence? */
1717                                scan_stat = 7;
1718                                break;
1719
1720                            case '\n':
1721                                scan_stat = 8;
1722                                break;
1723
1724                            case '=': /* first letter of an encoded chunk */
1725                                scan_stat = 1;
1726                                break;
1727
1728                            case ' ': case '\t': /* medial whitespaces */
1729                                spaces = p1;
1730                                scan_stat = 11;
1731                                break;
1732
1733                            default: /* first letter of a non-encoded word */
1734                                _php_iconv_appendc(pretval, *p1, cd_pl);
1735                                scan_stat = 12;
1736                                break;
1737                        }
1738                    } break;
1739                }
1740                break;
1741
1742            case 10: /* expects a language specifier. dismiss it for now */
1743                if (*p1 == '?') {
1744                    scan_stat = 3;
1745                }
1746                break;
1747
1748            case 11: /* expecting a chunk of whitespaces */
1749                switch (*p1) {
1750                    case '\r': /* part of an EOL sequence? */
1751                        scan_stat = 7;
1752                        break;
1753
1754                    case '\n':
1755                        scan_stat = 8;
1756                        break;
1757
1758                    case '=': /* first letter of an encoded chunk */
1759                        if (spaces != NULL && encoded_word == NULL) {
1760                            _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
1761                            spaces = NULL;
1762                        }
1763                        encoded_word = p1;
1764                        scan_stat = 1;
1765                        break;
1766
1767                    case ' ': case '\t':
1768                        break;
1769
1770                    default: /* first letter of a non-encoded word */
1771                        if (spaces != NULL) {
1772                            _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
1773                            spaces = NULL;
1774                        }
1775                        _php_iconv_appendc(pretval, *p1, cd_pl);
1776                        encoded_word = NULL;
1777                        if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1778                            scan_stat = 12;
1779                        } else {
1780                            scan_stat = 0;
1781                        }
1782                        break;
1783                }
1784                break;
1785
1786            case 12: /* expecting a non-encoded word */
1787                switch (*p1) {
1788                    case '\r': /* part of an EOL sequence? */
1789                        scan_stat = 7;
1790                        break;
1791
1792                    case '\n':
1793                        scan_stat = 8;
1794                        break;
1795
1796                    case ' ': case '\t':
1797                        spaces = p1;
1798                        scan_stat = 11;
1799                        break;
1800
1801                    case '=': /* first letter of an encoded chunk */
1802                        if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) {
1803                            encoded_word = p1;
1804                            scan_stat = 1;
1805                            break;
1806                        }
1807                        /* break is omitted intentionally */
1808
1809                    default:
1810                        _php_iconv_appendc(pretval, *p1, cd_pl);
1811                        break;
1812                }
1813                break;
1814        }
1815    }
1816    switch (scan_stat) {
1817        case 0: case 8: case 11: case 12:
1818            break;
1819        default:
1820            if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
1821                if (scan_stat == 1) {
1822                    _php_iconv_appendc(pretval, '=', cd_pl);
1823                }
1824                err = PHP_ICONV_ERR_SUCCESS;
1825            } else {
1826                err = PHP_ICONV_ERR_MALFORMED;
1827                goto out;
1828            }
1829    }
1830
1831    if (next_pos != NULL) {
1832        *next_pos = p1;
1833    }
1834
1835    smart_str_0(pretval);
1836out:
1837    if (cd != (iconv_t)(-1)) {
1838        iconv_close(cd);
1839    }
1840    if (cd_pl != (iconv_t)(-1)) {
1841        iconv_close(cd_pl);
1842    }
1843    return err;
1844}
1845/* }}} */
1846
1847/* {{{ php_iconv_show_error() */
1848static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset TSRMLS_DC)
1849{
1850    switch (err) {
1851        case PHP_ICONV_ERR_SUCCESS:
1852            break;
1853
1854        case PHP_ICONV_ERR_CONVERTER:
1855            php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Cannot open converter");
1856            break;
1857
1858        case PHP_ICONV_ERR_WRONG_CHARSET:
1859            php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Wrong charset, conversion from `%s' to `%s' is not allowed",
1860                      in_charset, out_charset);
1861            break;
1862
1863        case PHP_ICONV_ERR_ILLEGAL_CHAR:
1864            php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Detected an incomplete multibyte character in input string");
1865            break;
1866
1867        case PHP_ICONV_ERR_ILLEGAL_SEQ:
1868            php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Detected an illegal character in input string");
1869            break;
1870
1871        case PHP_ICONV_ERR_TOO_BIG:
1872            /* should not happen */
1873            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Buffer length exceeded");
1874            break;
1875
1876        case PHP_ICONV_ERR_MALFORMED:
1877            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Malformed string");
1878            break;
1879
1880        default:
1881            /* other error */
1882            php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Unknown error (%d)", errno);
1883            break;
1884    }
1885}
1886/* }}} */
1887
1888/* {{{ proto int iconv_strlen(string str [, string charset])
1889   Returns the character count of str */
1890PHP_FUNCTION(iconv_strlen)
1891{
1892    char *charset = ICONVG(internal_encoding);
1893    int charset_len = 0;
1894    char *str;
1895    int str_len;
1896
1897    php_iconv_err_t err;
1898
1899    unsigned int retval;
1900
1901    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
1902        &str, &str_len, &charset, &charset_len) == FAILURE) {
1903        RETURN_FALSE;
1904    }
1905
1906    if (charset_len >= ICONV_CSNMAXLEN) {
1907        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
1908        RETURN_FALSE;
1909    }
1910
1911    err = _php_iconv_strlen(&retval, str, str_len, charset);
1912    _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset TSRMLS_CC);
1913    if (err == PHP_ICONV_ERR_SUCCESS) {
1914        RETVAL_LONG(retval);
1915    } else {
1916        RETVAL_FALSE;
1917    }
1918}
1919/* }}} */
1920
1921/* {{{ proto string iconv_substr(string str, int offset, [int length, string charset])
1922   Returns specified part of a string */
1923PHP_FUNCTION(iconv_substr)
1924{
1925    char *charset = ICONVG(internal_encoding);
1926    int charset_len = 0;
1927    char *str;
1928    int str_len;
1929    long offset, length = 0;
1930
1931    php_iconv_err_t err;
1932
1933    smart_str retval = {0};
1934
1935    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|ls",
1936        &str, &str_len, &offset, &length,
1937        &charset, &charset_len) == FAILURE) {
1938        RETURN_FALSE;
1939    }
1940
1941    if (charset_len >= ICONV_CSNMAXLEN) {
1942        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
1943        RETURN_FALSE;
1944    }
1945
1946    if (ZEND_NUM_ARGS() < 3) {
1947        length = str_len;
1948    }
1949
1950    err = _php_iconv_substr(&retval, str, str_len, offset, length, charset);
1951    _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset TSRMLS_CC);
1952
1953    if (err == PHP_ICONV_ERR_SUCCESS && str != NULL && retval.c != NULL) {
1954        RETURN_STRINGL(retval.c, retval.len, 0);
1955    }
1956    smart_str_free(&retval);
1957    RETURN_FALSE;
1958}
1959/* }}} */
1960
1961/* {{{ proto int iconv_strpos(string haystack, string needle [, int offset [, string charset]])
1962   Finds position of first occurrence of needle within part of haystack beginning with offset */
1963PHP_FUNCTION(iconv_strpos)
1964{
1965    char *charset = ICONVG(internal_encoding);
1966    int charset_len = 0;
1967    char *haystk;
1968    int haystk_len;
1969    char *ndl;
1970    int ndl_len;
1971    long offset = 0;
1972
1973    php_iconv_err_t err;
1974
1975    unsigned int retval;
1976
1977    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|ls",
1978        &haystk, &haystk_len, &ndl, &ndl_len,
1979        &offset, &charset, &charset_len) == FAILURE) {
1980        RETURN_FALSE;
1981    }
1982
1983    if (charset_len >= ICONV_CSNMAXLEN) {
1984        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
1985        RETURN_FALSE;
1986    }
1987
1988    if (offset < 0) {
1989        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string.");
1990        RETURN_FALSE;
1991    }
1992
1993    if (ndl_len < 1) {
1994        RETURN_FALSE;
1995    }
1996
1997    err = _php_iconv_strpos(&retval, haystk, haystk_len, ndl, ndl_len,
1998                            offset, charset);
1999    _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset TSRMLS_CC);
2000
2001    if (err == PHP_ICONV_ERR_SUCCESS && retval != (unsigned int)-1) {
2002        RETVAL_LONG((long)retval);
2003    } else {
2004        RETVAL_FALSE;
2005    }
2006}
2007/* }}} */
2008
2009/* {{{ proto int iconv_strrpos(string haystack, string needle [, string charset])
2010   Finds position of last occurrence of needle within part of haystack beginning with offset */
2011PHP_FUNCTION(iconv_strrpos)
2012{
2013    char *charset = ICONVG(internal_encoding);
2014    int charset_len = 0;
2015    char *haystk;
2016    int haystk_len;
2017    char *ndl;
2018    int ndl_len;
2019
2020    php_iconv_err_t err;
2021
2022    unsigned int retval;
2023
2024    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|s",
2025        &haystk, &haystk_len, &ndl, &ndl_len,
2026        &charset, &charset_len) == FAILURE) {
2027        RETURN_FALSE;
2028    }
2029
2030    if (ndl_len < 1) {
2031        RETURN_FALSE;
2032    }
2033
2034    if (charset_len >= ICONV_CSNMAXLEN) {
2035        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2036        RETURN_FALSE;
2037    }
2038
2039    err = _php_iconv_strpos(&retval, haystk, haystk_len, ndl, ndl_len,
2040                            -1, charset);
2041    _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset TSRMLS_CC);
2042
2043    if (err == PHP_ICONV_ERR_SUCCESS && retval != (unsigned int)-1) {
2044        RETVAL_LONG((long)retval);
2045    } else {
2046        RETVAL_FALSE;
2047    }
2048}
2049/* }}} */
2050
2051/* {{{ proto string iconv_mime_encode(string field_name, string field_value [, array preference])
2052   Composes a mime header field with field_name and field_value in a specified scheme */
2053PHP_FUNCTION(iconv_mime_encode)
2054{
2055    const char *field_name = NULL;
2056    int field_name_len;
2057    const char *field_value = NULL;
2058    int field_value_len;
2059    zval *pref = NULL;
2060    zval tmp_zv, *tmp_zv_p = NULL;
2061    smart_str retval = {0};
2062    php_iconv_err_t err;
2063
2064    const char *in_charset = ICONVG(internal_encoding);
2065    const char *out_charset = in_charset;
2066    long line_len = 76;
2067    const char *lfchars = "\r\n";
2068    php_iconv_enc_scheme_t scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
2069
2070    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|a",
2071        &field_name, &field_name_len, &field_value, &field_value_len,
2072        &pref) == FAILURE) {
2073
2074        RETURN_FALSE;
2075    }
2076
2077    if (pref != NULL) {
2078        zval **ppval;
2079
2080        if (zend_hash_find(Z_ARRVAL_P(pref), "scheme", sizeof("scheme"), (void **)&ppval) == SUCCESS) {
2081            if (Z_TYPE_PP(ppval) == IS_STRING && Z_STRLEN_PP(ppval) > 0) {
2082                switch (Z_STRVAL_PP(ppval)[0]) {
2083                    case 'B': case 'b':
2084                        scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
2085                        break;
2086
2087                    case 'Q': case 'q':
2088                        scheme_id = PHP_ICONV_ENC_SCHEME_QPRINT;
2089                        break;
2090                }
2091            }
2092        }
2093
2094        if (zend_hash_find(Z_ARRVAL_P(pref), "input-charset", sizeof("input-charset"), (void **)&ppval) == SUCCESS) {
2095            if (Z_STRLEN_PP(ppval) >= ICONV_CSNMAXLEN) {
2096                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2097                RETURN_FALSE;
2098            }
2099
2100            if (Z_TYPE_PP(ppval) == IS_STRING && Z_STRLEN_PP(ppval) > 0) {
2101                in_charset = Z_STRVAL_PP(ppval);
2102            }
2103        }
2104
2105
2106        if (zend_hash_find(Z_ARRVAL_P(pref), "output-charset", sizeof("output-charset"), (void **)&ppval) == SUCCESS) {
2107            if (Z_STRLEN_PP(ppval) >= ICONV_CSNMAXLEN) {
2108                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2109                RETURN_FALSE;
2110            }
2111
2112            if (Z_TYPE_PP(ppval) == IS_STRING && Z_STRLEN_PP(ppval) > 0) {
2113                out_charset = Z_STRVAL_PP(ppval);
2114            }
2115        }
2116
2117        if (zend_hash_find(Z_ARRVAL_P(pref), "line-length", sizeof("line-length"), (void **)&ppval) == SUCCESS) {
2118            zval val, *pval = *ppval;
2119
2120            if (Z_TYPE_P(pval) != IS_LONG) {
2121                val = *pval;
2122                zval_copy_ctor(&val);
2123                convert_to_long(&val);
2124                pval = &val;
2125            }
2126
2127            line_len = Z_LVAL_P(pval);
2128
2129            if (pval == &val) {
2130                zval_dtor(&val);
2131            }
2132        }
2133
2134        if (zend_hash_find(Z_ARRVAL_P(pref), "line-break-chars", sizeof("line-break-chars"), (void **)&ppval) == SUCCESS) {
2135            if (Z_TYPE_PP(ppval) != IS_STRING) {
2136                tmp_zv = **ppval;
2137                zval_copy_ctor(&tmp_zv);
2138                convert_to_string(&tmp_zv);
2139
2140                lfchars = Z_STRVAL(tmp_zv);
2141
2142                tmp_zv_p = &tmp_zv;
2143            } else {
2144                lfchars = Z_STRVAL_PP(ppval);
2145            }
2146        }
2147    }
2148
2149    err = _php_iconv_mime_encode(&retval, field_name, field_name_len,
2150        field_value, field_value_len, line_len, lfchars, scheme_id,
2151        out_charset, in_charset);
2152    _php_iconv_show_error(err, out_charset, in_charset TSRMLS_CC);
2153
2154    if (err == PHP_ICONV_ERR_SUCCESS) {
2155        if (retval.c != NULL) {
2156            RETVAL_STRINGL(retval.c, retval.len, 0);
2157        } else {
2158            RETVAL_EMPTY_STRING();
2159        }
2160    } else {
2161        smart_str_free(&retval);
2162        RETVAL_FALSE;
2163    }
2164
2165    if (tmp_zv_p != NULL) {
2166        zval_dtor(tmp_zv_p);
2167    }
2168}
2169/* }}} */
2170
2171/* {{{ proto string iconv_mime_decode(string encoded_string [, int mode, string charset])
2172   Decodes a mime header field */
2173PHP_FUNCTION(iconv_mime_decode)
2174{
2175    char *encoded_str;
2176    int encoded_str_len;
2177    char *charset = ICONVG(internal_encoding);
2178    int charset_len = 0;
2179    long mode = 0;
2180
2181    smart_str retval = {0};
2182
2183    php_iconv_err_t err;
2184
2185    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ls",
2186        &encoded_str, &encoded_str_len, &mode, &charset, &charset_len) == FAILURE) {
2187
2188        RETURN_FALSE;
2189    }
2190
2191    if (charset_len >= ICONV_CSNMAXLEN) {
2192        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2193        RETURN_FALSE;
2194    }
2195
2196    err = _php_iconv_mime_decode(&retval, encoded_str, encoded_str_len, charset, NULL, mode);
2197    _php_iconv_show_error(err, charset, "???" TSRMLS_CC);
2198
2199    if (err == PHP_ICONV_ERR_SUCCESS) {
2200        if (retval.c != NULL) {
2201            RETVAL_STRINGL(retval.c, retval.len, 0);
2202        } else {
2203            RETVAL_EMPTY_STRING();
2204        }
2205    } else {
2206        smart_str_free(&retval);
2207        RETVAL_FALSE;
2208    }
2209}
2210/* }}} */
2211
2212/* {{{ proto array iconv_mime_decode_headers(string headers [, int mode, string charset])
2213   Decodes multiple mime header fields */
2214PHP_FUNCTION(iconv_mime_decode_headers)
2215{
2216    const char *encoded_str;
2217    int encoded_str_len;
2218    char *charset = ICONVG(internal_encoding);
2219    int charset_len = 0;
2220    long mode = 0;
2221
2222    php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
2223
2224    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ls",
2225        &encoded_str, &encoded_str_len, &mode, &charset, &charset_len) == FAILURE) {
2226
2227        RETURN_FALSE;
2228    }
2229
2230    if (charset_len >= ICONV_CSNMAXLEN) {
2231        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2232        RETURN_FALSE;
2233    }
2234
2235    array_init(return_value);
2236
2237    while (encoded_str_len > 0) {
2238        smart_str decoded_header = {0};
2239        char *header_name = NULL;
2240        size_t header_name_len = 0;
2241        char *header_value = NULL;
2242        size_t header_value_len = 0;
2243        char *p, *limit;
2244        const char *next_pos;
2245
2246        if (PHP_ICONV_ERR_SUCCESS != (err = _php_iconv_mime_decode(&decoded_header, encoded_str, encoded_str_len, charset, &next_pos, mode))) {
2247            smart_str_free(&decoded_header);
2248            break;
2249        }
2250
2251        if (decoded_header.c == NULL) {
2252            break;
2253        }
2254
2255        limit = decoded_header.c + decoded_header.len;
2256        for (p = decoded_header.c; p < limit; p++) {
2257            if (*p == ':') {
2258                *p = '\0';
2259                header_name = decoded_header.c;
2260                header_name_len = (p - decoded_header.c) + 1;
2261
2262                while (++p < limit) {
2263                    if (*p != ' ' && *p != '\t') {
2264                        break;
2265                    }
2266                }
2267
2268                header_value = p;
2269                header_value_len = limit - p;
2270
2271                break;
2272            }
2273        }
2274
2275        if (header_name != NULL) {
2276            zval **elem, *new_elem;
2277
2278            if (zend_hash_find(Z_ARRVAL_P(return_value), header_name, header_name_len, (void **)&elem) == SUCCESS) {
2279                if (Z_TYPE_PP(elem) != IS_ARRAY) {
2280                    MAKE_STD_ZVAL(new_elem);
2281                    array_init(new_elem);
2282
2283                    Z_ADDREF_PP(elem);
2284                    add_next_index_zval(new_elem, *elem);
2285
2286                    zend_hash_update(Z_ARRVAL_P(return_value), header_name, header_name_len, (void *)&new_elem, sizeof(new_elem), NULL);
2287
2288                    elem = &new_elem;
2289                }
2290                add_next_index_stringl(*elem, header_value, header_value_len, 1);
2291            } else {
2292                add_assoc_stringl_ex(return_value, header_name, header_name_len, header_value, header_value_len, 1);
2293            }
2294        }
2295        encoded_str_len -= next_pos - encoded_str;
2296        encoded_str = next_pos;
2297
2298        smart_str_free(&decoded_header);
2299    }
2300
2301    if (err != PHP_ICONV_ERR_SUCCESS) {
2302        _php_iconv_show_error(err, charset, "???" TSRMLS_CC);
2303        zval_dtor(return_value);
2304        RETVAL_FALSE;
2305    }
2306}
2307/* }}} */
2308
2309/* {{{ proto string iconv(string in_charset, string out_charset, string str)
2310   Returns str converted to the out_charset character set */
2311PHP_NAMED_FUNCTION(php_if_iconv)
2312{
2313    char *in_charset, *out_charset, *in_buffer, *out_buffer;
2314    size_t out_len;
2315    int in_charset_len = 0, out_charset_len = 0, in_buffer_len;
2316    php_iconv_err_t err;
2317
2318    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss",
2319        &in_charset, &in_charset_len, &out_charset, &out_charset_len, &in_buffer, &in_buffer_len) == FAILURE)
2320        return;
2321
2322    if (in_charset_len >= ICONV_CSNMAXLEN || out_charset_len >= ICONV_CSNMAXLEN) {
2323        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2324        RETURN_FALSE;
2325    }
2326
2327    err = php_iconv_string(in_buffer, (size_t)in_buffer_len,
2328        &out_buffer, &out_len, out_charset, in_charset);
2329    _php_iconv_show_error(err, out_charset, in_charset TSRMLS_CC);
2330    if (out_buffer != NULL) {
2331        RETVAL_STRINGL(out_buffer, out_len, 0);
2332    } else {
2333        RETURN_FALSE;
2334    }
2335}
2336/* }}} */
2337
2338/* {{{ proto string ob_iconv_handler(string contents, int status)
2339   Returns str in output buffer converted to the iconv.output_encoding character set */
2340PHP_FUNCTION(ob_iconv_handler)
2341{
2342    char *out_buffer, *content_type, *mimetype = NULL, *s;
2343    zval *zv_string;
2344    size_t out_len;
2345    int mimetype_alloced  = 0;
2346    long status;
2347
2348    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zl", &zv_string, &status) == FAILURE)
2349        return;
2350
2351    convert_to_string(zv_string);
2352
2353    if (SG(sapi_headers).mimetype &&
2354        strncasecmp(SG(sapi_headers).mimetype, "text/", 5) == 0) {
2355        if ((s = strchr(SG(sapi_headers).mimetype,';')) == NULL){
2356            mimetype = SG(sapi_headers).mimetype;
2357        } else {
2358            mimetype = estrndup(SG(sapi_headers).mimetype, s-SG(sapi_headers).mimetype);
2359            mimetype_alloced = 1;
2360        }
2361    } else if (SG(sapi_headers).send_default_content_type) {
2362        mimetype =(SG(default_mimetype) ? SG(default_mimetype) : SAPI_DEFAULT_MIMETYPE);
2363    }
2364    if (mimetype != NULL) {
2365        php_iconv_err_t err = php_iconv_string(Z_STRVAL_P(zv_string),
2366                Z_STRLEN_P(zv_string), &out_buffer, &out_len,
2367                ICONVG(output_encoding), ICONVG(internal_encoding));
2368        _php_iconv_show_error(err, ICONVG(output_encoding), ICONVG(internal_encoding) TSRMLS_CC);
2369        if (out_buffer != NULL) {
2370            int len;
2371            char *p = strstr(ICONVG(output_encoding), "//");
2372            if (p) {
2373                len = spprintf(&content_type, 0, "Content-Type:%s; charset=%.*s", mimetype, (int)(p - ICONVG(output_encoding)), ICONVG(output_encoding));
2374            } else {
2375                len = spprintf(&content_type, 0, "Content-Type:%s; charset=%s", mimetype, ICONVG(output_encoding));
2376            }
2377            if (content_type && sapi_add_header(content_type, len, 0) != FAILURE) {
2378                SG(sapi_headers).send_default_content_type = 0;
2379            }
2380            if (mimetype_alloced) {
2381                efree(mimetype);
2382            }
2383            RETURN_STRINGL(out_buffer, out_len, 0);
2384        }
2385        if (mimetype_alloced) {
2386            efree(mimetype);
2387        }
2388    }
2389
2390    zval_dtor(return_value);
2391    *return_value = *zv_string;
2392    zval_copy_ctor(return_value);
2393}
2394/* }}} */
2395
2396/* {{{ proto bool iconv_set_encoding(string type, string charset)
2397   Sets internal encoding and output encoding for ob_iconv_handler() */
2398PHP_FUNCTION(iconv_set_encoding)
2399{
2400    char *type, *charset;
2401    int type_len, charset_len =0, retval;
2402
2403    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &type, &type_len, &charset, &charset_len) == FAILURE)
2404        return;
2405
2406    if (charset_len >= ICONV_CSNMAXLEN) {
2407        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2408        RETURN_FALSE;
2409    }
2410
2411    if(!strcasecmp("input_encoding", type)) {
2412        retval = zend_alter_ini_entry("iconv.input_encoding", sizeof("iconv.input_encoding"), charset, charset_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
2413    } else if(!strcasecmp("output_encoding", type)) {
2414        retval = zend_alter_ini_entry("iconv.output_encoding", sizeof("iconv.output_encoding"), charset, charset_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
2415    } else if(!strcasecmp("internal_encoding", type)) {
2416        retval = zend_alter_ini_entry("iconv.internal_encoding", sizeof("iconv.internal_encoding"), charset, charset_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
2417    } else {
2418        RETURN_FALSE;
2419    }
2420
2421    if (retval == SUCCESS) {
2422        RETURN_TRUE;
2423    } else {
2424        RETURN_FALSE;
2425    }
2426}
2427/* }}} */
2428
2429/* {{{ proto mixed iconv_get_encoding([string type])
2430   Get internal encoding and output encoding for ob_iconv_handler() */
2431PHP_FUNCTION(iconv_get_encoding)
2432{
2433    char *type = "all";
2434    int type_len = sizeof("all")-1;
2435
2436    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &type, &type_len) == FAILURE)
2437        return;
2438
2439    if (!strcasecmp("all", type)) {
2440        array_init(return_value);
2441        add_assoc_string(return_value, "input_encoding",    ICONVG(input_encoding), 1);
2442        add_assoc_string(return_value, "output_encoding",   ICONVG(output_encoding), 1);
2443        add_assoc_string(return_value, "internal_encoding", ICONVG(internal_encoding), 1);
2444    } else if (!strcasecmp("input_encoding", type)) {
2445        RETVAL_STRING(ICONVG(input_encoding), 1);
2446    } else if (!strcasecmp("output_encoding", type)) {
2447        RETVAL_STRING(ICONVG(output_encoding), 1);
2448    } else if (!strcasecmp("internal_encoding", type)) {
2449        RETVAL_STRING(ICONVG(internal_encoding), 1);
2450    } else {
2451        RETURN_FALSE;
2452    }
2453
2454}
2455/* }}} */
2456
2457/* {{{ iconv stream filter */
2458typedef struct _php_iconv_stream_filter {
2459    iconv_t cd;
2460    int persistent;
2461    char *to_charset;
2462    size_t to_charset_len;
2463    char *from_charset;
2464    size_t from_charset_len;
2465    char stub[128];
2466    size_t stub_len;
2467} php_iconv_stream_filter;
2468/* }}} iconv stream filter */
2469
2470/* {{{ php_iconv_stream_filter_dtor */
2471static void php_iconv_stream_filter_dtor(php_iconv_stream_filter *self)
2472{
2473    iconv_close(self->cd);
2474    pefree(self->to_charset, self->persistent);
2475    pefree(self->from_charset, self->persistent);
2476}
2477/* }}} */
2478
2479/* {{{ php_iconv_stream_filter_ctor() */
2480static php_iconv_err_t php_iconv_stream_filter_ctor(php_iconv_stream_filter *self,
2481        const char *to_charset, size_t to_charset_len,
2482        const char *from_charset, size_t from_charset_len, int persistent)
2483{
2484    if (NULL == (self->to_charset = pemalloc(to_charset_len + 1, persistent))) {
2485        return PHP_ICONV_ERR_ALLOC;
2486    }
2487    self->to_charset_len = to_charset_len;
2488    if (NULL == (self->from_charset = pemalloc(from_charset_len + 1, persistent))) {
2489        pefree(self->to_charset, persistent);
2490        return PHP_ICONV_ERR_ALLOC;
2491    }
2492    self->from_charset_len = from_charset_len;
2493
2494    memcpy(self->to_charset, to_charset, to_charset_len);
2495    self->to_charset[to_charset_len] = '\0';
2496    memcpy(self->from_charset, from_charset, from_charset_len);
2497    self->from_charset[from_charset_len] = '\0';
2498
2499    if ((iconv_t)-1 == (self->cd = iconv_open(self->to_charset, self->from_charset))) {
2500        pefree(self->from_charset, persistent);
2501        pefree(self->to_charset, persistent);
2502        return PHP_ICONV_ERR_UNKNOWN;
2503    }
2504    self->persistent = persistent;
2505    self->stub_len = 0;
2506    return PHP_ICONV_ERR_SUCCESS;
2507}
2508/* }}} */
2509
2510/* {{{ php_iconv_stream_filter_append_bucket */
2511static int php_iconv_stream_filter_append_bucket(
2512        php_iconv_stream_filter *self,
2513        php_stream *stream, php_stream_filter *filter,
2514        php_stream_bucket_brigade *buckets_out,
2515        const char *ps, size_t buf_len, size_t *consumed,
2516        int persistent TSRMLS_DC)
2517{
2518    php_stream_bucket *new_bucket;
2519    char *out_buf = NULL;
2520    size_t out_buf_size;
2521    char *pd, *pt;
2522    size_t ocnt, prev_ocnt, icnt, tcnt;
2523    size_t initial_out_buf_size;
2524
2525    if (ps == NULL) {
2526        initial_out_buf_size = 64;
2527        icnt = 1;
2528    } else {
2529        initial_out_buf_size = buf_len;
2530        icnt = buf_len;
2531    }
2532
2533    out_buf_size = ocnt = prev_ocnt = initial_out_buf_size;
2534    if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2535        return FAILURE;
2536    }
2537
2538    pd = out_buf;
2539
2540    if (self->stub_len > 0) {
2541        pt = self->stub;
2542        tcnt = self->stub_len;
2543
2544        while (tcnt > 0) {
2545            if (iconv(self->cd, &pt, &tcnt, &pd, &ocnt) == (size_t)-1) {
2546#if ICONV_SUPPORTS_ERRNO
2547                switch (errno) {
2548                    case EILSEQ:
2549                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset);
2550                        goto out_failure;
2551
2552                    case EINVAL:
2553                        if (ps != NULL) {
2554                            if (icnt > 0) {
2555                                if (self->stub_len >= sizeof(self->stub)) {
2556                                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset);
2557                                    goto out_failure;
2558                                }
2559                                self->stub[self->stub_len++] = *(ps++);
2560                                icnt--;
2561                                pt = self->stub;
2562                                tcnt = self->stub_len;
2563                            } else {
2564                                tcnt = 0;
2565                                break;
2566                            }
2567                        }
2568                        break;
2569
2570                    case E2BIG: {
2571                        char *new_out_buf;
2572                        size_t new_out_buf_size;
2573
2574                        new_out_buf_size = out_buf_size << 1;
2575
2576                        if (new_out_buf_size < out_buf_size) {
2577                            /* whoa! no bigger buckets are sold anywhere... */
2578                            if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
2579                                goto out_failure;
2580                            }
2581
2582                            php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
2583
2584                            out_buf_size = ocnt = initial_out_buf_size;
2585                            if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2586                                return FAILURE;
2587                            }
2588                            pd = out_buf;
2589                        } else {
2590                            if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
2591                                if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
2592                                    goto out_failure;
2593                                }
2594
2595                                php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
2596                                return FAILURE;
2597                            }
2598                            pd = new_out_buf + (pd - out_buf);
2599                            ocnt += (new_out_buf_size - out_buf_size);
2600                            out_buf = new_out_buf;
2601                            out_buf_size = new_out_buf_size;
2602                        }
2603                    } break;
2604
2605                    default:
2606                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2607                        goto out_failure;
2608                }
2609#else
2610                if (ocnt == prev_ocnt) {
2611                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2612                    goto out_failure;
2613                }
2614#endif
2615            }
2616            prev_ocnt = ocnt;
2617        }
2618        memmove(self->stub, pt, tcnt);
2619        self->stub_len = tcnt;
2620    }
2621
2622    while (icnt > 0) {
2623        if ((ps == NULL ? iconv(self->cd, NULL, NULL, &pd, &ocnt):
2624                    iconv(self->cd, (char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) {
2625#if ICONV_SUPPORTS_ERRNO
2626            switch (errno) {
2627                case EILSEQ:
2628                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset);
2629                    goto out_failure;
2630
2631                case EINVAL:
2632                    if (ps != NULL) {
2633                        if (icnt > sizeof(self->stub)) {
2634                            php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset);
2635                            goto out_failure;
2636                        }
2637                        memcpy(self->stub, ps, icnt);
2638                        self->stub_len = icnt;
2639                        ps += icnt;
2640                        icnt = 0;
2641                    } else {
2642                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unexpected octet values", self->from_charset, self->to_charset);
2643                        goto out_failure;
2644                    }
2645                    break;
2646
2647                case E2BIG: {
2648                    char *new_out_buf;
2649                    size_t new_out_buf_size;
2650
2651                    new_out_buf_size = out_buf_size << 1;
2652
2653                    if (new_out_buf_size < out_buf_size) {
2654                        /* whoa! no bigger buckets are sold anywhere... */
2655                        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
2656                            goto out_failure;
2657                        }
2658
2659                        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
2660
2661                        out_buf_size = ocnt = initial_out_buf_size;
2662                        if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2663                            return FAILURE;
2664                        }
2665                        pd = out_buf;
2666                    } else {
2667                        if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
2668                            if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
2669                                goto out_failure;
2670                            }
2671
2672                            php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
2673                            return FAILURE;
2674                        }
2675                        pd = new_out_buf + (pd - out_buf);
2676                        ocnt += (new_out_buf_size - out_buf_size);
2677                        out_buf = new_out_buf;
2678                        out_buf_size = new_out_buf_size;
2679                    }
2680                } break;
2681
2682                default:
2683                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2684                    goto out_failure;
2685            }
2686#else
2687            if (ocnt == prev_ocnt) {
2688                php_error_docref(NULL TSRMLS_CC, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2689                goto out_failure;
2690            }
2691#endif
2692        } else {
2693            if (ps == NULL) {
2694                break;
2695            }
2696        }
2697        prev_ocnt = ocnt;
2698    }
2699
2700    if (out_buf_size - ocnt > 0) {
2701        if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent TSRMLS_CC))) {
2702            goto out_failure;
2703        }
2704        php_stream_bucket_append(buckets_out, new_bucket TSRMLS_CC);
2705    } else {
2706        pefree(out_buf, persistent);
2707    }
2708    *consumed += buf_len - icnt;
2709
2710    return SUCCESS;
2711
2712out_failure:
2713    pefree(out_buf, persistent);
2714    return FAILURE;
2715}
2716/* }}} php_iconv_stream_filter_append_bucket */
2717
2718/* {{{ php_iconv_stream_filter_do_filter */
2719static php_stream_filter_status_t php_iconv_stream_filter_do_filter(
2720        php_stream *stream, php_stream_filter *filter,
2721        php_stream_bucket_brigade *buckets_in,
2722        php_stream_bucket_brigade *buckets_out,
2723        size_t *bytes_consumed, int flags TSRMLS_DC)
2724{
2725    php_stream_bucket *bucket = NULL;
2726    size_t consumed = 0;
2727    php_iconv_stream_filter *self = (php_iconv_stream_filter *)filter->abstract;
2728
2729    while (buckets_in->head != NULL) {
2730        bucket = buckets_in->head;
2731
2732        php_stream_bucket_unlink(bucket TSRMLS_CC);
2733
2734        if (php_iconv_stream_filter_append_bucket(self, stream, filter,
2735                buckets_out, bucket->buf, bucket->buflen, &consumed,
2736                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
2737            goto out_failure;
2738        }
2739
2740        php_stream_bucket_delref(bucket TSRMLS_CC);
2741    }
2742
2743    if (flags != PSFS_FLAG_NORMAL) {
2744        if (php_iconv_stream_filter_append_bucket(self, stream, filter,
2745                buckets_out, NULL, 0, &consumed,
2746                php_stream_is_persistent(stream) TSRMLS_CC) != SUCCESS) {
2747            goto out_failure;
2748        }
2749    }
2750
2751    if (bytes_consumed != NULL) {
2752        *bytes_consumed = consumed;
2753    }
2754
2755    return PSFS_PASS_ON;
2756
2757out_failure:
2758    if (bucket != NULL) {
2759        php_stream_bucket_delref(bucket TSRMLS_CC);
2760    }
2761    return PSFS_ERR_FATAL;
2762}
2763/* }}} */
2764
2765/* {{{ php_iconv_stream_filter_cleanup */
2766static void php_iconv_stream_filter_cleanup(php_stream_filter *filter TSRMLS_DC)
2767{
2768    php_iconv_stream_filter_dtor((php_iconv_stream_filter *)filter->abstract);
2769    pefree(filter->abstract, ((php_iconv_stream_filter *)filter->abstract)->persistent);
2770}
2771/* }}} */
2772
2773static php_stream_filter_ops php_iconv_stream_filter_ops = {
2774    php_iconv_stream_filter_do_filter,
2775    php_iconv_stream_filter_cleanup,
2776    "convert.iconv.*"
2777};
2778
2779/* {{{ php_iconv_stream_filter_create */
2780static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, int persistent TSRMLS_DC)
2781{
2782    php_stream_filter *retval = NULL;
2783    php_iconv_stream_filter *inst;
2784    char *from_charset = NULL, *to_charset = NULL;
2785    size_t from_charset_len, to_charset_len;
2786
2787    if ((from_charset = strchr(name, '.')) == NULL) {
2788        return NULL;
2789    }
2790    ++from_charset;
2791    if ((from_charset = strchr(from_charset, '.')) == NULL) {
2792        return NULL;
2793    }
2794    ++from_charset;
2795    if ((to_charset = strpbrk(from_charset, "/.")) == NULL) {
2796        return NULL;
2797    }
2798    from_charset_len = to_charset - from_charset;
2799    ++to_charset;
2800    to_charset_len = strlen(to_charset);
2801
2802    if (from_charset_len >= ICONV_CSNMAXLEN || to_charset_len >= ICONV_CSNMAXLEN) {
2803        return NULL;
2804    }
2805
2806    if (NULL == (inst = pemalloc(sizeof(php_iconv_stream_filter), persistent))) {
2807        return NULL;
2808    }
2809
2810    if (php_iconv_stream_filter_ctor(inst, to_charset, to_charset_len, from_charset, from_charset_len, persistent) != PHP_ICONV_ERR_SUCCESS) {
2811        pefree(inst, persistent);
2812        return NULL;
2813    }
2814
2815    if (NULL == (retval = php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent))) {
2816        php_iconv_stream_filter_dtor(inst);
2817        pefree(inst, persistent);
2818    }
2819
2820    return retval;
2821}
2822/* }}} */
2823
2824/* {{{ php_iconv_stream_register_factory */
2825static php_iconv_err_t php_iconv_stream_filter_register_factory(TSRMLS_D)
2826{
2827    static php_stream_filter_factory filter_factory = {
2828        php_iconv_stream_filter_factory_create
2829    };
2830
2831    if (FAILURE == php_stream_filter_register_factory(
2832                php_iconv_stream_filter_ops.label,
2833                &filter_factory TSRMLS_CC)) {
2834        return PHP_ICONV_ERR_UNKNOWN;
2835    }
2836    return PHP_ICONV_ERR_SUCCESS;
2837}
2838/* }}} */
2839
2840/* {{{ php_iconv_stream_unregister_factory */
2841static php_iconv_err_t php_iconv_stream_filter_unregister_factory(TSRMLS_D)
2842{
2843    if (FAILURE == php_stream_filter_unregister_factory(
2844                php_iconv_stream_filter_ops.label TSRMLS_CC)) {
2845        return PHP_ICONV_ERR_UNKNOWN;
2846    }
2847    return PHP_ICONV_ERR_SUCCESS;
2848}
2849/* }}} */
2850/* }}} */
2851#endif
2852
2853/*
2854 * Local variables:
2855 * tab-width: 4
2856 * c-basic-offset: 4
2857 * End:
2858 * vim600: sw=4 ts=4 fdm=marker
2859 * vim<600: sw=4 ts=4
2860 */
2861