Support for unescaped arguments, suitable for use with cmd /c.
Robust argument escaping that hopefully matches Windows' algorithm for unescaping.
This commit is contained in:
parent
c01d6f928c
commit
d84b249616
@ -504,6 +504,7 @@ typedef struct uv_process_options_s {
|
||||
char** args;
|
||||
char** env;
|
||||
char* cwd;
|
||||
int windows_verbatim_arguments;
|
||||
/*
|
||||
* The user should supply pointers to initialized uv_pipe_t structs for
|
||||
* stdio. The user is reponsible for calling uv_close on them.
|
||||
|
||||
@ -75,48 +75,6 @@ static void uv_process_init(uv_process_t* handle) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Quotes command line arguments
|
||||
* Returns a pointer to the end (next char to be written) of the buffer
|
||||
*/
|
||||
static wchar_t* quote_cmd_arg(wchar_t *source, wchar_t *target,
|
||||
wchar_t terminator) {
|
||||
int len = wcslen(source),
|
||||
i;
|
||||
|
||||
// Check if the string must be quoted;
|
||||
// if unnecessary, don't do it, it may only confuse older programs.
|
||||
if (len == 0) {
|
||||
goto quote;
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
if (source[i] == L' ' || source[i] == L'"') {
|
||||
goto quote;
|
||||
}
|
||||
}
|
||||
|
||||
// No quotation needed
|
||||
wcsncpy(target, source, len);
|
||||
target += len;
|
||||
*(target++) = terminator;
|
||||
return target;
|
||||
|
||||
quote:
|
||||
// Quote
|
||||
*(target++) = L'"';
|
||||
for (i = 0; i < len; i++) {
|
||||
if (source[i] == L'"' || source[i] == L'\\') {
|
||||
*(target++) = '\\';
|
||||
}
|
||||
*(target++) = source[i];
|
||||
}
|
||||
*(target++) = L'"';
|
||||
*(target++) = terminator;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Path search functions
|
||||
*/
|
||||
@ -403,8 +361,83 @@ static wchar_t* search_path(const wchar_t *file,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Quotes command line arguments
|
||||
* Returns a pointer to the end (next char to be written) of the buffer
|
||||
*/
|
||||
wchar_t* quote_cmd_arg(const wchar_t *source, wchar_t *target) {
|
||||
int len = wcslen(source),
|
||||
i, quote_hit;
|
||||
wchar_t* start;
|
||||
|
||||
static wchar_t* make_program_args(char** args) {
|
||||
/*
|
||||
* Check if the string must be quoted;
|
||||
* if unnecessary, don't do it, it may only confuse older programs.
|
||||
*/
|
||||
if (len == 0) {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (NULL == wcspbrk(source, L" \t\"")) {
|
||||
/* No quotation needed */
|
||||
wcsncpy(target, source, len);
|
||||
target += len;
|
||||
return target;
|
||||
}
|
||||
|
||||
if (NULL == wcspbrk(source, L"\"\\")) {
|
||||
/*
|
||||
* No embedded double quotes or backlashes, so I can just wrap
|
||||
* quote marks around the whole thing.
|
||||
*/
|
||||
*(target++) = L'"';
|
||||
wcsncpy(target, source, len);
|
||||
target += len;
|
||||
*(target++) = L'"';
|
||||
return target;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expected intput/output:
|
||||
* input : hello"world
|
||||
* output: "hello\"world"
|
||||
* input : hello""world
|
||||
* output: "hello\"\"world"
|
||||
* input : hello\world
|
||||
* output: "hello\world"
|
||||
* input : hello\\world
|
||||
* output: "hello\\world"
|
||||
* input : hello\"world
|
||||
* output: "hello\\\"world"
|
||||
* input : hello\\"world
|
||||
* output: "hello\\\\\"world"
|
||||
* input : hello world\
|
||||
* output: "hello world\"
|
||||
*/
|
||||
|
||||
*(target++) = L'"';
|
||||
start = target;
|
||||
quote_hit = 1;
|
||||
|
||||
for (i = len; i > 0; --i) {
|
||||
*(target++) = source[i - 1];
|
||||
|
||||
if (quote_hit && source[i - 1] == L'\\') {
|
||||
*(target++) = L'\\';
|
||||
} else if(source[i - 1] == L'"') {
|
||||
quote_hit = 1;
|
||||
*(target++) = L'\\';
|
||||
} else {
|
||||
quote_hit = 0;
|
||||
}
|
||||
}
|
||||
target[0] = L'\0';
|
||||
wcsrev(start);
|
||||
*(target++) = L'"';
|
||||
return target;
|
||||
}
|
||||
|
||||
wchar_t* make_program_args(char** args, int verbatim_arguments) {
|
||||
wchar_t* dst;
|
||||
wchar_t* ptr;
|
||||
char** arg;
|
||||
@ -443,8 +476,13 @@ static wchar_t* make_program_args(char** args) {
|
||||
if (!len) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ptr = quote_cmd_arg(buffer, ptr, *(arg + 1) ? L' ' : L'\0');
|
||||
if (verbatim_arguments) {
|
||||
wcscpy(ptr, buffer);
|
||||
ptr += len - 1;
|
||||
} else {
|
||||
ptr = quote_cmd_arg(buffer, ptr);
|
||||
}
|
||||
*ptr++ = *(arg + 1) ? L' ' : L'\0';
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
@ -739,7 +777,7 @@ int uv_spawn(uv_process_t* process, uv_process_options_t options) {
|
||||
|
||||
process->exit_cb = options.exit_cb;
|
||||
UTF8_TO_UTF16(options.file, application);
|
||||
arguments = options.args ? make_program_args(options.args) : NULL;
|
||||
arguments = options.args ? make_program_args(options.args, options.windows_verbatim_arguments) : NULL;
|
||||
env = options.env ? make_program_env(options.env) : NULL;
|
||||
|
||||
if (options.cwd) {
|
||||
|
||||
@ -69,6 +69,7 @@ TEST_DECLARE (spawn_stdin)
|
||||
TEST_DECLARE (spawn_and_kill)
|
||||
#ifdef _WIN32
|
||||
TEST_DECLARE (spawn_detect_pipe_name_collisions_on_windows)
|
||||
TEST_DECLARE (argument_escaping)
|
||||
#endif
|
||||
HELPER_DECLARE (tcp4_echo_server)
|
||||
HELPER_DECLARE (tcp6_echo_server)
|
||||
@ -153,6 +154,7 @@ TASK_LIST_START
|
||||
TEST_ENTRY (spawn_and_kill)
|
||||
#ifdef _WIN32
|
||||
TEST_ENTRY (spawn_detect_pipe_name_collisions_on_windows)
|
||||
TEST_ENTRY (argument_escaping)
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
|
||||
@ -269,4 +269,77 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
wchar_t* make_program_args(char** args, int verbatim_arguments);
|
||||
wchar_t* quote_cmd_arg(const wchar_t *source, wchar_t *target);
|
||||
|
||||
TEST_IMPL(argument_escaping) {
|
||||
const wchar_t* test_str[] = {
|
||||
L"HelloWorld",
|
||||
L"Hello World",
|
||||
L"Hello\"World",
|
||||
L"Hello World\\",
|
||||
L"Hello\\\"World",
|
||||
L"Hello\\World",
|
||||
L"Hello\\\\World",
|
||||
L"Hello World\\",
|
||||
L"c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\""
|
||||
};
|
||||
const int count = sizeof(test_str) / sizeof(*test_str);
|
||||
wchar_t** test_output;
|
||||
wchar_t* command_line;
|
||||
wchar_t** cracked;
|
||||
size_t total_size = 0;
|
||||
int i;
|
||||
int num_args;
|
||||
|
||||
char* verbatim[] = {
|
||||
"cmd.exe",
|
||||
"/c",
|
||||
"c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\"",
|
||||
NULL
|
||||
};
|
||||
wchar_t* verbatim_output;
|
||||
wchar_t* non_verbatim_output;
|
||||
|
||||
test_output = calloc(count, sizeof(wchar_t*));
|
||||
for (i = 0; i < count; ++i) {
|
||||
test_output[i] = calloc(2 * (wcslen(test_str[i]) + 2), sizeof(wchar_t));
|
||||
quote_cmd_arg(test_str[i], test_output[i]);
|
||||
wprintf(L"input : %s\n", test_str[i]);
|
||||
wprintf(L"output: %s\n", test_output[i]);
|
||||
total_size += wcslen(test_output[i]) + 1;
|
||||
}
|
||||
command_line = calloc(total_size + 1, sizeof(wchar_t));
|
||||
for (i = 0; i < count; ++i) {
|
||||
wcscat(command_line, test_output[i]);
|
||||
wcscat(command_line, L" ");
|
||||
}
|
||||
command_line[total_size - 1] = L'\0';
|
||||
|
||||
wprintf(L"command_line: %s\n", command_line);
|
||||
|
||||
cracked = CommandLineToArgvW(command_line, &num_args);
|
||||
for (i = 0; i < num_args; ++i) {
|
||||
wprintf(L"%d: %s\t%s\n", i, test_str[i], cracked[i]);
|
||||
ASSERT(wcscmp(test_str[i], cracked[i]) == 0);
|
||||
}
|
||||
|
||||
LocalFree(cracked);
|
||||
for (i = 0; i < count; ++i) {
|
||||
free(test_output[i]);
|
||||
}
|
||||
|
||||
verbatim_output = make_program_args(verbatim, 1);
|
||||
non_verbatim_output = make_program_args(verbatim, 0);
|
||||
|
||||
wprintf(L" verbatim_output: %s\n", verbatim_output);
|
||||
wprintf(L"non_verbatim_output: %s\n", non_verbatim_output);
|
||||
|
||||
ASSERT(wcscmp(verbatim_output, L"cmd.exe /c c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\"") == 0);
|
||||
ASSERT(wcscmp(non_verbatim_output, L"cmd.exe /c \"c:\\path\\to\\node.exe --eval \\\"require('c:\\\\path\\\\to\\\\test.js')\\\"\"") == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user