diff --git a/src/core/Chef.js b/src/core/Chef.js index 9fa23d22..7da8ab59 100755 --- a/src/core/Chef.js +++ b/src/core/Chef.js @@ -123,4 +123,38 @@ Chef.prototype.silentBake = function(recipeConfig) { return new Date().getTime() - startTime; }; + +/** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @returns {Object} + */ +Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) { + const recipe = new Recipe(recipeConfig); + const highlights = recipe.generateHighlightList(); + + if (!highlights) return false; + + for (let i = 0; i < highlights.length; i++) { + // Remove multiple highlights before processing again + pos = [pos[0]]; + + const func = direction === "forward" ? highlights[i].f : highlights[i].b; + + if (typeof func == "function") { + pos = func(pos, highlights[i].args); + } + } + + return { + pos: pos, + direction: direction + }; +}; + export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index 56e607b9..22b5b81c 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -53,6 +53,13 @@ self.addEventListener("message", function(e) { // imported into an inline worker. self.docURL = e.data.data; break; + case "highlight": + calculateHighlights( + e.data.data.recipeConfig, + e.data.data.direction, + e.data.data.pos + ); + break; default: break; } @@ -121,6 +128,25 @@ function loadRequiredModules(recipeConfig) { } +/** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ +function calculateHighlights(recipeConfig, direction, pos) { + pos = self.chef.calculateHighlights(recipeConfig, direction, pos); + + self.postMessage({ + action: "highlightsCalculated", + data: pos + }); +} + + /** * Send status update to the app. * diff --git a/src/core/Operation.js b/src/core/Operation.js index 9a2b92af..d4ee21d3 100755 --- a/src/core/Operation.js +++ b/src/core/Operation.js @@ -54,6 +54,14 @@ Operation.prototype._parseConfig = function(operationConfig) { const ingredient = new Ingredient(ingredientConfig); this.addIngredient(ingredient); } + + if (this.highlight === "func") { + this.highlight = OpModules[this.module][`${this.name}-highlight`]; + } + + if (this.highlightReverse === "func") { + this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`]; + } }; diff --git a/src/core/Recipe.js b/src/core/Recipe.js index df158432..d703ac1e 100755 --- a/src/core/Recipe.js +++ b/src/core/Recipe.js @@ -215,4 +215,37 @@ Recipe.prototype.fromString = function(recipeStr) { this._parseConfig(recipeConfig); }; + +/** + * Generates a list of all the highlight functions assigned to operations in the recipe, if the + * entire recipe supports highlighting. + * + * @returns {Object[]} highlights + * @returns {function} highlights[].f + * @returns {function} highlights[].b + * @returns {Object[]} highlights[].args + */ +Recipe.prototype.generateHighlightList = function() { + const highlights = []; + + for (let i = 0; i < this.opList.length; i++) { + let op = this.opList[i]; + if (op.isDisabled()) continue; + + // If any breakpoints are set, do not attempt to highlight + if (op.isBreakpoint()) return false; + + // If any of the operations do not support highlighting, fail immediately. + if (op.highlight === false || op.highlight === undefined) return false; + + highlights.push({ + f: op.highlight, + b: op.highlightReverse, + args: op.getIngValues() + }); + } + + return highlights; +}; + export default Recipe; diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 38c5a715..d8b75ca5 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -184,8 +184,8 @@ const OperationConfig = { "From Base64": { module: "Default", description: "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello", - highlight: Base64.highlightFrom, - highlightReverse: Base64.highlightTo, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "byteArray", args: [ @@ -195,7 +195,7 @@ const OperationConfig = { value: Base64.ALPHABET_OPTIONS }, { - name: "Remove non‑alphabet chars", + name: "Remove non-alphabet chars", type: "boolean", value: Base64.REMOVE_NON_ALPH_CHARS } @@ -204,8 +204,8 @@ const OperationConfig = { "To Base64": { module: "Default", description: "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation encodes data in an ASCII Base64 string.

e.g. hello becomes aGVsbG8=", - highlight: Base64.highlightTo, - highlightReverse: Base64.highlightFrom, + highlight: "func", + highlightReverse: "func", inputType: "byteArray", outputType: "string", args: [ @@ -228,7 +228,7 @@ const OperationConfig = { value: Base58.ALPHABET_OPTIONS }, { - name: "Remove non‑alphabet chars", + name: "Remove non-alphabet chars", type: "boolean", value: Base58.REMOVE_NON_ALPH_CHARS } @@ -259,7 +259,7 @@ const OperationConfig = { value: Base64.BASE32_ALPHABET }, { - name: "Remove non‑alphabet chars", + name: "Remove non-alphabet chars", type: "boolean", value: Base64.REMOVE_NON_ALPH_CHARS } @@ -446,8 +446,8 @@ const OperationConfig = { "From Hex": { module: "Default", description: "Converts a hexadecimal byte string back into its raw value.

e.g. ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a becomes the UTF-8 encoded string Γειά σου", - highlight: ByteRepr.highlightFrom, - highlightReverse: ByteRepr.highlightTo, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "byteArray", args: [ @@ -461,8 +461,8 @@ const OperationConfig = { "To Hex": { module: "Default", description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a", - highlight: ByteRepr.highlightTo, - highlightReverse: ByteRepr.highlightFrom, + highlight: "func", + highlightReverse: "func", inputType: "byteArray", outputType: "string", args: [ @@ -506,8 +506,8 @@ const OperationConfig = { "From Charcode": { module: "Default", description: "Converts unicode character codes back into text.

e.g. 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 becomes Γειά σου", - highlight: ByteRepr.highlightFrom, - highlightReverse: ByteRepr.highlightTo, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "byteArray", args: [ @@ -527,8 +527,8 @@ const OperationConfig = { "To Charcode": { module: "Default", description: "Converts text to its unicode character code equivalent.

e.g. Γειά σου becomes 0393 03b5 03b9 03ac 20 03c3 03bf 03c5", - highlight: ByteRepr.highlightTo, - highlightReverse: ByteRepr.highlightFrom, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "string", args: [ @@ -547,8 +547,8 @@ const OperationConfig = { "From Binary": { module: "Default", description: "Converts a binary string back into its raw form.

e.g. 01001000 01101001 becomes Hi", - highlight: ByteRepr.highlightFromBinary, - highlightReverse: ByteRepr.highlightToBinary, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "byteArray", args: [ @@ -562,8 +562,8 @@ const OperationConfig = { "To Binary": { module: "Default", description: "Displays the input data as a binary string.

e.g. Hi becomes 01001000 01101001", - highlight: ByteRepr.highlightToBinary, - highlightReverse: ByteRepr.highlightFromBinary, + highlight: "func", + highlightReverse: "func", inputType: "byteArray", outputType: "string", args: [ @@ -603,8 +603,8 @@ const OperationConfig = { "From Hexdump": { module: "Default", description: "Attempts to convert a hexdump back into raw data. This operation supports many different hexdump variations, but probably not all. Make sure you verify that the data it gives you is correct before continuing analysis.", - highlight: Hexdump.highlightFrom, - highlightReverse: Hexdump.highlightTo, + highlight: "func", + highlightReverse: "func", inputType: "string", outputType: "byteArray", args: [] @@ -612,8 +612,8 @@ const OperationConfig = { "To Hexdump": { module: "Default", description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.", - highlight: Hexdump.highlightTo, - highlightReverse: Hexdump.highlightFrom, + highlight: "func", + highlightReverse: "func", inputType: "byteArray", outputType: "string", args: [ diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index a5dd5027..9af165d0 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -158,6 +158,34 @@ OpModules.Default = { "Conditional Jump": FlowControl.runCondJump, "Return": FlowControl.runReturn, "Comment": FlowControl.runComment, + + + /* + Highlighting functions. + + This is a temporary solution as highlighting should be entirely + overhauled at some point. + */ + "From Base64-highlight": Base64.highlightFrom, + "From Base64-highlightReverse": Base64.highlightTo, + "To Base64-highlight": Base64.highlightTo, + "To Base64-highlightReverse": Base64.highlightFrom, + "From Hex-highlight": ByteRepr.highlightFrom, + "From Hex-highlightReverse": ByteRepr.highlightTo, + "To Hex-highlight": ByteRepr.highlightTo, + "To Hex-highlightReverse": ByteRepr.highlightFrom, + "From Charcode-highlight": ByteRepr.highlightFrom, + "From Charcode-highlightReverse": ByteRepr.highlightTo, + "To Charcode-highlight": ByteRepr.highlightTo, + "To Charcode-highlightReverse": ByteRepr.highlightFrom, + "From Binary-highlight": ByteRepr.highlightFromBinary, + "From Binary-highlightReverse": ByteRepr.highlightToBinary, + "To Binary-highlight": ByteRepr.highlightToBinary, + "To Binary-highlightReverse": ByteRepr.highlightFromBinary, + "From Hexdump-highlight": Hexdump.highlightFrom, + "From Hexdump-highlightReverse": Hexdump.highlightTo, + "To Hexdump-highlight": Hexdump.highlightTo, + "To Hexdump-highlightReverse": Hexdump.highlightFrom, }; export default OpModules; diff --git a/src/core/operations/Hexdump.js b/src/core/operations/Hexdump.js index c05a9139..f6d995f5 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/Hexdump.js @@ -92,7 +92,7 @@ const Hexdump = { const w = (width - 13) / 4; // w should be the specified width of the hexdump and therefore a round number if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { - if (self) self.setOption("attemptHighlight", false); + //TODO if (self) self.setOption("attemptHighlight", false); } return output; }, diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js index cafedc22..c650e6ba 100755 --- a/src/web/HighlighterWaiter.js +++ b/src/web/HighlighterWaiter.js @@ -10,9 +10,11 @@ import Utils from "../core/Utils.js"; * * @constructor * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. */ -const HighlighterWaiter = function(app) { +const HighlighterWaiter = function(app, manager) { this.app = app; + this.manager = manager; this.mouseButtonDown = false; this.mouseTarget = null; @@ -329,41 +331,6 @@ HighlighterWaiter.prototype.removeHighlights = function() { }; -/** - * Generates a list of all the highlight functions assigned to operations in the recipe, if the - * entire recipe supports highlighting. - * - * @returns {Object[]} highlights - * @returns {function} highlights[].f - * @returns {function} highlights[].b - * @returns {Object[]} highlights[].args - */ -HighlighterWaiter.prototype.generateHighlightList = function() { - const recipeConfig = this.app.getRecipeConfig(); - const highlights = []; - - for (let i = 0; i < recipeConfig.length; i++) { - if (recipeConfig[i].disabled) continue; - - // If any breakpoints are set, do not attempt to highlight - if (recipeConfig[i].breakpoint) return false; - - const op = this.app.operations[recipeConfig[i].op]; - - // If any of the operations do not support highlighting, fail immediately. - if (op.highlight === false || op.highlight === undefined) return false; - - highlights.push({ - f: op.highlight, - b: op.highlightReverse, - args: recipeConfig[i].args - }); - } - - return highlights; -}; - - /** * Highlights the given offsets in the output. * We will only highlight if: @@ -376,26 +343,8 @@ HighlighterWaiter.prototype.generateHighlightList = function() { * @param {number} pos.end - The end offset. */ HighlighterWaiter.prototype.highlightOutput = function(pos) { - const highlights = this.generateHighlightList(); - - if (!highlights || !this.app.autoBake_) { - return false; - } - - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; - - if (typeof highlights[i].f == "function") { - pos = highlights[i].f(pos, highlights[i].args); - } - } - - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById("output-text"), - document.getElementById("output-highlighter"), - pos); + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); }; @@ -411,25 +360,28 @@ HighlighterWaiter.prototype.highlightOutput = function(pos) { * @param {number} pos.end - The end offset. */ HighlighterWaiter.prototype.highlightInput = function(pos) { - const highlights = this.generateHighlightList(); + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); +}; - if (!highlights || !this.app.autoBake_) { - return false; - } - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; +/** + * Displays highlight offsets sent back from the Chef. + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @param {string} direction + */ +HighlighterWaiter.prototype.displayHighlights = function(pos, direction) { + if (!pos) return; - if (typeof highlights[i].b == "function") { - pos = highlights[i].b(pos, highlights[i].args); - } - } + const io = direction === "forward" ? "output" : "input"; - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); + document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); this.highlight( - document.getElementById("input-text"), - document.getElementById("input-highlighter"), + document.getElementById(io + "-text"), + document.getElementById(io + "-highlighter"), pos); }; diff --git a/src/web/Manager.js b/src/web/Manager.js index fccbc9ff..39efc458 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -58,7 +58,7 @@ const Manager = function(app) { this.input = new InputWaiter(this.app, this); this.output = new OutputWaiter(this.app, this); this.options = new OptionsWaiter(this.app); - this.highlighter = new HighlighterWaiter(this.app); + this.highlighter = new HighlighterWaiter(this.app, this); this.seasonal = new SeasonalWaiter(this.app, this); // Object to store dynamic handlers to fire on elements that may not exist yet diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index e01481ae..0d38c4f8 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -57,6 +57,9 @@ WorkerWaiter.prototype.handleChefMessage = function(e) { case "statusMessage": this.manager.output.setStatusMsg(e.data.data); break; + case "highlightsCalculated": + this.manager.highlighter.displayHighlights(e.data.data.pos, e.data.data.direction); + break; default: console.error("Unrecognised message from ChefWorker", e); break; @@ -150,4 +153,25 @@ WorkerWaiter.prototype.silentBake = function(recipeConfig) { }; +/** + * Asks the ChefWorker to calculate highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ +WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) { + this.chefWorker.postMessage({ + action: "highlight", + data: { + recipeConfig: recipeConfig, + direction: direction, + pos: pos + } + }); +}; + + export default WorkerWaiter;