diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 8bbdc5caabe5..eeed7212f85b 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -366,4 +366,4 @@ PHP 8.6 INTERNALS UPGRADE NOTES __thread storage on ZTS builds. - php-cli functionality is now available in embed builds via the do_php_cli() - function. + function. On Windows, custom argv must use UTF-8. diff --git a/main/main.c b/main/main.c index 0539220de362..594626470a50 100644 --- a/main/main.c +++ b/main/main.c @@ -644,7 +644,7 @@ static PHP_INI_MH(OnUpdateInternalEncoding) } if (new_value) { #ifdef PHP_WIN32 - php_win32_cp_do_update(ZSTR_VAL(new_value)); + php_win32_cp_do_update(NULL); #endif } return SUCCESS; @@ -2236,8 +2236,11 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi load zend extensions and register php function extensions to be loaded later */ zend_stream_init(); - php_init_config(); + zend_result config_result = php_init_config(); zend_stream_shutdown(); + if (config_result == FAILURE) { + return FAILURE; + } /* Register PHP core ini entries */ zend_register_ini_entries_ex(ini_entries, module_number, MODULE_PERSISTENT); diff --git a/main/php_ini.c b/main/php_ini.c index a3b33da65bab..b45f3261db79 100644 --- a/main/php_ini.c +++ b/main/php_ini.c @@ -25,6 +25,8 @@ #ifdef PHP_WIN32 #include "win32/php_registry.h" #include "win32/winutil.h" +#include "win32/codepage.h" +#include "win32/console.h" #endif #if defined(HAVE_SCANDIR) && defined(HAVE_ALPHASORT) && defined(HAVE_DIRENT_H) @@ -307,6 +309,205 @@ static void php_ini_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_t } /* }}} */ +#ifdef PHP_WIN32 +typedef struct { + zval args[3]; + int callback_type; +} php_ini_utf8_entry; + +typedef struct { + const struct php_win32_cp *cp; + const struct php_win32_cp *utf8; + HashTable values; + zend_llist entries; + bool failed; +} php_ini_utf8_context; + +static php_ini_utf8_context *php_ini_utf8; + +static const struct php_win32_cp *php_ini_utf8_codepage(HashTable *hash) +{ + const char *names[] = {"internal_encoding", "default_charset"}; + for (int i = 0; i < 2; i++) { + zval *value = zend_hash_str_find(hash, names[i], strlen(names[i])); + if (value && Z_TYPE_P(value) == IS_PTR) { + value = Z_PTR_P(value); + } + if (value && Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value)) { + return php_win32_cp_get_by_enc(Z_STRVAL_P(value)); + } + } + return php_win32_cp_get_by_id(CP_UTF8); +} + +static bool php_ini_utf8_extension_path(zend_string *name) +{ + return zend_string_equals_literal_ci(name, PHP_EXTENSION_TOKEN) + || zend_string_equals_literal_ci(name, ZEND_EXTENSION_TOKEN) + || zend_string_equals_literal(name, "extension_dir"); +} + +static void php_ini_utf8_convert(php_ini_utf8_context *ctx, zval *value, const struct php_win32_cp *from, const struct php_win32_cp *to) +{ + if (ctx->failed || Z_TYPE_P(value) != IS_STRING || from == to) { + return; + } + wchar_t *wide = php_win32_cp_conv_ascii_to_w(Z_STRVAL_P(value), Z_STRLEN_P(value), PHP_WIN32_CP_IGNORE_LEN_P); + if (wide) { + free(wide); + return; + } + size_t length, wide_len; + wide = php_win32_cp_conv_to_w(from->id, from->to_w_fl, Z_STRVAL_P(value), Z_STRLEN_P(value), &wide_len); + char *bytes = NULL; + if (wide) { + bytes = php_win32_cp_conv_from_w(to->id, to->from_w_fl, wide, wide_len, &length); + free(wide); + } + if (!bytes) { + ctx->failed = true; + return; + } + zend_string_release(Z_STR_P(value)); + ZVAL_STR(value, zend_string_init(bytes, length, true)); + free(bytes); +} + +static zval *php_ini_utf8_get_config(zend_string *name) +{ + php_ini_utf8_context *ctx = php_ini_utf8; + zval *value = zend_hash_find(&ctx->values, name); + if (!value || Z_TYPE_P(value) != IS_PTR) { + return value; + } + value = Z_PTR_P(value); + if (value && Z_TYPE_P(value) == IS_STRING) { + zval converted; + ZVAL_STR(&converted, zend_string_copy(Z_STR_P(value))); + const struct php_win32_cp *cp = php_ini_utf8_extension_path(name) + ? php_win32_cp_get_by_id(CP_ACP) : ctx->cp; + php_ini_utf8_convert(ctx, &converted, cp, ctx->utf8); + return zend_hash_update(&ctx->values, name, &converted); + } + return value; +} + +static char *php_ini_utf8_getenv(const char *name, size_t name_len) +{ + wchar_t *wide_name = php_win32_cp_conv_utf8_to_w(name, name_len, PHP_WIN32_CP_IGNORE_LEN_P); + if (!wide_name) { + php_ini_utf8->failed = true; + return estrdup(""); + } + const wchar_t *value = _wgetenv(wide_name); + free(wide_name); + if (!value) { + return NULL; + } + char *bytes = php_win32_cp_w_to_utf8(value); + if (!bytes) { + php_ini_utf8->failed = true; + return estrdup(""); + } + char *result = estrdup(bytes); + free(bytes); + return result; +} + +static void php_ini_utf8_entry_dtor(void *data) +{ + php_ini_utf8_entry *entry = data; + for (int i = 0; i < 3; i++) { + zval_internal_ptr_dtor(&entry->args[i]); + } +} + +static void php_ini_utf8_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *data) +{ + php_ini_utf8_context *ctx = data; + php_ini_utf8_entry entry; + zval *args[3] = {arg1, arg2, arg3}; + for (int i = 0; i < 3; i++) { + if (args[i]) { + ZVAL_STR(&entry.args[i], zend_string_dup(Z_STR_P(args[i]), true)); + } else { + ZVAL_UNDEF(&entry.args[i]); + } + } + entry.callback_type = callback_type; + zend_llist_add_element(&ctx->entries, &entry); + /* Keep earlier -d values available for expansion, without queuing extensions twice. */ + if (callback_type == ZEND_INI_PARSER_ENTRY && !is_special_section + && (zend_string_equals_literal_ci(Z_STR_P(arg1), PHP_EXTENSION_TOKEN) + || zend_string_equals_literal_ci(Z_STR_P(arg1), ZEND_EXTENSION_TOKEN))) { + return; + } + php_ini_parser_cb(arg1, arg2, arg3, callback_type, &ctx->values); +} + +static zend_result php_ini_parse_utf8(const char *entries) +{ + php_ini_utf8_context ctx = {0}; + char *(*getenv_save)(const char *, size_t) = zend_getenv; + zend_hash_init(&ctx.values, 8, NULL, config_zval_dtor, true); + zend_llist_init(&ctx.entries, sizeof(php_ini_utf8_entry), php_ini_utf8_entry_dtor, true); + ctx.cp = php_ini_utf8_codepage(&configuration_hash); + ctx.utf8 = php_win32_cp_get_by_id(CP_UTF8); + if (!ctx.cp || !ctx.utf8) { + ctx.failed = true; + goto cleanup; + } + /* Borrow php.ini scalars; section arrays only need their structure while parsing. */ + zend_string *key; + zval *value; + ZEND_HASH_FOREACH_STR_KEY_VAL(&configuration_hash, key, value) { + if (key) { + zval name, borrowed; + ZVAL_STR(&name, zend_string_copy(key)); + php_ini_utf8_convert(&ctx, &name, ctx.cp, ctx.utf8); + if (Z_TYPE_P(value) == IS_ARRAY) { + ZVAL_NEW_PERSISTENT_ARR(&borrowed); + zend_hash_init(Z_ARRVAL(borrowed), 0, NULL, config_zval_dtor, true); + } else { + ZVAL_PTR(&borrowed, value); + } + zend_hash_update(&ctx.values, Z_STR(name), &borrowed); + zend_string_release(Z_STR(name)); + } + } ZEND_HASH_FOREACH_END(); + php_ini_utf8 = &ctx; + zend_getenv = php_ini_utf8_getenv; + /* Parse in UTF-8, then apply the values after resolving the final code page. */ + zend_parse_ini_string(entries, true, ZEND_INI_SCANNER_NORMAL, php_ini_utf8_parser_cb, &ctx); + zend_getenv = getenv_save; + php_ini_utf8 = NULL; + RESET_ACTIVE_INI_HASH(); + ctx.cp = php_ini_utf8_codepage(&ctx.values); + ctx.failed |= !ctx.cp; + for (zend_llist_element *element = ctx.entries.head; element && !ctx.failed; element = element->next) { + php_ini_utf8_entry *entry = (php_ini_utf8_entry *) element->data; + const struct php_win32_cp *value_cp = entry->callback_type == ZEND_INI_PARSER_ENTRY + && php_ini_utf8_extension_path(Z_STR(entry->args[0])) + ? php_win32_cp_get_by_id(CP_ACP) : ctx.cp; + for (int i = 0; i < 3; i++) { + php_ini_utf8_convert(&ctx, &entry->args[i], ctx.utf8, i == 1 ? value_cp : ctx.cp); + } + if (!ctx.failed) { + php_ini_parser_cb(&entry->args[0], Z_ISUNDEF(entry->args[1]) ? NULL : &entry->args[1], + Z_ISUNDEF(entry->args[2]) ? NULL : &entry->args[2], entry->callback_type, &configuration_hash); + } + } +cleanup: + zend_llist_destroy(&ctx.entries); + zend_hash_destroy(&ctx.values); + if (ctx.failed) { + php_error(E_CORE_WARNING, "Could not convert command line INI values to the configured Windows code page"); + return FAILURE; + } + return SUCCESS; +} +#endif + /* {{{ php_load_php_extension_cb */ static void php_load_php_extension_cb(void *arg) { @@ -404,7 +605,7 @@ static void append_ini_path(char *php_ini_search_path, size_t search_path_size, } /* {{{ php_init_config */ -void php_init_config(void) +zend_result php_init_config(void) { char *php_ini_file_name = NULL; char *php_ini_search_path = NULL; @@ -716,8 +917,15 @@ void php_init_config(void) if (sapi_module.ini_entries) { /* Reset active ini section */ RESET_ACTIVE_INI_HASH(); + +#ifdef PHP_WIN32 + if (php_win32_console_is_cli_sapi()) { + return php_ini_parse_utf8(sapi_module.ini_entries); + } +#endif zend_parse_ini_string(sapi_module.ini_entries, true, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash); } + return SUCCESS; } /* }}} */ @@ -876,6 +1084,11 @@ PHPAPI void php_ini_activate_per_host_config(const char *host, size_t host_len) /* {{{ cfg_get_entry */ PHPAPI zval *cfg_get_entry_ex(zend_string *name) { +#ifdef PHP_WIN32 + if (php_ini_utf8) { + return php_ini_utf8_get_config(name); + } +#endif return zend_hash_find(&configuration_hash, name); } /* }}} */ diff --git a/main/php_ini.h b/main/php_ini.h index ffc2c36186c9..6d485444483f 100644 --- a/main/php_ini.h +++ b/main/php_ini.h @@ -19,7 +19,7 @@ BEGIN_EXTERN_C() PHPAPI void config_zval_dtor(zval *zvalue); -void php_init_config(void); +zend_result php_init_config(void); void php_shutdown_config(void); void php_ini_register_extensions(void); PHPAPI zval *cfg_get_entry_ex(zend_string *name); diff --git a/sapi/cli/cli.h b/sapi/cli/cli.h index 9502a6726fe4..a2ee8475d9db 100644 --- a/sapi/cli/cli.h +++ b/sapi/cli/cli.h @@ -54,7 +54,8 @@ typedef struct php_cli_server_context { php_cli_mode mode; } php_cli_server_context; -/* this performs full cli-SAPI boot, loads modules, sets up TSRM and co. */ +/** This performs full cli-SAPI boot, loads modules, sets up TSRM and co. + * On Windows, custom argv strings must be UTF-8. */ extern PHP_CLI_API int do_php_cli(int argc, char *argv[]); #endif /* CLI_H */ diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 9cd1f5a8c158..4ea30ff82a64 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -34,7 +34,7 @@ #include "win32/signal.h" #include "win32/console.h" #include -#include +#include #endif #ifdef HAVE_SYS_TIME_H #include @@ -1185,13 +1185,6 @@ static int do_cli(int argc, char **argv) /* {{{ */ /* {{{ do_php_cli */ PHP_CLI_API int do_php_cli(int argc, char *argv[]) { -#if defined(PHP_WIN32) - int num_args; - wchar_t **argv_wide; - char **argv_save = argv; - BOOL using_wide_argv = 0; -#endif - int c; int exit_status = SUCCESS; int module_started = 0, sapi_started = 0; @@ -1202,12 +1195,42 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[]) int ini_ignore = 0; sapi_module_struct *sapi_module_ptr = &cli_sapi_module; +#ifdef PHP_WIN32 + char **native_argv = NULL; + char **converted_argv = NULL; + char **argv_save; + + if (argv == __argv) { + if (!__wargv && _configure_wide_argv(_crt_argv_unexpanded_arguments)) { + return 1; + } + PHP_WIN32_CP_CONVERT_ARRAY(__wargv, __argc, native_argv, argc, php_win32_cp_w_to_utf8) + if (!native_argv) { + return 1; + } + argv = native_argv; + } + argv_save = argv; +#endif + /* * Do not move this initialization. It needs to happen before argv is used * in any way. */ argv = save_ps_args(argc, argv); +#ifdef PHP_WIN32 + if (!native_argv) { + for (int i = 0; i < argc; i++) { + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, argv[i], -1, NULL, 0)) { + fprintf(stderr, "Invalid UTF-8 in command line argument %d.\n", i); + cleanup_ps_args(argv); + return 1; + } + } + } +#endif + #if defined(PHP_WIN32) && !defined(PHP_CLI_WIN32_NO_CONSOLE) php_win32_console_fileno_set_vt100(STDOUT_FILENO, TRUE); php_win32_console_fileno_set_vt100(STDERR_FILENO, TRUE); @@ -1333,12 +1356,15 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[]) #if defined(PHP_WIN32) php_win32_cp_cli_setup(); orig_cp = (php_win32_cp_get_orig())->id; - /* Ignore the delivered argv and argc, read from W API. This place - might be too late though, but this is the earliest place ATW - we can access the internal charset information from PHP. */ - argv_wide = CommandLineToArgvW(GetCommandLineW(), &num_args); - PHP_WIN32_CP_W_TO_ANY_ARRAY(argv_wide, num_args, argv, argc) - using_wide_argv = 1; + if (!php_win32_cp_use_unicode()) { + PHP_WIN32_CP_CONVERT_ARRAY(argv_save, argc, converted_argv, argc, php_win32_cp_utf8_to_any) + if (!converted_argv) { + fprintf(stderr, "Could not convert command line arguments.\n"); + exit_status = 1; + goto out; + } + argv = converted_argv; + } SetConsoleCtrlHandler(php_cli_win32_ctrl_handler, TRUE); #endif @@ -1377,9 +1403,8 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[]) #if defined(PHP_WIN32) (void)php_win32_cp_cli_restore(); - if (using_wide_argv) { - PHP_WIN32_CP_FREE_ARRAY(argv, argc); - LocalFree(argv_wide); + if (converted_argv) { + PHP_WIN32_CP_FREE_ARRAY(converted_argv, argc); } argv = argv_save; #endif @@ -1388,6 +1413,11 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[]) * exiting. */ cleanup_ps_args(argv); +#ifdef PHP_WIN32 + if (native_argv) { + PHP_WIN32_CP_FREE_ARRAY(native_argv, argc); + } +#endif return exit_status; } /* }}} */ diff --git a/sapi/cli/tests/windows_ini_quoted_argument.phpt b/sapi/cli/tests/windows_ini_quoted_argument.phpt new file mode 100644 index 000000000000..e5648c16f75f --- /dev/null +++ b/sapi/cli/tests/windows_ini_quoted_argument.phpt @@ -0,0 +1,20 @@ +--TEST-- +Windows CLI INI options preserve CRT doubled-quote escaping +--SKIPIF-- + +--FILE-- + ['pipe', 'w'], 2 => ['redirect', 1]], $pipes, + null, null, ['bypass_shell' => true]); +echo stream_get_contents($pipes[1]); +fclose($pipes[1]); +var_dump(proc_close($process)); +?> +--EXPECT-- +68656c6c6f20776f726c64 +int(0) diff --git a/sapi/cli/tests/windows_ini_utf7.phpt b/sapi/cli/tests/windows_ini_utf7.phpt new file mode 100644 index 000000000000..6045da7858ed --- /dev/null +++ b/sapi/cli/tests/windows_ini_utf7.phpt @@ -0,0 +1,17 @@ +--TEST-- +Windows CLI INI conversion preserves ASCII names and values with UTF-7 +--SKIPIF-- + +--INI-- +internal_encoding=UTF-7 +user_agent="foo+bar" +--FILE-- + +--EXPECT-- +string(7) "foo+bar" +int(65000) diff --git a/sapi/embed/Makefile.frag.w32 b/sapi/embed/Makefile.frag.w32 new file mode 100644 index 000000000000..0fee41a07483 --- /dev/null +++ b/sapi/embed/Makefile.frag.w32 @@ -0,0 +1,8 @@ +test_helpers: $(BUILD_DIR)\php_embed_cli_test.exe + +$(BUILD_DIR)\sapi\embed\cli_argv.obj: $(PHP_SRC_DIR)\sapi\embed\tests\cli_argv.c + cd $(PHP_SRC_DIR) + $(CC) $(CFLAGS_EMBED_OBJ) $(CFLAGS) $(CFLAGS_BD_SAPI_EMBED) /Fo$@ /c $(PHP_SRC_DIR)\sapi\embed\tests\cli_argv.c + +$(BUILD_DIR)\php_embed_cli_test.exe: $(BUILD_DIR)\sapi\embed\cli_argv.obj $(BUILD_DIR)\$(EMBED_LIB) + "$(LINK)" /nologo $(BUILD_DIR)\sapi\embed\cli_argv.obj $(BUILD_DIR)\$(EMBED_LIB) /out:$@ $(LDFLAGS) $(LDFLAGS_EMBED) /stack:67108864 diff --git a/sapi/embed/config.w32 b/sapi/embed/config.w32 index 3ac4668bce8c..6fb5345ac492 100644 --- a/sapi/embed/config.w32 +++ b/sapi/embed/config.w32 @@ -10,4 +10,6 @@ if (PHP_EMBED != "no") { ADD_FLAG("LIBS_EMBED", "ws2_32.lib"); ADD_FLAG("LIBS_EMBED", "shell32.lib"); PHP_INSTALL_HEADERS("sapi/embed", "php_embed.h"); + DEFINE('EMBED_LIB', 'php' + PHP_VERSION + 'embed.lib'); + ADD_MAKEFILE_FRAGMENT(); } diff --git a/sapi/embed/tests/cli_argv.c b/sapi/embed/tests/cli_argv.c new file mode 100644 index 000000000000..d40103962a9d --- /dev/null +++ b/sapi/embed/tests/cli_argv.c @@ -0,0 +1,29 @@ +#include "php.h" +#include "sapi/cli/cli.h" +#include "win32/codepage.h" + +int wmain(int argc, wchar_t *argv[]) +{ + if (argc < 2 || wcscmp(argv[1], L"--") != 0) { + return 1; + } + argc--; + char **args = calloc((size_t) argc + 1, sizeof(char *)); + if (!args) { + return 1; + } + /* Omit the host's separator so the supplied argv differs from the process argv. */ + for (int i = 0; i < argc; i++) { + args[i] = php_win32_cp_w_to_utf8(argv[i ? i + 1 : 0]); + if (!args[i]) { + PHP_WIN32_CP_FREE_ARRAY(args, argc); + return 1; + } + } + if (getenv("PHP_EMBED_TEST_INVALID_UTF8") && argc > 1 && args[argc - 1][0]) { + args[argc - 1][0] = '\xff'; + } + int status = do_php_cli(argc, args); + PHP_WIN32_CP_FREE_ARRAY(args, argc); + return status; +} diff --git a/sapi/embed/tests/cli_argv.phpt b/sapi/embed/tests/cli_argv.phpt new file mode 100644 index 000000000000..46b50642634b --- /dev/null +++ b/sapi/embed/tests/cli_argv.phpt @@ -0,0 +1,99 @@ +--TEST-- +Windows CLI and embed argv use the configured code page after UTF-8 option parsing +--SKIPIF-- + +--FILE-- + '1'] + getenv() : null; + $process = proc_open($command, [1 => STDOUT, 2 => STDOUT], $pipes, null, $env); + var_dump(proc_close($process)); +} +$code = <<<'PHP' +echo json_encode([$argc, array_map('bin2hex', array_slice($argv, 1))]), PHP_EOL; +echo sapi_windows_cp_get(), PHP_EOL; +exit(23); +PHP; +foreach ([ + ['default_charset=UTF-8', "\xf0\x9f\x98\x80"], + ['default_charset=Windows-1252', "\xe2\x82\xac"], + ['internal_encoding=Windows-1252', "\xe2\x82\xac"], +] as [$setting, $last_arg]) { + $options = ['-n', '-d', $setting, '-r', $code, '--', "caf\xc3\xa9", 'argument with spaces', '', $last_arg]; + run_cli([$host, '--', ...$options]); + run_cli([$host, '--', ...$options], true); +} +run_cli([$host, '--', '-n', '-d', 'default_charset=Windows-1252', '-d', 'internal_encoding=', + '-d', "user_agent=caf\xc3\xa9", '-r', + "echo sapi_windows_cp_get(), PHP_EOL; echo bin2hex(ini_get('user_agent')), PHP_EOL;"]); +chdir(__DIR__); +file_put_contents("cli_argv_caf\xc3\xa9.php", ' +--CLEAN-- + +--EXPECT-- +[5,["636166c3a9","617267756d656e74207769746820737061636573","","f09f9880"]] +65001 +int(23) +Invalid UTF-8 in command line argument 10. +int(1) +[5,["636166e9","617267756d656e74207769746820737061636573","","80"]] +1252 +int(23) +Invalid UTF-8 in command line argument 10. +int(1) +[5,["636166e9","617267756d656e74207769746820737061636573","","80"]] +1252 +int(23) +Invalid UTF-8 in command line argument 10. +int(1) +1252 +636166e9 +int(0) +Unicode filename works +int(0) +Unicode filename works +Main script works +1252 +636c695f617267765f636166e92e706870 +int(0) +Unicode filename works +Main script works +1252 +636c695f617267765f636166e92e706870 +int(0) +Unicode filename works +Main script works +65001 +636c695f617267765f636166c3a92e706870 +int(0) +Unicode filename works +Main script works +1252 +636c695f617267765f636166e92e706870 +int(0) +Unicode filename works +Main script works +1252 +636c695f617267765f636166e92e706870 +int(0) diff --git a/sapi/embed/tests/cli_ini_utf8.phpt b/sapi/embed/tests/cli_ini_utf8.phpt new file mode 100644 index 000000000000..02f2fed6f8ea --- /dev/null +++ b/sapi/embed/tests/cli_ini_utf8.phpt @@ -0,0 +1,111 @@ +--TEST-- +Windows CLI INI expansion preserves UTF-8 until the final code page is selected +--SKIPIF-- + +--FILE-- + [ + '-n', '-d', "prefix=cli_ini_caf\xc3\xa9", '-d', "caf\xc3\xa9=cli_ini_caf\xc3\xa9", + '-d', 'auto_prepend_file=${prefix}.php', '-d', 'include_path=${café}', + '-d', 'user_agent=${CLI_INI_UTF8}', '-d', 'alias=Windows-1252', + '-d', 'default_charset=${alias}', '-f', 'cli_ini_utf8.php', + ], + 'ini-file-expansion' => [ + '-c', 'cli_ini_utf8.ini', '-d', 'auto_prepend_file=${prefix}${suffix}.php', + '-d', "include_path=cli_ini_caf\xc3\xa9" . '${suffix}', '-d', 'user_agent=${café}', + '-f', 'cli_ini_utf8.php', + ], + 'ini-cp932' => [ + '-n', '-d', 'auto_prepend_file=cli_ini_cp932.php', + '-d', 'include_path=ソ\main.php', '-d', 'token=tail', + '-d', 'user_agent=ソ${token}', '-d', 'default_charset=CP932', '-f', 'cli_ini_utf8.php', + ], + 'ini-section' => [ + '-c', 'cli_ini_utf8.ini', '-d', "before=1\n[PATH=collision]\ndefault_charset=UTF-8", + '-d', 'auto_prepend_file=${prefix}.php', '-d', 'include_path=${prefix}', + '-d', 'user_agent=${CLI_INI_UTF8}', '-f', 'cli_ini_utf8.php', + ], +]; +foreach ($cases as $options) { + $process = proc_open([$host, '--', ...$options], [1 => STDOUT, 2 => STDERR], $pipes); + var_dump(proc_close($process)); + $process = proc_open([PHP_BINARY, ...$options], [1 => STDOUT, 2 => STDERR], $pipes); + var_dump(proc_close($process)); +} +?> +--CLEAN-- + +--EXPECT-- +Prepend works +1252 +636c695f696e695f636166e92e706870 +636c695f696e695f636166e9 +636166e9 +int(0) +Prepend works +1252 +636c695f696e695f636166e92e706870 +636c695f696e695f636166e9 +636166e9 +int(0) +Prepend works +1252 +636c695f696e695f636166e9e92e706870 +636c695f696e695f636166e9e9 +636c695f696e695f636166e9e9 +int(0) +Prepend works +1252 +636c695f696e695f636166e9e92e706870 +636c695f696e695f636166e9e9 +636c695f696e695f636166e9e9 +int(0) +Prepend works +932 +636c695f696e695f63703933322e706870 +835c5c6d61696e2e706870 +835c7461696c +int(0) +Prepend works +932 +636c695f696e695f63703933322e706870 +835c5c6d61696e2e706870 +835c7461696c +int(0) +Prepend works +65001 +636c695f696e695f636166c3a92e706870 +636c695f696e695f636166c3a9 +636166c3a9 +int(0) +Prepend works +65001 +636c695f696e695f636166c3a92e706870 +636c695f696e695f636166c3a9 +636166c3a9 +int(0) diff --git a/win32/codepage.h b/win32/codepage.h index 779589b54c90..c980a599aedc 100644 --- a/win32/codepage.h +++ b/win32/codepage.h @@ -121,19 +121,6 @@ zend_always_inline static char *php_win32_cp_conv_w_to_any(const wchar_t* in, si }/*}}}*/ #define php_win32_cp_w_to_any(in) php_win32_cp_conv_w_to_any(in, PHP_WIN32_CP_IGNORE_LEN, PHP_WIN32_CP_IGNORE_LEN_P) -#define PHP_WIN32_CP_W_TO_ANY_ARRAY(aw, aw_len, aa, aa_len) do { \ - int i; \ - aa_len = aw_len; \ - aa = (char **) malloc(aw_len * sizeof(char *)); \ - if (!aa) { \ - break; \ - } \ - for (i = 0; i < aw_len; i++) { \ - aa[i] = php_win32_cp_w_to_any(aw[i]); \ - } \ -} while (0); - - #define PHP_WIN32_CP_FREE_ARRAY(a, a_len) do { \ int i; \ for (i = 0; i < a_len; i++) { \ @@ -142,6 +129,36 @@ zend_always_inline static char *php_win32_cp_conv_w_to_any(const wchar_t* in, si free(a); \ } while (0); +zend_always_inline static char *php_win32_cp_utf8_to_any(const char *in) +{ + wchar_t *wide = php_win32_cp_utf8_to_w(in); + if (!wide) { + return NULL; + } + char *result = php_win32_cp_w_to_any(wide); + free(wide); + return result; +} + +#define PHP_WIN32_CP_CONVERT_ARRAY(src, src_len, dst, dst_len, convert) do { \ + (dst_len) = (src_len); \ + (dst) = calloc((size_t) (dst_len) + 1, sizeof(char *)); \ + if (!(dst)) { \ + break; \ + } \ + for (int _php_cp_i = 0; _php_cp_i < (dst_len); _php_cp_i++) { \ + (dst)[_php_cp_i] = convert((src)[_php_cp_i]); \ + if (!(dst)[_php_cp_i]) { \ + PHP_WIN32_CP_FREE_ARRAY((dst), (dst_len)) \ + (dst) = NULL; \ + break; \ + } \ + } \ +} while (0); + +#define PHP_WIN32_CP_W_TO_ANY_ARRAY(aw, aw_len, aa, aa_len) \ + PHP_WIN32_CP_CONVERT_ARRAY(aw, aw_len, aa, aa_len, php_win32_cp_w_to_any) + #ifdef __cplusplus } #endif