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