Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
node_modules
test/argv.exe
test/argv
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"name": "spawn-cmd",
"version": "0.0.2",
"description": "Wraps child_process.spawn with 'cmd /c <command>' when it's a windows machine",
"version": "0.0.3",
"description": "Wraps child_process.spawn with 'cmd /s /c <command>' and proper arguments escaping when it's a windows machine",
"main": "spawn.js",
"scripts": {
"test": "pogo -c ./spawn.pogo && mocha ./test/spawn_test.pogo"
"test": "(gcc test/argv.c -o test/argv || cl test/argv.c /Fotest/argv) && pogo -c ./spawn.pogo && mocha ./test/spawn_test.pogo"
},
"repository": "https://github.com/featurist/spawn-cmd.git",
"keywords": [
"windows",
"child_process"
],
"author": "@joshski",
"contributors": ["@joshski", "@yeputons"],
"license": "BSD",
"devDependencies": {
"should": "~2.1.0",
Expand Down
33 changes: 31 additions & 2 deletions spawn.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
(function() {
var self = this;
var spawn, windowsSpawn;
var spawn, escapeArgument, windowsSpawn;
spawn = require("child_process").spawn;
escapeArgument = function(arg) {
var res, i, quote_hit;
if (arg === "") {
return '""';
} else if (!/[ \t"]/.test(arg)) {
return arg;
} else if (!/[\\"]/.test(arg)) {
return '"' + arg + '"';
} else {
res = '"';
for (i = arg.length - 1; i >= 0; i = i - 1) {
res = arg.charAt(i) + res;
if (quote_hit && arg.charAt(i) === "\\") {
res = "\\" + res;
} else if (arg.charAt(i) === '"') {
quote_hit = true;
res = "\\" + res;
} else {
quote_hit = 0;
}
}
return res = '"' + res;
}
};
windowsSpawn = function(executable, args, options) {
return spawn(process.env.comspec || "cmd.exe", [ "/c", executable ].concat(args), options);
args = args.map(escapeArgument);
args = [ '"' + executable + '"' ].concat(args);
args = args.join(" ");
options = options || {};
options.windowsVerbatimArguments = true;
return spawn(process.env.comspec || "cmd.exe", [ "/s", "/c", '"' + args + '"' ], options);
};
if (process.platform === "win32") {
exports.spawn = windowsSpawn;
Expand Down
34 changes: 33 additions & 1 deletion spawn.pogo
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
spawn = require 'child_process'.spawn

// See quote_cmd_arg() from https://github.com/joyent/node/blob/master/deps/uv/src/win/process.c#L427
// This function is a direct copy
escape argument (arg) =
if (arg == "")
'""'
else if (!r/[ \t"]/.test(arg))
arg
else if (!r/[\\"]/.test(arg))
'"' + arg + '"'
else
res = '"'
for (i = arg.length - 1, i >= 0, i := i - 1)
res := arg.charAt(i) + res
if (quote_hit && arg.charAt(i) == '\')
res := '\' + res
else if (arg.charAt(i) == '"')
quote_hit = true
res := '\' + res
else
quote_hit := 0

res := '"' + res

windows spawn (executable, args, options) =
spawn (process.env.comspec || 'cmd.exe', ['/c', executable].concat(args), options)
// We have to manually escape because of featurist/spawn-cmd#3

args := args.map(escape argument)
args := ['"' + executable + '"'].concat(args)
args := args.join(" ")
options := options || {}
options.windowsVerbatimArguments := true

// See https://github.com/joyent/node/issues/2318#issuecomment-32706753 for details on the '/s' key
spawn (process.env.comspec || 'cmd.exe', ['/s', '/c', '"' + args + '"'], options)

if (process.platform == 'win32')
exports.spawn = windows spawn
Expand Down
10 changes: 10 additions & 0 deletions test/argv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <stdio.h>

int main(int argc, char* argv[]) {
int i;

printf("argc=%d\n", argc);
for (i = 1; i < argc; i++)
printf("%d %s\n", i, argv[i]);
return 0;
}
33 changes: 29 additions & 4 deletions test/spawn_test.pogo
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
spawn = require '../spawn.js'.spawn
os = require 'os'
path = require 'path'

describe 'spawn'

it 'spawns a process' @(done)
spawn and wait for (command, args, match, done) =
stdout = ''
stderr = ''

spawned = spawn('echo', ['zomg'])
spawned = spawn(command, args)

spawned.stdout.on "data" @(data)
stdout := stdout + data.toString()
Expand All @@ -18,7 +18,32 @@ describe 'spawn'
spawned.on 'exit' @(code)
set timeout
stderr.should.equal ""
stdout.should.match r/zomg/
stdout.should.match(match)
code.should.equal 0
done()
1

it 'spawns a process' @(done)
spawn and wait for ('echo', ['zomg'], r/zomg/, done)

it 'escapes quotes and slashes properly' @(done)
spawn and wait for (path.normalize('test/argv'), ['a', 'b"c', 'd\g', 'h"" i'], @new RegExp(['argc=5', '1 a', '2 b"c', '3 d\\g', '4 h"" i'].join(os.EOL)), done)

it 'escapes empty arguments properly' @(done)
spawn and wait for (path.normalize('test/argv'), ['a', '', 'c'], @new RegExp(['argc=4', '1 a', '2 ', '3 c'].join(os.EOL)), done)

if (process.platform == 'win32')
it 'spawns a batch file' @(done)
spawn and wait for ('test\test-batch', ['2', '3', '9'], r/2\r\n5\r\n8\r\n/, done)

it 'passes arguments with spaces to batch file' @(done)
spawn and wait for ('test\test-args', ['a b', 'cd', 'e f'], r/ok/, done)

it 'spawns a batch file with spaces and some simple arguments' @(done)
spawn and wait for ('test\test spaces', ['a'], r/ok/, done)

it 'spawns a batch file with spaces with no arguments' @(done)
spawn and wait for ('test\test spaces', [], r/ok/, done)

it 'passes arguments with spaces to a batch file with spaces' @(done)
spawn and wait for ('test\test spaces args.cmd', ['a', 'b c', 'd e', 'fg', 'h i'], r/ok/, done)
7 changes: 7 additions & 0 deletions test/test spaces args.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@echo off
if not a==%1 exit 1
if not "b c"==%2 exit 1
if not "d e"==%3 exit 1
if not fg==%4 exit 1
if not "h i"==%5 exit 1
echo ok
2 changes: 2 additions & 0 deletions test/test spaces.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
echo ok
5 changes: 5 additions & 0 deletions test/test-args.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@echo off
if not "a b"==%1 exit 1
if not cd==%2 exit 1
if not "e f"==%3 exit 1
echo ok
4 changes: 4 additions & 0 deletions test/test-batch.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@echo off
for /L %%i IN (%1, %2, %3) DO (
echo %%i
)