• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'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;
114