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