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