parse.js 3.5 KB

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