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