mirror of
https://github.com/gchq/CyberChef
synced 2025-01-09 02:58:46 +00:00
465 lines
13 KiB
JavaScript
Executable file
465 lines
13 KiB
JavaScript
Executable file
import HTMLOperation from "./HTMLOperation.js";
|
|
import Sortable from "sortablejs";
|
|
import Utils from "../core/Utils.js";
|
|
|
|
|
|
/**
|
|
* Waiter to handle events related to the recipe.
|
|
*
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @copyright Crown Copyright 2016
|
|
* @license Apache-2.0
|
|
*
|
|
* @constructor
|
|
* @param {App} app - The main view object for CyberChef.
|
|
* @param {Manager} manager - The CyberChef event manager.
|
|
*/
|
|
const RecipeWaiter = function(app, manager) {
|
|
this.app = app;
|
|
this.manager = manager;
|
|
this.removeIntent = false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets up the drag and drop capability for operations in the operations and recipe areas.
|
|
*/
|
|
RecipeWaiter.prototype.initialiseOperationDragNDrop = function() {
|
|
const recList = document.getElementById("rec-list");
|
|
|
|
// Recipe list
|
|
Sortable.create(recList, {
|
|
group: "recipe",
|
|
sort: true,
|
|
animation: 0,
|
|
delay: 0,
|
|
filter: ".arg-input,.arg",
|
|
preventOnFilter: false,
|
|
setData: function(dataTransfer, dragEl) {
|
|
dataTransfer.setData("Text", dragEl.querySelector(".arg-title").textContent);
|
|
},
|
|
onEnd: function(evt) {
|
|
if (this.removeIntent) {
|
|
evt.item.remove();
|
|
evt.target.dispatchEvent(this.manager.operationremove);
|
|
}
|
|
}.bind(this),
|
|
onSort: function(evt) {
|
|
if (evt.from.id === "rec-list") {
|
|
document.dispatchEvent(this.manager.statechange);
|
|
}
|
|
}.bind(this)
|
|
});
|
|
|
|
Sortable.utils.on(recList, "dragover", function() {
|
|
this.removeIntent = false;
|
|
}.bind(this));
|
|
|
|
Sortable.utils.on(recList, "dragleave", function() {
|
|
this.removeIntent = true;
|
|
this.app.progress = 0;
|
|
}.bind(this));
|
|
|
|
Sortable.utils.on(recList, "touchend", function(e) {
|
|
const loc = e.changedTouches[0];
|
|
const target = document.elementFromPoint(loc.clientX, loc.clientY);
|
|
|
|
this.removeIntent = !recList.contains(target);
|
|
}.bind(this));
|
|
|
|
// Favourites category
|
|
document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this));
|
|
document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this));
|
|
document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this));
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates a drag-n-droppable seed list of operations.
|
|
*
|
|
* @param {element} listEl - The list to initialise
|
|
*/
|
|
RecipeWaiter.prototype.createSortableSeedList = function(listEl) {
|
|
Sortable.create(listEl, {
|
|
group: {
|
|
name: "recipe",
|
|
pull: "clone",
|
|
put: false,
|
|
},
|
|
sort: false,
|
|
setData: function(dataTransfer, dragEl) {
|
|
dataTransfer.setData("Text", dragEl.textContent);
|
|
},
|
|
onStart: function(evt) {
|
|
// Removes popover element and event bindings from the dragged operation but not the
|
|
// event bindings from the one left in the operations list. Without manually removing
|
|
// these bindings, we cannot re-initialise the popover on the stub operation.
|
|
$(evt.item).popover("destroy").removeData("bs.popover").off("mouseenter").off("mouseleave");
|
|
$(evt.clone).off(".popover").removeData("bs.popover");
|
|
evt.item.setAttribute("data-toggle", "popover-disabled");
|
|
},
|
|
onEnd: this.opSortEnd.bind(this)
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operation sort end events.
|
|
* Removes the operation from the list if it has been dropped outside. If not, adds it to the list
|
|
* at the appropriate place and initialises it.
|
|
*
|
|
* @fires Manager#operationadd
|
|
* @param {event} evt
|
|
*/
|
|
RecipeWaiter.prototype.opSortEnd = function(evt) {
|
|
if (this.removeIntent) {
|
|
if (evt.item.parentNode.id === "rec-list") {
|
|
evt.item.remove();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Reinitialise the popover on the original element in the ops list because for some reason it
|
|
// gets destroyed and recreated.
|
|
this.manager.ops.enableOpsListPopovers(evt.clone);
|
|
|
|
if (evt.item.parentNode.id !== "rec-list") {
|
|
return;
|
|
}
|
|
|
|
this.buildRecipeOperation(evt.item);
|
|
evt.item.dispatchEvent(this.manager.operationadd);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for favourite dragover events.
|
|
* If the element being dragged is an operation, displays a visual cue so that the user knows it can
|
|
* be dropped here.
|
|
*
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.favDragover = function(e) {
|
|
if (e.dataTransfer.effectAllowed !== "move")
|
|
return false;
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
if (e.target.className && e.target.className.indexOf("category-title") > -1) {
|
|
// Hovering over the a
|
|
e.target.classList.add("favourites-hover");
|
|
} else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) {
|
|
// Hovering over the Edit button
|
|
e.target.parentNode.classList.add("favourites-hover");
|
|
} else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) {
|
|
// Hovering over the image on the Edit button
|
|
e.target.parentNode.parentNode.classList.add("favourites-hover");
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for favourite dragleave events.
|
|
* Removes the visual cue.
|
|
*
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.favDragleave = function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
document.querySelector("#categories a").classList.remove("favourites-hover");
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for favourite drop events.
|
|
* Adds the dragged operation to the favourites list.
|
|
*
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.favDrop = function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
e.target.classList.remove("favourites-hover");
|
|
|
|
const opName = e.dataTransfer.getData("Text");
|
|
this.app.addFavourite(opName);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for ingredient change events.
|
|
*
|
|
* @fires Manager#statechange
|
|
*/
|
|
RecipeWaiter.prototype.ingChange = function(e) {
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for disable click events.
|
|
* Updates the icon status.
|
|
*
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.disableClick = function(e) {
|
|
const icon = e.target;
|
|
|
|
if (icon.getAttribute("disabled") === "false") {
|
|
icon.setAttribute("disabled", "true");
|
|
icon.classList.add("disable-icon-selected");
|
|
icon.parentNode.parentNode.classList.add("disabled");
|
|
} else {
|
|
icon.setAttribute("disabled", "false");
|
|
icon.classList.remove("disable-icon-selected");
|
|
icon.parentNode.parentNode.classList.remove("disabled");
|
|
}
|
|
|
|
this.app.progress = 0;
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for breakpoint click events.
|
|
* Updates the icon status.
|
|
*
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.breakpointClick = function(e) {
|
|
const bp = e.target;
|
|
|
|
if (bp.getAttribute("break") === "false") {
|
|
bp.setAttribute("break", "true");
|
|
bp.classList.add("breakpoint-selected");
|
|
} else {
|
|
bp.setAttribute("break", "false");
|
|
bp.classList.remove("breakpoint-selected");
|
|
}
|
|
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operation doubleclick events.
|
|
* Removes the operation from the recipe and auto bakes.
|
|
*
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.operationDblclick = function(e) {
|
|
e.target.remove();
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operation child doubleclick events.
|
|
* Removes the operation from the recipe.
|
|
*
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.operationChildDblclick = function(e) {
|
|
e.target.parentNode.remove();
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Generates a configuration object to represent the current recipe.
|
|
*
|
|
* @returns {recipeConfig}
|
|
*/
|
|
RecipeWaiter.prototype.getConfig = function() {
|
|
const config = [];
|
|
let ingredients, ingList, disabled, bp, item;
|
|
const operations = document.querySelectorAll("#rec-list li.operation");
|
|
|
|
for (let i = 0; i < operations.length; i++) {
|
|
ingredients = [];
|
|
disabled = operations[i].querySelector(".disable-icon");
|
|
bp = operations[i].querySelector(".breakpoint");
|
|
ingList = operations[i].querySelectorAll(".arg");
|
|
|
|
for (let j = 0; j < ingList.length; j++) {
|
|
if (ingList[j].getAttribute("type") === "checkbox") {
|
|
// checkbox
|
|
ingredients[j] = ingList[j].checked;
|
|
} else if (ingList[j].classList.contains("toggle-string")) {
|
|
// toggleString
|
|
ingredients[j] = {
|
|
option: ingList[j].previousSibling.children[0].textContent.slice(0, -1),
|
|
string: ingList[j].value
|
|
};
|
|
} else if (ingList[j].getAttribute("type") === "number") {
|
|
// number
|
|
ingredients[j] = parseFloat(ingList[j].value, 10);
|
|
} else {
|
|
// all others
|
|
ingredients[j] = ingList[j].value;
|
|
}
|
|
}
|
|
|
|
item = {
|
|
op: operations[i].querySelector(".arg-title").textContent,
|
|
args: ingredients
|
|
};
|
|
|
|
if (disabled && disabled.getAttribute("disabled") === "true") {
|
|
item.disabled = true;
|
|
}
|
|
|
|
if (bp && bp.getAttribute("break") === "true") {
|
|
item.breakpoint = true;
|
|
}
|
|
|
|
config.push(item);
|
|
}
|
|
|
|
return config;
|
|
};
|
|
|
|
|
|
/**
|
|
* Moves or removes the breakpoint indicator in the recipe based on the position.
|
|
*
|
|
* @param {number} position
|
|
*/
|
|
RecipeWaiter.prototype.updateBreakpointIndicator = function(position) {
|
|
const operations = document.querySelectorAll("#rec-list li.operation");
|
|
for (let i = 0; i < operations.length; i++) {
|
|
if (i === position) {
|
|
operations[i].classList.add("break");
|
|
} else {
|
|
operations[i].classList.remove("break");
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Given an operation stub element, this function converts it into a full recipe element with
|
|
* arguments.
|
|
*
|
|
* @param {element} el - The operation stub element from the operations pane
|
|
*/
|
|
RecipeWaiter.prototype.buildRecipeOperation = function(el) {
|
|
const opName = el.textContent;
|
|
const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
|
|
el.innerHTML = op.toFullHtml();
|
|
|
|
if (this.app.operations[opName].flowControl) {
|
|
el.classList.add("flow-control-op");
|
|
}
|
|
|
|
// Disable auto-bake if this is a manual op
|
|
if (op.manualBake && this.app.autoBake_) {
|
|
this.manager.controls.setAutoBake(false);
|
|
this.app.alert("Auto-Bake is disabled by default when using this operation.", "info", 5000);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds the specified operation to the recipe.
|
|
*
|
|
* @fires Manager#operationadd
|
|
* @param {string} name - The name of the operation to add
|
|
* @returns {element}
|
|
*/
|
|
RecipeWaiter.prototype.addOperation = function(name) {
|
|
const item = document.createElement("li");
|
|
|
|
item.classList.add("operation");
|
|
item.innerHTML = name;
|
|
this.buildRecipeOperation(item);
|
|
document.getElementById("rec-list").appendChild(item);
|
|
|
|
item.dispatchEvent(this.manager.operationadd);
|
|
return item;
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes all operations from the recipe.
|
|
*
|
|
* @fires Manager#operationremove
|
|
*/
|
|
RecipeWaiter.prototype.clearRecipe = function() {
|
|
const recList = document.getElementById("rec-list");
|
|
while (recList.firstChild) {
|
|
recList.removeChild(recList.firstChild);
|
|
}
|
|
recList.dispatchEvent(this.manager.operationremove);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operation dropdown events from toggleString arguments.
|
|
* Sets the selected option as the name of the button.
|
|
*
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.dropdownToggleClick = function(e) {
|
|
const el = e.target;
|
|
const button = el.parentNode.parentNode.previousSibling;
|
|
|
|
button.innerHTML = el.textContent + " <span class='caret'></span>";
|
|
this.ingChange();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operationadd events.
|
|
*
|
|
* @listens Manager#operationadd
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.opAdd = function(e) {
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Handler for operationremove events.
|
|
*
|
|
* @listens Manager#operationremove
|
|
* @fires Manager#statechange
|
|
* @param {event} e
|
|
*/
|
|
RecipeWaiter.prototype.opRemove = function(e) {
|
|
window.dispatchEvent(this.manager.statechange);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets register values.
|
|
*
|
|
* @param {number} opIndex
|
|
* @param {number} numPrevRegisters
|
|
* @param {string[]} registers
|
|
*/
|
|
RecipeWaiter.prototype.setRegisters = function(opIndex, numPrevRegisters, registers) {
|
|
const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`),
|
|
prevRegList = op.querySelector(".register-list");
|
|
|
|
// Remove previous div
|
|
if (prevRegList) prevRegList.remove();
|
|
|
|
let registerList = [];
|
|
for (let i = 0; i < registers.length; i++) {
|
|
registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`);
|
|
}
|
|
const registerListEl = `<div class="register-list">
|
|
${registerList.join("<br>")}
|
|
</div>`;
|
|
|
|
op.insertAdjacentHTML("beforeend", registerListEl);
|
|
};
|
|
|
|
export default RecipeWaiter;
|