parse.js 3.9 KB

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