diff --git a/.eslintrc.json b/.eslintrc.json
index d5e4e768..7dcb705c 100755
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -102,6 +102,7 @@
"$": false,
"jQuery": false,
"log": false,
+ "app": false,
"COMPILE_TIME": false,
"COMPILE_MSG": false,
diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs
index f70e2941..8e69b020 100755
--- a/src/core/Utils.mjs
+++ b/src/core/Utils.mjs
@@ -832,8 +832,9 @@ class Utils {
const buff = await Utils.readFile(file);
const blob = new Blob(
[buff],
- {type: "octet/stream"}
+ {type: file.type || "octet/stream"}
);
+ const blobURL = URL.createObjectURL(blob);
const html = `
@@ -1163,6 +1173,21 @@ String.prototype.count = function(chr) {
};
+/**
+ * Wrapper for self.sendStatusMessage to handle different environments.
+ *
+ * @param {string} msg
+ */
+export function sendStatusMessage(msg) {
+ if (ENVIRONMENT_IS_WORKER())
+ self.sendStatusMessage(msg);
+ else if (ENVIRONMENT_IS_WEB())
+ app.alert(msg, 10000);
+ else if (ENVIRONMENT_IS_NODE())
+ log.debug(msg);
+}
+
+
/*
* Polyfills
*/
diff --git a/src/core/lib/FileSignatures.mjs b/src/core/lib/FileSignatures.mjs
index 36e6818e..61e37b88 100644
--- a/src/core/lib/FileSignatures.mjs
+++ b/src/core/lib/FileSignatures.mjs
@@ -1518,26 +1518,26 @@ export function extractELF(bytes, offset) {
}
+// Construct required Huffman Tables
+const fixedLiteralTableLengths = new Array(288);
+for (let i = 0; i < fixedLiteralTableLengths.length; i++) {
+ fixedLiteralTableLengths[i] =
+ (i <= 143) ? 8 :
+ (i <= 255) ? 9 :
+ (i <= 279) ? 7 :
+ 8;
+}
+const fixedLiteralTable = buildHuffmanTable(fixedLiteralTableLengths);
+const fixedDistanceTableLengths = new Array(30).fill(5);
+const fixedDistanceTable = buildHuffmanTable(fixedDistanceTableLengths);
+const huffmanOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+
/**
* Steps through a DEFLATE stream
*
* @param {Stream} stream
*/
function parseDEFLATE(stream) {
- // Construct required Huffman Tables
- const fixedLiteralTableLengths = new Uint8Array(288);
- for (let i = 0; i < fixedLiteralTableLengths.length; i++) {
- fixedLiteralTableLengths[i] =
- (i <= 143) ? 8 :
- (i <= 255) ? 9 :
- (i <= 279) ? 7 :
- 8;
- }
- const fixedLiteralTable = buildHuffmanTable(fixedLiteralTableLengths);
- const fixedDistanceTableLengths = new Uint8Array(30).fill(5);
- const fixedDistanceTable = buildHuffmanTable(fixedDistanceTableLengths);
- const huffmanOrder = new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
-
// Parse DEFLATE data
let finalBlock = 0;
@@ -1619,6 +1619,14 @@ function parseDEFLATE(stream) {
}
+// Static length tables
+const lengthExtraTable = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0
+];
+const distanceExtraTable = [
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
+];
+
/**
* Parses a Huffman Block given the literal and distance tables
*
@@ -1627,20 +1635,18 @@ function parseDEFLATE(stream) {
* @param {Uint32Array} distTab
*/
function parseHuffmanBlock(stream, litTab, distTab) {
- const lengthExtraTable = new Uint8Array([
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0
- ]);
- const distanceExtraTable = new Uint8Array([
- 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
- ]);
-
let code;
+ let loops = 0;
while ((code = readHuffmanCode(stream, litTab))) {
// console.log("Code: " + code + " (" + Utils.chr(code) + ") " + Utils.bin(code));
// End of block
if (code === 256) break;
+ // Detect probably infinite loops
+ if (++loops > 10000)
+ throw new Error("Caught in probable infinite loop while parsing Huffman Block");
+
// Literal
if (code < 256) continue;
@@ -1657,7 +1663,7 @@ function parseHuffmanBlock(stream, litTab, distTab) {
/**
* Builds a Huffman table given the relevant code lengths
*
- * @param {Uint8Array} lengths
+ * @param {Array} lengths
* @returns {Array} result
* @returns {Uint32Array} result.table
* @returns {number} result.maxCodeLength
diff --git a/src/core/lib/FileType.mjs b/src/core/lib/FileType.mjs
index e5d990d9..e961a76f 100644
--- a/src/core/lib/FileType.mjs
+++ b/src/core/lib/FileType.mjs
@@ -7,6 +7,7 @@
*
*/
import {FILE_SIGNATURES} from "./FileSignatures";
+import {sendStatusMessage} from "../Utils";
/**
@@ -148,6 +149,7 @@ export function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {
let pos = 0;
while ((pos = locatePotentialSig(buf, sig, pos)) >= 0) {
if (bytesMatch(sig, buf, pos)) {
+ sendStatusMessage(`Found potential signature for ${filetype.name} at pos ${pos}`);
foundFiles.push({
offset: pos,
fileDetails: filetype
@@ -249,9 +251,12 @@ export function isImage(buf) {
*/
export function extractFile(bytes, fileDetail, offset) {
if (fileDetail.extractor) {
+ sendStatusMessage(`Attempting to extract ${fileDetail.name} at pos ${offset}...`);
const fileData = fileDetail.extractor(bytes, offset);
const ext = fileDetail.extension.split(",")[0];
- return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`);
+ return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`, {
+ type: fileDetail.mime
+ });
}
throw new Error(`No extraction algorithm available for "${fileDetail.mime}" files`);
diff --git a/src/core/operations/ExtractFiles.mjs b/src/core/operations/ExtractFiles.mjs
index d2b87990..b9b260bb 100644
--- a/src/core/operations/ExtractFiles.mjs
+++ b/src/core/operations/ExtractFiles.mjs
@@ -62,12 +62,13 @@ class ExtractFiles extends Operation {
// Extract each file that we support
const files = [];
+ const errors = [];
detectedFiles.forEach(detectedFile => {
try {
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
} catch (err) {
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
- throw new OperationError(
+ errors.push(
`Error while attempting to extract ${detectedFile.fileDetails.name} ` +
`at offset ${detectedFile.offset}:\n` +
`${err.message}`
@@ -76,9 +77,14 @@ class ExtractFiles extends Operation {
}
});
+ if (errors.length) {
+ throw new OperationError(errors.join("\n\n"));
+ }
+
return files;
}
+
/**
* Displays the files in HTML for web apps.
*
diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs
index 30cb4943..5fa0e8c1 100755
--- a/src/web/Manager.mjs
+++ b/src/web/Manager.mjs
@@ -173,6 +173,7 @@ class Manager {
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
+ this.addDynamicListener(".extract-file,.extract-file i", "click", this.output.extractFileClick, this.output);
// Options
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
diff --git a/src/web/OutputWaiter.mjs b/src/web/OutputWaiter.mjs
index 2d93507c..0a10b8b2 100755
--- a/src/web/OutputWaiter.mjs
+++ b/src/web/OutputWaiter.mjs
@@ -494,6 +494,24 @@ class OutputWaiter {
magicButton.setAttribute("data-original-title", "Magic!");
}
+
+ /**
+ * Handler for extract file events.
+ *
+ * @param {Event} e
+ */
+ async extractFileClick(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const el = e.target.nodeName === "I" ? e.target.parentNode : e.target;
+ const blobURL = el.getAttribute("blob-url");
+ const fileName = el.getAttribute("file-name");
+
+ const blob = await fetch(blobURL).then(r => r.blob());
+ this.manager.input.loadFile(new File([blob], fileName, {type: blob.type}));
+ }
+
}
export default OutputWaiter;
diff --git a/src/web/html/index.html b/src/web/html/index.html
index 74eb0ed8..302355d9 100755
--- a/src/web/html/index.html
+++ b/src/web/html/index.html
@@ -271,7 +271,7 @@
content_copy