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