parse.js 3.8 KB

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