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