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