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