diff --git a/windows/pageant.c b/windows/pageant.c index d1368903..7a0970a7 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1598,7 +1598,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * started up the main agent. Details of keys to be added are * stored in the 'clkeys' array. */ - split_into_argv(cmdline, &argc, &argv, &argstart); + split_into_argv(cmdline, false, &argc, &argv, &argstart); bool add_keys_encrypted = false; AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); while (!aux_match_done(&amo)) { diff --git a/windows/platform.h b/windows/platform.h index 52358692..098b3bc3 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -399,7 +399,8 @@ int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid); void MakeDlgItemBorderless(HWND parent, int id); char *GetDlgItemText_alloc(HWND hwnd, int id); -void split_into_argv(char *, int *, char ***, char ***); +void split_into_argv(char *, bool includes_program_name, + int *, char ***, char ***); /* * Private structure for prefslist state. Only in the header file diff --git a/windows/pterm.c b/windows/pterm.c index bb68245d..13face06 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -17,7 +17,7 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) int argc; char **argv, **argstart; - split_into_argv(cmdline, &argc, &argv, &argstart); + split_into_argv(cmdline, false, &argc, &argv, &argstart); for (int i = 0; i < argc; i++) { char *arg = argv[i]; diff --git a/windows/putty.c b/windows/putty.c index 3c41b557..f3cf31d0 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -51,7 +51,7 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) int argc, i; char **argv; - split_into_argv(cmdline, &argc, &argv, NULL); + split_into_argv(cmdline, false, &argc, &argv, NULL); for (i = 0; i < argc; i++) { char *p = argv[i]; diff --git a/windows/puttygen.c b/windows/puttygen.c index 457dbfa1..0c4b279b 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -2417,7 +2417,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) save_params = ppk_save_default_parameters; - split_into_argv(cmdline, &argc, &argv, NULL); + split_into_argv(cmdline, false, &argc, &argv, NULL); int argbits = -1; AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index c42c7a0b..1c17f0ce 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -161,8 +161,8 @@ #define MOD3 0 #endif -void split_into_argv(char *cmdline, int *argc, char ***argv, - char ***argstart) +void split_into_argv(char *cmdline, bool includes_program_name, + int *argc, char ***argv, char ***argstart) { char *p; char *outputline, *q; @@ -198,6 +198,40 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, while (*p && isspace(*p)) p++; if (!*p) break; + /* + * Check if this argument is the program name. If so, + * different rules apply. + * + * In most arguments, the special characters are the double + * quote and the backslash. An exception is the program name + * at the start of the command line, in which backslashes are + * _not_ special - if one appears before a quote, it does not + * make the quote literal. + * + * The C library must implement this special rule, and we must + * follow suit here, in order to match the way CreateProcess + * scans the command line to determine the program name. It + * will consider that all these commands refer to the same + * file equally validly: + * + * "C:\Program Files\Foo"\bar.exe + * "C:\Program Files\Foo\"bar.exe + * "C:\Program Files\Foo\bar.exe" + * + * Each one contains a quoted section that protects the space + * in "Program Files", and the closing quote takes effect the + * same in all cases - even though, in the middle case, it's + * immediately preceded by one of the path separators in the + * name. For CreateProcess, backslashes aren't special. + * + * So, if our caller told us that the input command line + * includes the program name (which it does if it came from + * GetCommandLine, but not if it was passed in to WinMain), + * then we must treat the 0th output argument specially, by + * not considering backslashes to affect the quoting. + */ + bool backslash_special = !(outputargc == 0 && includes_program_name); + /* We have an argument; start it. */ outputargv[outputargc] = q; outputargstart[outputargc] = p; @@ -209,7 +243,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, if (!quote && isspace(*p)) break; /* argument is finished */ - if (*p == '"' || *p == '\\') { + if (*p == '"' || (*p == '\\' && backslash_special)) { /* * We have a sequence of zero or more backslashes * followed by a sequence of zero or more quotes. @@ -273,6 +307,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, const struct argv_test { const char *cmdline; const char *argv[10]; + bool include_program_name; } argv_tests[] = { /* * We generate this set of tests by invoking ourself with @@ -463,6 +498,9 @@ const struct argv_test { {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, #endif /* MOD3 */ + /* Common tests that check the special program-name rule. */ + {"\"a b\\\"c \"d e\" \"f g\"", {"a b\\c", "d e", "f g", NULL}, true}, + {"\"a b\\\"c \"d e\" \"f g\"", {"a b\"c d", "e f", "g", NULL}, false}, }; void out_of_memory(void) @@ -658,7 +696,8 @@ int main(int argc, char **argv) char **av; bool failed = false; - split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL); + split_into_argv((char *)argv_tests[i].cmdline, + argv_tests[i].include_program_name, &ac, &av, NULL); for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { if (strcmp(av[j], argv_tests[i].argv[j])) {