xref: /PHP-7.0/ext/iconv/iconv.c (revision ee76a5ae)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 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 */
66 ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strlen, 0, 0, 1)
67 	ZEND_ARG_INFO(0, str)
68 	ZEND_ARG_INFO(0, charset)
69 ZEND_END_ARG_INFO()
70 
71 ZEND_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)
76 ZEND_END_ARG_INFO()
77 
78 ZEND_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)
83 ZEND_END_ARG_INFO()
84 
85 ZEND_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)
89 ZEND_END_ARG_INFO()
90 
91 ZEND_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) */
95 ZEND_END_ARG_INFO()
96 
97 ZEND_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)
101 ZEND_END_ARG_INFO()
102 
103 ZEND_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)
107 ZEND_END_ARG_INFO()
108 
109 ZEND_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)
113 ZEND_END_ARG_INFO()
114 
115 ZEND_BEGIN_ARG_INFO(arginfo_iconv_set_encoding, 0)
116 	ZEND_ARG_INFO(0, type)
117 	ZEND_ARG_INFO(0, charset)
118 ZEND_END_ARG_INFO()
119 
120 ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_get_encoding, 0, 0, 0)
121 	ZEND_ARG_INFO(0, type)
122 ZEND_END_ARG_INFO()
123 
124 /* }}} */
125 
126 /* {{{ iconv_functions[]
127  */
128 const 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 
143 ZEND_DECLARE_MODULE_GLOBALS(iconv)
144 static PHP_GINIT_FUNCTION(iconv);
145 
146 /* {{{ iconv_module_entry
147  */
148 zend_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
168 ZEND_TSRMLS_CACHE_DEFINE()
169 #endif
ZEND_GET_MODULE(iconv)170 ZEND_GET_MODULE(iconv)
171 #endif
172 
173 /* {{{ PHP_GINIT_FUNCTION */
174 static 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 */
190 typedef 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 */
200 static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd);
201 static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd);
202 
203 static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset);
204 
205 static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_t nbytes, const char *enc);
206 
207 static 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 
209 static 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 
211 static 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 
213 static 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 
215 static php_iconv_err_t php_iconv_stream_filter_register_factory(void);
216 static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void);
217 
218 static int php_iconv_output_conflict(const char *handler_name, size_t handler_name_len);
219 static php_output_handler *php_iconv_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags);
220 static int php_iconv_output_handler(void **nothing, php_output_context *output_context);
221 /* }}} */
222 
223 /* {{{ static globals */
224 static char _generic_superset_name[] = ICONV_UCS4_ENCODING;
225 #define GENERIC_SUPERSET_NAME _generic_superset_name
226 #define GENERIC_SUPERSET_NBYTES 4
227 /* }}} */
228 
229 
PHP_INI_MH(OnUpdateInputEncoding)230 static 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 
PHP_INI_MH(OnUpdateOutputEncoding)243 static 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 
PHP_INI_MH(OnUpdateInternalEncoding)256 static 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  */
271 PHP_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)
PHP_INI_END()275 PHP_INI_END()
276 /* }}} */
277 
278 /* {{{ PHP_MINIT_FUNCTION */
279 PHP_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 */
PHP_MSHUTDOWN_FUNCTION(miconv)324 PHP_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 */
PHP_MINFO_FUNCTION(miconv)333 PHP_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 
get_internal_encoding(void)350 static 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 
get_input_encoding(void)361 static 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 
get_output_encoding(void)372 static 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 
php_iconv_output_conflict(const char * handler_name,size_t handler_name_len)384 static 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 
php_iconv_output_handler_init(const char * handler_name,size_t handler_name_len,size_t chunk_size,int flags)395 static 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 
php_iconv_output_handler(void ** nothing,php_output_context * output_context)400 static 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() */
_php_iconv_appendl(smart_str * d,const char * s,size_t l,iconv_t cd)456 static 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() */
_php_iconv_appendc(smart_str * d,const char c,iconv_t cd)531 static 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
_php_check_ignore(const char * charset)539 static 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  */
php_iconv_string(const char * in_p,size_t in_len,zend_string ** out,const char * out_charset,const char * in_charset)557 PHP_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() */
_php_iconv_strlen(size_t * pretval,const char * str,size_t nbytes,const char * enc)737 static 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() */
_php_iconv_substr(smart_str * pretval,const char * str,size_t nbytes,zend_long offset,zend_long len,const char * enc)821 static 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() */
_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)967 static 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() */
_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)1152 static 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 
1465 out:
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() */
_php_iconv_mime_decode(smart_str * pretval,const char * str,size_t str_nbytes,const char * enc,const char ** next_pos,int mode)1483 static 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);
1996 out:
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() */
_php_iconv_show_error(php_iconv_err_t err,const char * out_charset,const char * in_charset)2008 static 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 */
PHP_FUNCTION(iconv_strlen)2050 PHP_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 */
PHP_FUNCTION(iconv_substr)2082 PHP_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 */
PHP_FUNCTION(iconv_strpos)2121 PHP_FUNCTION(iconv_strpos)
2122 {
2123 	char *charset = get_internal_encoding();
2124 	size_t charset_len = 0;
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 		php_error_docref(NULL, E_WARNING, "Offset not contained in string.");
2146 		RETURN_FALSE;
2147 	}
2148 
2149 	if (ZSTR_LEN(ndl) < 1) {
2150 		RETURN_FALSE;
2151 	}
2152 
2153 	err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl),
2154 	                        offset, charset);
2155 	_php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset);
2156 
2157 	if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) {
2158 		RETVAL_LONG((zend_long)retval);
2159 	} else {
2160 		RETVAL_FALSE;
2161 	}
2162 }
2163 /* }}} */
2164 
2165 /* {{{ proto int iconv_strrpos(string haystack, string needle [, string charset])
2166    Finds position of last occurrence of needle within part of haystack beginning with offset */
PHP_FUNCTION(iconv_strrpos)2167 PHP_FUNCTION(iconv_strrpos)
2168 {
2169 	char *charset = get_internal_encoding();
2170 	size_t charset_len = 0;
2171 	zend_string *haystk;
2172 	zend_string *ndl;
2173 
2174 	php_iconv_err_t err;
2175 
2176 	size_t retval;
2177 
2178 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|s",
2179 		&haystk, &ndl,
2180 		&charset, &charset_len) == FAILURE) {
2181 		RETURN_FALSE;
2182 	}
2183 
2184 	if (ZSTR_LEN(ndl) < 1) {
2185 		RETURN_FALSE;
2186 	}
2187 
2188 	if (charset_len >= ICONV_CSNMAXLEN) {
2189 		php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2190 		RETURN_FALSE;
2191 	}
2192 
2193 	err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl),
2194 	                        -1, charset);
2195 	_php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset);
2196 
2197 	if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) {
2198 		RETVAL_LONG((zend_long)retval);
2199 	} else {
2200 		RETVAL_FALSE;
2201 	}
2202 }
2203 /* }}} */
2204 
2205 /* {{{ proto string iconv_mime_encode(string field_name, string field_value [, array preference])
2206    Composes a mime header field with field_name and field_value in a specified scheme */
PHP_FUNCTION(iconv_mime_encode)2207 PHP_FUNCTION(iconv_mime_encode)
2208 {
2209 	zend_string *field_name = NULL;
2210 	zend_string *field_value = NULL;
2211 	zend_string *tmp_str = NULL;
2212 	zval *pref = NULL;
2213 	smart_str retval = {0};
2214 	php_iconv_err_t err;
2215 
2216 	const char *in_charset = get_internal_encoding();
2217 	const char *out_charset = in_charset;
2218 	zend_long line_len = 76;
2219 	const char *lfchars = "\r\n";
2220 	php_iconv_enc_scheme_t scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
2221 
2222 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|a",
2223 		&field_name, &field_value,
2224 		&pref) == FAILURE) {
2225 
2226 		RETURN_FALSE;
2227 	}
2228 
2229 	if (pref != NULL) {
2230 		zval *pzval;
2231 
2232 		if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "scheme", sizeof("scheme") - 1)) != NULL) {
2233 			if (Z_TYPE_P(pzval) == IS_STRING && Z_STRLEN_P(pzval) > 0) {
2234 				switch (Z_STRVAL_P(pzval)[0]) {
2235 					case 'B': case 'b':
2236 						scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
2237 						break;
2238 
2239 					case 'Q': case 'q':
2240 						scheme_id = PHP_ICONV_ENC_SCHEME_QPRINT;
2241 						break;
2242 				}
2243 			}
2244 		}
2245 
2246 		if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "input-charset", sizeof("input-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) {
2247 			if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) {
2248 				php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2249 				RETURN_FALSE;
2250 			}
2251 
2252 			if (Z_STRLEN_P(pzval) > 0) {
2253 				in_charset = Z_STRVAL_P(pzval);
2254 			}
2255 		}
2256 
2257 
2258 		if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "output-charset", sizeof("output-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) {
2259 			if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) {
2260 				php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2261 				RETURN_FALSE;
2262 			}
2263 
2264 			if (Z_STRLEN_P(pzval) > 0) {
2265 				out_charset = Z_STRVAL_P(pzval);
2266 			}
2267 		}
2268 
2269 		if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-length", sizeof("line-length") - 1)) != NULL) {
2270 			line_len = zval_get_long(pzval);
2271 		}
2272 
2273 		if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-break-chars", sizeof("line-break-chars") - 1)) != NULL) {
2274 			if (Z_TYPE_P(pzval) != IS_STRING) {
2275 				tmp_str = zval_get_string(pzval);
2276 				lfchars = ZSTR_VAL(tmp_str);
2277 			} else {
2278 				lfchars = Z_STRVAL_P(pzval);
2279 			}
2280 		}
2281 	}
2282 
2283 	err = _php_iconv_mime_encode(&retval, ZSTR_VAL(field_name), ZSTR_LEN(field_name),
2284 		ZSTR_VAL(field_value), ZSTR_LEN(field_value), line_len, lfchars, scheme_id,
2285 		out_charset, in_charset);
2286 	_php_iconv_show_error(err, out_charset, in_charset);
2287 
2288 	if (err == PHP_ICONV_ERR_SUCCESS) {
2289 		if (retval.s != NULL) {
2290 			RETVAL_STR(retval.s);
2291 		} else {
2292 			RETVAL_EMPTY_STRING();
2293 		}
2294 	} else {
2295 		smart_str_free(&retval);
2296 		RETVAL_FALSE;
2297 	}
2298 
2299 	if (tmp_str) {
2300 		zend_string_release(tmp_str);
2301 	}
2302 }
2303 /* }}} */
2304 
2305 /* {{{ proto string iconv_mime_decode(string encoded_string [, int mode, string charset])
2306    Decodes a mime header field */
PHP_FUNCTION(iconv_mime_decode)2307 PHP_FUNCTION(iconv_mime_decode)
2308 {
2309 	zend_string *encoded_str;
2310 	char *charset = get_internal_encoding();
2311 	size_t charset_len = 0;
2312 	zend_long mode = 0;
2313 
2314 	smart_str retval = {0};
2315 
2316 	php_iconv_err_t err;
2317 
2318 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls",
2319 		&encoded_str, &mode, &charset, &charset_len) == FAILURE) {
2320 
2321 		RETURN_FALSE;
2322 	}
2323 
2324 	if (charset_len >= ICONV_CSNMAXLEN) {
2325 		php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2326 		RETURN_FALSE;
2327 	}
2328 
2329 	err = _php_iconv_mime_decode(&retval, ZSTR_VAL(encoded_str), ZSTR_LEN(encoded_str), charset, NULL, (int)mode);
2330 	_php_iconv_show_error(err, charset, "???");
2331 
2332 	if (err == PHP_ICONV_ERR_SUCCESS) {
2333 		if (retval.s != NULL) {
2334 			RETVAL_STR(retval.s);
2335 		} else {
2336 			RETVAL_EMPTY_STRING();
2337 		}
2338 	} else {
2339 		smart_str_free(&retval);
2340 		RETVAL_FALSE;
2341 	}
2342 }
2343 /* }}} */
2344 
2345 /* {{{ proto array iconv_mime_decode_headers(string headers [, int mode, string charset])
2346    Decodes multiple mime header fields */
PHP_FUNCTION(iconv_mime_decode_headers)2347 PHP_FUNCTION(iconv_mime_decode_headers)
2348 {
2349 	zend_string *encoded_str;
2350 	char *charset = get_internal_encoding();
2351 	size_t charset_len = 0;
2352 	zend_long mode = 0;
2353 	char *enc_str_tmp;
2354 	size_t enc_str_len_tmp;
2355 
2356 	php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
2357 
2358 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls",
2359 		&encoded_str, &mode, &charset, &charset_len) == FAILURE) {
2360 
2361 		RETURN_FALSE;
2362 	}
2363 
2364 	if (charset_len >= ICONV_CSNMAXLEN) {
2365 		php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2366 		RETURN_FALSE;
2367 	}
2368 
2369 	array_init(return_value);
2370 
2371 	enc_str_tmp = ZSTR_VAL(encoded_str);
2372 	enc_str_len_tmp = ZSTR_LEN(encoded_str);
2373 	while (enc_str_len_tmp > 0) {
2374 		smart_str decoded_header = {0};
2375 		char *header_name = NULL;
2376 		size_t header_name_len = 0;
2377 		char *header_value = NULL;
2378 		size_t header_value_len = 0;
2379 		char *p, *limit;
2380 		const char *next_pos;
2381 
2382 		if (PHP_ICONV_ERR_SUCCESS != (err = _php_iconv_mime_decode(&decoded_header, enc_str_tmp, enc_str_len_tmp, charset, &next_pos, (int)mode))) {
2383 			smart_str_free(&decoded_header);
2384 			break;
2385 		}
2386 
2387 		if (decoded_header.s == NULL) {
2388 			break;
2389 		}
2390 
2391 		limit = ZSTR_VAL(decoded_header.s) + ZSTR_LEN(decoded_header.s);
2392 		for (p = ZSTR_VAL(decoded_header.s); p < limit; p++) {
2393 			if (*p == ':') {
2394 				*p = '\0';
2395 				header_name = ZSTR_VAL(decoded_header.s);
2396 				header_name_len = p - ZSTR_VAL(decoded_header.s);
2397 
2398 				while (++p < limit) {
2399 					if (*p != ' ' && *p != '\t') {
2400 						break;
2401 					}
2402 				}
2403 
2404 				header_value = p;
2405 				header_value_len = limit - p;
2406 
2407 				break;
2408 			}
2409 		}
2410 
2411 		if (header_name != NULL) {
2412 			zval *elem;
2413 
2414 			if ((elem = zend_hash_str_find(Z_ARRVAL_P(return_value), header_name, header_name_len)) != NULL) {
2415 				if (Z_TYPE_P(elem) != IS_ARRAY) {
2416 					zval new_elem;
2417 
2418 					array_init(&new_elem);
2419 					Z_ADDREF_P(elem);
2420 					add_next_index_zval(&new_elem, elem);
2421 
2422 					elem = zend_hash_str_update(Z_ARRVAL_P(return_value), header_name, header_name_len, &new_elem);
2423 				}
2424 				add_next_index_stringl(elem, header_value, header_value_len);
2425 			} else {
2426 				add_assoc_stringl_ex(return_value, header_name, header_name_len, header_value, header_value_len);
2427 			}
2428 		}
2429 		enc_str_len_tmp -= next_pos - enc_str_tmp;
2430 		enc_str_tmp = (char *)next_pos;
2431 
2432 		smart_str_free(&decoded_header);
2433 	}
2434 
2435 	if (err != PHP_ICONV_ERR_SUCCESS) {
2436 		_php_iconv_show_error(err, charset, "???");
2437 		zval_dtor(return_value);
2438 		RETVAL_FALSE;
2439 	}
2440 }
2441 /* }}} */
2442 
2443 /* {{{ proto string iconv(string in_charset, string out_charset, string str)
2444    Returns str converted to the out_charset character set */
PHP_NAMED_FUNCTION(php_if_iconv)2445 PHP_NAMED_FUNCTION(php_if_iconv)
2446 {
2447 	char *in_charset, *out_charset;
2448 	zend_string *in_buffer;
2449 	size_t in_charset_len = 0, out_charset_len = 0;
2450 	php_iconv_err_t err;
2451 	zend_string *out_buffer;
2452 
2453 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssS",
2454 		&in_charset, &in_charset_len, &out_charset, &out_charset_len, &in_buffer) == FAILURE)
2455 		return;
2456 
2457 	if (in_charset_len >= ICONV_CSNMAXLEN || out_charset_len >= ICONV_CSNMAXLEN) {
2458 		php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2459 		RETURN_FALSE;
2460 	}
2461 
2462 	err = php_iconv_string(ZSTR_VAL(in_buffer), (size_t)ZSTR_LEN(in_buffer), &out_buffer, out_charset, in_charset);
2463 	_php_iconv_show_error(err, out_charset, in_charset);
2464 	if (err == PHP_ICONV_ERR_SUCCESS && out_buffer != NULL) {
2465 		RETVAL_STR(out_buffer);
2466 	} else {
2467 		if (out_buffer != NULL) {
2468 			zend_string_free(out_buffer);
2469 		}
2470 		RETURN_FALSE;
2471 	}
2472 }
2473 /* }}} */
2474 
2475 /* {{{ proto bool iconv_set_encoding(string type, string charset)
2476    Sets internal encoding and output encoding for ob_iconv_handler() */
PHP_FUNCTION(iconv_set_encoding)2477 PHP_FUNCTION(iconv_set_encoding)
2478 {
2479 	char *type;
2480 	zend_string *charset;
2481 	size_t type_len, retval;
2482 	zend_string *name;
2483 
2484 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &type, &type_len, &charset) == FAILURE)
2485 		return;
2486 
2487 	if (ZSTR_LEN(charset) >= ICONV_CSNMAXLEN) {
2488 		php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN);
2489 		RETURN_FALSE;
2490 	}
2491 
2492 	if(!strcasecmp("input_encoding", type)) {
2493 		name = zend_string_init("iconv.input_encoding", sizeof("iconv.input_encoding") - 1, 0);
2494 	} else if(!strcasecmp("output_encoding", type)) {
2495 		name = zend_string_init("iconv.output_encoding", sizeof("iconv.output_encoding") - 1, 0);
2496 	} else if(!strcasecmp("internal_encoding", type)) {
2497 		name = zend_string_init("iconv.internal_encoding", sizeof("iconv.internal_encoding") - 1, 0);
2498 	} else {
2499 		RETURN_FALSE;
2500 	}
2501 
2502 	retval = zend_alter_ini_entry(name, charset, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
2503 	zend_string_release(name);
2504 
2505 	if (retval == SUCCESS) {
2506 		RETURN_TRUE;
2507 	} else {
2508 		RETURN_FALSE;
2509 	}
2510 }
2511 /* }}} */
2512 
2513 /* {{{ proto mixed iconv_get_encoding([string type])
2514    Get internal encoding and output encoding for ob_iconv_handler() */
PHP_FUNCTION(iconv_get_encoding)2515 PHP_FUNCTION(iconv_get_encoding)
2516 {
2517 	char *type = "all";
2518 	size_t type_len = sizeof("all")-1;
2519 
2520 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &type, &type_len) == FAILURE)
2521 		return;
2522 
2523 	if (!strcasecmp("all", type)) {
2524 		array_init(return_value);
2525 		add_assoc_string(return_value, "input_encoding",    get_input_encoding());
2526 		add_assoc_string(return_value, "output_encoding",   get_output_encoding());
2527 		add_assoc_string(return_value, "internal_encoding", get_internal_encoding());
2528 	} else if (!strcasecmp("input_encoding", type)) {
2529 		RETVAL_STRING(get_input_encoding());
2530 	} else if (!strcasecmp("output_encoding", type)) {
2531 		RETVAL_STRING(get_output_encoding());
2532 	} else if (!strcasecmp("internal_encoding", type)) {
2533 		RETVAL_STRING(get_internal_encoding());
2534 	} else {
2535 		RETURN_FALSE;
2536 	}
2537 
2538 }
2539 /* }}} */
2540 
2541 /* {{{ iconv stream filter */
2542 typedef struct _php_iconv_stream_filter {
2543 	iconv_t cd;
2544 	int persistent;
2545 	char *to_charset;
2546 	size_t to_charset_len;
2547 	char *from_charset;
2548 	size_t from_charset_len;
2549 	char stub[128];
2550 	size_t stub_len;
2551 } php_iconv_stream_filter;
2552 /* }}} iconv stream filter */
2553 
2554 /* {{{ php_iconv_stream_filter_dtor */
php_iconv_stream_filter_dtor(php_iconv_stream_filter * self)2555 static void php_iconv_stream_filter_dtor(php_iconv_stream_filter *self)
2556 {
2557 	iconv_close(self->cd);
2558 	pefree(self->to_charset, self->persistent);
2559 	pefree(self->from_charset, self->persistent);
2560 }
2561 /* }}} */
2562 
2563 /* {{{ php_iconv_stream_filter_ctor() */
php_iconv_stream_filter_ctor(php_iconv_stream_filter * self,const char * to_charset,size_t to_charset_len,const char * from_charset,size_t from_charset_len,int persistent)2564 static php_iconv_err_t php_iconv_stream_filter_ctor(php_iconv_stream_filter *self,
2565 		const char *to_charset, size_t to_charset_len,
2566 		const char *from_charset, size_t from_charset_len, int persistent)
2567 {
2568 	if (NULL == (self->to_charset = pemalloc(to_charset_len + 1, persistent))) {
2569 		return PHP_ICONV_ERR_ALLOC;
2570 	}
2571 	self->to_charset_len = to_charset_len;
2572 	if (NULL == (self->from_charset = pemalloc(from_charset_len + 1, persistent))) {
2573 		pefree(self->to_charset, persistent);
2574 		return PHP_ICONV_ERR_ALLOC;
2575 	}
2576 	self->from_charset_len = from_charset_len;
2577 
2578 	memcpy(self->to_charset, to_charset, to_charset_len);
2579 	self->to_charset[to_charset_len] = '\0';
2580 	memcpy(self->from_charset, from_charset, from_charset_len);
2581 	self->from_charset[from_charset_len] = '\0';
2582 
2583 	if ((iconv_t)-1 == (self->cd = iconv_open(self->to_charset, self->from_charset))) {
2584 		pefree(self->from_charset, persistent);
2585 		pefree(self->to_charset, persistent);
2586 		return PHP_ICONV_ERR_UNKNOWN;
2587 	}
2588 	self->persistent = persistent;
2589 	self->stub_len = 0;
2590 	return PHP_ICONV_ERR_SUCCESS;
2591 }
2592 /* }}} */
2593 
2594 /* {{{ php_iconv_stream_filter_append_bucket */
php_iconv_stream_filter_append_bucket(php_iconv_stream_filter * self,php_stream * stream,php_stream_filter * filter,php_stream_bucket_brigade * buckets_out,const char * ps,size_t buf_len,size_t * consumed,int persistent)2595 static int php_iconv_stream_filter_append_bucket(
2596 		php_iconv_stream_filter *self,
2597 		php_stream *stream, php_stream_filter *filter,
2598 		php_stream_bucket_brigade *buckets_out,
2599 		const char *ps, size_t buf_len, size_t *consumed,
2600 		int persistent)
2601 {
2602 	php_stream_bucket *new_bucket;
2603 	char *out_buf = NULL;
2604 	size_t out_buf_size;
2605 	char *pd, *pt;
2606 	size_t ocnt, prev_ocnt, icnt, tcnt;
2607 	size_t initial_out_buf_size;
2608 
2609 	if (ps == NULL) {
2610 		initial_out_buf_size = 64;
2611 		icnt = 1;
2612 	} else {
2613 		initial_out_buf_size = buf_len;
2614 		icnt = buf_len;
2615 	}
2616 
2617 	out_buf_size = ocnt = prev_ocnt = initial_out_buf_size;
2618 	if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2619 		return FAILURE;
2620 	}
2621 
2622 	pd = out_buf;
2623 
2624 	if (self->stub_len > 0) {
2625 		pt = self->stub;
2626 		tcnt = self->stub_len;
2627 
2628 		while (tcnt > 0) {
2629 			if (iconv(self->cd, &pt, &tcnt, &pd, &ocnt) == (size_t)-1) {
2630 #if ICONV_SUPPORTS_ERRNO
2631 				switch (errno) {
2632 					case EILSEQ:
2633 						php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset);
2634 						goto out_failure;
2635 
2636 					case EINVAL:
2637 						if (ps != NULL) {
2638 							if (icnt > 0) {
2639 								if (self->stub_len >= sizeof(self->stub)) {
2640 									php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset);
2641 									goto out_failure;
2642 								}
2643 								self->stub[self->stub_len++] = *(ps++);
2644 								icnt--;
2645 								pt = self->stub;
2646 								tcnt = self->stub_len;
2647 							} else {
2648 								tcnt = 0;
2649 								break;
2650 							}
2651 						} else {
2652 						    php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset);
2653 						    goto out_failure;
2654 						}
2655 						break;
2656 
2657 					case E2BIG: {
2658 						char *new_out_buf;
2659 						size_t new_out_buf_size;
2660 
2661 						new_out_buf_size = out_buf_size << 1;
2662 
2663 						if (new_out_buf_size < out_buf_size) {
2664 							/* whoa! no bigger buckets are sold anywhere... */
2665 							if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
2666 								goto out_failure;
2667 							}
2668 
2669 							php_stream_bucket_append(buckets_out, new_bucket);
2670 
2671 							out_buf_size = ocnt = initial_out_buf_size;
2672 							if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2673 								return FAILURE;
2674 							}
2675 							pd = out_buf;
2676 						} else {
2677 							if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
2678 								if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
2679 									goto out_failure;
2680 								}
2681 
2682 								php_stream_bucket_append(buckets_out, new_bucket);
2683 								return FAILURE;
2684 							}
2685 							pd = new_out_buf + (pd - out_buf);
2686 							ocnt += (new_out_buf_size - out_buf_size);
2687 							out_buf = new_out_buf;
2688 							out_buf_size = new_out_buf_size;
2689 						}
2690 					} break;
2691 
2692 					default:
2693 						php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2694 						goto out_failure;
2695 				}
2696 #else
2697 				if (ocnt == prev_ocnt) {
2698 					php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2699 					goto out_failure;
2700 				}
2701 #endif
2702 			}
2703 			prev_ocnt = ocnt;
2704 		}
2705 		memmove(self->stub, pt, tcnt);
2706 		self->stub_len = tcnt;
2707 	}
2708 
2709 	while (icnt > 0) {
2710 		if ((ps == NULL ? iconv(self->cd, NULL, NULL, &pd, &ocnt):
2711 					iconv(self->cd, (char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) {
2712 #if ICONV_SUPPORTS_ERRNO
2713 			switch (errno) {
2714 				case EILSEQ:
2715 					php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset);
2716 					goto out_failure;
2717 
2718 				case EINVAL:
2719 					if (ps != NULL) {
2720 						if (icnt > sizeof(self->stub)) {
2721 							php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset);
2722 							goto out_failure;
2723 						}
2724 						memcpy(self->stub, ps, icnt);
2725 						self->stub_len = icnt;
2726 						ps += icnt;
2727 						icnt = 0;
2728 					} else {
2729 						php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unexpected octet values", self->from_charset, self->to_charset);
2730 						goto out_failure;
2731 					}
2732 					break;
2733 
2734 				case E2BIG: {
2735 					char *new_out_buf;
2736 					size_t new_out_buf_size;
2737 
2738 					new_out_buf_size = out_buf_size << 1;
2739 
2740 					if (new_out_buf_size < out_buf_size) {
2741 						/* whoa! no bigger buckets are sold anywhere... */
2742 						if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
2743 							goto out_failure;
2744 						}
2745 
2746 						php_stream_bucket_append(buckets_out, new_bucket);
2747 
2748 						out_buf_size = ocnt = initial_out_buf_size;
2749 						if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) {
2750 							return FAILURE;
2751 						}
2752 						pd = out_buf;
2753 					} else {
2754 						if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) {
2755 							if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
2756 								goto out_failure;
2757 							}
2758 
2759 							php_stream_bucket_append(buckets_out, new_bucket);
2760 							return FAILURE;
2761 						}
2762 						pd = new_out_buf + (pd - out_buf);
2763 						ocnt += (new_out_buf_size - out_buf_size);
2764 						out_buf = new_out_buf;
2765 						out_buf_size = new_out_buf_size;
2766 					}
2767 				} break;
2768 
2769 				default:
2770 					php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2771 					goto out_failure;
2772 			}
2773 #else
2774 			if (ocnt == prev_ocnt) {
2775 				php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset);
2776 				goto out_failure;
2777 			}
2778 #endif
2779 		} else {
2780 			if (ps == NULL) {
2781 				break;
2782 			}
2783 		}
2784 		prev_ocnt = ocnt;
2785 	}
2786 
2787 	if (out_buf_size > ocnt) {
2788 		if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) {
2789 			goto out_failure;
2790 		}
2791 		php_stream_bucket_append(buckets_out, new_bucket);
2792 	} else {
2793 		pefree(out_buf, persistent);
2794 	}
2795 	*consumed += buf_len - icnt;
2796 
2797 	return SUCCESS;
2798 
2799 out_failure:
2800 	pefree(out_buf, persistent);
2801 	return FAILURE;
2802 }
2803 /* }}} php_iconv_stream_filter_append_bucket */
2804 
2805 /* {{{ php_iconv_stream_filter_do_filter */
php_iconv_stream_filter_do_filter(php_stream * stream,php_stream_filter * filter,php_stream_bucket_brigade * buckets_in,php_stream_bucket_brigade * buckets_out,size_t * bytes_consumed,int flags)2806 static php_stream_filter_status_t php_iconv_stream_filter_do_filter(
2807 		php_stream *stream, php_stream_filter *filter,
2808 		php_stream_bucket_brigade *buckets_in,
2809 		php_stream_bucket_brigade *buckets_out,
2810 		size_t *bytes_consumed, int flags)
2811 {
2812 	php_stream_bucket *bucket = NULL;
2813 	size_t consumed = 0;
2814 	php_iconv_stream_filter *self = (php_iconv_stream_filter *)Z_PTR(filter->abstract);
2815 
2816 	while (buckets_in->head != NULL) {
2817 		bucket = buckets_in->head;
2818 
2819 		php_stream_bucket_unlink(bucket);
2820 
2821 		if (php_iconv_stream_filter_append_bucket(self, stream, filter,
2822 				buckets_out, bucket->buf, bucket->buflen, &consumed,
2823 				php_stream_is_persistent(stream)) != SUCCESS) {
2824 			goto out_failure;
2825 		}
2826 
2827 		php_stream_bucket_delref(bucket);
2828 	}
2829 
2830 	if (flags != PSFS_FLAG_NORMAL) {
2831 		if (php_iconv_stream_filter_append_bucket(self, stream, filter,
2832 				buckets_out, NULL, 0, &consumed,
2833 				php_stream_is_persistent(stream)) != SUCCESS) {
2834 			goto out_failure;
2835 		}
2836 	}
2837 
2838 	if (bytes_consumed != NULL) {
2839 		*bytes_consumed = consumed;
2840 	}
2841 
2842 	return PSFS_PASS_ON;
2843 
2844 out_failure:
2845 	if (bucket != NULL) {
2846 		php_stream_bucket_delref(bucket);
2847 	}
2848 	return PSFS_ERR_FATAL;
2849 }
2850 /* }}} */
2851 
2852 /* {{{ php_iconv_stream_filter_cleanup */
php_iconv_stream_filter_cleanup(php_stream_filter * filter)2853 static void php_iconv_stream_filter_cleanup(php_stream_filter *filter)
2854 {
2855 	php_iconv_stream_filter_dtor((php_iconv_stream_filter *)Z_PTR(filter->abstract));
2856 	pefree(Z_PTR(filter->abstract), ((php_iconv_stream_filter *)Z_PTR(filter->abstract))->persistent);
2857 }
2858 /* }}} */
2859 
2860 static php_stream_filter_ops php_iconv_stream_filter_ops = {
2861 	php_iconv_stream_filter_do_filter,
2862 	php_iconv_stream_filter_cleanup,
2863 	"convert.iconv.*"
2864 };
2865 
2866 /* {{{ php_iconv_stream_filter_create */
php_iconv_stream_filter_factory_create(const char * name,zval * params,int persistent)2867 static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, int persistent)
2868 {
2869 	php_stream_filter *retval = NULL;
2870 	php_iconv_stream_filter *inst;
2871 	char *from_charset = NULL, *to_charset = NULL;
2872 	size_t from_charset_len, to_charset_len;
2873 
2874 	if ((from_charset = strchr(name, '.')) == NULL) {
2875 		return NULL;
2876 	}
2877 	++from_charset;
2878 	if ((from_charset = strchr(from_charset, '.')) == NULL) {
2879 		return NULL;
2880 	}
2881 	++from_charset;
2882 	if ((to_charset = strpbrk(from_charset, "/.")) == NULL) {
2883 		return NULL;
2884 	}
2885 	from_charset_len = to_charset - from_charset;
2886 	++to_charset;
2887 	to_charset_len = strlen(to_charset);
2888 
2889 	if (from_charset_len >= ICONV_CSNMAXLEN || to_charset_len >= ICONV_CSNMAXLEN) {
2890 		return NULL;
2891 	}
2892 
2893 	if (NULL == (inst = pemalloc(sizeof(php_iconv_stream_filter), persistent))) {
2894 		return NULL;
2895 	}
2896 
2897 	if (php_iconv_stream_filter_ctor(inst, to_charset, to_charset_len, from_charset, from_charset_len, persistent) != PHP_ICONV_ERR_SUCCESS) {
2898 		pefree(inst, persistent);
2899 		return NULL;
2900 	}
2901 
2902 	if (NULL == (retval = php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent))) {
2903 		php_iconv_stream_filter_dtor(inst);
2904 		pefree(inst, persistent);
2905 	}
2906 
2907 	return retval;
2908 }
2909 /* }}} */
2910 
2911 /* {{{ php_iconv_stream_register_factory */
php_iconv_stream_filter_register_factory(void)2912 static php_iconv_err_t php_iconv_stream_filter_register_factory(void)
2913 {
2914 	static php_stream_filter_factory filter_factory = {
2915 		php_iconv_stream_filter_factory_create
2916 	};
2917 
2918 	if (FAILURE == php_stream_filter_register_factory(
2919 				php_iconv_stream_filter_ops.label,
2920 				&filter_factory)) {
2921 		return PHP_ICONV_ERR_UNKNOWN;
2922 	}
2923 	return PHP_ICONV_ERR_SUCCESS;
2924 }
2925 /* }}} */
2926 
2927 /* {{{ php_iconv_stream_unregister_factory */
php_iconv_stream_filter_unregister_factory(void)2928 static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void)
2929 {
2930 	if (FAILURE == php_stream_filter_unregister_factory(
2931 				php_iconv_stream_filter_ops.label)) {
2932 		return PHP_ICONV_ERR_UNKNOWN;
2933 	}
2934 	return PHP_ICONV_ERR_SUCCESS;
2935 }
2936 /* }}} */
2937 /* }}} */
2938 #endif
2939 
2940 /*
2941  * Local variables:
2942  * tab-width: 4
2943  * c-basic-offset: 4
2944  * End:
2945  * vim600: sw=4 ts=4 fdm=marker
2946  * vim<600: sw=4 ts=4
2947  */
2948