Skip to content

Instantly share code, notes, and snippets.

@igorzi
Created August 5, 2011 01:11
Show Gist options
  • Save igorzi/1126720 to your computer and use it in GitHub Desktop.
Save igorzi/1126720 to your computer and use it in GitHub Desktop.
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