Compress.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /* globals Zlib, bzip2 */
  2. /**
  3. * Compression operations.
  4. *
  5. * @author n1474335 [n1474335@gmail.com]
  6. * @copyright Crown Copyright 2016
  7. * @license Apache-2.0
  8. *
  9. * @namespace
  10. */
  11. var Compress = {
  12. /**
  13. * @constant
  14. * @default
  15. */
  16. COMPRESSION_TYPE: ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"],
  17. /**
  18. * @constant
  19. * @default
  20. */
  21. INFLATE_BUFFER_TYPE: ["Adaptive", "Block"],
  22. /**
  23. * @constant
  24. * @default
  25. */
  26. COMPRESSION_METHOD: ["Deflate", "None (Store)"],
  27. /**
  28. * @constant
  29. * @default
  30. */
  31. OS: ["MSDOS", "Unix", "Macintosh"],
  32. /**
  33. * @constant
  34. * @default
  35. */
  36. RAW_COMPRESSION_TYPE_LOOKUP: {
  37. "Fixed Huffman Coding" : Zlib.RawDeflate.CompressionType.FIXED,
  38. "Dynamic Huffman Coding" : Zlib.RawDeflate.CompressionType.DYNAMIC,
  39. "None (Store)" : Zlib.RawDeflate.CompressionType.NONE,
  40. },
  41. /**
  42. * Raw Deflate operation.
  43. *
  44. * @param {byteArray} input
  45. * @param {Object[]} args
  46. * @returns {byteArray}
  47. */
  48. runRawDeflate: function(input, args) {
  49. var deflate = new Zlib.RawDeflate(input, {
  50. compressionType: Compress.RAW_COMPRESSION_TYPE_LOOKUP[args[0]]
  51. });
  52. return Array.prototype.slice.call(deflate.compress());
  53. },
  54. /**
  55. * @constant
  56. * @default
  57. */
  58. INFLATE_INDEX: 0,
  59. /**
  60. * @constant
  61. * @default
  62. */
  63. INFLATE_BUFFER_SIZE: 0,
  64. /**
  65. * @constant
  66. * @default
  67. */
  68. INFLATE_RESIZE: false,
  69. /**
  70. * @constant
  71. * @default
  72. */
  73. INFLATE_VERIFY: false,
  74. /**
  75. * @constant
  76. * @default
  77. */
  78. RAW_BUFFER_TYPE_LOOKUP: {
  79. "Adaptive" : Zlib.RawInflate.BufferType.ADAPTIVE,
  80. "Block" : Zlib.RawInflate.BufferType.BLOCK,
  81. },
  82. /**
  83. * Raw Inflate operation.
  84. *
  85. * @param {byteArray} input
  86. * @param {Object[]} args
  87. * @returns {byteArray}
  88. */
  89. runRawInflate: function(input, args) {
  90. // Deal with character encoding issues
  91. input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
  92. var inflate = new Zlib.RawInflate(input, {
  93. index: args[0],
  94. bufferSize: args[1],
  95. bufferType: Compress.RAW_BUFFER_TYPE_LOOKUP[args[2]],
  96. resize: args[3],
  97. verify: args[4]
  98. }),
  99. result = Array.prototype.slice.call(inflate.decompress());
  100. // Raw Inflate somethimes messes up and returns nonsense like this:
  101. // ]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]...
  102. // e.g. Input data of [8b, 1d, dc, 44]
  103. // Look for the first two square brackets:
  104. if (result.length > 158 && result[0] === 93 && result[5] === 93) {
  105. // If the first two square brackets are there, check that the others
  106. // are also there. If they are, throw an error. If not, continue.
  107. var valid = false;
  108. for (var i = 0; i < 155; i += 5) {
  109. if (result[i] !== 93) {
  110. valid = true;
  111. }
  112. }
  113. if (!valid) {
  114. throw "Error: Unable to inflate data";
  115. }
  116. }
  117. // Trust me, this is the easiest way...
  118. return result;
  119. },
  120. /**
  121. * @constant
  122. * @default
  123. */
  124. ZLIB_COMPRESSION_TYPE_LOOKUP: {
  125. "Fixed Huffman Coding" : Zlib.Deflate.CompressionType.FIXED,
  126. "Dynamic Huffman Coding" : Zlib.Deflate.CompressionType.DYNAMIC,
  127. "None (Store)" : Zlib.Deflate.CompressionType.NONE,
  128. },
  129. /**
  130. * Zlib Deflate operation.
  131. *
  132. * @param {byteArray} input
  133. * @param {Object[]} args
  134. * @returns {byteArray}
  135. */
  136. runZlibDeflate: function(input, args) {
  137. var deflate = new Zlib.Deflate(input, {
  138. compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]
  139. });
  140. return Array.prototype.slice.call(deflate.compress());
  141. },
  142. /**
  143. * @constant
  144. * @default
  145. */
  146. ZLIB_BUFFER_TYPE_LOOKUP: {
  147. "Adaptive" : Zlib.Inflate.BufferType.ADAPTIVE,
  148. "Block" : Zlib.Inflate.BufferType.BLOCK,
  149. },
  150. /**
  151. * Zlib Inflate operation.
  152. *
  153. * @param {byteArray} input
  154. * @param {Object[]} args
  155. * @returns {byteArray}
  156. */
  157. runZlibInflate: function(input, args) {
  158. // Deal with character encoding issues
  159. input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
  160. var inflate = new Zlib.Inflate(input, {
  161. index: args[0],
  162. bufferSize: args[1],
  163. bufferType: Compress.ZLIB_BUFFER_TYPE_LOOKUP[args[2]],
  164. resize: args[3],
  165. verify: args[4]
  166. });
  167. return Array.prototype.slice.call(inflate.decompress());
  168. },
  169. /**
  170. * @constant
  171. * @default
  172. */
  173. GZIP_CHECKSUM: false,
  174. /**
  175. * Gzip operation.
  176. *
  177. * @param {byteArray} input
  178. * @param {Object[]} args
  179. * @returns {byteArray}
  180. */
  181. runGzip: function(input, args) {
  182. var filename = args[1],
  183. comment = args[2],
  184. options = {
  185. deflateOptions: {
  186. compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]
  187. },
  188. flags: {
  189. fhcrc: args[3]
  190. }
  191. };
  192. if (filename.length) {
  193. options.flags.fname = true;
  194. options.filename = filename;
  195. }
  196. if (comment.length) {
  197. options.flags.fcommenct = true;
  198. options.comment = comment;
  199. }
  200. var gzip = new Zlib.Gzip(input, options);
  201. return Array.prototype.slice.call(gzip.compress());
  202. },
  203. /**
  204. * Gunzip operation.
  205. *
  206. * @param {byteArray} input
  207. * @param {Object[]} args
  208. * @returns {byteArray}
  209. */
  210. runGunzip: function(input, args) {
  211. // Deal with character encoding issues
  212. input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
  213. var gunzip = new Zlib.Gunzip(input);
  214. return Array.prototype.slice.call(gunzip.decompress());
  215. },
  216. /**
  217. * @constant
  218. * @default
  219. */
  220. PKZIP_FILENAME: "file.txt",
  221. /**
  222. * @constant
  223. * @default
  224. */
  225. ZIP_COMPRESSION_METHOD_LOOKUP: {
  226. "Deflate" : Zlib.Zip.CompressionMethod.DEFLATE,
  227. "None (Store)" : Zlib.Zip.CompressionMethod.STORE
  228. },
  229. /**
  230. * @constant
  231. * @default
  232. */
  233. ZIP_OS_LOOKUP: {
  234. "MSDOS" : Zlib.Zip.OperatingSystem.MSDOS,
  235. "Unix" : Zlib.Zip.OperatingSystem.UNIX,
  236. "Macintosh" : Zlib.Zip.OperatingSystem.MACINTOSH
  237. },
  238. /**
  239. * Zip operation.
  240. *
  241. * @param {byteArray} input
  242. * @param {Object[]} args
  243. * @returns {byteArray}
  244. */
  245. runPkzip: function(input, args) {
  246. var password = Utils.strToByteArray(args[2]),
  247. options = {
  248. filename: Utils.strToByteArray(args[0]),
  249. comment: Utils.strToByteArray(args[1]),
  250. compressionMethod: Compress.ZIP_COMPRESSION_METHOD_LOOKUP[args[3]],
  251. os: Compress.ZIP_OS_LOOKUP[args[4]],
  252. deflateOption: {
  253. compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]]
  254. },
  255. },
  256. zip = new Zlib.Zip();
  257. if (password.length)
  258. zip.setPassword(password);
  259. zip.addFile(input, options);
  260. return Array.prototype.slice.call(zip.compress());
  261. },
  262. /**
  263. * @constant
  264. * @default
  265. */
  266. PKUNZIP_VERIFY: false,
  267. /**
  268. * Unzip operation.
  269. *
  270. * @param {byteArray} input
  271. * @param {Object[]} args
  272. * @returns {string}
  273. */
  274. runPkunzip: function(input, args) {
  275. var options = {
  276. password: Utils.strToByteArray(args[0]),
  277. verify: args[1]
  278. },
  279. file = "",
  280. unzip = new Zlib.Unzip(input, options),
  281. filenames = unzip.getFilenames(),
  282. output = "<div style='padding: 5px;'>" + filenames.length + " file(s) found</div>\n";
  283. output += "<div class='panel-group' id='zip-accordion' role='tablist' aria-multiselectable='true'>";
  284. window.uzip = unzip;
  285. for (var i = 0; i < filenames.length; i++) {
  286. file = Utils.byteArrayToUtf8(unzip.decompress(filenames[i]));
  287. output += "<div class='panel panel-default'>" +
  288. "<div class='panel-heading' role='tab' id='heading" + i + "'>" +
  289. "<h4 class='panel-title'>" +
  290. "<a class='collapsed' role='button' data-toggle='collapse' data-parent='#zip-accordion' href='#collapse" + i +
  291. "' aria-expanded='true' aria-controls='collapse" + i + "'>" +
  292. filenames[i] + "<span class='pull-right'>" + file.length.toLocaleString() + " bytes</span></a></h4></div>" +
  293. "<div id='collapse" + i + "' class='panel-collapse collapse' role='tabpanel' aria-labelledby='heading" + i + "'>" +
  294. "<div class='panel-body'>" +
  295. Utils.escapeHtml(file) + "</div></div></div>";
  296. }
  297. return output + "</div>";
  298. },
  299. /**
  300. * Bzip2 Decompress operation.
  301. *
  302. * @param {byteArray} input
  303. * @param {Object[]} args
  304. * @returns {string}
  305. */
  306. runBzip2Decompress: function(input, args) {
  307. var compressed = new Uint8Array(input),
  308. bzip2Reader,
  309. plain = "";
  310. bzip2Reader = bzip2.array(compressed);
  311. plain = bzip2.simple(bzip2Reader);
  312. return plain;
  313. },
  314. /**
  315. * @constant
  316. * @default
  317. */
  318. TAR_FILENAME: "file.txt",
  319. /**
  320. * Tar unpack operation.
  321. *
  322. * @author tlwr [toby@toby.codes]
  323. *
  324. * @param {byteArray} input
  325. * @param {Object[]} args
  326. * @returns {byteArray}
  327. */
  328. runTar: function(input, args) {
  329. var zeroFillBytes = function(string, bytes) {
  330. var paddedString = new Array(bytes);
  331. paddedString.fill(0);
  332. string = string.toString().slice(0, bytes);
  333. Array.prototype.map.call(string, function(chr, i) {
  334. paddedString[i] = chr;
  335. });
  336. return paddedString;
  337. };
  338. var padLeft = function(string, length, padChar) {
  339. while(string.length < length) {
  340. string = padChar + string;
  341. }
  342. return string;
  343. };
  344. var Tarball = function() {
  345. this.bytes = new Array(512);
  346. this.position = 0;
  347. };
  348. Tarball.prototype.addEmptyBlock = function() {
  349. var filler = new Array(512);
  350. filler.fill(0);
  351. this.bytes = this.bytes.concat(filler);
  352. };
  353. Tarball.prototype.writeBytes = function(bytes) {
  354. var self = this;
  355. if(this.position + bytes.length > this.bytes.length) {
  356. this.addEmptyBlock();
  357. }
  358. Array.prototype.forEach.call(bytes, function(b, i) {
  359. if(typeof b.charCodeAt !== "undefined") {
  360. b = b.charCodeAt();
  361. }
  362. self.bytes[self.position] = b;
  363. self.position += 1;
  364. });
  365. };
  366. Tarball.prototype.writeEndBlocks = function() {
  367. var numEmptyBlocks = 2;
  368. for(var i = 0; i < numEmptyBlocks; i++) {
  369. this.addEmptyBlock();
  370. }
  371. };
  372. var fileSize = padLeft(input.length.toString(8), 11, "0");
  373. var currentUnixTimestamp = Math.floor(Date.now() / 1000);
  374. var lastModTime = padLeft(currentUnixTimestamp.toString(8), 11, "0");
  375. var file = {
  376. fileName: zeroFillBytes(args[0], 100),
  377. fileMode: zeroFillBytes("0000664", 8),
  378. ownerUID: zeroFillBytes("0", 8),
  379. ownerGID: zeroFillBytes("0", 8),
  380. size: zeroFillBytes(fileSize, 12),
  381. lastModTime: zeroFillBytes(lastModTime, 12),
  382. checksum: " ",
  383. type: "0",
  384. linkedFileName: zeroFillBytes("", 100),
  385. USTARFormat: zeroFillBytes("ustar", 6),
  386. version: "00",
  387. ownerUserName: zeroFillBytes("", 32),
  388. ownerGroupName: zeroFillBytes("", 32),
  389. deviceMajor: zeroFillBytes("", 8),
  390. deviceMinor: zeroFillBytes("", 8),
  391. fileNamePrefix: zeroFillBytes("", 155),
  392. };
  393. var checksum = 0;
  394. for(var key in file) {
  395. var bytes = file[key];
  396. Array.prototype.forEach.call(bytes, function(b) {
  397. if(typeof b.charCodeAt !== "undefined") {
  398. checksum += b.charCodeAt();
  399. } else {
  400. checksum += b;
  401. }
  402. });
  403. }
  404. checksum = zeroFillBytes(padLeft(checksum.toString(8), 7, "0"), 8);
  405. file.checksum = checksum;
  406. var tarball = new Tarball();
  407. tarball.writeBytes(file.fileName);
  408. tarball.writeBytes(file.fileMode);
  409. tarball.writeBytes(file.ownerUID);
  410. tarball.writeBytes(file.ownerGID);
  411. tarball.writeBytes(file.size);
  412. tarball.writeBytes(file.lastModTime);
  413. tarball.writeBytes(file.checksum);
  414. tarball.writeBytes(file.type);
  415. tarball.writeBytes(file.linkedFileName);
  416. tarball.writeBytes(file.USTARFormat);
  417. tarball.writeBytes(file.version);
  418. tarball.writeBytes(file.ownerUserName);
  419. tarball.writeBytes(file.ownerGroupName);
  420. tarball.writeBytes(file.deviceMajor);
  421. tarball.writeBytes(file.deviceMinor);
  422. tarball.writeBytes(file.fileNamePrefix);
  423. tarball.writeBytes(zeroFillBytes("", 12));
  424. tarball.writeBytes(input);
  425. tarball.writeEndBlocks();
  426. return tarball.bytes;
  427. },
  428. /**
  429. * Untar unpack operation.
  430. *
  431. * @author tlwr [toby@toby.codes]
  432. *
  433. * @param {byteArray} input
  434. * @param {Object[]} args
  435. * @returns {html}
  436. */
  437. runUntar: function(input, args) {
  438. var Stream = function(input) {
  439. this.bytes = input;
  440. this.position = 0;
  441. };
  442. Stream.prototype.readString = function(numBytes) {
  443. var result = "";
  444. for(var i = this.position; i < this.position + numBytes; i++) {
  445. var currentByte = this.bytes[i];
  446. if(currentByte === 0) break;
  447. result += String.fromCharCode(currentByte);
  448. }
  449. this.position += numBytes;
  450. return result;
  451. };
  452. Stream.prototype.readInt = function(numBytes, base) {
  453. var string = this.readString(numBytes);
  454. return parseInt(string, base);
  455. };
  456. Stream.prototype.hasMore = function() {
  457. return this.position < this.bytes.length;
  458. };
  459. var stream = new Stream(input),
  460. files = [];
  461. while(stream.hasMore()) {
  462. var dataPosition = stream.position + 512;
  463. var file = {
  464. fileName: stream.readString(100),
  465. fileMode: stream.readString(8),
  466. ownerUID: stream.readString(8),
  467. ownerGID: stream.readString(8),
  468. size: parseInt(stream.readString(12), 8), // Octal
  469. lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal
  470. checksum: stream.readString(8),
  471. type: stream.readString(1),
  472. linkedFileName: stream.readString(100),
  473. USTARFormat: stream.readString(6).indexOf("ustar") >= 0,
  474. };
  475. if(file.USTARFormat) {
  476. file.version = stream.readString(2);
  477. file.ownerUserName = stream.readString(32);
  478. file.ownerGroupName = stream.readString(32);
  479. file.deviceMajor = stream.readString(8);
  480. file.deviceMinor = stream.readString(8);
  481. file.filenamePrefix = stream.readString(155);
  482. }
  483. stream.position = dataPosition;
  484. if(file.type === "0") {
  485. // File
  486. files.push(file);
  487. var endPosition = stream.position + file.size;
  488. if(file.size % 512 !== 0) {
  489. endPosition += 512 - (file.size % 512);
  490. }
  491. file.contents = "";
  492. while(stream.position < endPosition) {
  493. file.contents += stream.readString(512);
  494. }
  495. } else if(file.type === "5") {
  496. // Directory
  497. files.push(file);
  498. } else {
  499. // Symlink or empty bytes
  500. }
  501. }
  502. return Utils.displayFilesAsHTML(files);
  503. },
  504. };