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