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