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