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