xref: /PHP-8.4/win32/codepage.c (revision 02881a76)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Anatol Belski <ab@php.net>                                   |
14    +----------------------------------------------------------------------+
15 */
16 
17 #include <assert.h>
18 
19 #include "php.h"
20 #include "SAPI.h"
21 #ifdef _M_ARM64
22 # include <arm_neon.h>
23 #else
24 # include <emmintrin.h>
25 #endif // _M_ARM64
26 
27 #include "win32/console.h"
28 
29 ZEND_TLS const struct php_win32_cp *cur_cp = NULL;
30 ZEND_TLS const struct php_win32_cp *orig_cp = NULL;
31 ZEND_TLS const struct php_win32_cp *cur_out_cp = NULL;
32 ZEND_TLS const struct php_win32_cp *orig_out_cp = NULL;
33 ZEND_TLS const struct php_win32_cp *cur_in_cp = NULL;
34 ZEND_TLS const struct php_win32_cp *orig_in_cp = NULL;
35 
36 #include "cp_enc_map.c"
37 
php_win32_cp_to_w_int(const char * in,size_t in_len,size_t * out_len,UINT cp,DWORD flags)38 __forceinline static wchar_t *php_win32_cp_to_w_int(const char* in, size_t in_len, size_t *out_len, UINT cp, DWORD flags)
39 {/*{{{*/
40 	wchar_t *ret;
41 	int ret_len, tmp_len;
42 
43 	if (!in || in_len > (size_t)INT_MAX) {
44 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
45 		return NULL;
46 	}
47 	assert(in_len ? (in[in_len] == L'\0') : 1);
48 
49 	tmp_len = !in_len ? -1 : (int)in_len + 1;
50 
51 	ret_len = MultiByteToWideChar(cp, flags, in, tmp_len, NULL, 0);
52 	if (ret_len == 0) {
53 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
54 		return NULL;
55 	}
56 
57 	ret = malloc(ret_len * sizeof(wchar_t));
58 	if (!ret) {
59 		SET_ERRNO_FROM_WIN32_CODE(ERROR_OUTOFMEMORY);
60 		return NULL;
61 	}
62 
63 	tmp_len = MultiByteToWideChar(cp, flags, in, tmp_len, ret, ret_len);
64 	if (tmp_len == 0) {
65 		free(ret);
66 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
67 		return NULL;
68 	}
69 
70 	assert(ret ? tmp_len == ret_len : 1);
71 	assert(ret && !in_len ? wcslen(ret) == ret_len - 1 : 1);
72 
73 	ret[ret_len-1] = L'\0';
74 
75 	if (PHP_WIN32_CP_IGNORE_LEN_P != out_len) {
76 		*out_len = ret_len - 1;
77 	}
78 
79 	return ret;
80 }/*}}}*/
81 
php_win32_cp_conv_utf8_to_w(const char * in,size_t in_len,size_t * out_len)82 PW32CP wchar_t *php_win32_cp_conv_utf8_to_w(const char* in, size_t in_len, size_t *out_len)
83 {/*{{{*/
84 	return php_win32_cp_to_w_int(in, in_len, out_len, CP_UTF8, MB_ERR_INVALID_CHARS);
85 }/*}}}*/
86 
php_win32_cp_conv_cur_to_w(const char * in,size_t in_len,size_t * out_len)87 PW32CP wchar_t *php_win32_cp_conv_cur_to_w(const char* in, size_t in_len, size_t *out_len)
88 {/*{{{*/
89 	wchar_t *ret;
90 
91 	ret = php_win32_cp_to_w_int(in, in_len, out_len, cur_cp->id, cur_cp->from_w_fl);
92 
93 	return ret;
94 }/*}}}*/
95 
php_win32_cp_conv_to_w(DWORD cp,DWORD flags,const char * in,size_t in_len,size_t * out_len)96 PW32CP wchar_t *php_win32_cp_conv_to_w(DWORD cp, DWORD flags, const char* in, size_t in_len, size_t *out_len)
97 {/*{{{*/
98 	return php_win32_cp_to_w_int(in, in_len, out_len, cp, flags);
99 }/*}}}*/
100 
101 #define ASCII_FAIL_RETURN() \
102 	if (PHP_WIN32_CP_IGNORE_LEN_P != out_len) { \
103 		*out_len = 0; \
104 	} \
105 	return NULL;
php_win32_cp_conv_ascii_to_w(const char * in,size_t in_len,size_t * out_len)106 PW32CP wchar_t *php_win32_cp_conv_ascii_to_w(const char* in, size_t in_len, size_t *out_len)
107 {/*{{{*/
108 	wchar_t *ret, *ret_idx;
109 	const char *idx = in, *end;
110 	char ch_err = 0;
111 
112 #if PHP_DEBUG
113 	size_t save_in_len = in_len;
114 #endif
115 
116 	assert(in && in_len ? in[in_len] == '\0' : 1);
117 
118 	if (!in) {
119 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
120 		return NULL;
121 	} else if (0 == in_len) {
122 		/* Not binary safe. */
123 		in_len = strlen(in);
124 	}
125 
126 	end = in + in_len;
127 
128 	if (in_len > 15) {
129 		const char *aidx = (const char *)ZEND_SLIDE_TO_ALIGNED16(in);
130 
131 		/* Process unaligned chunk. */
132 		while (idx < aidx) {
133 			ch_err |= *idx;
134 			idx++;
135 		}
136 		if (ch_err & 0x80) {
137 			ASCII_FAIL_RETURN()
138 		}
139 
140 		/* Process aligned chunk. */
141 #ifdef _M_ARM64
142 		uint8x16_t ver_err = {0};
143 		while (end - idx > 15) {
144 			const uint8x16_t block = vld1q_u8((const void*)idx);
145 			ver_err = vorrq_u8(ver_err, block);
146 			idx += 16;
147 		}
148 		ver_err = vshrq_n_u8(ver_err, 7);
149 		if (vmaxvq_u8(ver_err)) {
150 			ASCII_FAIL_RETURN()
151 		}
152 #else
153 		__m128i vec_err = _mm_setzero_si128();
154 		while (end - idx > 15) {
155 			const __m128i block = _mm_load_si128((__m128i *)idx);
156 			vec_err = _mm_or_si128(vec_err, block);
157 			idx += 16;
158 		}
159 		if (_mm_movemask_epi8(vec_err)) {
160 			ASCII_FAIL_RETURN()
161 		}
162 #endif // _M_ARM64
163 	}
164 
165 	/* Process the trailing part, or otherwise process string < 16 bytes. */
166 	while (idx < end) {
167 		ch_err |= *idx;
168 		idx++;
169 	}
170 	if (ch_err & 0x80) {
171 		ASCII_FAIL_RETURN()
172 	}
173 
174 	ret = malloc((in_len+1)*sizeof(wchar_t));
175 	if (!ret) {
176 		SET_ERRNO_FROM_WIN32_CODE(ERROR_OUTOFMEMORY);
177 		return NULL;
178 	}
179 
180 	ret_idx = ret;
181 	idx = in;
182 
183 	if (in_len > 15) {
184 		const char *aidx = (const char *)ZEND_SLIDE_TO_ALIGNED16(in);
185 
186 		/* Process unaligned chunk. */
187 		while (idx < aidx) {
188 			*ret_idx++ = (wchar_t)*idx++;
189 		}
190 
191 		/* Process aligned chunk. */
192 		if (end - idx > 15) {
193 #ifdef _M_ARM64
194 			while (end - idx > 15) {
195 				/*
196 				vst2q_u8 below will store interlaced vector by 8bits, so this will be little endian wchar
197 				at wrote time all windows arm64 is little endian
198 				 */
199 				uint8x16x2_t vec = {/* .val = */{
200 					vld1q_u8((const void*)idx),
201 					{0},
202 				}};
203 
204 				vst2q_u8((void*)ret_idx, vec);
205 				idx += 16;
206 				ret_idx += 16;
207 			}
208 #else
209 			const __m128i mask = _mm_set1_epi32(0);
210 			while (end - idx > 15) {
211 				const __m128i block = _mm_load_si128((__m128i *)idx);
212 
213 				const __m128i lo = _mm_unpacklo_epi8(block, mask);
214 				_mm_storeu_si128((__m128i *)ret_idx, lo);
215 
216 				ret_idx += 8;
217 				const __m128i hi = _mm_unpackhi_epi8(block, mask);
218 				_mm_storeu_si128((__m128i *)ret_idx, hi);
219 
220 				idx += 16;
221 				ret_idx += 8;
222 			}
223 #endif // _M_ARM64
224 		}
225 	}
226 
227 	/* Process the trailing part, or otherwise process string < 16 bytes. */
228 	while (idx < end) {
229 		*ret_idx++ = (wchar_t)*idx++;
230 	}
231 
232 	ret[in_len] = L'\0';
233 
234 	assert(ret && !save_in_len ? wcslen(ret) == in_len : 1);
235 
236 	if (PHP_WIN32_CP_IGNORE_LEN_P != out_len) {
237 		*out_len = in_len;
238 	}
239 
240 	return ret;
241 }/*}}}*/
242 #undef ASCII_FAIL_RETURN
243 
php_win32_cp_from_w_int(const wchar_t * in,size_t in_len,size_t * out_len,UINT cp,DWORD flags)244 __forceinline static char *php_win32_cp_from_w_int(const wchar_t* in, size_t in_len, size_t *out_len, UINT cp, DWORD flags)
245 {/*{{{*/
246 	int r;
247 	int target_len, tmp_len;
248 	char* target;
249 
250 	if (!in || in_len > INT_MAX) {
251 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
252 		return NULL;
253 	}
254 	assert(in_len ? in[in_len] == '\0' : 1);
255 
256 	tmp_len = !in_len ? -1 : (int)in_len + 1;
257 
258 	target_len = WideCharToMultiByte(cp, flags, in, tmp_len, NULL, 0, NULL, NULL);
259 	if (target_len == 0) {
260 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
261 		return NULL;
262 	}
263 
264 	target = malloc(target_len);
265 	if (target == NULL) {
266 		SET_ERRNO_FROM_WIN32_CODE(ERROR_OUTOFMEMORY);
267 		return NULL;
268 	}
269 
270 	r = WideCharToMultiByte(cp, flags, in, tmp_len, target, target_len, NULL, NULL);
271 	if (r == 0) {
272 		free(target);
273 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
274 		return NULL;
275 	}
276 
277 	assert(target ? r == target_len : 1);
278 	assert(target && !in_len ? strlen(target) == target_len - 1 : 1);
279 
280 	target[target_len-1] = '\0';
281 
282 	if (PHP_WIN32_CP_IGNORE_LEN_P != out_len) {
283 		*out_len = target_len - 1;
284 	}
285 
286 	return target;
287 }/*}}}*/
288 
php_win32_cp_conv_w_to_utf8(const wchar_t * in,size_t in_len,size_t * out_len)289 PW32CP char *php_win32_cp_conv_w_to_utf8(const wchar_t* in, size_t in_len, size_t *out_len)
290 {/*{{{*/
291 	return php_win32_cp_from_w_int(in, in_len, out_len, CP_UTF8, WC_ERR_INVALID_CHARS);
292 }/*}}}*/
293 
php_win32_cp_conv_w_to_cur(const wchar_t * in,size_t in_len,size_t * out_len)294 PW32CP char *php_win32_cp_conv_w_to_cur(const wchar_t* in, size_t in_len, size_t *out_len)
295 {/*{{{*/
296 	char *ret;
297 
298 	ret = php_win32_cp_from_w_int(in, in_len, out_len, cur_cp->id, cur_cp->from_w_fl);
299 
300 	return ret;
301 }/*}}}*/
302 
php_win32_cp_conv_from_w(DWORD cp,DWORD flags,const wchar_t * in,size_t in_len,size_t * out_len)303 PW32CP char *php_win32_cp_conv_from_w(DWORD cp, DWORD flags, const wchar_t* in, size_t in_len, size_t *out_len)
304 {/*{{{*/
305 	return php_win32_cp_from_w_int(in, in_len, out_len, cp, flags);
306 }/*}}}*/
307 
308 /* This is only usable after the startup phase*/
php_win32_cp_get_enc(void)309 __forceinline static char *php_win32_cp_get_enc(void)
310 {/*{{{*/
311 	char *enc = NULL;
312 	const zend_encoding *zenc;
313 
314 	if (PG(internal_encoding) && PG(internal_encoding)[0]) {
315 		enc = PG(internal_encoding);
316 	} else if (SG(default_charset) && SG(default_charset)[0] ) {
317 		enc = SG(default_charset);
318 	} else {
319 		zenc = zend_multibyte_get_internal_encoding();
320 		if (zenc) {
321 			enc = (char *)zend_multibyte_get_encoding_name(zenc);
322 		}
323 	}
324 
325 	return enc;
326 }/*}}}*/
327 
php_win32_cp_get_current(void)328 PW32CP const struct php_win32_cp *php_win32_cp_get_current(void)
329 {/*{{{*/
330 	return cur_cp;
331 }/*}}}*/
332 
php_win32_cp_get_orig(void)333 PW32CP const struct php_win32_cp *php_win32_cp_get_orig(void)
334 {/*{{{*/
335 	return orig_cp;
336 }/*}}}*/
337 
php_win32_cp_get_by_id(DWORD id)338 PW32CP const struct php_win32_cp *php_win32_cp_get_by_id(DWORD id)
339 {/*{{{*/
340 	size_t i;
341 
342 	if (id < php_win32_cp_map[0].id) {
343 		switch (id) {
344 			case CP_ACP:
345 				id = GetACP();
346 				break;
347 			case CP_OEMCP:
348 				id = GetOEMCP();
349 				break;
350 		}
351 	}
352 
353 	for (i = 0; i < sizeof(php_win32_cp_map)/sizeof(struct php_win32_cp); i++) {
354 		if (php_win32_cp_map[i].id == id) {
355 			return &php_win32_cp_map[i];
356 		}
357 	}
358 
359 	SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_FOUND);
360 
361 	return NULL;
362 }/*}}}*/
363 
php_win32_cp_get_by_enc(const char * enc)364 PW32CP const struct php_win32_cp *php_win32_cp_get_by_enc(const char *enc)
365 {/*{{{*/
366 	size_t enc_len = 0, i;
367 
368 	if (!enc || !enc[0]) {
369 		return php_win32_cp_get_by_id(65001U);
370 	}
371 
372 	enc_len = strlen(enc);
373 
374 	for (i = 0; i < sizeof(php_win32_cp_map)/sizeof(struct php_win32_cp); i++) {
375 		const struct php_win32_cp *cp = &php_win32_cp_map[i];
376 
377 		if (!cp->name || !cp->name[0]) {
378 			continue;
379 		}
380 
381 		if (0 == zend_binary_strcasecmp(enc, enc_len, cp->name, strlen(cp->name))) {
382 			return cp;
383 		}
384 
385 		if (cp->enc) {
386 			char *start = cp->enc, *idx;
387 
388 			idx = strpbrk(start, "|");
389 
390 			while (NULL != idx) {
391 				if (0 == zend_binary_strcasecmp(enc, enc_len, start, idx - start)) {
392 					return cp;
393 				}
394 				start = idx + 1;
395 				idx = strpbrk(start, "|");
396 			}
397 			/* Last in the list, or single charset specified. */
398 			if (0 == zend_binary_strcasecmp(enc, enc_len, start, strlen(start))) {
399 				return cp;
400 			}
401 		}
402 	}
403 
404 	return php_win32_cp_get_by_id(GetACP());
405 }/*}}}*/
406 
php_win32_cp_set_by_id(DWORD id)407 PW32CP const struct php_win32_cp *php_win32_cp_set_by_id(DWORD id)
408 {/*{{{*/
409 	const struct php_win32_cp *tmp;
410 	if (!IsValidCodePage(id)) {
411 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
412 		return NULL;
413 	}
414 
415 	tmp = php_win32_cp_get_by_id(id);
416 	if (tmp) {
417 		cur_cp = tmp;
418 	}
419 
420 	return cur_cp;
421 }/*}}}*/
422 
php_win32_cp_use_unicode(void)423 PW32CP BOOL php_win32_cp_use_unicode(void)
424 {/*{{{*/
425 	return 65001 == cur_cp->id;
426 }/*}}}*/
427 
php_win32_cp_env_any_to_w(const char * env)428 PW32CP wchar_t *php_win32_cp_env_any_to_w(const char* env)
429 {/*{{{*/
430 	wchar_t *envw = NULL, ew[32760];
431 	char *cur = (char *)env, *prev;
432 	size_t bin_len = 0;
433 
434 	if (!env) {
435 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
436 		return NULL;
437 	}
438 
439 	do {
440 		wchar_t *tmp;
441 
442 		tmp = php_win32_cp_any_to_w(cur);
443 		if (tmp) {
444 			size_t tmp_len = wcslen(tmp) + 1;
445 			memmove(ew + bin_len, tmp, tmp_len * sizeof(wchar_t));
446 			free(tmp);
447 
448 			bin_len += tmp_len;
449 		}
450 
451 		prev = cur;
452 
453 	} while (NULL != (cur = strchr(prev, '\0')) && cur++ && *cur && bin_len + (cur - prev) < 32760);
454 
455 	envw = (wchar_t *) malloc((bin_len + 3) * sizeof(wchar_t));
456 	if (!envw) {
457 		SET_ERRNO_FROM_WIN32_CODE(ERROR_OUTOFMEMORY);
458 		return NULL;
459 	}
460 	memmove(envw, ew, bin_len * sizeof(wchar_t));
461 	envw[bin_len] = L'\0';
462 	envw[bin_len + 1] = L'\0';
463 	envw[bin_len + 2] = L'\0';
464 
465 	return envw;
466 }/*}}}*/
467 
php_win32_cp_cli_io_setup(void)468 static BOOL php_win32_cp_cli_io_setup(void)
469 {/*{{{*/
470 	BOOL ret = TRUE;
471 
472 	if (PG(input_encoding) && PG(input_encoding)[0]) {
473 		cur_in_cp = php_win32_cp_get_by_enc(PG(input_encoding));
474 		if (!cur_in_cp) {
475 			cur_in_cp = cur_cp;
476 		}
477 	} else {
478 		cur_in_cp = cur_cp;
479 	}
480 
481 	if (PG(output_encoding) && PG(output_encoding)[0]) {
482 		cur_out_cp = php_win32_cp_get_by_enc(PG(output_encoding));
483 		if (!cur_out_cp) {
484 			cur_out_cp = cur_cp;
485 		}
486 	} else {
487 		cur_out_cp = cur_cp;
488 	}
489 
490 	if(php_get_module_initialized()) {
491 		ret = SetConsoleCP(cur_in_cp->id) && SetConsoleOutputCP(cur_out_cp->id);
492 	}
493 
494 	return ret;
495 }/*}}}*/
496 
php_win32_cp_do_setup(const char * enc)497 PW32CP const struct php_win32_cp *php_win32_cp_do_setup(const char *enc)
498 {/*{{{*/
499 	if (!enc) {
500 		enc = php_win32_cp_get_enc();
501 	}
502 
503 	cur_cp = php_win32_cp_get_by_enc(enc);
504 	if (!orig_cp) {
505 		orig_cp = php_win32_cp_get_by_id(GetACP());
506 	}
507 	if (php_win32_console_is_cli_sapi()) {
508 		if (!orig_in_cp) {
509 			orig_in_cp = php_win32_cp_get_by_id(GetConsoleCP());
510 			if (!orig_in_cp) {
511 				orig_in_cp = orig_cp;
512 			}
513 		}
514 		if (!orig_out_cp) {
515 			orig_out_cp = php_win32_cp_get_by_id(GetConsoleOutputCP());
516 			if (!orig_out_cp) {
517 				orig_out_cp = orig_cp;
518 			}
519 		}
520 		php_win32_cp_cli_io_setup();
521 	}
522 
523 	return cur_cp;
524 }/*}}}*/
525 
php_win32_cp_do_update(const char * enc)526 PW32CP const struct php_win32_cp *php_win32_cp_do_update(const char *enc)
527 {/*{{{*/
528 	if (!enc) {
529 		enc = php_win32_cp_get_enc();
530 	}
531 	cur_cp = php_win32_cp_get_by_enc(enc);
532 
533 	if (php_win32_console_is_cli_sapi()) {
534 		php_win32_cp_cli_do_setup(cur_cp->id);
535 	}
536 
537 	return cur_cp;
538 }/*}}}*/
539 
php_win32_cp_shutdown(void)540 PW32CP const struct php_win32_cp *php_win32_cp_shutdown(void)
541 {/*{{{*/
542 	return orig_cp;
543 }/*}}}*/
544 
545 /* php_win32_cp_setup() needs to have run before! */
php_win32_cp_cli_do_setup(DWORD id)546 PW32CP const struct php_win32_cp *php_win32_cp_cli_do_setup(DWORD id)
547 {/*{{{*/
548 	const struct php_win32_cp *cp;
549 
550 	if (!orig_cp) {
551 		php_win32_cp_setup();
552 	}
553 
554 	if (id) {
555 		cp = php_win32_cp_set_by_id(id);
556 	} else {
557 		cp = cur_cp;
558 	}
559 
560 	if (!cp) {
561 		return NULL;
562 	}
563 
564 	if (php_win32_cp_cli_io_setup()) {
565 		return cp;
566 	}
567 
568 	return cp;
569 }/*}}}*/
570 
php_win32_cp_cli_do_restore(DWORD id)571 PW32CP const struct php_win32_cp *php_win32_cp_cli_do_restore(DWORD id)
572 {/*{{{*/
573 	BOOL cli_io_restored = TRUE;
574 
575 	if (orig_in_cp) {
576 		cli_io_restored = cli_io_restored && SetConsoleCP(orig_in_cp->id);
577 	}
578 
579 	if (orig_out_cp) {
580 		cli_io_restored = cli_io_restored && SetConsoleOutputCP(orig_out_cp->id);
581 	}
582 
583 	if (cli_io_restored && id) {
584 		return php_win32_cp_set_by_id(id);
585 	}
586 
587 	return NULL;
588 }/*}}}*/
589 
590 /* Userspace functions, see basic_functions.* for arginfo and decls. */
591 
592 /* {{{ Set process codepage. */
PHP_FUNCTION(sapi_windows_cp_set)593 PHP_FUNCTION(sapi_windows_cp_set)
594 {
595 	zend_long id;
596 	const struct php_win32_cp *cp;
597 
598 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &id) == FAILURE) {
599 		RETURN_THROWS();
600 	}
601 
602 	if (ZEND_LONG_UINT_OVFL(id)) {
603 		zend_argument_value_error(1, "must be between 0 and %u", UINT_MAX);
604 		RETURN_THROWS();
605 	}
606 
607 	if (php_win32_console_is_cli_sapi()) {
608 		cp = php_win32_cp_cli_do_setup((DWORD)id);
609 	} else {
610 		cp = php_win32_cp_set_by_id((DWORD)id);
611 	}
612 	if (!cp) {
613 		php_error_docref(NULL, E_WARNING, "Failed to switch to codepage %d", id);
614 		RETURN_FALSE;
615 	}
616 
617 	RETURN_BOOL(cp->id == id);
618 }
619 /* }}} */
620 
621 /* {{{ Get process codepage. */
PHP_FUNCTION(sapi_windows_cp_get)622 PHP_FUNCTION(sapi_windows_cp_get)
623 {
624 	zend_string *kind = NULL;
625 
626 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &kind) == FAILURE) {
627 		RETURN_THROWS();
628 	}
629 
630 	if (!kind) {
631 		const struct php_win32_cp *cp = php_win32_cp_get_current();
632 		RETURN_LONG(cp->id);
633 	} else if (zend_string_equals_literal_ci(kind, "ansi")) {
634 		RETURN_LONG(GetACP());
635 	} else if (zend_string_equals_literal_ci(kind, "oem")) {
636 		RETURN_LONG(GetOEMCP());
637 	} else {
638 		/* TODO Warn/ValueError for invalid kind? */
639 		const struct php_win32_cp *cp = php_win32_cp_get_current();
640 		RETURN_LONG(cp->id);
641 	}
642 }
643 /* }}} */
644 
645 
646 /* {{{ Indicates whether the codepage is UTF-8 compatible. */
PHP_FUNCTION(sapi_windows_cp_is_utf8)647 PHP_FUNCTION(sapi_windows_cp_is_utf8)
648 {
649 	if (zend_parse_parameters_none() == FAILURE) {
650 		RETURN_THROWS();
651 	}
652 
653 	RETURN_BOOL(php_win32_cp_use_unicode());
654 }
655 /* }}} */
656 
657 /* {{{ Convert string from one codepage to another. */
PHP_FUNCTION(sapi_windows_cp_conv)658 PHP_FUNCTION(sapi_windows_cp_conv)
659 {
660 	char *ret;
661 	size_t ret_len, tmpw_len;
662 	wchar_t *tmpw;
663 	const struct php_win32_cp *in_cp, *out_cp;
664 	zend_string *string_in_codepage = NULL;
665 	zend_long int_in_codepage = 0;
666 	zend_string *string_out_codepage = NULL;
667 	zend_long int_out_codepage = 0;
668 	zend_string *subject;
669 
670 	ZEND_PARSE_PARAMETERS_START(3, 3)
671 		Z_PARAM_STR_OR_LONG(string_in_codepage, int_in_codepage)
672 		Z_PARAM_STR_OR_LONG(string_out_codepage, int_out_codepage)
673 		Z_PARAM_STR(subject)
674 	ZEND_PARSE_PARAMETERS_END();
675 
676 	if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(subject))) {
677 		zend_argument_value_error(1, "is too long");
678 		RETURN_THROWS();
679 	}
680 
681 	if (string_in_codepage != NULL) {
682 		in_cp = php_win32_cp_get_by_enc(ZSTR_VAL(string_in_codepage));
683 		if (!in_cp) {
684 			zend_argument_value_error(1, "must be a valid charset");
685 			RETURN_THROWS();
686 		}
687 	} else {
688 		if (ZEND_LONG_UINT_OVFL(int_in_codepage)) {
689 			zend_argument_value_error(1, "must be between 0 and %u", UINT_MAX);
690 			RETURN_THROWS();
691 		}
692 
693 		in_cp = php_win32_cp_get_by_id((DWORD)int_in_codepage);
694 		if (!in_cp) {
695 			zend_argument_value_error(1, "must be a valid codepage");
696 			RETURN_THROWS();
697 		}
698 	}
699 
700 	if (string_out_codepage != NULL) {
701 		out_cp = php_win32_cp_get_by_enc(ZSTR_VAL(string_out_codepage));
702 		if (!out_cp) {
703 			zend_argument_value_error(2, "must be a valid charset");
704 			RETURN_THROWS();
705 		}
706 	} else {
707 		if (ZEND_LONG_UINT_OVFL(int_out_codepage)) {
708 			zend_argument_value_error(2, "must be between 0 and %u", UINT_MAX);
709 			RETURN_THROWS();
710 		}
711 
712 		out_cp = php_win32_cp_get_by_id((DWORD)int_out_codepage);
713 		if (!out_cp) {
714 			zend_argument_value_error(2, "must be a valid codepage");
715 			RETURN_THROWS();
716 		}
717 	}
718 
719 	tmpw = php_win32_cp_conv_to_w(in_cp->id, in_cp->to_w_fl, ZSTR_VAL(subject), ZSTR_LEN(subject), &tmpw_len);
720 	if (!tmpw) {
721 		php_error_docref(NULL, E_WARNING, "Wide char conversion failed");
722 		RETURN_NULL();
723 	}
724 
725 	ret = php_win32_cp_conv_from_w(out_cp->id, out_cp->from_w_fl, tmpw, tmpw_len, &ret_len);
726 	if (!ret) {
727 		free(tmpw);
728 		php_error_docref(NULL, E_WARNING, "Wide char conversion failed");
729 		RETURN_NULL();
730 	}
731 
732 	RETVAL_STRINGL(ret, ret_len);
733 
734 	free(tmpw);
735 	free(ret);
736 }
737 /* }}} */
738 
739 /* }}} */
740