Created
August 5, 2011 01:11
-
-
Save igorzi/1126720 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 1db6a414ce5a6799d5a9458a2e03fb91e9082aa7 Mon Sep 17 00:00:00 2001 | |
From: Peter Bright <drpizza@quiscalusmexicanus.org> | |
Date: Fri, 5 Aug 2011 00:14:17 +0100 | |
Subject: [PATCH] Support for unescaped arguments, suitable for use with cmd /c. | |
Robust argument escaping that hopefully matches Windows' algorithm for unescaping. | |
--- | |
include/uv.h | 1 + | |
msvs/libuv-test.vcxproj | 1 + | |
src/win/process.c | 130 ++++++++++++++++++++++++++-------------- | |
test/test-argument-escaping.c | 97 ++++++++++++++++++++++++++++++ | |
test/test-list.h | 2 + | |
5 files changed, 185 insertions(+), 46 deletions(-) | |
create mode 100644 test/test-argument-escaping.c | |
diff --git a/include/uv.h b/include/uv.h | |
index bb3e37c..2ee7c31 100644 | |
--- a/include/uv.h | |
+++ b/include/uv.h | |
@@ -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. | |
diff --git a/msvs/libuv-test.vcxproj b/msvs/libuv-test.vcxproj | |
index 44b138b..3ad81fc 100644 | |
--- a/msvs/libuv-test.vcxproj | |
+++ b/msvs/libuv-test.vcxproj | |
@@ -143,6 +143,7 @@ | |
</ItemDefinitionGroup> | |
<ItemGroup> | |
<ClCompile Include="..\test\echo-server.c" /> | |
+ <ClCompile Include="..\test\test-argument-escaping.c" /> | |
<ClCompile Include="..\test\test-async.c" /> | |
<ClCompile Include="..\test\test-delayed-accept.c" /> | |
<ClCompile Include="..\test\test-callback-stack.c" /> | |
diff --git a/src/win/process.c b/src/win/process.c | |
index 4a53d7b..3b4e1d5 100644 | |
--- a/src/win/process.c | |
+++ b/src/win/process.c | |
@@ -76,48 +76,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; | |
+ | |
+ /* | |
+ * 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; | |
+} | |
-static wchar_t* make_program_args(char** args) { | |
+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) { | |
diff --git a/test/test-argument-escaping.c b/test/test-argument-escaping.c | |
new file mode 100644 | |
index 0000000..3a841fd | |
--- /dev/null | |
+++ b/test/test-argument-escaping.c | |
@@ -0,0 +1,97 @@ | |
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. | |
+ * | |
+ * Permission is hereby granted, free of charge, to any person obtaining a copy | |
+ * of this software and associated documentation files (the "Software"), to | |
+ * deal in the Software without restriction, including without limitation the | |
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
+ * sell copies of the Software, and to permit persons to whom the Software is | |
+ * furnished to do so, subject to the following conditions: | |
+ * | |
+ * The above copyright notice and this permission notice shall be included in | |
+ * all copies or substantial portions of the Software. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
+ * IN THE SOFTWARE. | |
+ */ | |
+ | |
+#include "uv.h" | |
+#include "task.h" | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+ | |
+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; | |
+} | |
diff --git a/test/test-list.h b/test/test-list.h | |
index ebfdeb6..08ed92a 100644 | |
--- a/test/test-list.h | |
+++ b/test/test-list.h | |
@@ -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 | |
-- | |
1.7.4.msysgit.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment