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