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