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