From ff9c68db5648df7c0ff9e60e079ae1c85c7682d0 Mon Sep 17 00:00:00 2001 From: j433866 Date: Wed, 1 May 2019 17:08:36 +0100 Subject: [PATCH] Update handling of bake errors. Add preview thumbnail for image input. --- src/core/ChefWorker.js | 33 +++++--- src/web/App.mjs | 3 + src/web/InputWaiter.mjs | 35 +++++++-- src/web/InputWorker.mjs | 27 ++++--- src/web/OutputWaiter.mjs | 162 +++++++++++++++++++++------------------ src/web/WorkerWaiter.mjs | 49 ++++++++---- 6 files changed, 193 insertions(+), 116 deletions(-) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index adbca918..995503c8 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -95,7 +95,7 @@ async function bake(data) { self.loadRequiredModules(data.recipeConfig); try { - self.inputNum = data.inputNum; + self.inputNum = parseInt(data.inputNum, 10); const response = await self.chef.bake( data.input, // The user's input data.recipeConfig, // The configuration of the recipe @@ -104,13 +104,25 @@ async function bake(data) { data.step // Whether or not to take one step or execute the whole recipe ); if (typeof response.result === "string") { - self.postMessage({ - action: "bakeComplete", - data: Object.assign(response, { - id: data.id, - inputNum: data.inputNum - }) - }); + if (response.progress !== data.progress + data.recipeConfig.length) { + self.postMessage({ + action: "bakeError", + data: { + error: response.result, + id: data.id, + inputNum: data.inputNum, + progress: response.progress + } + }); + } else { + self.postMessage({ + action: "bakeComplete", + data: Object.assign(response, { + id: data.id, + inputNum: data.inputNum + }) + }); + } } else { self.postMessage({ action: "bakeComplete", @@ -123,10 +135,11 @@ async function bake(data) { } catch (err) { self.postMessage({ action: "bakeError", - data: Object.assign(err, { + data: { + error: err, id: data.id, inputNum: data.inputNum - }) + } }); } self.inputNum = -1; diff --git a/src/web/App.mjs b/src/web/App.mjs index a6b21627..534e7983 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -79,6 +79,9 @@ class App { if (!this.workerLoaded || !this.appLoaded || !document.getElementById("loader-wrapper")) return; + // Bake initial input + this.getAllInput(); + // Trigger CSS animations to remove preloader document.body.classList.add("loaded"); diff --git a/src/web/InputWaiter.mjs b/src/web/InputWaiter.mjs index aa0d6f25..08cfb986 100644 --- a/src/web/InputWaiter.mjs +++ b/src/web/InputWaiter.mjs @@ -20,8 +20,8 @@ class InputWaiter { /** * InputWaiter constructor. * - * @param {App} app - The main view object for CyberChef - * @param {Manager} manager- The CyberChef event manager. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. */ constructor(app, manager) { this.app = app; @@ -341,7 +341,6 @@ class InputWaiter { this.setInputInfo(inputData.input.length, lines); } else { this.setFile(inputData); - // show file info here } if (!silent) window.dispatchEvent(this.manager.statechange); @@ -372,6 +371,15 @@ class InputWaiter { this.displayFilePreview(inputData); } + + /** + * Reset the input thumbnail to the default icon + */ + resetFileThumb() { + const fileThumb = document.getElementById("input-file-thumbnail"); + fileThumb.src = require("./static/images/file-128x128.png"); + } + /** * Shows a chunk of the file in the input behind the file overlay * @@ -382,11 +390,28 @@ class InputWaiter { displayFilePreview(inputData) { const activeTab = this.getActiveTab(), input = inputData.input, - inputText = document.getElementById("input-text"); + inputText = document.getElementById("input-text"), + fileThumb = document.getElementById("input-file-thumbnail"); if (inputData.inputNum !== activeTab) return; inputText.style.overflow = "hidden"; inputText.classList.add("blur"); - inputText.value = Utils.printable(Utils.arrayBufferToStr(input)); + inputText.value = Utils.printable(Utils.arrayBufferToStr(input.slice(0, 4096))); + + // Display image in thumbnail if we want + if (this.app.options.imagePreview) { + const inputArr = new Uint8Array(input), + type = isImage(inputArr); + if (type && type !== "image/tiff" && inputArr.byteLength <= 512000) { + const blob = new Blob([inputArr], {type: type}), + url = URL.createObjectURL(blob); + fileThumb.src = url; + } else { + this.resetFileThumb(); + } + } else { + this.resetFileThumb(); + } + } /** diff --git a/src/web/InputWorker.mjs b/src/web/InputWorker.mjs index 945dcef6..d02d3cf7 100644 --- a/src/web/InputWorker.mjs +++ b/src/web/InputWorker.mjs @@ -312,20 +312,29 @@ self.setInput = function(inputData) { input: inputVal }; if (typeof inputVal !== "string") { - inputObj.input = inputVal.fileBuffer.slice(0, 4096); + const fileSlice = inputVal.fileBuffer.slice(0, 512001); + inputObj.input = fileSlice; inputObj.name = inputVal.name; inputObj.size = inputVal.size; inputObj.type = inputVal.type; inputObj.progress = input.progress; - } - self.postMessage({ - action: "setInput", - data: { - inputObj: inputObj, - silent: silent - } - }); + self.postMessage({ + action: "setInput", + data: { + inputObj: inputObj, + silent: silent + } + }, [fileSlice]); + } else { + self.postMessage({ + action: "setInput", + data: { + inputObj: inputObj, + silent: silent + } + }); + } self.getInputProgress(inputNum); }; diff --git a/src/web/OutputWaiter.mjs b/src/web/OutputWaiter.mjs index c0f1ba0e..9c4c320b 100755 --- a/src/web/OutputWaiter.mjs +++ b/src/web/OutputWaiter.mjs @@ -132,15 +132,17 @@ class OutputWaiter { * * @param {Error} error * @param {number} inputNum + * @param {number} [progress=0] */ - updateOutputError(error, inputNum) { + updateOutputError(error, inputNum, progress=0) { if (this.getOutput(inputNum) === -1) return; this.outputs[inputNum].error = error; + this.outputs[inputNum].progress = progress; + this.updateOutputStatus("error", inputNum); // call handle error here // or make the error handling part of set() - this.set(inputNum); } /** @@ -153,6 +155,11 @@ class OutputWaiter { if (this.getOutput(inputNum) === -1) return; this.outputs[inputNum].status = status; + if (status !== "error") { + delete this.outputs[inputNum].error; + delete this.outputs[inputNum].progress; + } + this.set(inputNum); } @@ -186,9 +193,10 @@ class OutputWaiter { set(inputNum) { const output = this.outputs[inputNum]; if (output === undefined || output === null) return; - if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); + if (inputNum !== this.getActiveTab()) return; + const outputText = document.getElementById("output-text"); const outputHtml = document.getElementById("output-html"); const outputFile = document.getElementById("output-file"); @@ -204,11 +212,35 @@ class OutputWaiter { this.manager.controls.hideStaleIndicator(); } - if (output.status === "inactive") { - // An output is inactive when it has been created but has not been baked at all - // show a blank here - if (inputNum === this.getActiveTab()) { - this.toggleLoader(false); + this.manager.recipe.updateBreakpointIndicator(false); + + if (output.status === "pending" || output.status === "baking") { + // show the loader and the status message if it's being shown + // otherwise don't do anything + this.toggleLoader(true); + document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; + + } else if (output.status === "error") { + // style the tab if it's being shown + // run app.handleError() + this.toggleLoader(false); + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; + + outputText.value = output.error; + outputHtml.innerHTML = ""; + + this.manager.recipe.updateBreakpointIndicator(output.progress); + } else if (output.status === "baked" || output.status === "inactive") { + this.displayTabInfo(inputNum); + this.toggleLoader(false); + this.closeFile(); + let scriptElements, lines, length; + + if (output.data === null) { outputText.style.display = "block"; outputHtml.style.display = "none"; outputFile.style.display = "none"; @@ -218,83 +250,63 @@ class OutputWaiter { outputText.value = ""; outputHtml.innerHTML = ""; - } - } else if (output.status === "pending" || output.status === "baking") { - // show the loader and the status message if it's being shown - // otherwise don't do anything - if (inputNum === this.getActiveTab()) { - this.toggleLoader(true); - document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; + lines = 0; + length = 0; + return; } - } else if (output.status === "error") { - // style the tab if it's being shown - // run app.handleError() - if (inputNum === this.getActiveTab()) { - this.toggleLoader(false); - } - } else if (output.status === "baked") { - // Display the output if it's the active tab - this.displayTabInfo(inputNum); - if (inputNum === this.getActiveTab()) { - this.toggleLoader(false); - this.closeFile(); - let scriptElements, lines, length; - const duration = output.data.duration; + switch (output.data.type) { + case "html": + outputText.style.display = "none"; + outputHtml.style.display = "block"; + outputFile.style.display = "none"; + outputHighlighter.style.display = "none"; + inputHighlighter.style.display = "none"; - switch (output.data.type) { - case "html": - outputText.style.display = "none"; - outputHtml.style.display = "block"; - outputFile.style.display = "none"; - outputHighlighter.style.display = "none"; - inputHighlighter.style.display = "none"; + outputText.value = ""; + outputHtml.innerHTML = output.data.result; - outputText.value = ""; - outputHtml.innerHTML = output.data.result; - - // Execute script sections - scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - log.error(err); - } + // Execute script sections + scriptElements = outputHtml.querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); } - length = output.data.dish.value.length; + } + length = output.data.dish.value.length; - break; - case "ArrayBuffer": - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; + break; + case "ArrayBuffer": + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = ""; + outputText.value = ""; + outputHtml.innerHTML = ""; - length = output.data.result.length; - this.setFile(output.data.result); - break; - case "string": - default: - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; + length = output.data.result.length; + this.setFile(output.data.result); + break; + case "string": + default: + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "block"; + inputHighlighter.display = "block"; - outputText.value = Utils.printable(output.data.result, true); - outputHtml.innerHTML = ""; + outputText.value = Utils.printable(output.data.result, true); + outputHtml.innerHTML = ""; - lines = output.data.result.count("\n") + 1; - length = output.data.result.length; - break; - } - this.setOutputInfo(length, lines, duration); - this.backgroundMagic(); + lines = output.data.result.count("\n") + 1; + length = output.data.result.length; + break; } + this.setOutputInfo(length, lines, output.data.duration); + this.backgroundMagic(); } } @@ -555,7 +567,7 @@ class OutputWaiter { */ goToTab() { const tabNum = parseInt(window.prompt("Enter tab number:", this.getActiveTab().toString()), 10); - if (this.getOutputIndex(tabNum) >= 0) { + if (this.getOutput(tabNum) !== undefined) { this.changeTab(tabNum, this.app.options.syncTabs); } } diff --git a/src/web/WorkerWaiter.mjs b/src/web/WorkerWaiter.mjs index aeab8e8d..f3c5e31d 100644 --- a/src/web/WorkerWaiter.mjs +++ b/src/web/WorkerWaiter.mjs @@ -137,24 +137,13 @@ class WorkerWaiter { case "bakeComplete": log.debug(`Bake ${inputNum} complete.`); this.updateOutput(r.data, r.data.inputNum); - - if (this.inputs.length > 0) { - this.bakeNextInput(this.chefWorkers.indexOf(currentWorker)); - } else { - // The ChefWorker is no longer needed - log.debug("No more inputs to bake. Closing ChefWorker."); - currentWorker.active = false; - this.removeChefWorker(currentWorker); - - const progress = this.getBakeProgress(); - if (progress.total === progress.baked) { - this.bakingComplete(); - } - } + this.workerFinished(currentWorker); break; - case "BakeError": - this.manager.output.updateOutputError(r.data, inputNum); + case "bakeError": + if (!r.data.hasOwnProperty("progress")) this.app.handleError(r.data.error); + this.manager.output.updateOutputError(r.data.error, inputNum, r.data.progress); + this.workerFinished(currentWorker); // do more here break; case "dishReturned": @@ -196,7 +185,6 @@ class WorkerWaiter { * @param {number} inputNum */ updateOutput(data, inputNum) { - this.manager.output.updateOutputValue(data, inputNum); this.manager.output.updateOutputStatus("baked", inputNum); @@ -242,14 +230,40 @@ class WorkerWaiter { */ cancelBake() { for (let i = this.chefWorkers.length - 1; i >= 0; i--) { + const inputNum = this.chefWorkers[i].inputNum; this.removeChefWorker(this.chefWorkers[i]); + this.manager.output.updateOutputStatus("inactive", inputNum); } this.setBakingStatus(false); + + for (let i = 0; i < this.inputs.length; i++) { + this.manager.output.updateOutputStatus("inactive", this.inputs[i].inputNum); + } + this.inputs = []; this.totalOutputs = 0; this.manager.controls.showStaleIndicator(); } + /** + * Handle a worker completing baking + */ + workerFinished(workerObj) { + if (this.inputs.length > 0) { + this.bakeNextInput(this.chefWorkers.indexOf(workerObj)); + } else { + // The ChefWorker is no longer needed + log.debug("No more inputs to bake. Closing ChefWorker."); + workerObj.active = false; + this.removeChefWorker(workerObj); + + const progress = this.getBakeProgress(); + if (progress.total === progress.baked) { + this.bakingComplete(); + } + } + } + /** * Handler for completed bakes */ @@ -371,6 +385,7 @@ class WorkerWaiter { */ queueInput(inputData) { this.manager.output.updateOutputStatus("pending", inputData.inputNum); + this.manager.output.updateOutputMessage(`Input ${inputData.inputNum} has not been baked yet.`); this.totalOutputs++; this.inputs.push(inputData);