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