blob: 10a013625be10a093be24e2b11e0ed90db276417 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2
3var resolveCommand = require('./util/resolveCommand');
4var hasEmptyArgumentBug = require('./util/hasEmptyArgumentBug');
5var escapeArgument = require('./util/escapeArgument');
6var escapeCommand = require('./util/escapeCommand');
7var readShebang = require('./util/readShebang');
8
9var isWin = process.platform === 'win32';
10var skipShellRegExp = /\.(?:com|exe)$/i;
11
12// Supported in Node >= 6 and >= 4.8
13var supportsShellOption = parseInt(process.version.substr(1).split('.')[0], 10) >= 6 ||
14 parseInt(process.version.substr(1).split('.')[0], 10) === 4 && parseInt(process.version.substr(1).split('.')[1], 10) >= 8;
15
16function parseNonShell(parsed) {
17 var shebang;
18 var needsShell;
19 var applyQuotes;
20
21 if (!isWin) {
22 return parsed;
23 }
24
25 // Detect & add support for shebangs
26 parsed.file = resolveCommand(parsed.command);
27 parsed.file = parsed.file || resolveCommand(parsed.command, true);
28 shebang = parsed.file && readShebang(parsed.file);
29
30 if (shebang) {
31 parsed.args.unshift(parsed.file);
32 parsed.command = shebang;
33 needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(resolveCommand(shebang) || resolveCommand(shebang, true));
34 } else {
35 needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(parsed.file);
36 }
37
38 // If a shell is required, use cmd.exe and take care of escaping everything correctly
39 if (needsShell) {
40 // Escape command & arguments
41 applyQuotes = (parsed.command !== 'echo'); // Do not quote arguments for the special "echo" command
42 parsed.command = escapeCommand(parsed.command);
43 parsed.args = parsed.args.map(function (arg) {
44 return escapeArgument(arg, applyQuotes);
45 });
46
47 // Make use of cmd.exe
48 parsed.args = ['/d', '/s', '/c', '"' + parsed.command + (parsed.args.length ? ' ' + parsed.args.join(' ') : '') + '"'];
49 parsed.command = process.env.comspec || 'cmd.exe';
50 parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
51 }
52
53 return parsed;
54}
55
56function parseShell(parsed) {
57 var shellCommand;
58
59 // If node supports the shell option, there's no need to mimic its behavior
60 if (supportsShellOption) {
61 return parsed;
62 }
63
64 // Mimic node shell option, see: https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
65 shellCommand = [parsed.command].concat(parsed.args).join(' ');
66
67 if (isWin) {
68 parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
69 parsed.args = ['/d', '/s', '/c', '"' + shellCommand + '"'];
70 parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
71 } else {
72 if (typeof parsed.options.shell === 'string') {
73 parsed.command = parsed.options.shell;
74 } else if (process.platform === 'android') {
75 parsed.command = '/system/bin/sh';
76 } else {
77 parsed.command = '/bin/sh';
78 }
79
80 parsed.args = ['-c', shellCommand];
81 }
82
83 return parsed;
84}
85
86// ------------------------------------------------
87
88function parse(command, args, options) {
89 var parsed;
90
91 // Normalize arguments, similar to nodejs
92 if (args && !Array.isArray(args)) {
93 options = args;
94 args = null;
95 }
96
97 args = args ? args.slice(0) : []; // Clone array to avoid changing the original
98 options = options || {};
99
100 // Build our parsed object
101 parsed = {
102 command: command,
103 args: args,
104 options: options,
105 file: undefined,
106 original: command,
107 };
108
109 // Delegate further parsing to shell or non-shell
110 return options.shell ? parseShell(parsed) : parseNonShell(parsed);
111}
112
113module.exports = parse;