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