Selaa lähdekoodia

Added 'Move to input' button to output file list. Improved zlib extraction efficiency.

n1474335 6 vuotta sitten
vanhempi
commit
84d31c1d59

+ 1 - 0
.eslintrc.json

@@ -102,6 +102,7 @@
         "$": false,
         "jQuery": false,
         "log": false,
+        "app": false,
 
         "COMPILE_TIME": false,
         "COMPILE_MSG": false,

+ 28 - 3
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 = `<div class='card' style='white-space: normal;'>
                     <div class='card-header' id='heading${i}'>
@@ -848,10 +849,19 @@ class Utils {
                             <span class='float-right' style="margin-top: -3px">
                                 ${file.size.toLocaleString()} bytes
                                 <a title="Download ${Utils.escapeHtml(file.name)}"
-                                    href='${URL.createObjectURL(blob)}'
-                                    download='${Utils.escapeHtml(file.name)}'>
+                                    href="${blobURL}"
+                                    download="${Utils.escapeHtml(file.name)}"
+                                    data-toggle="tooltip">
                                     <i class="material-icons" style="vertical-align: bottom">save</i>
                                 </a>
+                                <a title="Move to input"
+                                    href="#"
+                                    blob-url="${blobURL}"
+                                    file-name="${Utils.escapeHtml(file.name)}"
+                                    class="extract-file"
+                                    data-toggle="tooltip">
+                                    <i class="material-icons" style="vertical-align: bottom">open_in_browser</i>
+                                </a>
                             </span>
                         </h6>
                     </div>
@@ -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
  */

+ 28 - 22
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

+ 6 - 1
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`);

+ 7 - 1
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.
      *

+ 1 - 0
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));

+ 18 - 0
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;

+ 1 - 1
src/web/html/index.html

@@ -271,7 +271,7 @@
                                     <i class="material-icons">content_copy</i>
                                 </button>
                                 <button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Move output to input">
-                                    <i class="material-icons">loop</i>
+                                    <i class="material-icons">open_in_browser</i>
                                 </button>
                                 <button type="button" class="btn btn-primary bmd-btn-icon" id="undo-switch" data-toggle="tooltip" title="Undo" disabled="disabled">
                                     <i class="material-icons">undo</i>

+ 4 - 0
src/web/stylesheets/components/_pane.css

@@ -91,3 +91,7 @@
     padding-right: 6px;
     padding-left: 6px;
 }
+
+#files .card-header .float-right a:hover {
+    text-decoration: none;
+}