From ed3c9da68e4db2981fa1851a7923bd3b522a1d4b Mon Sep 17 00:00:00 2001 From: Tereza Tomcova Date: Tue, 11 Oct 2016 00:19:10 +0200 Subject: [PATCH] Fixes #137: Follow Windows conventions when composing cmdline --- lib/pty_win.js | 54 ++++++++++++++++++++++++++-- package.json | 3 +- test-windows/argvToCommandLine.js | 59 +++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 test-windows/argvToCommandLine.js diff --git a/lib/pty_win.js b/lib/pty_win.js index ece4de88a..1360fb0cb 100644 --- a/lib/pty_win.js +++ b/lib/pty_win.js @@ -60,11 +60,15 @@ function Agent(file, args, env, cwd, cols, rows, debug) { // Sanitize input variable. file = file; - args = args.join(' '); cwd = path.resolve(cwd); + // Compose command line + var cmdline = [file]; + Array.prototype.push.apply(cmdline, args); + cmdline = argvToCommandLine(cmdline); + // Start terminal session. - pty.startProcess(self.pid, file, args, env, cwd); + pty.startProcess(self.pid, file, cmdline, env, cwd); // Emit ready event. self.ptySocket.emit('ready_datapipe', socket); @@ -357,6 +361,48 @@ function environ(env) { return pairs; } +// Convert argc/argv into a Win32 command-line following the escaping convention +// documented on MSDN. (e.g. see CommandLineToArgvW documentation) +// Copied from winpty project. +function argvToCommandLine(argv) { + var result = ''; + for (var argIndex = 0; argIndex < argv.length; argIndex++) { + if (argIndex > 0) { + result += ' '; + } + var arg = argv[argIndex]; + var quote = + arg.indexOf(' ') != -1 || + arg.indexOf('\t') != -1 || + arg == ''; + if (quote) { + result += '\"'; + } + var bsCount = 0; + for (var i = 0; i < arg.length; i++) { + var p = arg[i]; + if (p == '\\') { + bsCount++; + } else if (p == '"') { + result += '\\'.repeat(bsCount * 2 + 1); + result += '"'; + bsCount = 0; + } else { + result += '\\'.repeat(bsCount); + bsCount = 0; + result += p; + } + } + if (quote) { + result += '\\'.repeat(bsCount * 2); + result += '\"'; + } else { + result += '\\'.repeat(bsCount); + } + } + return result; +} + /** * Expose */ @@ -364,3 +410,7 @@ function environ(env) { module.exports = exports = Terminal; exports.Terminal = Terminal; exports.native = pty; + +if (process.env.NODE_ENV == 'test') { + exports.argvToCommandLine = argvToCommandLine; +} diff --git a/package.json b/package.json index b86e781f5..211d336a0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "scripts": { "install": "node scripts/install.js", "postinstall": "node scripts/post-install.js", - "test": "NODE_ENV=test mocha -R spec" + "test": "NODE_ENV=test mocha -R spec", + "test-windows": "set \"NODE_ENV=test\" && mocha -R spec test-windows" }, "tags": [ "pty", diff --git a/test-windows/argvToCommandLine.js b/test-windows/argvToCommandLine.js new file mode 100644 index 000000000..ecb2715cf --- /dev/null +++ b/test-windows/argvToCommandLine.js @@ -0,0 +1,59 @@ +var argvToCommandLine = require('../').argvToCommandLine +var assert = require("assert"); + +function check(input, expected) { + assert.equal(argvToCommandLine(input), expected); +} + +describe("argvToCommandLine", function() { + describe("Plain strings", function() { + it("doesn't quote plain string", function() { + check(['asdf'], 'asdf'); + }); + it("doesn't escape backslashes", function() { + check(['\\asdf\\qwer\\'], '\\asdf\\qwer\\'); + }); + it("doesn't escape multiple backslashes", function() { + check(['asdf\\\\qwer'], 'asdf\\\\qwer'); + }); + it("adds backslashes before quotes", function() { + check(['"asdf"qwer"'], '\\"asdf\\"qwer\\"'); + }); + it("escapes backslashes before quotes", function() { + check(['asdf\\"qwer'], 'asdf\\\\\\"qwer'); + }); + }); + + describe("Quoted strings", function() { + it("quotes string with spaces", function() { + check(['asdf qwer'], '"asdf qwer"'); + }); + it("quotes empty string", function() { + check([''], '""'); + }); + it("quotes string with tabs", function() { + check(['asdf\tqwer'], '"asdf\tqwer"'); + }); + it("escapes only the last backslash", function() { + check(['\\asdf \\qwer\\'], '"\\asdf \\qwer\\\\"'); + }); + it("doesn't escape multiple backslashes", function() { + check(['asdf \\\\qwer'], '"asdf \\\\qwer"'); + }); + it("adds backslashes before quotes", function() { + check(['"asdf "qwer"'], '"\\"asdf \\"qwer\\""'); + }); + it("escapes backslashes before quotes", function() { + check(['asdf \\"qwer'], '"asdf \\\\\\"qwer"'); + }); + it("escapes multiple backslashes at the end", function() { + check(['asdf qwer\\\\'], '"asdf qwer\\\\\\\\"'); + }); + }); + + describe("Multiple arguments", function() { + it("joins arguments with spaces", function() { + check(['asdf', 'qwer zxcv', '', '"'], 'asdf "qwer zxcv" "" \\"'); + }); + }); +});