parse.js
3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'use strict';
var fs = require('fs');
var LRU = require('lru-cache');
var resolveCommand = require('./resolveCommand');
var hasBrokenSpawn = require('./hasBrokenSpawn');
var isWin = process.platform === 'win32';
var shebangCache = new LRU({ max: 50, maxAge: 30 * 1000 }); // Cache just for 30sec
function readShebang(command) {
var buffer;
var fd;
var match;
var shebang;
// Check if it is in the cache first
if (shebangCache.has(command)) {
return shebangCache.get(command);
}
// Read the first 150 bytes from the file
buffer = new Buffer(150);
try {
fd = fs.openSync(command, 'r');
fs.readSync(fd, buffer, 0, 150, 0);
fs.closeSync(fd);
} catch (e) { /* empty */ }
// Check if it is a shebang
match = buffer.toString().trim().match(/#!(.+)/i);
if (match) {
shebang = match[1].replace(/\/usr\/bin\/env\s+/i, ''); // Remove /usr/bin/env
}
// Store the shebang in the cache
shebangCache.set(command, shebang);
return shebang;
}
function escapeArg(arg, quote) {
// Convert to string
arg = '' + arg;
// If we are not going to quote the argument,
// escape shell metacharacters, including double and single quotes:
if (!quote) {
arg = arg.replace(/([\(\)%!\^<>&|;,"'\s])/g, '^$1');
} else {
// Sequence of backslashes followed by a double quote:
// double up all the backslashes and escape the double quote
arg = arg.replace(/(\\*)"/g, '$1$1\\"');
// Sequence of backslashes followed by the end of the string
// (which will become a double quote later):
// double up all the backslashes
arg = arg.replace(/(\\*)$/, '$1$1');
// All other backslashes occur literally
// Quote the whole thing:
arg = '"' + arg + '"';
}
return arg;
}
function escapeCommand(command) {
// Do not escape if this command is not dangerous..
// We do this so that commands like "echo" or "ifconfig" work
// Quoting them, will make them unaccessible
return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArg(command, true);
}
function requiresShell(command) {
return !/\.(?:com|exe)$/i.test(command);
}
function parse(command, args, options) {
var shebang;
var applyQuotes;
var file;
var original;
var shell;
// Normalize arguments, similar to nodejs
if (args && !Array.isArray(args)) {
options = args;
args = null;
}
args = args ? args.slice(0) : []; // Clone array to avoid changing the original
options = options || {};
original = command;
if (isWin) {
// Detect & add support for shebangs
file = resolveCommand(command);
file = file || resolveCommand(command, true);
shebang = file && readShebang(file);
shell = options.shell || hasBrokenSpawn;
if (shebang) {
args.unshift(file);
command = shebang;
shell = shell || requiresShell(resolveCommand(shebang) || resolveCommand(shebang, true));
} else {
shell = shell || requiresShell(file);
}
if (shell) {
// Escape command & arguments
applyQuotes = (command !== 'echo'); // Do not quote arguments for the special "echo" command
command = escapeCommand(command);
args = args.map(function (arg) {
return escapeArg(arg, applyQuotes);
});
// Use cmd.exe
args = ['/s', '/c', '"' + command + (args.length ? ' ' + args.join(' ') : '') + '"'];
command = process.env.comspec || 'cmd.exe';
// Tell node's spawn that the arguments are already escaped
options.windowsVerbatimArguments = true;
}
}
return {
command: command,
args: args,
options: options,
file: file,
original: original,
};
}
module.exports = parse;