Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions sapi/cli/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ typedef struct php_cli_server_context {
} php_cli_server_context;

/* 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 */
54 changes: 45 additions & 9 deletions sapi/cli/php_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -1187,9 +1187,8 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[])
{
#if defined(PHP_WIN32)
int num_args;
wchar_t **argv_wide;
wchar_t **argv_wide = NULL;
char **argv_save = argv;
BOOL using_wide_argv = 0;
#endif

int c;
Expand All @@ -1208,6 +1207,18 @@ PHP_CLI_API int do_php_cli(int argc, char *argv[])
*/
argv = save_ps_args(argc, argv);

#ifdef PHP_WIN32
if (argv_save != __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);
Expand Down Expand Up @@ -1333,12 +1344,35 @@ 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;
/* Embedders supply their own arguments, we mustn't replace them with
* the ones by the host process command line. */
if (argv_save == __argv) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller's own argv skips the transcoding to PHP's internal charset that the CommandLineToArgvW() branch performs, so a narrow main() host passing café.php in the ANSI code page hands raw ANSI bytes to the script lookup and to $argv. You may want to document in UPGRADING.INTERNALS that do_php_cli() expects argv in the internal encoding on Windows, or convert from CP_ACP on this branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, fair point. I'm not aware of a safe detection + conversion in winapi, so it might indeed have to be documented.

/* 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)
} else if (!php_win32_cp_use_unicode()) {
/* Custom arguments are UTF-8, regardless of PHP's configured encoding. */
char **converted_argv = calloc((size_t) argc + 1, sizeof(char *));
if (!converted_argv) {
exit_status = 1;
goto out;
}
argv = converted_argv;
for (int i = 0; i < argc; i++) {
wchar_t *arg = php_win32_cp_utf8_to_w(argv_save[i]);
if (arg) {
argv[i] = php_win32_cp_w_to_any(arg);
free(arg);
}
if (!argv[i]) {
fprintf(stderr, "Could not convert command line argument %d.\n", i);
exit_status = 1;
goto out;
}
}
}

SetConsoleCtrlHandler(php_cli_win32_ctrl_handler, TRUE);
#endif
Expand Down Expand Up @@ -1377,8 +1411,10 @@ 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) {
if (argv != argv_save) {
PHP_WIN32_CP_FREE_ARRAY(argv, argc);
}
if (argv_wide) {
LocalFree(argv_wide);
}
argv = argv_save;
Expand Down
8 changes: 8 additions & 0 deletions sapi/embed/Makefile.frag.w32
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions sapi/embed/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
26 changes: 26 additions & 0 deletions sapi/embed/tests/cli_argv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "php.h"
#include "sapi/cli/cli.h"

int main(int argc, char *argv[])
{
char *php_argv[] = {
"embedded-php", "-n", "-d", "default_charset=UTF-8", "-r",
"echo json_encode([$argc, array_map('bin2hex', array_slice($argv, 1))]), PHP_EOL; exit(23);",
"--", "caf\xc3\xa9", "argument with spaces", "", "\xf0\x9f\x98\x80", NULL
};
if (argc > 1 && strcmp(argv[1], "default-charset") == 0) {
php_argv[3] = "default_charset=Windows-1252";
php_argv[10] = "\xe2\x82\xac";
} else if (argc > 1 && strcmp(argv[1], "internal-encoding") == 0) {
php_argv[3] = "internal_encoding=Windows-1252";
php_argv[10] = "\xe2\x82\xac";
}
if (argc > 2 && strcmp(argv[2], "file") == 0) {
php_argv[4] = "-f";
php_argv[5] = "cli_argv_caf\xc3\xa9.php";
} else if (argc > 2 && strcmp(argv[2], "invalid-utf8") == 0) {
/* Latin-1 cafe with an accented e, which is not valid UTF-8. */
php_argv[7] = "caf\xe9";
}
return do_php_cli((int) (sizeof(php_argv) / sizeof(*php_argv)) - 1, php_argv);
}
42 changes: 42 additions & 0 deletions sapi/embed/tests/cli_argv.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
do_php_cli() validates and converts custom UTF-8 argv on Windows
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only');
if (!file_exists(dirname(PHP_BINARY) . '/php_embed_cli_test.exe')) {
die('skip embed SAPI test host is not built (requires --enable-embed)');
}
?>
--FILE--
<?php
$host = dirname(PHP_BINARY) . '/php_embed_cli_test.exe';
foreach (['ignored-host-argument', 'default-charset', 'internal-encoding'] as $mode) {
passthru(escapeshellarg($host) . ' ' . $mode, $status);
var_dump($status);
passthru(escapeshellarg($host) . ' ' . $mode . ' invalid-utf8 2>&1', $status);
var_dump($status);
}
chdir(__DIR__);
file_put_contents("cli_argv_caf\xc3\xa9.php", '<?php echo "Unicode filename works\n";');
passthru(escapeshellarg($host) . ' default-charset file', $status);
var_dump($status);
?>
--CLEAN--
<?php
unlink(__DIR__ . "/cli_argv_caf\xc3\xa9.php");
?>
--EXPECT--
[5,["636166c3a9","617267756d656e74207769746820737061636573","","f09f9880"]]
int(23)
Invalid UTF-8 in command line argument 7.
int(1)
[5,["636166e9","617267756d656e74207769746820737061636573","","80"]]
int(23)
Invalid UTF-8 in command line argument 7.
int(1)
[5,["636166e9","617267756d656e74207769746820737061636573","","80"]]
int(23)
Invalid UTF-8 in command line argument 7.
int(1)
Unicode filename works
int(0)
Loading