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