/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Utils from "../core/Utils.mjs";
/**
* Object to handle the creation of operation ingredients.
*/
class HTMLIngredient {
/**
* HTMLIngredient constructor.
*
* @param {Object} config - The configuration object for this ingredient.
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(config, app, manager) {
this.app = app;
this.manager = manager;
this.name = config.name;
this.type = config.type;
this.value = config.value;
this.disabled = config.disabled || false;
this.hint = config.hint || false;
this.rows = config.rows || false;
this.target = config.target;
this.defaultIndex = config.defaultIndex || 0;
this.toggleValues = config.toggleValues;
this.ingId = this.app.nextIngId();
this.id = "ing-" + this.ingId;
this.tabIndex = this.ingId + 2; // Input = 1, Search = 2
this.min = (typeof config.min === "number") ? config.min : "";
this.max = (typeof config.max === "number") ? config.max : "";
this.step = config.step || 1;
}
/**
* Renders the ingredient in HTML.
*
* @returns {string}
*/
toHtml() {
let html = "",
i, m, eventFn;
switch (this.type) {
case "string":
case "binaryString":
case "byteArray":
html += `
`;
break;
case "shortString":
case "binaryShortString":
html += `
`;
break;
case "toggleString":
html += ``;
break;
case "number":
html += `
`;
break;
case "boolean":
html += ``;
break;
case "option":
html += `
`;
break;
case "populateOption":
case "populateMultiOption":
html += `
`;
eventFn = this.type === "populateMultiOption" ?
this.populateMultiOptionChange :
this.populateOptionChange;
this.manager.addDynamicListener("#" + this.id, "change", eventFn, this);
break;
case "editableOption":
html += ``;
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
break;
case "editableOptionShort":
html += ``;
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
break;
case "text":
html += `
`;
break;
case "argSelector":
html += `
`;
this.manager.addDynamicListener(".arg-selector", "change", this.argSelectorChange, this);
break;
case "label":
html += `
`;
break;
default:
break;
}
return html;
}
/**
* Handler for populate option changes.
* Populates the relevant argument with the specified value.
*
* @param {event} e
*/
populateOptionChange(e) {
e.preventDefault();
e.stopPropagation();
const el = e.target;
const op = el.parentNode.parentNode;
const target = op.querySelectorAll(".arg")[this.target];
const popVal = el.childNodes[el.selectedIndex].getAttribute("populate-value");
if (popVal !== "") target.value = popVal;
const evt = new Event("change");
target.dispatchEvent(evt);
this.manager.recipe.ingChange();
}
/**
* Handler for populate multi option changes.
* Populates the relevant arguments with the specified values.
*
* @param {event} e
*/
populateMultiOptionChange(e) {
e.preventDefault();
e.stopPropagation();
const el = e.target;
const op = el.parentNode.parentNode;
const args = op.querySelectorAll(".arg");
const targets = this.target.map(i => args[i]);
const vals = JSON.parse(el.childNodes[el.selectedIndex].getAttribute("populate-value"));
const evt = new Event("change");
for (let i = 0; i < targets.length; i++) {
targets[i].value = vals[i];
}
// Fire change event after all targets have been assigned
this.manager.recipe.ingChange();
// Send change event for each target once all have been assigned, to update the label placement.
for (const target of targets) {
target.dispatchEvent(evt);
}
}
/**
* Handler for editable option clicks.
* Populates the input box with the selected value.
*
* @param {event} e
*/
editableOptionClick(e) {
e.preventDefault();
e.stopPropagation();
const link = e.target,
input = link.parentNode.parentNode.parentNode.querySelector("input");
input.value = link.getAttribute("value");
const evt = new Event("change");
input.dispatchEvent(evt);
this.manager.recipe.ingChange();
}
/**
* Handler for argument selector changes.
* Shows or hides the relevant arguments for this operation.
*
* @param {event} e
*/
argSelectorChange(e) {
e.preventDefault();
e.stopPropagation();
const option = e.target.options[e.target.selectedIndex];
const op = e.target.closest(".operation");
const args = op.querySelectorAll(".ingredients .form-group");
const turnon = JSON.parse(option.getAttribute("turnon"));
const turnoff = JSON.parse(option.getAttribute("turnoff"));
args.forEach((arg, i) => {
if (turnon.includes(i)) {
arg.classList.remove("d-none");
}
if (turnoff.includes(i)) {
arg.classList.add("d-none");
}
});
}
}
export default HTMLIngredient;