index.js 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. "use strict";
  2. var fs = require("fs");
  3. var path = require("path");
  4. var jszip = require("jszip");
  5. var mkdirp = require("mkdirp");
  6. var promisify = require("yaku/lib/promisify");
  7. var writeFile = promisify(fs.writeFile);
  8. var readFile = promisify(fs.readFile);
  9. var mkdir = promisify(mkdirp);
  10. function crxToZip(buf) {
  11. function calcLength(a, b, c, d) {
  12. var length = 0;
  13. length += a;
  14. length += b << 8;
  15. length += c << 16;
  16. length += d << 24;
  17. return length;
  18. }
  19. // 50 4b 03 04
  20. // This is actually a zip file
  21. if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) {
  22. return buf;
  23. }
  24. // 43 72 32 34 (Cr24)
  25. if (buf[0] !== 67 || buf[1] !== 114 || buf[2] !== 50 || buf[3] !== 52) {
  26. throw new Error("Invalid header: Does not start with Cr24");
  27. }
  28. // 02 00 00 00
  29. // or
  30. // 03 00 00 00
  31. var isV3 = buf[4] === 3;
  32. var isV2 = buf[4] === 2;
  33. if (!isV2 && !isV3 || buf[5] || buf[6] || buf[7]) {
  34. throw new Error("Unexpected crx format version number.");
  35. }
  36. if (isV2) {
  37. var publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]);
  38. var signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]);
  39. // 16 = Magic number (4), CRX format version (4), lengths (2x4)
  40. var _zipStartOffset = 16 + publicKeyLength + signatureLength;
  41. return buf.slice(_zipStartOffset, buf.length);
  42. }
  43. // v3 format has header size and then header
  44. var headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]);
  45. var zipStartOffset = 12 + headerSize;
  46. return buf.slice(zipStartOffset, buf.length);
  47. }
  48. function unzip(crxFilePath, destination) {
  49. var filePath = path.resolve(crxFilePath);
  50. var extname = path.extname(crxFilePath);
  51. var basename = path.basename(crxFilePath, extname);
  52. var dirname = path.dirname(crxFilePath);
  53. destination = destination || path.resolve(dirname, basename);
  54. return readFile(filePath).then(function (buf) {
  55. return jszip.loadAsync(crxToZip(buf));
  56. }).then(function (zip) {
  57. var zipFileKeys = Object.keys(zip.files);
  58. return Promise.all(zipFileKeys.map(function (filename) {
  59. var isFile = !zip.files[filename].dir;
  60. var fullPath = path.join(destination, filename);
  61. var directory = isFile && path.dirname(fullPath) || fullPath;
  62. var content = zip.files[filename].async("nodebuffer");
  63. return mkdir(directory).then(function () {
  64. return isFile ? content : false;
  65. }).then(function (data) {
  66. return data ? writeFile(fullPath, data) : true;
  67. });
  68. }));
  69. });
  70. }
  71. module.exports = unzip;