| Leo Repp | 58b9f11 | 2021-11-22 11:57:47 +0100 | [diff] [blame^] | 1 | 'use strict'; |
| 2 | |
| 3 | var resolveCommand = require('./util/resolveCommand'); |
| 4 | var hasEmptyArgumentBug = require('./util/hasEmptyArgumentBug'); |
| 5 | var escapeArgument = require('./util/escapeArgument'); |
| 6 | var escapeCommand = require('./util/escapeCommand'); |
| 7 | var readShebang = require('./util/readShebang'); |
| 8 | |
| 9 | var isWin = process.platform === 'win32'; |
| 10 | var skipShellRegExp = /\.(?:com|exe)$/i; |
| 11 | |
| 12 | // Supported in Node >= 6 and >= 4.8 |
| 13 | var 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 | |
| 16 | function 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 | |
| 56 | function 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 | |
| 88 | function 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 | |
| 113 | module.exports = parse; |