Restructured src files into a more logical hierarchy
360
src/web/ControlsWaiter.js
Executable file
|
@ -0,0 +1,360 @@
|
|||
var Utils = require("../core/Utils.js");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.)
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var ControlsWaiter = module.exports = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the display properties of the control buttons so that they fit within the current width
|
||||
* without wrapping or overflowing.
|
||||
*/
|
||||
ControlsWaiter.prototype.adjustWidth = function() {
|
||||
var controls = document.getElementById("controls"),
|
||||
step = document.getElementById("step"),
|
||||
clrBreaks = document.getElementById("clr-breaks"),
|
||||
saveImg = document.querySelector("#save img"),
|
||||
loadImg = document.querySelector("#load img"),
|
||||
stepImg = document.querySelector("#step img"),
|
||||
clrRecipImg = document.querySelector("#clr-recipe img"),
|
||||
clrBreaksImg = document.querySelector("#clr-breaks img");
|
||||
|
||||
if (controls.clientWidth < 470) {
|
||||
step.childNodes[1].nodeValue = " Step";
|
||||
} else {
|
||||
step.childNodes[1].nodeValue = " Step through";
|
||||
}
|
||||
|
||||
if (controls.clientWidth < 400) {
|
||||
saveImg.style.display = "none";
|
||||
loadImg.style.display = "none";
|
||||
stepImg.style.display = "none";
|
||||
clrRecipImg.style.display = "none";
|
||||
clrBreaksImg.style.display = "none";
|
||||
} else {
|
||||
saveImg.style.display = "inline";
|
||||
loadImg.style.display = "inline";
|
||||
stepImg.style.display = "inline";
|
||||
clrRecipImg.style.display = "inline";
|
||||
clrBreaksImg.style.display = "inline";
|
||||
}
|
||||
|
||||
if (controls.clientWidth < 330) {
|
||||
clrBreaks.childNodes[1].nodeValue = " Clear breaks";
|
||||
} else {
|
||||
clrBreaks.childNodes[1].nodeValue = " Clear breakpoints";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks or unchecks the Auto Bake checkbox based on the given value.
|
||||
*
|
||||
* @param {boolean} value - The new value for Auto Bake.
|
||||
*/
|
||||
ControlsWaiter.prototype.setAutoBake = function(value) {
|
||||
var autoBakeCheckbox = document.getElementById("auto-bake");
|
||||
|
||||
if (autoBakeCheckbox.checked !== value) {
|
||||
autoBakeCheckbox.click();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler to trigger baking.
|
||||
*/
|
||||
ControlsWaiter.prototype.bakeClick = function() {
|
||||
this.app.bake();
|
||||
var outputText = document.getElementById("output-text");
|
||||
outputText.focus();
|
||||
outputText.setSelectionRange(0, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'Step through' command. Executes the next step of the recipe.
|
||||
*/
|
||||
ControlsWaiter.prototype.stepClick = function() {
|
||||
this.app.bake(true);
|
||||
var outputText = document.getElementById("output-text");
|
||||
outputText.focus();
|
||||
outputText.setSelectionRange(0, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for changes made to the Auto Bake checkbox.
|
||||
*/
|
||||
ControlsWaiter.prototype.autoBakeChange = function() {
|
||||
var autoBakeLabel = document.getElementById("auto-bake-label"),
|
||||
autoBakeCheckbox = document.getElementById("auto-bake");
|
||||
|
||||
this.app.autoBake_ = autoBakeCheckbox.checked;
|
||||
|
||||
if (autoBakeCheckbox.checked) {
|
||||
autoBakeLabel.classList.remove("btn-default");
|
||||
autoBakeLabel.classList.add("btn-success");
|
||||
} else {
|
||||
autoBakeLabel.classList.remove("btn-success");
|
||||
autoBakeLabel.classList.add("btn-default");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'Clear recipe' command. Removes all operations from the recipe.
|
||||
*/
|
||||
ControlsWaiter.prototype.clearRecipeClick = function() {
|
||||
this.manager.recipe.clearRecipe();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'Clear breakpoints' command. Removes all breakpoints from operations in the
|
||||
* recipe.
|
||||
*/
|
||||
ControlsWaiter.prototype.clearBreaksClick = function() {
|
||||
var bps = document.querySelectorAll("#rec-list li.operation .breakpoint");
|
||||
|
||||
for (var i = 0; i < bps.length; i++) {
|
||||
bps[i].setAttribute("break", "false");
|
||||
bps[i].classList.remove("breakpoint-selected");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populates the save disalog box with a URL incorporating the recipe and input.
|
||||
*
|
||||
* @param {Object[]} [recipeConfig] - The recipe configuration object array.
|
||||
*/
|
||||
ControlsWaiter.prototype.initialiseSaveLink = function(recipeConfig) {
|
||||
recipeConfig = recipeConfig || this.app.getRecipeConfig();
|
||||
|
||||
var includeRecipe = document.getElementById("save-link-recipe-checkbox").checked,
|
||||
includeInput = document.getElementById("save-link-input-checkbox").checked,
|
||||
saveLinkEl = document.getElementById("save-link"),
|
||||
saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig);
|
||||
|
||||
saveLinkEl.innerHTML = Utils.truncate(saveLink, 120);
|
||||
saveLinkEl.setAttribute("href", saveLink);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generates a URL containing the current recipe and input state.
|
||||
*
|
||||
* @param {boolean} includeRecipe - Whether to include the recipe in the URL.
|
||||
* @param {boolean} includeInput - Whether to include the input in the URL.
|
||||
* @param {Object[]} [recipeConfig] - The recipe configuration object array.
|
||||
* @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included
|
||||
* @returns {string}
|
||||
*/
|
||||
ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput, recipeConfig, baseURL) {
|
||||
recipeConfig = recipeConfig || this.app.getRecipeConfig();
|
||||
|
||||
var link = baseURL || window.location.protocol + "//" +
|
||||
window.location.host +
|
||||
window.location.pathname,
|
||||
recipeStr = JSON.stringify(recipeConfig),
|
||||
inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding
|
||||
|
||||
includeRecipe = includeRecipe && (recipeConfig.length > 0);
|
||||
includeInput = includeInput && (inputStr.length > 0) && (inputStr.length < 8000);
|
||||
|
||||
if (includeRecipe) {
|
||||
link += "?recipe=" + encodeURIComponent(recipeStr);
|
||||
}
|
||||
|
||||
if (includeRecipe && includeInput) {
|
||||
link += "&input=" + encodeURIComponent(inputStr);
|
||||
} else if (includeInput) {
|
||||
link += "?input=" + encodeURIComponent(inputStr);
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for changes made to the save dialog text area. Re-initialises the save link.
|
||||
*/
|
||||
ControlsWaiter.prototype.saveTextChange = function() {
|
||||
try {
|
||||
var recipeConfig = JSON.parse(document.getElementById("save-text").value);
|
||||
this.initialiseSaveLink(recipeConfig);
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'Save' command. Pops up the save dialog box.
|
||||
*/
|
||||
ControlsWaiter.prototype.saveClick = function() {
|
||||
var recipeConfig = this.app.getRecipeConfig(),
|
||||
recipeStr = JSON.stringify(recipeConfig).replace(/},{/g, "},\n{");
|
||||
|
||||
document.getElementById("save-text").value = recipeStr;
|
||||
|
||||
this.initialiseSaveLink(recipeConfig);
|
||||
$("#save-modal").modal();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the save link recipe checkbox change event.
|
||||
*/
|
||||
ControlsWaiter.prototype.slrCheckChange = function() {
|
||||
this.initialiseSaveLink();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the save link input checkbox change event.
|
||||
*/
|
||||
ControlsWaiter.prototype.sliCheckChange = function() {
|
||||
this.initialiseSaveLink();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'Load' command. Pops up the load dialog box.
|
||||
*/
|
||||
ControlsWaiter.prototype.loadClick = function() {
|
||||
this.populateLoadRecipesList();
|
||||
$("#load-modal").modal();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saves the recipe specified in the save textarea to local storage.
|
||||
*/
|
||||
ControlsWaiter.prototype.saveButtonClick = function() {
|
||||
var recipeName = document.getElementById("save-name").value,
|
||||
recipeStr = document.getElementById("save-text").value;
|
||||
|
||||
if (!recipeName) {
|
||||
this.app.alert("Please enter a recipe name", "danger", 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
var savedRecipes = localStorage.savedRecipes ?
|
||||
JSON.parse(localStorage.savedRecipes) : [],
|
||||
recipeId = localStorage.recipeId || 0;
|
||||
|
||||
savedRecipes.push({
|
||||
id: ++recipeId,
|
||||
name: recipeName,
|
||||
recipe: recipeStr
|
||||
});
|
||||
|
||||
localStorage.savedRecipes = JSON.stringify(savedRecipes);
|
||||
localStorage.recipeId = recipeId;
|
||||
|
||||
this.app.alert("Recipe saved as \"" + recipeName + "\".", "success", 2000);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populates the list of saved recipes in the load dialog box from local storage.
|
||||
*/
|
||||
ControlsWaiter.prototype.populateLoadRecipesList = function() {
|
||||
var loadNameEl = document.getElementById("load-name");
|
||||
|
||||
// Remove current recipes from select
|
||||
var i = loadNameEl.options.length;
|
||||
while (i--) {
|
||||
loadNameEl.remove(i);
|
||||
}
|
||||
|
||||
// Add recipes to select
|
||||
var savedRecipes = localStorage.savedRecipes ?
|
||||
JSON.parse(localStorage.savedRecipes) : [];
|
||||
|
||||
for (i = 0; i < savedRecipes.length; i++) {
|
||||
var opt = document.createElement("option");
|
||||
opt.value = savedRecipes[i].id;
|
||||
opt.innerHTML = savedRecipes[i].name;
|
||||
|
||||
loadNameEl.appendChild(opt);
|
||||
}
|
||||
|
||||
// Populate textarea with first recipe
|
||||
document.getElementById("load-text").value = savedRecipes.length ? savedRecipes[0].recipe : "";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the currently selected recipe from local storage.
|
||||
*/
|
||||
ControlsWaiter.prototype.loadDeleteClick = function() {
|
||||
var id = parseInt(document.getElementById("load-name").value, 10),
|
||||
savedRecipes = localStorage.savedRecipes ?
|
||||
JSON.parse(localStorage.savedRecipes) : [];
|
||||
|
||||
savedRecipes = savedRecipes.filter(function(r) {
|
||||
return r.id !== id;
|
||||
});
|
||||
|
||||
localStorage.savedRecipes = JSON.stringify(savedRecipes);
|
||||
this.populateLoadRecipesList();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Displays the selected recipe in the load text box.
|
||||
*/
|
||||
ControlsWaiter.prototype.loadNameChange = function(e) {
|
||||
var el = e.target,
|
||||
savedRecipes = localStorage.savedRecipes ?
|
||||
JSON.parse(localStorage.savedRecipes) : [],
|
||||
id = parseInt(el.value, 10);
|
||||
|
||||
var recipe = savedRecipes.filter(function(r) {
|
||||
return r.id === id;
|
||||
})[0];
|
||||
|
||||
document.getElementById("load-text").value = recipe.recipe;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads the selected recipe and populates the Recipe with its operations.
|
||||
*/
|
||||
ControlsWaiter.prototype.loadButtonClick = function() {
|
||||
try {
|
||||
var recipeConfig = JSON.parse(document.getElementById("load-text").value);
|
||||
this.app.setRecipeConfig(recipeConfig);
|
||||
|
||||
$("#rec-list [data-toggle=popover]").popover();
|
||||
} catch (e) {
|
||||
this.app.alert("Invalid recipe", "danger", 2000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populates the bug report information box with useful technical info.
|
||||
*/
|
||||
ControlsWaiter.prototype.supportButtonClick = function() {
|
||||
var reportBugInfo = document.getElementById("report-bug-info"),
|
||||
saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/");
|
||||
|
||||
reportBugInfo.innerHTML = "* CyberChef compile time: " + COMPILE_TIME + "\n" +
|
||||
"* User-Agent: \n" + navigator.userAgent + "\n" +
|
||||
"* [Link to reproduce](" + saveLink + ")\n\n";
|
||||
};
|
676
src/web/HTMLApp.js
Executable file
|
@ -0,0 +1,676 @@
|
|||
var Utils = require("../core/Utils.js"),
|
||||
Chef = require("../core/Chef.js"),
|
||||
Manager = require("./Manager.js"),
|
||||
HTMLCategory = require("./HTMLCategory.js"),
|
||||
HTMLOperation = require("./HTMLOperation.js"),
|
||||
Split = require("split.js");
|
||||
|
||||
|
||||
/**
|
||||
* HTML view for CyberChef responsible for building the web page and dealing with all user
|
||||
* interactions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {CatConf[]} categories - The list of categories and operations to be populated.
|
||||
* @param {Object.<string, OpConf>} operations - The list of operation configuration objects.
|
||||
* @param {String[]} defaultFavourites - A list of default favourite operations.
|
||||
* @param {Object} options - Default setting for app options.
|
||||
*/
|
||||
var HTMLApp = module.exports = function(categories, operations, defaultFavourites, defaultOptions) {
|
||||
this.categories = categories;
|
||||
this.operations = operations;
|
||||
this.dfavourites = defaultFavourites;
|
||||
this.doptions = defaultOptions;
|
||||
this.options = Utils.extend({}, defaultOptions);
|
||||
|
||||
this.chef = new Chef();
|
||||
this.manager = new Manager(this);
|
||||
|
||||
this.autoBake_ = false;
|
||||
this.progress = 0;
|
||||
this.ingId = 0;
|
||||
|
||||
window.chef = this.chef;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function sets up the stage and creates listeners for all events.
|
||||
*
|
||||
* @fires Manager#appstart
|
||||
*/
|
||||
HTMLApp.prototype.setup = function() {
|
||||
document.dispatchEvent(this.manager.appstart);
|
||||
this.initialiseSplitter();
|
||||
this.loadLocalStorage();
|
||||
this.populateOperationsList();
|
||||
this.manager.setup();
|
||||
this.resetLayout();
|
||||
this.setCompileMessage();
|
||||
this.loadURIParams();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An error handler for displaying the error to the user.
|
||||
*
|
||||
* @param {Error} err
|
||||
*/
|
||||
HTMLApp.prototype.handleError = function(err) {
|
||||
console.error(err);
|
||||
var msg = err.displayStr || err.toString();
|
||||
this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the Chef to bake the current input using the current recipe.
|
||||
*
|
||||
* @param {boolean} [step] - Set to true if we should only execute one operation instead of the
|
||||
* whole recipe.
|
||||
*/
|
||||
HTMLApp.prototype.bake = function(step) {
|
||||
var response;
|
||||
|
||||
try {
|
||||
response = this.chef.bake(
|
||||
this.getInput(), // The user's input
|
||||
this.getRecipeConfig(), // The configuration of the recipe
|
||||
this.options, // Options set by the user
|
||||
this.progress, // The current position in the recipe
|
||||
step // Whether or not to take one step or execute the whole recipe
|
||||
);
|
||||
} catch (err) {
|
||||
this.handleError(err);
|
||||
}
|
||||
|
||||
if (!response) return;
|
||||
|
||||
if (response.error) {
|
||||
this.handleError(response.error);
|
||||
}
|
||||
|
||||
this.options = response.options;
|
||||
this.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
|
||||
this.progress = response.progress;
|
||||
this.manager.recipe.updateBreakpointIndicator(response.progress);
|
||||
this.manager.output.set(response.result, response.type, response.duration);
|
||||
|
||||
// If baking took too long, disable auto-bake
|
||||
if (response.duration > this.options.autoBakeThreshold && this.autoBake_) {
|
||||
this.manager.controls.setAutoBake(false);
|
||||
this.alert("Baking took longer than " + this.options.autoBakeThreshold +
|
||||
"ms, Auto Bake has been disabled.", "warning", 5000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs Auto Bake if it is set.
|
||||
*/
|
||||
HTMLApp.prototype.autoBake = function() {
|
||||
if (this.autoBake_) {
|
||||
this.bake();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs a silent bake forcing the browser to load and cache all the relevant JavaScript code needed
|
||||
* to do a real bake.
|
||||
*
|
||||
* The output will not be modified (hence "silent" bake). This will only actually execute the
|
||||
* recipe if auto-bake is enabled, otherwise it will just load the recipe, ingredients and dish.
|
||||
*
|
||||
* @returns {number} - The number of miliseconds it took to run the silent bake.
|
||||
*/
|
||||
HTMLApp.prototype.silentBake = function() {
|
||||
var startTime = new Date().getTime(),
|
||||
recipeConfig = this.getRecipeConfig();
|
||||
|
||||
if (this.autoBake_) {
|
||||
this.chef.silentBake(recipeConfig);
|
||||
}
|
||||
|
||||
return new Date().getTime() - startTime;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the user's input data.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
HTMLApp.prototype.getInput = function() {
|
||||
var input = this.manager.input.get();
|
||||
|
||||
// Save to session storage in case we need to restore it later
|
||||
sessionStorage.setItem("inputLength", input.length);
|
||||
sessionStorage.setItem("input", input);
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the user's input data.
|
||||
*
|
||||
* @param {string} input - The string to set the input to
|
||||
*/
|
||||
HTMLApp.prototype.setInput = function(input) {
|
||||
sessionStorage.setItem("inputLength", input.length);
|
||||
sessionStorage.setItem("input", input);
|
||||
this.manager.input.set(input);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populates the operations accordion list with the categories and operations specified in the
|
||||
* view constructor.
|
||||
*
|
||||
* @fires Manager#oplistcreate
|
||||
*/
|
||||
HTMLApp.prototype.populateOperationsList = function() {
|
||||
// Move edit button away before we overwrite it
|
||||
document.body.appendChild(document.getElementById("edit-favourites"));
|
||||
|
||||
var html = "";
|
||||
|
||||
for (var i = 0; i < this.categories.length; i++) {
|
||||
var catConf = this.categories[i],
|
||||
selected = i === 0,
|
||||
cat = new HTMLCategory(catConf.name, selected);
|
||||
|
||||
for (var j = 0; j < catConf.ops.length; j++) {
|
||||
var opName = catConf.ops[j],
|
||||
op = new HTMLOperation(opName, this.operations[opName], this, this.manager);
|
||||
cat.addOperation(op);
|
||||
}
|
||||
|
||||
html += cat.toHtml();
|
||||
}
|
||||
|
||||
document.getElementById("categories").innerHTML = html;
|
||||
|
||||
var opLists = document.querySelectorAll("#categories .op-list");
|
||||
|
||||
for (i = 0; i < opLists.length; i++) {
|
||||
opLists[i].dispatchEvent(this.manager.oplistcreate);
|
||||
}
|
||||
|
||||
// Add edit button to first category (Favourites)
|
||||
document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites"));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the adjustable splitter to allow the user to resize areas of the page.
|
||||
*/
|
||||
HTMLApp.prototype.initialiseSplitter = function() {
|
||||
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
|
||||
sizes: [20, 30, 50],
|
||||
minSize: [240, 325, 440],
|
||||
gutterSize: 4,
|
||||
onDrag: function() {
|
||||
this.manager.controls.adjustWidth();
|
||||
this.manager.output.adjustWidth();
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
this.ioSplitter = Split(["#input", "#output"], {
|
||||
direction: "vertical",
|
||||
gutterSize: 4,
|
||||
});
|
||||
|
||||
this.resetLayout();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads the information previously saved to the HTML5 local storage object so that user options
|
||||
* and favourites can be restored.
|
||||
*/
|
||||
HTMLApp.prototype.loadLocalStorage = function() {
|
||||
// Load options
|
||||
var lOptions;
|
||||
if (localStorage.options !== undefined) {
|
||||
lOptions = JSON.parse(localStorage.options);
|
||||
}
|
||||
this.manager.options.load(lOptions);
|
||||
|
||||
// Load favourites
|
||||
this.loadFavourites();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads the user's favourite operations from the HTML5 local storage object and populates the
|
||||
* Favourites category with them.
|
||||
* If the user currently has no saved favourites, the defaults from the view constructor are used.
|
||||
*/
|
||||
HTMLApp.prototype.loadFavourites = function() {
|
||||
var favourites = localStorage.favourites &&
|
||||
localStorage.favourites.length > 2 ?
|
||||
JSON.parse(localStorage.favourites) :
|
||||
this.dfavourites;
|
||||
|
||||
favourites = this.validFavourites(favourites);
|
||||
this.saveFavourites(favourites);
|
||||
|
||||
var favCat = this.categories.filter(function(c) {
|
||||
return c.name === "Favourites";
|
||||
})[0];
|
||||
|
||||
if (favCat) {
|
||||
favCat.ops = favourites;
|
||||
} else {
|
||||
this.categories.unshift({
|
||||
name: "Favourites",
|
||||
ops: favourites
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Filters the list of favourite operations that the user had stored and removes any that are no
|
||||
* longer available. The user is notified if this is the case.
|
||||
|
||||
* @param {string[]} favourites - A list of the user's favourite operations
|
||||
* @returns {string[]} A list of the valid favourites
|
||||
*/
|
||||
HTMLApp.prototype.validFavourites = function(favourites) {
|
||||
var validFavs = [];
|
||||
for (var i = 0; i < favourites.length; i++) {
|
||||
if (this.operations.hasOwnProperty(favourites[i])) {
|
||||
validFavs.push(favourites[i]);
|
||||
} else {
|
||||
this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) +
|
||||
"\" is no longer available. It has been removed from your favourites.", "info");
|
||||
}
|
||||
}
|
||||
return validFavs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saves a list of favourite operations to the HTML5 local storage object.
|
||||
*
|
||||
* @param {string[]} favourites - A list of the user's favourite operations
|
||||
*/
|
||||
HTMLApp.prototype.saveFavourites = function(favourites) {
|
||||
localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets favourite operations back to the default as specified in the view constructor and
|
||||
* refreshes the operation list.
|
||||
*/
|
||||
HTMLApp.prototype.resetFavourites = function() {
|
||||
this.saveFavourites(this.dfavourites);
|
||||
this.loadFavourites();
|
||||
this.populateOperationsList();
|
||||
this.manager.recipe.initialiseOperationDragNDrop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an operation to the user's favourites.
|
||||
*
|
||||
* @param {string} name - The name of the operation
|
||||
*/
|
||||
HTMLApp.prototype.addFavourite = function(name) {
|
||||
var favourites = JSON.parse(localStorage.favourites);
|
||||
|
||||
if (favourites.indexOf(name) >= 0) {
|
||||
this.alert("'" + name + "' is already in your favourites", "info", 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
favourites.push(name);
|
||||
this.saveFavourites(favourites);
|
||||
this.loadFavourites();
|
||||
this.populateOperationsList();
|
||||
this.manager.recipe.initialiseOperationDragNDrop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks for input and recipe in the URI parameters and loads them if present.
|
||||
*/
|
||||
HTMLApp.prototype.loadURIParams = function() {
|
||||
// Load query string from URI
|
||||
this.queryString = (function(a) {
|
||||
if (a === "") return {};
|
||||
var b = {};
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
var p = a[i].split("=");
|
||||
if (p.length !== 2) {
|
||||
b[a[i]] = true;
|
||||
} else {
|
||||
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
|
||||
}
|
||||
}
|
||||
return b;
|
||||
})(window.location.search.substr(1).split("&"));
|
||||
|
||||
// Turn off auto-bake while loading
|
||||
var autoBakeVal = this.autoBake_;
|
||||
this.autoBake_ = false;
|
||||
|
||||
// Read in recipe from query string
|
||||
if (this.queryString.recipe) {
|
||||
try {
|
||||
var recipeConfig = JSON.parse(this.queryString.recipe);
|
||||
this.setRecipeConfig(recipeConfig);
|
||||
} catch (err) {}
|
||||
} else if (this.queryString.op) {
|
||||
// If there's no recipe, look for single operations
|
||||
this.manager.recipe.clearRecipe();
|
||||
try {
|
||||
this.manager.recipe.addOperation(this.queryString.op);
|
||||
} catch (err) {
|
||||
// If no exact match, search for nearest match and add that
|
||||
var matchedOps = this.manager.ops.filterOperations(this.queryString.op, false);
|
||||
if (matchedOps.length) {
|
||||
this.manager.recipe.addOperation(matchedOps[0].name);
|
||||
}
|
||||
|
||||
// Populate search with the string
|
||||
var search = document.getElementById("search");
|
||||
|
||||
search.value = this.queryString.op;
|
||||
search.dispatchEvent(new Event("search"));
|
||||
}
|
||||
}
|
||||
|
||||
// Read in input data from query string
|
||||
if (this.queryString.input) {
|
||||
try {
|
||||
var inputData = Utils.fromBase64(this.queryString.input);
|
||||
this.setInput(inputData);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// Restore auto-bake state
|
||||
this.autoBake_ = autoBakeVal;
|
||||
this.autoBake();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next ingredient ID and increments it for next time.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
HTMLApp.prototype.nextIngId = function() {
|
||||
return this.ingId++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current recipe configuration.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
HTMLApp.prototype.getRecipeConfig = function() {
|
||||
var recipeConfig = this.manager.recipe.getConfig();
|
||||
sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig));
|
||||
return recipeConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given a recipe configuration, sets the recipe to that configuration.
|
||||
*
|
||||
* @param {Object[]} recipeConfig - The recipe configuration
|
||||
*/
|
||||
HTMLApp.prototype.setRecipeConfig = function(recipeConfig) {
|
||||
sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig));
|
||||
document.getElementById("rec-list").innerHTML = null;
|
||||
|
||||
for (var i = 0; i < recipeConfig.length; i++) {
|
||||
var item = this.manager.recipe.addOperation(recipeConfig[i].op);
|
||||
|
||||
// Populate arguments
|
||||
var args = item.querySelectorAll(".arg");
|
||||
for (var j = 0; j < args.length; j++) {
|
||||
if (args[j].getAttribute("type") === "checkbox") {
|
||||
// checkbox
|
||||
args[j].checked = recipeConfig[i].args[j];
|
||||
} else if (args[j].classList.contains("toggle-string")) {
|
||||
// toggleString
|
||||
args[j].value = recipeConfig[i].args[j].string;
|
||||
args[j].previousSibling.children[0].innerHTML =
|
||||
Utils.escapeHtml(recipeConfig[i].args[j].option) +
|
||||
" <span class='caret'></span>";
|
||||
} else {
|
||||
// all others
|
||||
args[j].value = recipeConfig[i].args[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Set disabled and breakpoint
|
||||
if (recipeConfig[i].disabled) {
|
||||
item.querySelector(".disable-icon").click();
|
||||
}
|
||||
if (recipeConfig[i].breakpoint) {
|
||||
item.querySelector(".breakpoint").click();
|
||||
}
|
||||
|
||||
this.progress = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the splitter positions to default.
|
||||
*/
|
||||
HTMLApp.prototype.resetLayout = function() {
|
||||
this.columnSplitter.setSizes([20, 30, 50]);
|
||||
this.ioSplitter.setSizes([50, 50]);
|
||||
|
||||
this.manager.controls.adjustWidth();
|
||||
this.manager.output.adjustWidth();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the compile message.
|
||||
*/
|
||||
HTMLApp.prototype.setCompileMessage = function() {
|
||||
// Display time since last build and compile message
|
||||
var now = new Date(),
|
||||
timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime),
|
||||
compileInfo = "<span style=\"font-weight: normal\">Last build: " +
|
||||
timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1) + " ago";
|
||||
|
||||
if (window.compileMessage !== "") {
|
||||
compileInfo += " - " + window.compileMessage;
|
||||
}
|
||||
|
||||
compileInfo += "</span>";
|
||||
document.getElementById("notice").innerHTML = compileInfo;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pops up a message to the user and writes it to the console log.
|
||||
*
|
||||
* @param {string} str - The message to display (HTML supported)
|
||||
* @param {string} style - The colour of the popup
|
||||
* "danger" = red
|
||||
* "warning" = amber
|
||||
* "info" = blue
|
||||
* "success" = green
|
||||
* @param {number} timeout - The number of milliseconds before the popup closes automatically
|
||||
* 0 for never (until the user closes it)
|
||||
* @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the
|
||||
* console
|
||||
*
|
||||
* @example
|
||||
* // Pops up a red box with the message "[current time] Error: Something has gone wrong!"
|
||||
* // that will need to be dismissed by the user.
|
||||
* this.alert("Error: Something has gone wrong!", "danger", 0);
|
||||
*
|
||||
* // Pops up a blue information box with the message "[current time] Happy Christmas!"
|
||||
* // that will disappear after 5 seconds.
|
||||
* this.alert("Happy Christmas!", "info", 5000);
|
||||
*/
|
||||
HTMLApp.prototype.alert = function(str, style, timeout, silent) {
|
||||
var time = new Date();
|
||||
|
||||
console.log("[" + time.toLocaleString() + "] " + str);
|
||||
if (silent) return;
|
||||
|
||||
style = style || "danger";
|
||||
timeout = timeout || 0;
|
||||
|
||||
var alertEl = document.getElementById("alert"),
|
||||
alertContent = document.getElementById("alert-content");
|
||||
|
||||
alertEl.classList.remove("alert-danger");
|
||||
alertEl.classList.remove("alert-warning");
|
||||
alertEl.classList.remove("alert-info");
|
||||
alertEl.classList.remove("alert-success");
|
||||
alertEl.classList.add("alert-" + style);
|
||||
|
||||
// If the box hasn't been closed, append to it rather than replacing
|
||||
if (alertEl.style.display === "block") {
|
||||
alertContent.innerHTML +=
|
||||
"<br><br>[" + time.toLocaleTimeString() + "] " + str;
|
||||
} else {
|
||||
alertContent.innerHTML =
|
||||
"[" + time.toLocaleTimeString() + "] " + str;
|
||||
}
|
||||
|
||||
// Stop the animation if it is in progress
|
||||
$("#alert").stop();
|
||||
alertEl.style.display = "block";
|
||||
alertEl.style.opacity = 1;
|
||||
|
||||
if (timeout > 0) {
|
||||
clearTimeout(this.alertTimeout);
|
||||
this.alertTimeout = setTimeout(function(){
|
||||
$("#alert").slideUp(100);
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pops up a box asking the user a question and sending the answer to a specified callback function.
|
||||
*
|
||||
* @param {string} title - The title of the box
|
||||
* @param {string} body - The question (HTML supported)
|
||||
* @param {function} callback - A function accepting one boolean argument which handles the
|
||||
* response e.g. function(answer) {...}
|
||||
* @param {Object} [scope=this] - The object to bind to the callback function
|
||||
*
|
||||
* @example
|
||||
* // Pops up a box asking if the user would like a cookie. Prints the answer to the console.
|
||||
* this.confirm("Question", "Would you like a cookie?", function(answer) {console.log(answer);});
|
||||
*/
|
||||
HTMLApp.prototype.confirm = function(title, body, callback, scope) {
|
||||
scope = scope || this;
|
||||
document.getElementById("confirm-title").innerHTML = title;
|
||||
document.getElementById("confirm-body").innerHTML = body;
|
||||
document.getElementById("confirm-modal").style.display = "block";
|
||||
|
||||
this.confirmClosed = false;
|
||||
$("#confirm-modal").modal()
|
||||
.one("show.bs.modal", function(e) {
|
||||
this.confirmClosed = false;
|
||||
}.bind(this))
|
||||
.one("click", "#confirm-yes", function() {
|
||||
this.confirmClosed = true;
|
||||
callback.bind(scope)(true);
|
||||
$("#confirm-modal").modal("hide");
|
||||
}.bind(this))
|
||||
.one("hide.bs.modal", function(e) {
|
||||
if (!this.confirmClosed)
|
||||
callback.bind(scope)(false);
|
||||
this.confirmClosed = true;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the alert close button click event.
|
||||
* Closes the alert box.
|
||||
*/
|
||||
HTMLApp.prototype.alertCloseClick = function() {
|
||||
document.getElementById("alert").style.display = "none";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for CyerChef statechange events.
|
||||
* Fires whenever the input or recipe changes in any way.
|
||||
*
|
||||
* @listens Manager#statechange
|
||||
* @param {event} e
|
||||
*/
|
||||
HTMLApp.prototype.stateChange = function(e) {
|
||||
this.autoBake();
|
||||
|
||||
// Update the current history state (not creating a new one)
|
||||
if (this.options.updateUrl) {
|
||||
this.lastStateUrl = this.manager.controls.generateStateUrl(true, true);
|
||||
window.history.replaceState({}, "CyberChef", this.lastStateUrl);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the history popstate event.
|
||||
* Reloads parameters from the URL.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HTMLApp.prototype.popState = function(e) {
|
||||
if (window.location.href.split("#")[0] !== this.lastStateUrl) {
|
||||
this.loadURIParams();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function to call an external API from this view.
|
||||
*/
|
||||
HTMLApp.prototype.callApi = function(url, type, data, dataType, contentType) {
|
||||
type = type || "POST";
|
||||
data = data || {};
|
||||
dataType = dataType || undefined;
|
||||
contentType = contentType || "application/json";
|
||||
|
||||
var response = null,
|
||||
success = false;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
async: false,
|
||||
type: type,
|
||||
data: data,
|
||||
dataType: dataType,
|
||||
contentType: contentType,
|
||||
success: function(data) {
|
||||
success = true;
|
||||
response = data;
|
||||
},
|
||||
error: function(data) {
|
||||
success = false;
|
||||
response = data;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: success,
|
||||
response: response
|
||||
};
|
||||
};
|
50
src/web/HTMLCategory.js
Executable file
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Object to handle the creation of operation categories.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name - The name of the category.
|
||||
* @param {boolean} selected - Whether this category is pre-selected or not.
|
||||
*/
|
||||
var HTMLCategory = module.exports = function(name, selected) {
|
||||
this.name = name;
|
||||
this.selected = selected;
|
||||
this.opList = [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an operation to this category.
|
||||
*
|
||||
* @param {HTMLOperation} operation - The operation to add.
|
||||
*/
|
||||
HTMLCategory.prototype.addOperation = function(operation) {
|
||||
this.opList.push(operation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the category and all operations within it in HTML.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
HTMLCategory.prototype.toHtml = function() {
|
||||
var catName = "cat" + this.name.replace(/[\s/-:_]/g, "");
|
||||
var html = "<div class='panel category'>\
|
||||
<a class='category-title' data-toggle='collapse'\
|
||||
data-parent='#categories' href='#" + catName + "'>\
|
||||
" + this.name + "\
|
||||
</a>\
|
||||
<div id='" + catName + "' class='panel-collapse collapse\
|
||||
" + (this.selected ? " in" : "") + "'><ul class='op-list'>";
|
||||
|
||||
for (var i = 0; i < this.opList.length; i++) {
|
||||
html += this.opList[i].toStubHtml();
|
||||
}
|
||||
|
||||
html += "</ul></div></div>";
|
||||
return html;
|
||||
};
|
212
src/web/HTMLIngredient.js
Executable file
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* Object to handle the creation of operation ingredients.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} config - The configuration object for this ingredient.
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var HTMLIngredient = module.exports = function(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.disableArgs = config.disableArgs || false;
|
||||
this.placeholder = config.placeholder || false;
|
||||
this.target = config.target;
|
||||
this.toggleValues = config.toggleValues;
|
||||
this.id = "ing-" + this.app.nextIngId();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the ingredient in HTML.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
HTMLIngredient.prototype.toHtml = function() {
|
||||
var inline = (this.type === "boolean" ||
|
||||
this.type === "number" ||
|
||||
this.type === "option" ||
|
||||
this.type === "shortString" ||
|
||||
this.type === "binaryShortString"),
|
||||
html = inline ? "" : "<div class='clearfix'> </div>",
|
||||
i, m;
|
||||
|
||||
html += "<div class='arg-group" + (inline ? " inline-args" : "") +
|
||||
(this.type === "text" ? " arg-group-text" : "") + "'><label class='arg-label' for='" +
|
||||
this.id + "'>" + this.name + "</label>";
|
||||
|
||||
switch (this.type) {
|
||||
case "string":
|
||||
case "binaryString":
|
||||
case "byteArray":
|
||||
html += "<input type='text' id='" + this.id + "' class='arg arg-input' arg-name='" +
|
||||
this.name + "' value='" + this.value + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
|
||||
break;
|
||||
case "shortString":
|
||||
case "binaryShortString":
|
||||
html += "<input type='text' id='" + this.id +
|
||||
"'class='arg arg-input short-string' arg-name='" + this.name + "'value='" +
|
||||
this.value + "'" + (this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
|
||||
break;
|
||||
case "toggleString":
|
||||
html += "<div class='input-group'><div class='input-group-btn'>\
|
||||
<button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'\
|
||||
aria-haspopup='true' aria-expanded='false'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") + ">" + this.toggleValues[0] +
|
||||
" <span class='caret'></span></button><ul class='dropdown-menu'>";
|
||||
for (i = 0; i < this.toggleValues.length; i++) {
|
||||
html += "<li><a href='#'>" + this.toggleValues[i] + "</a></li>";
|
||||
}
|
||||
html += "</ul></div><input type='text' class='arg arg-input toggle-string'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + "></div>";
|
||||
break;
|
||||
case "number":
|
||||
html += "<input type='number' id='" + this.id + "'class='arg arg-input' arg-name='" +
|
||||
this.name + "'value='" + this.value + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
|
||||
break;
|
||||
case "boolean":
|
||||
html += "<input type='checkbox' id='" + this.id + "'class='arg' arg-name='" +
|
||||
this.name + "'" + (this.value ? " checked='checked' " : "") +
|
||||
(this.disabled ? " disabled='disabled'" : "") + ">";
|
||||
|
||||
if (this.disableArgs) {
|
||||
this.manager.addDynamicListener("#" + this.id, "click", this.toggleDisableArgs, this);
|
||||
}
|
||||
break;
|
||||
case "option":
|
||||
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") + ">";
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
if ((m = this.value[i].match(/\[([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "<optgroup label='" + m[1] + "'>";
|
||||
} else if ((m = this.value[i].match(/\[\/([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "</optgroup>";
|
||||
} else {
|
||||
html += "<option>" + this.value[i] + "</option>";
|
||||
}
|
||||
}
|
||||
html += "</select>";
|
||||
break;
|
||||
case "populateOption":
|
||||
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") + ">";
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
if ((m = this.value[i].name.match(/\[([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "<optgroup label='" + m[1] + "'>";
|
||||
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
|
||||
html += "</optgroup>";
|
||||
} else {
|
||||
html += "<option populate-value='" + this.value[i].value + "'>" +
|
||||
this.value[i].name + "</option>";
|
||||
}
|
||||
}
|
||||
html += "</select>";
|
||||
|
||||
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
|
||||
break;
|
||||
case "editableOption":
|
||||
html += "<div class='editable-option'>";
|
||||
html += "<select class='editable-option-select' id='sel-" + this.id + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") + ">";
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
html += "<option value='" + this.value[i].value + "'>" + this.value[i].name + "</option>";
|
||||
}
|
||||
html += "</select>";
|
||||
html += "<input class='arg arg-input editable-option-input' id='" + this.id +
|
||||
"'arg-name='" + this.name + "'" + " value='" + this.value[0].value + "'" +
|
||||
(this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
|
||||
html += "</div>";
|
||||
|
||||
|
||||
this.manager.addDynamicListener("#sel-" + this.id, "change", this.editableOptionChange, this);
|
||||
break;
|
||||
case "text":
|
||||
html += "<textarea id='" + this.id + "' class='arg' arg-name='" +
|
||||
this.name + "'" + (this.disabled ? " disabled='disabled'" : "") +
|
||||
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">" +
|
||||
this.value + "</textarea>";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
html += "</div>";
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for argument disable toggle.
|
||||
* Toggles disabled state for all arguments in the disableArgs list for this ingredient.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HTMLIngredient.prototype.toggleDisableArgs = function(e) {
|
||||
var el = e.target,
|
||||
op = el.parentNode.parentNode,
|
||||
args = op.querySelectorAll(".arg-group"),
|
||||
els;
|
||||
|
||||
for (var i = 0; i < this.disableArgs.length; i++) {
|
||||
els = args[this.disableArgs[i]].querySelectorAll("input, select, button");
|
||||
|
||||
for (var j = 0; j < els.length; j++) {
|
||||
if (els[j].getAttribute("disabled")) {
|
||||
els[j].removeAttribute("disabled");
|
||||
} else {
|
||||
els[j].setAttribute("disabled", "disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.manager.recipe.ingChange();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for populate option changes.
|
||||
* Populates the relevant argument with the specified value.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HTMLIngredient.prototype.populateOptionChange = function(e) {
|
||||
var el = e.target,
|
||||
op = el.parentNode.parentNode,
|
||||
target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea");
|
||||
|
||||
target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
|
||||
|
||||
this.manager.recipe.ingChange();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for editable option changes.
|
||||
* Populates the input box with the selected value.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HTMLIngredient.prototype.editableOptionChange = function(e) {
|
||||
var select = e.target,
|
||||
input = select.nextSibling;
|
||||
|
||||
input.value = select.childNodes[select.selectedIndex].value;
|
||||
|
||||
this.manager.recipe.ingChange();
|
||||
};
|
117
src/web/HTMLOperation.js
Executable file
|
@ -0,0 +1,117 @@
|
|||
var HTMLIngredient = require("./HTMLIngredient.js");
|
||||
|
||||
|
||||
/**
|
||||
* Object to handle the creation of operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name - The name of the operation.
|
||||
* @param {Object} config - The configuration object for this operation.
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var HTMLOperation = module.exports = function(name, config, app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
|
||||
this.name = name;
|
||||
this.description = config.description;
|
||||
this.manualBake = config.manualBake || false;
|
||||
this.config = config;
|
||||
this.ingList = [];
|
||||
|
||||
for (var i = 0; i < config.args.length; i++) {
|
||||
var ing = new HTMLIngredient(config.args[i], this.app, this.manager);
|
||||
this.ingList.push(ing);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
*/
|
||||
HTMLOperation.INFO_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAByElEQVR4XqVTzWoaYRQ9KZJmoVaS1J1QiYTIuOgqi9lEugguQhYhdGs3hTyAi0CWJTvJIks30ZBNsimUtlqkVLoQCuJsphRriyFjabWtEyf/Rv3iWcwwymTlgQuH851z5hu43wRGkEwmXwCIA4hiGAUAmUQikQbhEHwyGCWVSglVVUW73RYmyKnxjB56ncJ6NpsVxHGrI/ZLuniVb3DIqQmCHnrNkgcggNeSJPlisRgyJR2b737j/TcDsQUPwv6H5NR4BnroZcb6Z16N2PvyX6yna9Z8qp6JQ0Uf0ughmGHWBSAuyzJqrQ7eqKewY/dzE363C71e39LoWQq5wUwul4uzIBoIBHD01RgyrkZ8eDbvwUWnj623v2DHx4qB51IAzLIAXq8XP/7W0bUVVJtXWIk8wvlN364TA+/1IDMLwmWK/Hq3axmhaBdoGLeklm73ElaBYRgIzkyifHIOO4QQJKM3oJcZq6CgaVp0OTyHw9K/kQI4FiyHfdC0n2CWe5ApFosIPZ7C2tNpXpcDOehGyD/FIbd0euhlhllzFxRzC3fydbG4XRYbB9/tQ41n9m1U7l3lyp9LkfygiZeZCoecmtMqj/+Yxn7Od3v0j50qCO3zAAAAAElFTkSuQmCC";
|
||||
/**
|
||||
* @constant
|
||||
*/
|
||||
HTMLOperation.REMOVE_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwklEQVR42qRTPU8CQRB9K2CCMRJ6NTQajOUaqfxIbLCRghhjQixosLAgFNBQ3l8wsabxLxBJbCyVUBiMCVQEQkOEKBbCnefM3p4eohWXzM3uvHlv52b2hG3bmOWZw4yPn1/XQkCQ9wFxcgZZ0QLKpifpN8Z1n1L13griBBjHhYK0nMT4b+wom53ClAAFQacZJ/m8rNfrSOZy0vxJjPP6IJ2WzWYTO6mUwiwtILiJJSHUKVSWkchkZK1WQzQaxU2pVGUglkjIbreLUCiEx0qlStlFCpfPiPstYDtVKJH9ZFI2Gw1FGA6H6LTbCAaDeGu1FJl6UuYjpwTGzucokZW1NfnS66kyfT4fXns9RaZmlgNcuhZQU+jowLzuOK/HgwEW3E5ZlhLXVWKk11P3wNYNWw+HZdA0sUgx1zjGmD05nckx0ilGjBJdUq3fr7K5e8bGf43RdL7fOPSQb4lI8SLbrUfkUIuY32VTI1bJn5BqDnh4Dodt9ryPUDzyD7aquWoKQohl2i9sAbubwPkTcHkP3FHsg+yT+7sN7G0AF3Xg6sHB3onbdgWWKBDQg/BcTuVt51dQA/JrnIcyIu6rmPV3/hJgACPc0BMEYTg+AAAAAElFTkSuQmCC";
|
||||
|
||||
|
||||
/**
|
||||
* Renders the operation in HTML as a stub operation with no ingredients.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
HTMLOperation.prototype.toStubHtml = function(removeIcon) {
|
||||
var html = "<li class='operation'";
|
||||
|
||||
if (this.description) {
|
||||
html += " data-container='body' data-toggle='popover' data-placement='auto right'\
|
||||
data-content=\"" + this.description + "\" data-html='true' data-trigger='hover'";
|
||||
}
|
||||
|
||||
html += ">" + this.name;
|
||||
|
||||
if (removeIcon) {
|
||||
html += "<img src='data:image/png;base64," + HTMLOperation.REMOVE_ICON +
|
||||
"' class='op-icon remove-icon'>";
|
||||
}
|
||||
|
||||
if (this.description) {
|
||||
html += "<img src='data:image/png;base64," + HTMLOperation.INFO_ICON + "' class='op-icon'>";
|
||||
}
|
||||
|
||||
html += "</li>";
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the operation in HTML as a full operation with ingredients.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
HTMLOperation.prototype.toFullHtml = function() {
|
||||
var html = "<div class='arg-title'>" + this.name + "</div>";
|
||||
|
||||
for (var i = 0; i < this.ingList.length; i++) {
|
||||
html += this.ingList[i].toHtml();
|
||||
}
|
||||
|
||||
html += "<div class='recip-icons'>\
|
||||
<div class='breakpoint' title='Set breakpoint' break='false'></div>\
|
||||
<div class='disable-icon recip-icon' title='Disable operation'\
|
||||
disabled='false'></div>";
|
||||
|
||||
html += "</div>\
|
||||
<div class='clearfix'> </div>";
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Highlights the searched string in the name and description of the operation.
|
||||
*
|
||||
* @param {string} searchStr
|
||||
* @param {number} namePos - The position of the search string in the operation name
|
||||
* @param {number} descPos - The position of the search string in the operation description
|
||||
*/
|
||||
HTMLOperation.prototype.highlightSearchString = function(searchStr, namePos, descPos) {
|
||||
if (namePos >= 0) {
|
||||
this.name = this.name.slice(0, namePos) + "<b><u>" +
|
||||
this.name.slice(namePos, namePos + searchStr.length) + "</u></b>" +
|
||||
this.name.slice(namePos + searchStr.length);
|
||||
}
|
||||
|
||||
if (this.description && descPos >= 0) {
|
||||
this.description = this.description.slice(0, descPos) + "<b><u>" +
|
||||
this.description.slice(descPos, descPos + searchStr.length) + "</u></b>" +
|
||||
this.description.slice(descPos + searchStr.length);
|
||||
}
|
||||
};
|
509
src/web/HighlighterWaiter.js
Executable file
|
@ -0,0 +1,509 @@
|
|||
var Utils = require("../core/Utils.js");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to highlighting in CyberChef.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
*/
|
||||
var HighlighterWaiter = module.exports = function(app) {
|
||||
this.app = app;
|
||||
|
||||
this.mouseButtonDown = false;
|
||||
this.mouseTarget = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* HighlighterWaiter data type enum for the input.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
HighlighterWaiter.INPUT = 0;
|
||||
/**
|
||||
* HighlighterWaiter data type enum for the output.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
HighlighterWaiter.OUTPUT = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the current text selection is running backwards or forwards.
|
||||
* StackOverflow answer id: 12652116
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
HighlighterWaiter.prototype._isSelectionBackwards = function() {
|
||||
var backwards = false,
|
||||
sel = window.getSelection();
|
||||
|
||||
if (!sel.isCollapsed) {
|
||||
var range = document.createRange();
|
||||
range.setStart(sel.anchorNode, sel.anchorOffset);
|
||||
range.setEnd(sel.focusNode, sel.focusOffset);
|
||||
backwards = range.collapsed;
|
||||
range.detach();
|
||||
}
|
||||
return backwards;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the text offset of a position in an HTML element, ignoring HTML tags.
|
||||
*
|
||||
* @private
|
||||
* @param {element} node - The parent HTML node.
|
||||
* @param {number} offset - The offset since the last HTML element.
|
||||
* @returns {number}
|
||||
*/
|
||||
HighlighterWaiter.prototype._getOutputHtmlOffset = function(node, offset) {
|
||||
var sel = window.getSelection(),
|
||||
range = document.createRange();
|
||||
|
||||
range.selectNodeContents(document.getElementById("output-html"));
|
||||
range.setEnd(node, offset);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
return sel.toString().length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current selection offsets in the output HTML, ignoring HTML tags.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object} pos
|
||||
* @returns {number} pos.start
|
||||
* @returns {number} pos.end
|
||||
*/
|
||||
HighlighterWaiter.prototype._getOutputHtmlSelectionOffsets = function() {
|
||||
var sel = window.getSelection(),
|
||||
range,
|
||||
start = 0,
|
||||
end = 0,
|
||||
backwards = false;
|
||||
|
||||
if (sel.rangeCount) {
|
||||
range = sel.getRangeAt(sel.rangeCount - 1);
|
||||
backwards = this._isSelectionBackwards();
|
||||
start = this._getOutputHtmlOffset(range.startContainer, range.startOffset);
|
||||
end = this._getOutputHtmlOffset(range.endContainer, range.endOffset);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
if (backwards) {
|
||||
// If selecting backwards, reverse the start and end offsets for the selection to
|
||||
// prevent deselecting as the drag continues.
|
||||
sel.collapseToEnd();
|
||||
sel.extend(sel.anchorNode, range.startOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input scroll events.
|
||||
* Scrolls the highlighter pane to match the input textarea position.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.inputScroll = function(e) {
|
||||
var el = e.target;
|
||||
document.getElementById("input-highlighter").scrollTop = el.scrollTop;
|
||||
document.getElementById("input-highlighter").scrollLeft = el.scrollLeft;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output scroll events.
|
||||
* Scrolls the highlighter pane to match the output textarea position.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputScroll = function(e) {
|
||||
var el = e.target;
|
||||
document.getElementById("output-highlighter").scrollTop = el.scrollTop;
|
||||
document.getElementById("output-highlighter").scrollLeft = el.scrollLeft;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input mousedown events.
|
||||
* Calculates the current selection info, and highlights the corresponding data in the output.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.inputMousedown = function(e) {
|
||||
this.mouseButtonDown = true;
|
||||
this.mouseTarget = HighlighterWaiter.INPUT;
|
||||
this.removeHighlights();
|
||||
|
||||
var el = e.target,
|
||||
start = el.selectionStart,
|
||||
end = el.selectionEnd;
|
||||
|
||||
if (start !== 0 || end !== 0) {
|
||||
document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
|
||||
this.highlightOutput([{start: start, end: end}]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output mousedown events.
|
||||
* Calculates the current selection info, and highlights the corresponding data in the input.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputMousedown = function(e) {
|
||||
this.mouseButtonDown = true;
|
||||
this.mouseTarget = HighlighterWaiter.OUTPUT;
|
||||
this.removeHighlights();
|
||||
|
||||
var el = e.target,
|
||||
start = el.selectionStart,
|
||||
end = el.selectionEnd;
|
||||
|
||||
if (start !== 0 || end !== 0) {
|
||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
|
||||
this.highlightInput([{start: start, end: end}]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output HTML mousedown events.
|
||||
* Calculates the current selection info.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputHtmlMousedown = function(e) {
|
||||
this.mouseButtonDown = true;
|
||||
this.mouseTarget = HighlighterWaiter.OUTPUT;
|
||||
|
||||
var sel = this._getOutputHtmlSelectionOffsets();
|
||||
if (sel.start !== 0 || sel.end !== 0) {
|
||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input mouseup events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.inputMouseup = function(e) {
|
||||
this.mouseButtonDown = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output mouseup events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputMouseup = function(e) {
|
||||
this.mouseButtonDown = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output HTML mouseup events.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputHtmlMouseup = function(e) {
|
||||
this.mouseButtonDown = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input mousemove events.
|
||||
* Calculates the current selection info, and highlights the corresponding data in the output.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.inputMousemove = function(e) {
|
||||
// Check that the left mouse button is pressed
|
||||
if (!this.mouseButtonDown ||
|
||||
e.which !== 1 ||
|
||||
this.mouseTarget !== HighlighterWaiter.INPUT)
|
||||
return;
|
||||
|
||||
var el = e.target,
|
||||
start = el.selectionStart,
|
||||
end = el.selectionEnd;
|
||||
|
||||
if (start !== 0 || end !== 0) {
|
||||
document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
|
||||
this.highlightOutput([{start: start, end: end}]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output mousemove events.
|
||||
* Calculates the current selection info, and highlights the corresponding data in the input.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputMousemove = function(e) {
|
||||
// Check that the left mouse button is pressed
|
||||
if (!this.mouseButtonDown ||
|
||||
e.which !== 1 ||
|
||||
this.mouseTarget !== HighlighterWaiter.OUTPUT)
|
||||
return;
|
||||
|
||||
var el = e.target,
|
||||
start = el.selectionStart,
|
||||
end = el.selectionEnd;
|
||||
|
||||
if (start !== 0 || end !== 0) {
|
||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
|
||||
this.highlightInput([{start: start, end: end}]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for output HTML mousemove events.
|
||||
* Calculates the current selection info.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
HighlighterWaiter.prototype.outputHtmlMousemove = function(e) {
|
||||
// Check that the left mouse button is pressed
|
||||
if (!this.mouseButtonDown ||
|
||||
e.which !== 1 ||
|
||||
this.mouseTarget !== HighlighterWaiter.OUTPUT)
|
||||
return;
|
||||
|
||||
var sel = this._getOutputHtmlSelectionOffsets();
|
||||
if (sel.start !== 0 || sel.end !== 0) {
|
||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given start and end offsets, writes the HTML for the selection info element with the correct
|
||||
* padding.
|
||||
*
|
||||
* @param {number} start - The start offset.
|
||||
* @param {number} end - The end offset.
|
||||
* @returns {string}
|
||||
*/
|
||||
HighlighterWaiter.prototype.selectionInfo = function(start, end) {
|
||||
var width = end.toString().length;
|
||||
width = width < 2 ? 2 : width;
|
||||
var startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " "),
|
||||
endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " "),
|
||||
lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, " ");
|
||||
|
||||
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes highlighting and selection information.
|
||||
*/
|
||||
HighlighterWaiter.prototype.removeHighlights = function() {
|
||||
document.getElementById("input-highlighter").innerHTML = "";
|
||||
document.getElementById("output-highlighter").innerHTML = "";
|
||||
document.getElementById("input-selection-info").innerHTML = "";
|
||||
document.getElementById("output-selection-info").innerHTML = "";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
var recipeConfig = this.app.getRecipeConfig(),
|
||||
highlights = [];
|
||||
|
||||
for (var 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;
|
||||
|
||||
var 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:
|
||||
* - input hasn't changed since last bake
|
||||
* - last bake was a full bake
|
||||
* - all operations in the recipe support highlighting
|
||||
*
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
HighlighterWaiter.prototype.highlightOutput = function(pos) {
|
||||
var highlights = this.generateHighlightList();
|
||||
|
||||
if (!highlights || !this.app.autoBake_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var 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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Highlights the given offsets in the input.
|
||||
* We will only highlight if:
|
||||
* - input hasn't changed since last bake
|
||||
* - last bake was a full bake
|
||||
* - all operations in the recipe support highlighting
|
||||
*
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
HighlighterWaiter.prototype.highlightInput = function(pos) {
|
||||
var highlights = this.generateHighlightList();
|
||||
|
||||
if (!highlights || !this.app.autoBake_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < highlights.length; i++) {
|
||||
// Remove multiple highlights before processing again
|
||||
pos = [pos[0]];
|
||||
|
||||
if (typeof highlights[i].b == "function") {
|
||||
pos = highlights[i].b(pos, highlights[i].args);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("input-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
|
||||
this.highlight(
|
||||
document.getElementById("input-text"),
|
||||
document.getElementById("input-highlighter"),
|
||||
pos);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the relevant HTML to the specified highlight element such that highlighting appears
|
||||
* underneath the correct offset.
|
||||
*
|
||||
* @param {element} textarea - The input or output textarea.
|
||||
* @param {element} highlighter - The input or output highlighter element.
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) {
|
||||
if (!this.app.options.showHighlighter) return false;
|
||||
if (!this.app.options.attemptHighlight) return false;
|
||||
|
||||
// Check if there is a carriage return in the output dish as this will not
|
||||
// be displayed by the HTML textarea and will mess up highlighting offsets.
|
||||
if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false;
|
||||
|
||||
var startPlaceholder = "[startHighlight]",
|
||||
startPlaceholderRegex = /\[startHighlight\]/g,
|
||||
endPlaceholder = "[endHighlight]",
|
||||
endPlaceholderRegex = /\[endHighlight\]/g,
|
||||
text = textarea.value;
|
||||
|
||||
// Put placeholders in position
|
||||
// If there's only one value, select that
|
||||
// If there are multiple, ignore the first one and select all others
|
||||
if (pos.length === 1) {
|
||||
if (pos[0].end < pos[0].start) return;
|
||||
text = text.slice(0, pos[0].start) +
|
||||
startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder +
|
||||
text.slice(pos[0].end, text.length);
|
||||
} else {
|
||||
// O(n^2) - Can anyone improve this without overwriting placeholders?
|
||||
var result = "",
|
||||
endPlaced = true;
|
||||
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
for (var j = 1; j < pos.length; j++) {
|
||||
if (pos[j].end < pos[j].start) continue;
|
||||
if (pos[j].start === i) {
|
||||
result += startPlaceholder;
|
||||
endPlaced = false;
|
||||
}
|
||||
if (pos[j].end === i) {
|
||||
result += endPlaceholder;
|
||||
endPlaced = true;
|
||||
}
|
||||
}
|
||||
result += text[i];
|
||||
}
|
||||
if (!endPlaced) result += endPlaceholder;
|
||||
text = result;
|
||||
}
|
||||
|
||||
var cssClass = "hl1";
|
||||
//if (colour) cssClass += "-"+colour;
|
||||
|
||||
// Remove HTML tags
|
||||
text = text.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\n/g, " ")
|
||||
// Convert placeholders to tags
|
||||
.replace(startPlaceholderRegex, "<span class=\""+cssClass+"\">")
|
||||
.replace(endPlaceholderRegex, "</span>") + " ";
|
||||
|
||||
// Adjust width to allow for scrollbars
|
||||
highlighter.style.width = textarea.clientWidth + "px";
|
||||
highlighter.innerHTML = text;
|
||||
highlighter.scrollTop = textarea.scrollTop;
|
||||
highlighter.scrollLeft = textarea.scrollLeft;
|
||||
};
|
220
src/web/InputWaiter.js
Executable file
|
@ -0,0 +1,220 @@
|
|||
var Utils = require("../core/Utils.js");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to the input.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var InputWaiter = module.exports = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
|
||||
// Define keys that don't change the input so we don't have to autobake when they are pressed
|
||||
this.badKeys = [
|
||||
16, //Shift
|
||||
17, //Ctrl
|
||||
18, //Alt
|
||||
19, //Pause
|
||||
20, //Caps
|
||||
27, //Esc
|
||||
33, 34, 35, 36, //PgUp, PgDn, End, Home
|
||||
37, 38, 39, 40, //Directional
|
||||
44, //PrntScrn
|
||||
91, 92, //Win
|
||||
93, //Context
|
||||
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, //F1-12
|
||||
144, //Num
|
||||
145, //Scroll
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the user's input from the input textarea.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
InputWaiter.prototype.get = function() {
|
||||
return document.getElementById("input-text").value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the input in the input textarea.
|
||||
*
|
||||
* @param {string} input
|
||||
*
|
||||
* @fires Manager#statechange
|
||||
*/
|
||||
InputWaiter.prototype.set = function(input) {
|
||||
document.getElementById("input-text").value = input;
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Displays information about the input.
|
||||
*
|
||||
* @param {number} length - The length of the current input string
|
||||
* @param {number} lines - The number of the lines in the current input string
|
||||
*/
|
||||
InputWaiter.prototype.setInputInfo = function(length, lines) {
|
||||
var width = length.toString().length;
|
||||
width = width < 2 ? 2 : width;
|
||||
|
||||
var lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " ");
|
||||
var linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " ");
|
||||
|
||||
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input scroll events.
|
||||
* Scrolls the highlighter pane to match the input textarea position and updates history state.
|
||||
*
|
||||
* @param {event} e
|
||||
*
|
||||
* @fires Manager#statechange
|
||||
*/
|
||||
InputWaiter.prototype.inputChange = function(e) {
|
||||
// Remove highlighting from input and output panes as the offsets might be different now
|
||||
this.manager.highlighter.removeHighlights();
|
||||
|
||||
// Reset recipe progress as any previous processing will be redundant now
|
||||
this.app.progress = 0;
|
||||
|
||||
// Update the input metadata info
|
||||
var inputText = this.get(),
|
||||
lines = inputText.count("\n") + 1;
|
||||
|
||||
this.setInputInfo(inputText.length, lines);
|
||||
|
||||
|
||||
if (this.badKeys.indexOf(e.keyCode) < 0) {
|
||||
// Fire the statechange event as the input has been modified
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input dragover events.
|
||||
* Gives the user a visual cue to show that items can be dropped here.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
InputWaiter.prototype.inputDragover = function(e) {
|
||||
// This will be set if we're dragging an operation
|
||||
if (e.dataTransfer.effectAllowed === "move")
|
||||
return false;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.target.classList.add("dropping-file");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input dragleave events.
|
||||
* Removes the visual cue.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
InputWaiter.prototype.inputDragleave = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.target.classList.remove("dropping-file");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for input drop events.
|
||||
* Loads the dragged data into the input textarea.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
InputWaiter.prototype.inputDrop = function(e) {
|
||||
// This will be set if we're dragging an operation
|
||||
if (e.dataTransfer.effectAllowed === "move")
|
||||
return false;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
var el = e.target,
|
||||
file = e.dataTransfer.files[0],
|
||||
text = e.dataTransfer.getData("Text"),
|
||||
reader = new FileReader(),
|
||||
inputCharcode = "",
|
||||
offset = 0,
|
||||
CHUNK_SIZE = 20480; // 20KB
|
||||
|
||||
var setInput = function() {
|
||||
if (inputCharcode.length > 100000 && this.app.autoBake_) {
|
||||
this.manager.controls.setAutoBake(false);
|
||||
this.app.alert("Turned off Auto Bake as the input is large", "warning", 5000);
|
||||
}
|
||||
|
||||
this.set(inputCharcode);
|
||||
var recipeConfig = this.app.getRecipeConfig();
|
||||
if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") {
|
||||
recipeConfig.unshift({op:"From Hex", args:["Space"]});
|
||||
this.app.setRecipeConfig(recipeConfig);
|
||||
}
|
||||
|
||||
el.classList.remove("loadingFile");
|
||||
}.bind(this);
|
||||
|
||||
var seek = function() {
|
||||
if (offset >= file.size) {
|
||||
setInput();
|
||||
return;
|
||||
}
|
||||
el.value = "Processing... " + Math.round(offset / file.size * 100) + "%";
|
||||
var slice = file.slice(offset, offset + CHUNK_SIZE);
|
||||
reader.readAsArrayBuffer(slice);
|
||||
};
|
||||
|
||||
reader.onload = function(e) {
|
||||
var data = new Uint8Array(reader.result);
|
||||
inputCharcode += Utils.toHexFast(data);
|
||||
offset += CHUNK_SIZE;
|
||||
seek();
|
||||
};
|
||||
|
||||
|
||||
el.classList.remove("dropping-file");
|
||||
|
||||
if (file) {
|
||||
el.classList.add("loadingFile");
|
||||
seek();
|
||||
} else if (text) {
|
||||
this.set(text);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for clear IO events.
|
||||
* Resets the input, output and info areas.
|
||||
*
|
||||
* @fires Manager#statechange
|
||||
*/
|
||||
InputWaiter.prototype.clearIoClick = function() {
|
||||
this.manager.highlighter.removeHighlights();
|
||||
document.getElementById("input-text").value = "";
|
||||
document.getElementById("output-text").value = "";
|
||||
document.getElementById("input-info").innerHTML = "";
|
||||
document.getElementById("output-info").innerHTML = "";
|
||||
document.getElementById("input-selection-info").innerHTML = "";
|
||||
document.getElementById("output-selection-info").innerHTML = "";
|
||||
window.dispatchEvent(this.manager.statechange);
|
||||
};
|
276
src/web/Manager.js
Executable file
|
@ -0,0 +1,276 @@
|
|||
var WindowWaiter = require("./WindowWaiter.js"),
|
||||
ControlsWaiter = require("./ControlsWaiter.js"),
|
||||
RecipeWaiter = require("./RecipeWaiter.js"),
|
||||
OperationsWaiter = require("./OperationsWaiter.js"),
|
||||
InputWaiter = require("./InputWaiter.js"),
|
||||
OutputWaiter = require("./OutputWaiter.js"),
|
||||
OptionsWaiter = require("./OptionsWaiter.js"),
|
||||
HighlighterWaiter = require("./HighlighterWaiter.js"),
|
||||
SeasonalWaiter = require("./SeasonalWaiter.js");
|
||||
|
||||
|
||||
/**
|
||||
* This object controls the Waiters responsible for handling events from all areas of the app.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
*/
|
||||
var Manager = module.exports = function(app) {
|
||||
this.app = app;
|
||||
|
||||
// Define custom events
|
||||
/**
|
||||
* @event Manager#appstart
|
||||
*/
|
||||
this.appstart = new CustomEvent("appstart", {bubbles: true});
|
||||
/**
|
||||
* @event Manager#operationadd
|
||||
*/
|
||||
this.operationadd = new CustomEvent("operationadd", {bubbles: true});
|
||||
/**
|
||||
* @event Manager#operationremove
|
||||
*/
|
||||
this.operationremove = new CustomEvent("operationremove", {bubbles: true});
|
||||
/**
|
||||
* @event Manager#oplistcreate
|
||||
*/
|
||||
this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true});
|
||||
/**
|
||||
* @event Manager#statechange
|
||||
*/
|
||||
this.statechange = new CustomEvent("statechange", {bubbles: true});
|
||||
|
||||
// Define Waiter objects to handle various areas
|
||||
this.window = new WindowWaiter(this.app);
|
||||
this.controls = new ControlsWaiter(this.app, this);
|
||||
this.recipe = new RecipeWaiter(this.app, this);
|
||||
this.ops = new OperationsWaiter(this.app, this);
|
||||
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.seasonal = new SeasonalWaiter(this.app, this);
|
||||
|
||||
// Object to store dynamic handlers to fire on elements that may not exist yet
|
||||
this.dynamicHandlers = {};
|
||||
|
||||
this.initialiseEventListeners();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the various components and listeners.
|
||||
*/
|
||||
Manager.prototype.setup = function() {
|
||||
this.recipe.initialiseOperationDragNDrop();
|
||||
this.controls.autoBakeChange();
|
||||
this.seasonal.load();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Main function to handle the creation of the event listeners.
|
||||
*/
|
||||
Manager.prototype.initialiseEventListeners = function() {
|
||||
// Global
|
||||
window.addEventListener("resize", this.window.windowResize.bind(this.window));
|
||||
window.addEventListener("blur", this.window.windowBlur.bind(this.window));
|
||||
window.addEventListener("focus", this.window.windowFocus.bind(this.window));
|
||||
window.addEventListener("statechange", this.app.stateChange.bind(this.app));
|
||||
window.addEventListener("popstate", this.app.popState.bind(this.app));
|
||||
|
||||
// Controls
|
||||
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
|
||||
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
|
||||
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
|
||||
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
|
||||
document.getElementById("clr-breaks").addEventListener("click", this.controls.clearBreaksClick.bind(this.controls));
|
||||
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
|
||||
document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls));
|
||||
document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls));
|
||||
document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls));
|
||||
document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls));
|
||||
document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls));
|
||||
document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls));
|
||||
document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls));
|
||||
document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls));
|
||||
this.addMultiEventListener("#save-text", "keyup paste", this.controls.saveTextChange, this.controls);
|
||||
|
||||
// Operations
|
||||
this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops);
|
||||
this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops);
|
||||
document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops));
|
||||
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
|
||||
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
|
||||
this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops);
|
||||
this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops);
|
||||
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
|
||||
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd.bind(this.recipe));
|
||||
|
||||
// Recipe
|
||||
this.addDynamicListener(".arg", "keyup", this.recipe.ingChange, this.recipe);
|
||||
this.addDynamicListener(".arg", "change", this.recipe.ingChange, this.recipe);
|
||||
this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe);
|
||||
this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe);
|
||||
this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe);
|
||||
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
|
||||
this.addDynamicListener("#rec-list .input-group .dropdown-menu a", "click", this.recipe.dropdownToggleClick, this.recipe);
|
||||
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
|
||||
|
||||
// Input
|
||||
this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input);
|
||||
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
|
||||
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
|
||||
document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input));
|
||||
document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input));
|
||||
document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input));
|
||||
document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter));
|
||||
document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter));
|
||||
document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter));
|
||||
this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter);
|
||||
|
||||
// Output
|
||||
document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output));
|
||||
document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output));
|
||||
document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output));
|
||||
document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output));
|
||||
document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter));
|
||||
document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter));
|
||||
document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
|
||||
document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter));
|
||||
document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter));
|
||||
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
|
||||
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
|
||||
|
||||
// Options
|
||||
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
|
||||
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
|
||||
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
|
||||
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
|
||||
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
|
||||
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
|
||||
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
|
||||
|
||||
// Misc
|
||||
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener to each element in the specified group.
|
||||
*
|
||||
* @param {string} selector - A selector string for the element group to add the event to, see
|
||||
* this.getAll()
|
||||
* @param {string} eventType - The event to listen for
|
||||
* @param {function} callback - The function to execute when the event is triggered
|
||||
* @param {Object} [scope=this] - The object to bind to the callback function
|
||||
*
|
||||
* @example
|
||||
* // Calls the clickable function whenever any element with the .clickable class is clicked
|
||||
* this.addListeners(".clickable", "click", this.clickable, this);
|
||||
*/
|
||||
Manager.prototype.addListeners = function(selector, eventType, callback, scope) {
|
||||
scope = scope || this;
|
||||
[].forEach.call(document.querySelectorAll(selector), function(el) {
|
||||
el.addEventListener(eventType, callback.bind(scope));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple event listeners to the specified element.
|
||||
*
|
||||
* @param {string} selector - A selector string for the element to add the events to
|
||||
* @param {string} eventTypes - A space-separated string of all the event types to listen for
|
||||
* @param {function} callback - The function to execute when the events are triggered
|
||||
* @param {Object} [scope=this] - The object to bind to the callback function
|
||||
*
|
||||
* @example
|
||||
* // Calls the search function whenever the the keyup, paste or search events are triggered on the
|
||||
* // search element
|
||||
* this.addMultiEventListener("search", "keyup paste search", this.search, this);
|
||||
*/
|
||||
Manager.prototype.addMultiEventListener = function(selector, eventTypes, callback, scope) {
|
||||
var evs = eventTypes.split(" ");
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
document.querySelector(selector).addEventListener(evs[i], callback.bind(scope));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple event listeners to each element in the specified group.
|
||||
*
|
||||
* @param {string} selector - A selector string for the element group to add the events to
|
||||
* @param {string} eventTypes - A space-separated string of all the event types to listen for
|
||||
* @param {function} callback - The function to execute when the events are triggered
|
||||
* @param {Object} [scope=this] - The object to bind to the callback function
|
||||
*
|
||||
* @example
|
||||
* // Calls the save function whenever the the keyup or paste events are triggered on any element
|
||||
* // with the .saveable class
|
||||
* this.addMultiEventListener(".saveable", "keyup paste", this.save, this);
|
||||
*/
|
||||
Manager.prototype.addMultiEventListeners = function(selector, eventTypes, callback, scope) {
|
||||
var evs = eventTypes.split(" ");
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
this.addListeners(selector, evs[i], callback, scope);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener to the global document object which will listen on dynamic elements which
|
||||
* may not exist in the DOM yet.
|
||||
*
|
||||
* @param {string} selector - A selector string for the element(s) to add the event to
|
||||
* @param {string} eventType - The event(s) to listen for
|
||||
* @param {function} callback - The function to execute when the event(s) is/are triggered
|
||||
* @param {Object} [scope=this] - The object to bind to the callback function
|
||||
*
|
||||
* @example
|
||||
* // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this
|
||||
* // listener is created
|
||||
* this.addDynamicListener("button", "click", alert, this);
|
||||
*/
|
||||
Manager.prototype.addDynamicListener = function(selector, eventType, callback, scope) {
|
||||
var eventConfig = {
|
||||
selector: selector,
|
||||
callback: callback.bind(scope || this)
|
||||
};
|
||||
|
||||
if (this.dynamicHandlers.hasOwnProperty(eventType)) {
|
||||
// Listener already exists, add new handler to the appropriate list
|
||||
this.dynamicHandlers[eventType].push(eventConfig);
|
||||
} else {
|
||||
this.dynamicHandlers[eventType] = [eventConfig];
|
||||
// Set up listener for this new type
|
||||
document.addEventListener(eventType, this.dynamicListenerHandler.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for dynamic events. This function is called for any dynamic event and decides which
|
||||
* callback(s) to execute based on the type and selector.
|
||||
*
|
||||
* @param {Event} e - The event to be handled
|
||||
*/
|
||||
Manager.prototype.dynamicListenerHandler = function(e) {
|
||||
var handlers = this.dynamicHandlers[e.type],
|
||||
matches = e.target.matches ||
|
||||
e.target.webkitMatchesSelector ||
|
||||
e.target.mozMatchesSelector ||
|
||||
e.target.msMatchesSelector ||
|
||||
e.target.oMatchesSelector;
|
||||
|
||||
for (var i = 0; i < handlers.length; i++) {
|
||||
if (matches && e.target[matches.name](handlers[i].selector)) {
|
||||
handlers[i].callback(e);
|
||||
}
|
||||
}
|
||||
};
|
285
src/web/OperationsWaiter.js
Executable file
|
@ -0,0 +1,285 @@
|
|||
var HTMLOperation = require("./HTMLOperation.js"),
|
||||
Sortable = require("sortablejs");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to the operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var OperationsWaiter = module.exports = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
|
||||
this.options = {};
|
||||
this.removeIntent = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for search events.
|
||||
* Finds operations which match the given search term and displays them under the search box.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.searchOperations = function(e) {
|
||||
var ops, selected;
|
||||
|
||||
if (e.type === "search") { // Search
|
||||
e.preventDefault();
|
||||
ops = document.querySelectorAll("#search-results li");
|
||||
if (ops.length) {
|
||||
selected = this.getSelectedOp(ops);
|
||||
if (selected > -1) {
|
||||
this.manager.recipe.addOperation(ops[selected].innerHTML);
|
||||
this.app.autoBake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) { // Return
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 40) { // Down
|
||||
e.preventDefault();
|
||||
ops = document.querySelectorAll("#search-results li");
|
||||
if (ops.length) {
|
||||
selected = this.getSelectedOp(ops);
|
||||
if (selected > -1) {
|
||||
ops[selected].classList.remove("selected-op");
|
||||
}
|
||||
if (selected === ops.length-1) selected = -1;
|
||||
ops[selected+1].classList.add("selected-op");
|
||||
}
|
||||
} else if (e.keyCode === 38) { // Up
|
||||
e.preventDefault();
|
||||
ops = document.querySelectorAll("#search-results li");
|
||||
if (ops.length) {
|
||||
selected = this.getSelectedOp(ops);
|
||||
if (selected > -1) {
|
||||
ops[selected].classList.remove("selected-op");
|
||||
}
|
||||
if (selected === 0) selected = ops.length;
|
||||
ops[selected-1].classList.add("selected-op");
|
||||
}
|
||||
} else {
|
||||
var searchResultsEl = document.getElementById("search-results"),
|
||||
el = e.target,
|
||||
str = el.value;
|
||||
|
||||
while (searchResultsEl.firstChild) {
|
||||
$(searchResultsEl.firstChild).popover("destroy");
|
||||
searchResultsEl.removeChild(searchResultsEl.firstChild);
|
||||
}
|
||||
|
||||
$("#categories .in").collapse("hide");
|
||||
if (str) {
|
||||
var matchedOps = this.filterOperations(str, true),
|
||||
matchedOpsHtml = "";
|
||||
|
||||
for (var i = 0; i < matchedOps.length; i++) {
|
||||
matchedOpsHtml += matchedOps[i].toStubHtml();
|
||||
}
|
||||
|
||||
searchResultsEl.innerHTML = matchedOpsHtml;
|
||||
searchResultsEl.dispatchEvent(this.manager.oplistcreate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Filters operations based on the search string and returns the matching ones.
|
||||
*
|
||||
* @param {string} searchStr
|
||||
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
|
||||
* name and description
|
||||
* @returns {string[]}
|
||||
*/
|
||||
OperationsWaiter.prototype.filterOperations = function(searchStr, highlight) {
|
||||
var matchedOps = [],
|
||||
matchedDescs = [];
|
||||
|
||||
searchStr = searchStr.toLowerCase();
|
||||
|
||||
for (var opName in this.app.operations) {
|
||||
var op = this.app.operations[opName],
|
||||
namePos = opName.toLowerCase().indexOf(searchStr),
|
||||
descPos = op.description.toLowerCase().indexOf(searchStr);
|
||||
|
||||
if (namePos >= 0 || descPos >= 0) {
|
||||
var operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
|
||||
if (highlight) {
|
||||
operation.highlightSearchString(searchStr, namePos, descPos);
|
||||
}
|
||||
|
||||
if (namePos < 0) {
|
||||
matchedOps.push(operation);
|
||||
} else {
|
||||
matchedDescs.push(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchedDescs.concat(matchedOps);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the operation which has been selected using keyboard shortcuts. This will have the class
|
||||
* 'selected-op' set. Returns the index of the operation within the given list.
|
||||
*
|
||||
* @param {element[]} ops
|
||||
* @returns {number}
|
||||
*/
|
||||
OperationsWaiter.prototype.getSelectedOp = function(ops) {
|
||||
for (var i = 0; i < ops.length; i++) {
|
||||
if (ops[i].classList.contains("selected-op")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for oplistcreate events.
|
||||
*
|
||||
* @listens Manager#oplistcreate
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.opListCreate = function(e) {
|
||||
this.manager.recipe.createSortableSeedList(e.target);
|
||||
$("[data-toggle=popover]").popover();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for operation doubleclick events.
|
||||
* Adds the operation to the recipe and auto bakes.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.operationDblclick = function(e) {
|
||||
var li = e.target;
|
||||
|
||||
this.manager.recipe.addOperation(li.textContent);
|
||||
this.app.autoBake();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for edit favourites click events.
|
||||
* Sets up the 'Edit favourites' pane and displays it.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.editFavouritesClick = function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Add favourites to modal
|
||||
var favCat = this.app.categories.filter(function(c) {
|
||||
return c.name === "Favourites";
|
||||
})[0];
|
||||
|
||||
var html = "";
|
||||
for (var i = 0; i < favCat.ops.length; i++) {
|
||||
var opName = favCat.ops[i];
|
||||
var operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
|
||||
html += operation.toStubHtml(true);
|
||||
}
|
||||
|
||||
var editFavouritesList = document.getElementById("edit-favourites-list");
|
||||
editFavouritesList.innerHTML = html;
|
||||
this.removeIntent = false;
|
||||
|
||||
var editableList = Sortable.create(editFavouritesList, {
|
||||
filter: ".remove-icon",
|
||||
onFilter: function (evt) {
|
||||
var el = editableList.closest(evt.item);
|
||||
if (el) {
|
||||
$(el).popover("destroy");
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
},
|
||||
onEnd: function(evt) {
|
||||
if (this.removeIntent) evt.item.remove();
|
||||
}.bind(this),
|
||||
});
|
||||
|
||||
Sortable.utils.on(editFavouritesList, "dragleave", function() {
|
||||
this.removeIntent = true;
|
||||
}.bind(this));
|
||||
|
||||
Sortable.utils.on(editFavouritesList, "dragover", function() {
|
||||
this.removeIntent = false;
|
||||
}.bind(this));
|
||||
|
||||
$("#edit-favourites-list [data-toggle=popover]").popover();
|
||||
$("#favourites-modal").modal();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for save favourites click events.
|
||||
* Saves the selected favourites and reloads them.
|
||||
*/
|
||||
OperationsWaiter.prototype.saveFavouritesClick = function() {
|
||||
var favouritesList = [],
|
||||
favs = document.querySelectorAll("#edit-favourites-list li");
|
||||
|
||||
for (var i = 0; i < favs.length; i++) {
|
||||
favouritesList.push(favs[i].textContent);
|
||||
}
|
||||
|
||||
this.app.saveFavourites(favouritesList);
|
||||
this.app.loadFavourites();
|
||||
this.app.populateOperationsList();
|
||||
this.manager.recipe.initialiseOperationDragNDrop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for reset favourites click events.
|
||||
* Resets favourites to their defaults.
|
||||
*/
|
||||
OperationsWaiter.prototype.resetFavouritesClick = function() {
|
||||
this.app.resetFavourites();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for opIcon mouseover events.
|
||||
* Hides any popovers already showing on the operation so that there aren't two at once.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.opIconMouseover = function(e) {
|
||||
var opEl = e.target.parentNode;
|
||||
if (e.target.getAttribute("data-toggle") === "popover") {
|
||||
$(opEl).popover("hide");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for opIcon mouseleave events.
|
||||
* If this icon created a popover and we're moving back to the operation element, display the
|
||||
* operation popover again.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OperationsWaiter.prototype.opIconMouseleave = function(e) {
|
||||
var opEl = e.target.parentNode,
|
||||
toEl = e.toElement || e.relatedElement;
|
||||
|
||||
if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) {
|
||||
$(opEl).popover("show");
|
||||
}
|
||||
};
|
132
src/web/OptionsWaiter.js
Executable file
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* Waiter to handle events related to the CyberChef options.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
*/
|
||||
var OptionsWaiter = module.exports = function(app) {
|
||||
this.app = app;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads options and sets values of switches and inputs to match them.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
OptionsWaiter.prototype.load = function(options) {
|
||||
$(".option-item input:checkbox").bootstrapSwitch({
|
||||
size: "small",
|
||||
animate: false,
|
||||
});
|
||||
|
||||
for (var option in options) {
|
||||
this.app.options[option] = options[option];
|
||||
}
|
||||
|
||||
// Set options to match object
|
||||
var cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
|
||||
for (var i = 0; i < cboxes.length; i++) {
|
||||
$(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]);
|
||||
}
|
||||
|
||||
var nboxes = document.querySelectorAll("#options-body input[type=number]");
|
||||
for (i = 0; i < nboxes.length; i++) {
|
||||
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
|
||||
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
}
|
||||
|
||||
var selects = document.querySelectorAll("#options-body select");
|
||||
for (i = 0; i < selects.length; i++) {
|
||||
selects[i].value = this.app.options[selects[i].getAttribute("option")];
|
||||
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for options click events.
|
||||
* Dispays the options pane.
|
||||
*/
|
||||
OptionsWaiter.prototype.optionsClick = function() {
|
||||
$("#options-modal").modal();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for reset options click events.
|
||||
* Resets options back to their default values.
|
||||
*/
|
||||
OptionsWaiter.prototype.resetOptionsClick = function() {
|
||||
this.load(this.app.doptions);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for switch change events.
|
||||
* Modifies the option state and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
* @param {boolean} state
|
||||
*/
|
||||
OptionsWaiter.prototype.switchChange = function(e, state) {
|
||||
var el = e.target,
|
||||
option = el.getAttribute("option");
|
||||
|
||||
this.app.options[option] = state;
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for number change events.
|
||||
* Modifies the option value and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.numberChange = function(e) {
|
||||
var el = e.target,
|
||||
option = el.getAttribute("option");
|
||||
|
||||
this.app.options[option] = parseInt(el.value, 10);
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for select change events.
|
||||
* Modifies the option value and saves it to local storage.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
OptionsWaiter.prototype.selectChange = function(e) {
|
||||
var el = e.target,
|
||||
option = el.getAttribute("option");
|
||||
|
||||
this.app.options[option] = el.value;
|
||||
localStorage.setItem("options", JSON.stringify(this.app.options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
|
||||
*/
|
||||
OptionsWaiter.prototype.setWordWrap = function() {
|
||||
document.getElementById("input-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-text").classList.remove("word-wrap");
|
||||
document.getElementById("output-html").classList.remove("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.remove("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.remove("word-wrap");
|
||||
|
||||
if (!this.app.options.wordWrap) {
|
||||
document.getElementById("input-text").classList.add("word-wrap");
|
||||
document.getElementById("output-text").classList.add("word-wrap");
|
||||
document.getElementById("output-html").classList.add("word-wrap");
|
||||
document.getElementById("input-highlighter").classList.add("word-wrap");
|
||||
document.getElementById("output-highlighter").classList.add("word-wrap");
|
||||
}
|
||||
};
|
191
src/web/OutputWaiter.js
Executable file
|
@ -0,0 +1,191 @@
|
|||
var Utils = require("../core/Utils.js");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to the output.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var OutputWaiter = module.exports = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the output string from the output textarea.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
OutputWaiter.prototype.get = function() {
|
||||
return document.getElementById("output-text").value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the output in the output textarea.
|
||||
*
|
||||
* @param {string} dataStr - The output string/HTML
|
||||
* @param {string} type - The data type of the output
|
||||
* @param {number} duration - The length of time (ms) it took to generate the output
|
||||
*/
|
||||
OutputWaiter.prototype.set = function(dataStr, type, duration) {
|
||||
var outputText = document.getElementById("output-text"),
|
||||
outputHtml = document.getElementById("output-html"),
|
||||
outputHighlighter = document.getElementById("output-highlighter"),
|
||||
inputHighlighter = document.getElementById("input-highlighter");
|
||||
|
||||
if (type === "html") {
|
||||
outputText.style.display = "none";
|
||||
outputHtml.style.display = "block";
|
||||
outputHighlighter.display = "none";
|
||||
inputHighlighter.display = "none";
|
||||
|
||||
outputText.value = "";
|
||||
outputHtml.innerHTML = dataStr;
|
||||
|
||||
// Execute script sections
|
||||
var scriptElements = outputHtml.querySelectorAll("script");
|
||||
for (var i = 0; i < scriptElements.length; i++) {
|
||||
try {
|
||||
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputText.style.display = "block";
|
||||
outputHtml.style.display = "none";
|
||||
outputHighlighter.display = "block";
|
||||
inputHighlighter.display = "block";
|
||||
|
||||
outputText.value = Utils.printable(dataStr, true);
|
||||
outputHtml.innerHTML = "";
|
||||
}
|
||||
|
||||
this.manager.highlighter.removeHighlights();
|
||||
var lines = dataStr.count("\n") + 1;
|
||||
this.setOutputInfo(dataStr.length, lines, duration);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Displays information about the output.
|
||||
*
|
||||
* @param {number} length - The length of the current output string
|
||||
* @param {number} lines - The number of the lines in the current output string
|
||||
* @param {number} duration - The length of time (ms) it took to generate the output
|
||||
*/
|
||||
OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) {
|
||||
var width = length.toString().length;
|
||||
width = width < 4 ? 4 : width;
|
||||
|
||||
var lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " ");
|
||||
var linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " ");
|
||||
var timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " ");
|
||||
|
||||
document.getElementById("output-info").innerHTML = "time: " + timeStr +
|
||||
"<br>length: " + lengthStr +
|
||||
"<br>lines: " + linesStr;
|
||||
document.getElementById("input-selection-info").innerHTML = "";
|
||||
document.getElementById("output-selection-info").innerHTML = "";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts the display properties of the output buttons so that they fit within the current width
|
||||
* without wrapping or overflowing.
|
||||
*/
|
||||
OutputWaiter.prototype.adjustWidth = function() {
|
||||
var output = document.getElementById("output"),
|
||||
saveToFile = document.getElementById("save-to-file"),
|
||||
switchIO = document.getElementById("switch"),
|
||||
undoSwitch = document.getElementById("undo-switch"),
|
||||
maximiseOutput = document.getElementById("maximise-output");
|
||||
|
||||
if (output.clientWidth < 680) {
|
||||
saveToFile.childNodes[1].nodeValue = "";
|
||||
switchIO.childNodes[1].nodeValue = "";
|
||||
undoSwitch.childNodes[1].nodeValue = "";
|
||||
maximiseOutput.childNodes[1].nodeValue = "";
|
||||
} else {
|
||||
saveToFile.childNodes[1].nodeValue = " Save to file";
|
||||
switchIO.childNodes[1].nodeValue = " Move output to input";
|
||||
undoSwitch.childNodes[1].nodeValue = " Undo";
|
||||
maximiseOutput.childNodes[1].nodeValue =
|
||||
maximiseOutput.getAttribute("title") === "Maximise" ? " Max" : " Restore";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for save click events.
|
||||
* Saves the current output to a file, downloaded as a URL octet stream.
|
||||
*/
|
||||
OutputWaiter.prototype.saveClick = function() {
|
||||
var data = Utils.toBase64(this.app.dishStr),
|
||||
filename = window.prompt("Please enter a filename:", "download.dat");
|
||||
|
||||
if (filename) {
|
||||
var el = document.createElement("a");
|
||||
el.setAttribute("href", "data:application/octet-stream;base64;charset=utf-8," + data);
|
||||
el.setAttribute("download", filename);
|
||||
|
||||
// Firefox requires that the element be added to the DOM before it can be clicked
|
||||
el.style.display = "none";
|
||||
document.body.appendChild(el);
|
||||
|
||||
el.click();
|
||||
el.remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for switch click events.
|
||||
* Moves the current output into the input textarea.
|
||||
*/
|
||||
OutputWaiter.prototype.switchClick = function() {
|
||||
this.switchOrigData = this.manager.input.get();
|
||||
document.getElementById("undo-switch").disabled = false;
|
||||
this.app.setInput(this.app.dishStr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for undo switch click events.
|
||||
* Removes the output from the input and replaces the input that was removed.
|
||||
*/
|
||||
OutputWaiter.prototype.undoSwitchClick = function() {
|
||||
this.app.setInput(this.switchOrigData);
|
||||
document.getElementById("undo-switch").disabled = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for maximise output click events.
|
||||
* Resizes the output frame to be as large as possible, or restores it to its original size.
|
||||
*/
|
||||
OutputWaiter.prototype.maximiseOutputClick = function(e) {
|
||||
var el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
|
||||
|
||||
if (el.getAttribute("title") === "Maximise") {
|
||||
this.app.columnSplitter.collapse(0);
|
||||
this.app.columnSplitter.collapse(1);
|
||||
this.app.ioSplitter.collapse(0);
|
||||
|
||||
el.setAttribute("title", "Restore");
|
||||
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlUlEQVQ4y93RwQpBQRQG4C9ba1fxBteGPIj38BTejFJKLFnwCJIiCsW1mcV0k9yx82/OzGK+OXMGOpiiLTFjFNiilQI0sQ7IJiAjLKsgGVYB2YdaVO0kwy46/BVQi9ZDNPyQWen2ub/KufS8y7shfkq9tF9U7SC+/YluKvAI9YZeFeCECXJcA3JHP2WgMXJM/ZUcBwxeM+YuSWTgMtUAAAAASUVORK5CYII='> Restore";
|
||||
this.adjustWidth();
|
||||
} else {
|
||||
el.setAttribute("title", "Maximise");
|
||||
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAi0lEQVQ4y83TMQrCQBCF4S+5g4rJEdJ7KE+RQ1lrIQQCllroEULuoM0Ww3a7aXwwLAzMPzDvLcz4hnooUItT1rsoVNy+4lgLWNL7RlcCmDBij2eCfNCrUITc0dRCrhj8m5otw0O6SV8LuAV3uhrAAa8sJ2Np7KPFawhgscVLjH9bCDhjt8WNKft88w/HjCvuVqu53QAAAABJRU5ErkJggg=='> Max";
|
||||
this.app.resetLayout();
|
||||
}
|
||||
};
|
426
src/web/RecipeWaiter.js
Executable file
|
@ -0,0 +1,426 @@
|
|||
var HTMLOperation = require("./HTMLOperation.js"),
|
||||
Sortable = require("sortablejs");
|
||||
|
||||
|
||||
/**
|
||||
* Waiter to handle events related to the recipe.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var RecipeWaiter = module.exports = 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() {
|
||||
var 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)
|
||||
});
|
||||
|
||||
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) {
|
||||
var loc = e.changedTouches[0],
|
||||
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 the 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) {
|
||||
$(evt.item).popover("destroy");
|
||||
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.
|
||||
$(evt.clone).popover();
|
||||
$(evt.clone).children("[data-toggle=popover]").popover();
|
||||
|
||||
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");
|
||||
|
||||
var opName = e.dataTransfer.getData("Text");
|
||||
this.app.addFavourite(opName);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for ingredient change events.
|
||||
*
|
||||
* @fires Manager#statechange
|
||||
*/
|
||||
RecipeWaiter.prototype.ingChange = function() {
|
||||
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) {
|
||||
var 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) {
|
||||
var 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() {
|
||||
var config = [], ingredients, ingList, disabled, bp, item,
|
||||
operations = document.querySelectorAll("#rec-list li.operation");
|
||||
|
||||
for (var i = 0; i < operations.length; i++) {
|
||||
ingredients = [];
|
||||
disabled = operations[i].querySelector(".disable-icon");
|
||||
bp = operations[i].querySelector(".breakpoint");
|
||||
ingList = operations[i].querySelectorAll(".arg");
|
||||
|
||||
for (var 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 {
|
||||
// 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) {
|
||||
var operations = document.querySelectorAll("#rec-list li.operation");
|
||||
for (var 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) {
|
||||
var opName = el.textContent;
|
||||
var 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 - this should be moved to the 'operationadd'
|
||||
// handler after event restructuring
|
||||
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) {
|
||||
var 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() {
|
||||
var 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) {
|
||||
var el = e.target,
|
||||
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);
|
||||
};
|
152
src/web/SeasonalWaiter.js
Executable file
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Waiter to handle seasonal events and easter eggs.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
* @param {Manager} manager - The CyberChef event manager.
|
||||
*/
|
||||
var SeasonalWaiter = module.exports = function(app, manager) {
|
||||
this.app = app;
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads all relevant items depending on the current date.
|
||||
*/
|
||||
SeasonalWaiter.prototype.load = function() {
|
||||
//var now = new Date();
|
||||
|
||||
// SpiderChef
|
||||
// if (now.getMonth() === 3 && now.getDate() === 1) { // Apr 1
|
||||
// this.insertSpiderIcons();
|
||||
// this.insertSpiderText();
|
||||
// }
|
||||
|
||||
// Konami code
|
||||
this.kkeys = [];
|
||||
window.addEventListener("keydown", this.konamiCodeListener.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces chef icons with spider icons.
|
||||
* #spiderchef
|
||||
*/
|
||||
SeasonalWaiter.prototype.insertSpiderIcons = function() {
|
||||
var spider16 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB3UlEQVQ4y2NgGJaAmYGBgVnf0oKJgYGBobWtXamqqoYTn2I4CI+LTzM2NTulpKbu+vPHz2dV5RWlluZmi3j5+KqFJSSEzpw8uQPdAEYYIzo5Kfjrl28rWFlZzjAzMYuEBQao3Lh+g+HGvbsMzExMDN++fWf4/PXLBzY2tqYNK1f2+4eHM2xcuRLigsT09Igf3384MTExbf767etBI319jU8fPsi+//jx/72HDxh5uLkZ7ty7y/Dz1687Avz8n2UUFR3Z2NjOySoqfmdhYGBg+PbtuwI7O8e5H79+8X379t357PnzYo+ePP7y6cuXc9++f69nYGRsvf/w4XdtLS2R799/bBUWFHr57sP7Jbs3b/ZkzswvUP3165fZ7z9//r988WIVAyPDr8tXr576+u3bpb9//7YwMjKeV1dV41NWVGoVEhDgPH761DJREeHaz1+/lqlpafUx6+jrRfz4+fPy+w8fTu/fsf3uw7t3L39+//4cv7DwGQYGhpdPbt9m4BcRFlNWVJC4fuvWASszs4C379792Ldt2xZBUdEdDP5hYSqQGIjDGa965uYKCalpZQwMDAxhMTG9DAwMDLaurhIkJY7A8IgGBgYGBgd3Dz2yUpeFo6O4rasrA9T24ZRxAAMTwMpgEJwLAAAAAElFTkSuQmCC",
|
||||
spider32 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACYVBMVEUAAAAcJSU2Pz85QkM9RUWEhIWMjI2MkJEcJSU2Pz85QkM9RUWWlpc9RUVXXl4cJSU2Pz85QkM8REU9RUVRWFh6ens9RUVCSkpNVFRdY2McJSU5QkM7REQ9RUVGTk5KUlJQVldcY2Rla2uTk5WampscJSVUWltZX2BrcHF1e3scJSUjLCw9RUVASEhFTU1HTk9bYWJeZGRma2xudHV1eHiZmZocJSUyOjpJUFFQVldSWlpTWVpXXl5YXl5rb3B9fX6RkZIcJSUmLy8tNTU9RUVFTU1IT1BOVldRV1hTWlp0enocJSUfKChJUFBWXV1hZ2hnbGwcJSVETExLUlJLU1NNVVVPVlZYXl9cY2RiaGlobW5rcXFyd3h0eHgcJSUpMTFDS0tQV1dRV1hSWFlWXF1bYWJma2tobW5uc3SsrK0cJSVJUFBMVFROVlZVW1xZX2BdYmNhZ2hjaGhla2tqcHBscHE4Pz9KUlJRWVlSWVlXXF1aYGFbYWFfZWZlampqbW4cJSUgKSkiKysuNjY0PD01PT07QkNES0tHTk5JUFBMUlNMU1NOU1ROVVVPVVZRVlZRV1dSWVlWXFxXXV5aX2BbYWFbYWJcYmJcYmNcY2RdYmNgZmZhZmdkaWpkampkamtlamtla2tma2tma2xnbG1obW5pbG1pb3Bqb3Brb3BtcXJudHVvcHFvcXJvc3NwcXNwdXVxc3RzeXl1eXp2eXl3ent6e3x+gYKAhISBg4SKi4yLi4yWlpeampudnZ6fn6CkpaanqKiur6+vr7C4uLm6urq6u7u8vLy9vb3Av8DR0dL2b74UAAAAgHRSTlMAEBAQEBAQECAgICAgMDBAQEBAQEBAUFBQUGBgYGBgYGBgYGBgcHBwcHCAgICAgICAgICAgICPj4+Pj4+Pj4+Pj5+fn5+fn5+fn5+vr6+vr6+/v7+/v7+/v7+/v7+/z8/Pz8/Pz8/Pz8/P39/f39/f39/f39/f7+/v7+/v7+/v78x6RlYAAAGBSURBVDjLY2AYWUCSgUGAk4GBTdlUhQebvP7yjIgCPQbWzBMnjx5wwJSX37Rwfm1isqj9/iPHTuxYlyeMJi+yunfptBkZOw/uWj9h3vatcycu8eRGlldb3Vsts3ph/cFTh7fN3bCoe2Vf8+TZoQhTvBa6REozVC7cuPvQnmULJm1e2z+308eyJieEBSLPXbKQIUqQIczk+N6eNaumtnZMaWhaHM89m8XVCqJA02Y5w0xmga6yfVsamtrN4xoXNzS0JTHkK3CXy4EVFMumcxUy2LbENTVkZfEzMDAudtJyTmNwS2XQreAFyvOlK9louDNVaXurmjkGgnTMkWDgXswtNouFISEX6Awv+RihQi5OcYY4DtVARpCCFCMGhiJ1hjwFBpagEAaWEpFoC0WQOCOjFMRRwXYMDB4BDLJ+QLYsg7GBGjtasLnEMjCIrWBgyAZ7058FI9x1SoFEnTCDsCyIhynPILYYSFgbYpUDA5bpQBluXzxpI1yYAbd2sCMYRhwAAHB9ZPztbuMUAAAAAElFTkSuQmCC",
|
||||
spider64 = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAJZUlEQVR42u1ZaXMU1xXlJ+gHpFITOy5sAcnIYCi2aIL2bTSSZrSP1NpHK41kISQBHgFaQIJBCMwi4TFUGYcPzggwEMcxHVGxQaag5QR/np/QP+Hmnsdr0hpmtEACwulb9aq7p7d3zz333Pt61q2zzTbbbLPNNttss80222yzzTbbVmu7MzKcJRWVkXjntqam6jyURPeGQqeTpqbOqp+evxC5dGlam5m5rE3PzGi8Hzx/4aLzbXDe09HdYxwZHaPc4mLFXVoW9pRXGNv3pDngeHlNLfE2Ljjj4xPOUGjSYKfpq6/+TLdv36bbX39Nt27epGvXvqSLl6bp3LlPtdOnz7jWrPNZ7kLCKCovp5bOTmP/4EHq6vmYMtzuSKbbbQCAHE8Rxd47MjrmuHjxkjF3/z4tLCzQkyc6PX78mB49ekQPHjygub/P0d27f6FrX/6JpqbO0YkT48E1R/sCr9cYHZ+gqrp64mPq+riXcoqKKC0vP9q6VyV/fQOiH+LrsPVY7z82PBKZnb1Bd+7cpfn5eQbgCT1hAADC/MN5uj83R99881eanZ2lL5gN/nrxjihAXwvOJ7l9vuiBQ4dF9LEtLC0V+2rv/ijTX6luaCS3rxT57wADAMTBQ4c9PIIDg4PBwYOHaHhklM5MnSWkwLff/o0+v3qVHv34Iz344QEDc4d8VVXUEAhQXXMzVdQqzKweKq6oABARzOGNOZ+Wl6fD6T25ubQrPT0E5xF93o82tbdjkkZ+iZfAAgbD6fZ6o339A8S0p7HjJ2h4eIQOHf6EujlV9nX3UOj0JDXzfXje+KlTdOPGDeF0T1+fGHg+2JSen08tHZ0CiPySEoPn8vq1IaOgIAzneQK0UzjcQd6qaqrlCVfV1+tpubnRnv5+2p2ZqYMF/oZGPTh0xLhy5Sr9wLn9j++/p5nLn9FxBoLZQJ1dKrkys6iYNeTExEnx3PqWFuF4W9deKq2upkEGCyzyMBC709MFC7r391Fjayv9MSdHZyCU1xJ5FjrNdN6VnU1KS4CjU4Yoh/m8CsezCguFJgAMV05ueP+BfhF5OL+gL9A/f/qJ7t3TaPLMFB09eoy6mTkMGg2PjTELOsS20OcTACgMKqJugqA0NtE7ycn0202b6A+ZmYIVAAKApGZlgRHB/0lqQPAqFEVE9hntM0R0ZblTzeswWdCeU8HAtYW+Uu0AUx+0f/jwoXD+56c/073v7tHU2XMiFbrUfVTNAtfL10FIAQL2QftsBrOEnavld5kg7E7PoF+99x79ev162rJrV9RMi6a2dvKUlQsR5uAgII7/ivMsbEE4g2hggjzC7LQL1OftovoO0WJKUn0gYEAn2hmMXo4QHIXQIfLfsfOXPwuLvB86cpQqamooyEzg1BLMwv04RkoE+B3B4BBBMHEcCwIP0N+ByJdUVhpgBJ7j4WvdANDjeTUglOaWEChfJF7uJzPX2HEPaj1vg7EAbHO5QnAeIPgqKvUB7gtAdbBgcvKMqOnc/NAIVwCcq21qElFnCgvaI9cBBFKhlSPbPzBIbbzduGULpWzfLkDAdZs++sgEwSlZqoIJMg2CzFSNGzODwdBfOi26+w4YTCm9LhDQwQDzdzguFf4FALjciTws8/u1yyx2N2/dovPnL9DRY8PkZ204xtuhoSM0wI7V8DEiirQCCHD+99u2CUdx3Lmvmz7kfemoGDgPEDr4HNKAf1MlAC4wgMGLWFJXQUrklZSEX6rLE2rOyDIQGlhgBUAyYFEZkm2vAGVi4qQ+x83M0389pevXr6OToy07d4qcR+krr/KzqpeJ/IfjGO+npDx3FCKHVPjd1q2LAMBI3ryZ9vL7U56BEzLfD80ACFba876OlGCQV9dAcT0Pyw7PgWij6zPP5Xt9EYgg+n3LosdVzdfz5CI8KY1LH31+5Yro9KanZwjHmPzmHTsoOeVDemfDBuE8dGVnWpqx3unUrE4CDLCAG64XAHB88IFgQV5xMY7DFmc16A6CZvnNBYYVcW+yKj0A/VHTsQ8dwMPNc6X+Gg0VIGbVpzYGWundjRujmGQWi9Eol7+TJ0/R2Nhx2sNlM9YJRPDdDRsM5DGPJB4KHOIhngHhAwixAGAAuDZ2lsuiYnFWBQOYrdEYNochilyiV6YHoH+rRNJkAG+fUw31PzU7Z1EFKPD69CIuQ1Bm6URoh8tFmVym3nc6rZOPyi0cD8HxeHPg3x2InNrbS79JTsYzNXmPuBclsO3ZvKwAOJEGsmI5rT0M+gSf3y9K5LIA1LUEIlL1k0AhCYBH5r9TCqBqib4D+c/1PyInGOThkvuaHCYALhlpbQWBMGR/4IpzTqlpbKQyf0045vdoe0zATHagSYMeWFMkbscnHRYPZjoFJaIiUkz9EJy15j/X3qCsAIqMcFjSWrNE1Iygg0fEmrtLzEUTdT/OhBFht9fHDVCbEUt3LJxi08B8Xj6vTDESriq9lVWqBECgHujqiqAUmufb1X3cfRXoluhjZWiwkOnSUcUS6ZD8LUmmhks6b5j1ezkAkAKZBe5QvPPcNBnoCawMwT66Qxk0R2xwwRAui2iSDGuaPDcubzo3EJq8wcx/9Vmk3QryH42QBQCFF0UagIiJtjX6DskIXTLEucJSHIIIMuO0BOcjn3A3ybU/lu5RCUBc5qA0Ih0Q2EWiCPRk7VfMNhjLW1zETic1tLYZDMKyuSsdfh5l6bwho5+0il4kyA0VohlNcF5FP8DlWo/VB16HYB2hJ0pzgIe2mcXxP2IOumPRY17U0tll8KIkZNb+sppafOxYkQPSaYfchyYoL9GMqWYpTLRIq1QUcT4O3aPQgqVqPwIOIMwDhzX6mQUFIQAgo+9MzcrWrML3mj6+YIKiFCZyhL87RqVQKrEskF+P1BUvfLCAkfRwoPUtq6l5o5+lZb5SolJo6oT8avTCl+c9OTmat6pKW8mLkvBpGzlvsiGuQr4ZEEwA1EQgoR/gNtxIxKBluz+OtMJiF31jHxqXBiAqAUj4WRxpADFM0DCFlv1khvX7Wol4vF4AIldVVxdZqlrIfiCYQPHDy6bAGv7nKYRVY6JewExZVAP+ey5Rv+Ba97aaUHMW5NauLmMZFkegBb/EP14d6NoS9QLWFSzWBmuZza8CQmSpXsAqmGtVy14VALWuuYWWy+W3OteXa4jwceQX6+BKG6J1/8+2VCNkm2222WabbbbZZpttttlmm22rt38DCdA0vq3bcAkAAAAASUVORK5CYII=";
|
||||
|
||||
// Favicon
|
||||
document.querySelector("link[rel=icon]").setAttribute("href", "data:image/png;base64," + spider16);
|
||||
|
||||
// Bake button
|
||||
document.querySelector("#bake img").setAttribute("src", "data:image/png;base64," + spider32);
|
||||
|
||||
// About box
|
||||
document.querySelector(".about-img-left").setAttribute("src", "data:image/png;base64," + spider64);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces all instances of the word "cyber" with "spider".
|
||||
* #spiderchef
|
||||
*/
|
||||
SeasonalWaiter.prototype.insertSpiderText = function() {
|
||||
// Title
|
||||
document.title = document.title.replace(/Cyber/g, "Spider");
|
||||
|
||||
// Body
|
||||
SeasonalWaiter.treeWalk(document.body, function(node) {
|
||||
// process only text nodes
|
||||
if (node.nodeType === 3) {
|
||||
node.nodeValue = node.nodeValue.replace(/Cyber/g, "Spider");
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Bake button
|
||||
SeasonalWaiter.treeWalk(document.getElementById("bake-group"), function(node) {
|
||||
// process only text nodes
|
||||
if (node.nodeType === 3) {
|
||||
node.nodeValue = node.nodeValue.replace(/Bake/g, "Spin");
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Recipe title
|
||||
document.querySelector("#recipe .title").innerHTML = "Web";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in
|
||||
* sequence.
|
||||
* #konamicode
|
||||
*/
|
||||
SeasonalWaiter.prototype.konamiCodeListener = function(e) {
|
||||
this.kkeys.push(e.keyCode);
|
||||
var konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
|
||||
for (var i = 0; i < this.kkeys.length; i++) {
|
||||
if (this.kkeys[i] !== konami[i]) {
|
||||
this.kkeys = [];
|
||||
break;
|
||||
}
|
||||
if (i === konami.length - 1) {
|
||||
$("body").children().toggleClass("konami");
|
||||
this.kkeys = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Walks through the entire DOM starting at the specified element and operates on each node.
|
||||
*
|
||||
* @static
|
||||
* @param {element} parent - The DOM node to start from
|
||||
* @param {Function} fn - The callback function to operate on each node
|
||||
* @param {booleam} allNodes - Whether to operate on every node or not
|
||||
*/
|
||||
SeasonalWaiter.treeWalk = (function() {
|
||||
// Create closure for constants
|
||||
var skipTags = {
|
||||
"SCRIPT": true, "IFRAME": true, "OBJECT": true,
|
||||
"EMBED": true, "STYLE": true, "LINK": true, "META": true
|
||||
};
|
||||
|
||||
return function(parent, fn, allNodes) {
|
||||
var node = parent.firstChild;
|
||||
|
||||
while (node && node !== parent) {
|
||||
if (allNodes || node.nodeType === 1) {
|
||||
if (fn(node) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If it's an element &&
|
||||
// has children &&
|
||||
// has a tagname && is not in the skipTags list
|
||||
// then, we can enumerate children
|
||||
if (node.nodeType === 1 &&
|
||||
node.firstChild &&
|
||||
!(node.tagName && skipTags[node.tagName])) {
|
||||
node = node.firstChild;
|
||||
} else if (node.nextSibling) {
|
||||
node = node.nextSibling;
|
||||
} else {
|
||||
// No child and no nextsibling
|
||||
// Find parent that has a nextSibling
|
||||
while ((node = node.parentNode) !== parent) {
|
||||
if (node.nextSibling) {
|
||||
node = node.nextSibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
52
src/web/WindowWaiter.js
Executable file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Waiter to handle events related to the window object.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLApp} app - The main view object for CyberChef.
|
||||
*/
|
||||
var WindowWaiter = module.exports = function(app) {
|
||||
this.app = app;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for window resize events.
|
||||
* Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause
|
||||
* continuous resetting).
|
||||
*/
|
||||
WindowWaiter.prototype.windowResize = function() {
|
||||
clearTimeout(this.resetLayoutTimeout);
|
||||
this.resetLayoutTimeout = setTimeout(this.app.resetLayout.bind(this.app), 200);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for window blur events.
|
||||
* Saves the current time so that we can calculate how long the window was unfocussed for when
|
||||
* focus is returned.
|
||||
*/
|
||||
WindowWaiter.prototype.windowBlur = function() {
|
||||
this.windowBlurTime = new Date().getTime();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for window focus events.
|
||||
*
|
||||
* When a browser tab is unfocused and the browser has to run lots of dynamic content in other
|
||||
* tabs, it swaps out the memory for that tab.
|
||||
* If the CyberChef tab has been unfocused for more than a minute, we run a silent bake which will
|
||||
* force the browser to load and cache all the relevant JavaScript code needed to do a real bake.
|
||||
* This will stop baking taking a long time when the CyberChef browser tab has been unfocused for
|
||||
* a long time and the browser has swapped out all its memory.
|
||||
*/
|
||||
WindowWaiter.prototype.windowFocus = function() {
|
||||
var unfocusedTime = new Date().getTime() - this.windowBlurTime;
|
||||
if (unfocusedTime > 60000) {
|
||||
this.app.silentBake();
|
||||
}
|
||||
};
|
18
src/web/css/index.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* CSS index
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
require("google-code-prettify/src/prettify.css");
|
||||
|
||||
require("./lib/bootstrap.less");
|
||||
require("bootstrap-switch/src/less/bootstrap3/build.less");
|
||||
require("bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css");
|
||||
|
||||
require("./structure/overrides.css");
|
||||
require("./structure/layout.css");
|
||||
require("./structure/utils.css");
|
||||
require("./themes/classic.css");
|
58
src/web/css/lib/bootstrap.less
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Bootstrap imports
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
// Core variables and mixins
|
||||
@import "~bootstrap/less/variables.less";
|
||||
@import "~bootstrap/less/mixins.less";
|
||||
|
||||
// Reset and dependencies
|
||||
@import "~bootstrap/less/normalize.less";
|
||||
@import "~bootstrap/less/print.less";
|
||||
// @import "~bootstrap/less/glyphicons.less";
|
||||
|
||||
// Core CSS
|
||||
@import "~bootstrap/less/scaffolding.less";
|
||||
@import "~bootstrap/less/type.less";
|
||||
@import "~bootstrap/less/code.less";
|
||||
// @import "~bootstrap/less/grid.less";
|
||||
@import "~bootstrap/less/tables.less";
|
||||
@import "~bootstrap/less/forms.less";
|
||||
@import "~bootstrap/less/buttons.less";
|
||||
|
||||
// Components
|
||||
@import "~bootstrap/less/component-animations.less";
|
||||
@import "~bootstrap/less/dropdowns.less";
|
||||
@import "~bootstrap/less/button-groups.less";
|
||||
@import "~bootstrap/less/input-groups.less";
|
||||
@import "~bootstrap/less/navs.less";
|
||||
// @import "~bootstrap/less/navbar.less";
|
||||
// @import "~bootstrap/less/breadcrumbs.less";
|
||||
// @import "~bootstrap/less/pagination.less";
|
||||
// @import "~bootstrap/less/pager.less";
|
||||
@import "~bootstrap/less/labels.less";
|
||||
// @import "~bootstrap/less/badges.less";
|
||||
// @import "~bootstrap/less/jumbotron.less";
|
||||
// @import "~bootstrap/less/thumbnails.less";
|
||||
@import "~bootstrap/less/alerts.less";
|
||||
@import "~bootstrap/less/progress-bars.less";
|
||||
// @import "~bootstrap/less/media.less";
|
||||
@import "~bootstrap/less/list-group.less";
|
||||
@import "~bootstrap/less/panels.less";
|
||||
// @import "~bootstrap/less/responsive-embed.less";
|
||||
// @import "~bootstrap/less/wells.less";
|
||||
// @import "~bootstrap/less/close.less";
|
||||
|
||||
// Components w/ JavaScript
|
||||
@import "~bootstrap/less/modals.less";
|
||||
@import "~bootstrap/less/tooltip.less";
|
||||
@import "~bootstrap/less/popovers.less";
|
||||
// @import "~bootstrap/less/carousel.less";
|
||||
|
||||
// Utility classes
|
||||
@import "~bootstrap/less/utilities.less";
|
||||
// @import "~bootstrap/less/responsive-utilities.less";
|
432
src/web/css/structure/layout.css
Executable file
113
src/web/css/structure/overrides.css
Executable file
|
@ -0,0 +1,113 @@
|
|||
/* Bootstrap */
|
||||
|
||||
button,
|
||||
a:focus {
|
||||
outline: none;
|
||||
-moz-outline-style: none;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.btn-default:focus {
|
||||
background-color: #fff;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.btn-default:hover,
|
||||
.btn-default:active {
|
||||
background-color: #ebebeb;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.btn,
|
||||
.btn-lg,
|
||||
.nav-tabs>li>a,
|
||||
.form-control,
|
||||
.popover,
|
||||
.alert,
|
||||
.modal-content,
|
||||
.tooltip-inner,
|
||||
.dropdown-menu {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: searchfield;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: searchfield-cancel-button;
|
||||
}
|
||||
|
||||
.modal {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 0;
|
||||
white-space: pre-wrap;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
blockquote a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel-body:before,
|
||||
.panel-body:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.table-nonfluid {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
|
||||
/* Bootstrap-switch */
|
||||
|
||||
.bootstrap-switch,
|
||||
.bootstrap-switch-container,
|
||||
.bootstrap-switch-handle-on,
|
||||
.bootstrap-switch-handle-off,
|
||||
.bootstrap-switch-label {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
/* Sortable */
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
||||
/* Bootstrap Colorpicker */
|
||||
|
||||
.colorpicker-element {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.colorpicker-color,
|
||||
.colorpicker-color div {
|
||||
height: 100px;
|
||||
}
|
37
src/web/css/structure/utils.css
Executable file
|
@ -0,0 +1,37 @@
|
|||
.word-wrap {
|
||||
white-space: pre !important;
|
||||
word-wrap: normal !important;
|
||||
overflow-x: scroll !important;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.blur {
|
||||
color: transparent !important;
|
||||
text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important;
|
||||
}
|
||||
|
||||
.no-select {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.konami {
|
||||
-ms-transform: rotate(180deg);
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.hl1, .hlyellow { background-color: #fff000; }
|
||||
.hl2, .hlblue { background-color: #95dfff; }
|
||||
.hl3, .hlred { background-color: #ffb6b6; } /* Half-Life 3 confirmed :O */
|
||||
.hl4, .hlorange { background-color: #fcf8e3; }
|
||||
.hl5, .hlgreen { background-color: #8de768; }
|
258
src/web/css/themes/classic.css
Executable file
|
@ -0,0 +1,258 @@
|
|||
#banner {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-weight: bold;
|
||||
color: #424242;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
background-color: #eee;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
}
|
||||
|
||||
.gutter.gutter-horizontal {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAlSURBVChTYzxz5sx/BiBgAhEgwPju3TtUEZZ79+6BGcNcDQMDACWJMFs4hNOSAAAAAElFTkSuQmCC');
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.gutter.gutter-vertical {
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAACCAYAAABPJGxCAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACHDwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKL2lDQ1BJQ0MgUHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTDSGXqTLjCA9C4gHQRRGGYGGMoAwwxNbIioQEQREQFFkKCAAaOhSKyIYiEoqGAPSBBQYjCKqKhkRtZKfHl57+Xl98e939pn73P32XuftS4AJE8fLi8FlgIgmSfgB3o401eFR9Cx/QAGeIABpgAwWempvkHuwUAkLzcXerrICfyL3gwBSPy+ZejpT6eD/0/SrFS+AADIX8TmbE46S8T5Ik7KFKSK7TMipsYkihlGiZkvSlDEcmKOW+Sln30W2VHM7GQeW8TinFPZyWwx94h4e4aQI2LER8QFGVxOpohvi1gzSZjMFfFbcWwyh5kOAIoktgs4rHgRm4iYxA8OdBHxcgBwpLgvOOYLFnCyBOJDuaSkZvO5cfECui5Lj25qbc2ge3IykzgCgaE/k5XI5LPpLinJqUxeNgCLZ/4sGXFt6aIiW5paW1oamhmZflGo/7r4NyXu7SK9CvjcM4jW94ftr/xS6gBgzIpqs+sPW8x+ADq2AiB3/w+b5iEAJEV9a7/xxXlo4nmJFwhSbYyNMzMzjbgclpG4oL/rfzr8DX3xPSPxdr+Xh+7KiWUKkwR0cd1YKUkpQj49PZXJ4tAN/zzE/zjwr/NYGsiJ5fA5PFFEqGjKuLw4Ubt5bK6Am8Kjc3n/qYn/MOxPWpxrkSj1nwA1yghI3aAC5Oc+gKIQARJ5UNz13/vmgw8F4psXpjqxOPefBf37rnCJ+JHOjfsc5xIYTGcJ+RmLa+JrCdCAACQBFcgDFaABdIEhMANWwBY4AjewAviBYBAO1gIWiAfJgA8yQS7YDApAEdgF9oJKUAPqQSNoASdABzgNLoDL4Dq4Ce6AB2AEjIPnYAa8AfMQBGEhMkSB5CFVSAsygMwgBmQPuUE+UCAUDkVDcRAPEkK50BaoCCqFKqFaqBH6FjoFXYCuQgPQPWgUmoJ+hd7DCEyCqbAyrA0bwwzYCfaGg+E1cBycBufA+fBOuAKug4/B7fAF+Dp8Bx6Bn8OzCECICA1RQwwRBuKC+CERSCzCRzYghUg5Uoe0IF1IL3ILGUGmkXcoDIqCoqMMUbYoT1QIioVKQ21AFaMqUUdR7age1C3UKGoG9QlNRiuhDdA2aC/0KnQcOhNdgC5HN6Db0JfQd9Dj6DcYDIaG0cFYYTwx4ZgEzDpMMeYAphVzHjOAGcPMYrFYeawB1g7rh2ViBdgC7H7sMew57CB2HPsWR8Sp4sxw7rgIHA+XhyvHNeHO4gZxE7h5vBReC2+D98Oz8dn4Enw9vgt/Az+OnydIE3QIdoRgQgJhM6GC0EK4RHhIeEUkEtWJ1sQAIpe4iVhBPE68QhwlviPJkPRJLqRIkpC0k3SEdJ50j/SKTCZrkx3JEWQBeSe5kXyR/Jj8VoIiYSThJcGW2ChRJdEuMSjxQhIvqSXpJLlWMkeyXPKk5A3JaSm8lLaUixRTaoNUldQpqWGpWWmKtKm0n3SydLF0k/RV6UkZrIy2jJsMWyZf5rDMRZkxCkLRoLhQWJQtlHrKJco4FUPVoXpRE6hF1G+o/dQZWRnZZbKhslmyVbJnZEdoCE2b5kVLopXQTtCGaO+XKC9xWsJZsmNJy5LBJXNyinKOchy5QrlWuTty7+Xp8m7yifK75TvkHymgFPQVAhQyFQ4qXFKYVqQq2iqyFAsVTyjeV4KV9JUCldYpHVbqU5pVVlH2UE5V3q98UXlahabiqJKgUqZyVmVKlaJqr8pVLVM9p/qMLkt3oifRK+g99Bk1JTVPNaFarVq/2ry6jnqIep56q/ojDYIGQyNWo0yjW2NGU1XTVzNXs1nzvhZei6EVr7VPq1drTltHO0x7m3aH9qSOnI6XTo5Os85DXbKug26abp3ubT2MHkMvUe+A3k19WN9CP16/Sv+GAWxgacA1OGAwsBS91Hopb2nd0mFDkqGTYYZhs+GoEc3IxyjPqMPohbGmcYTxbuNe408mFiZJJvUmD0xlTFeY5pl2mf5qpm/GMqsyu21ONnc332jeaf5ymcEyzrKDy+5aUCx8LbZZdFt8tLSy5Fu2WE5ZaVpFW1VbDTOoDH9GMeOKNdra2Xqj9WnrdzaWNgKbEza/2BraJto22U4u11nOWV6/fMxO3Y5pV2s3Yk+3j7Y/ZD/ioObAdKhzeOKo4ch2bHCccNJzSnA65vTC2cSZ79zmPOdi47Le5bwr4urhWuja7ybjFuJW6fbYXd09zr3ZfcbDwmOdx3lPtKe3527PYS9lL5ZXo9fMCqsV61f0eJO8g7wrvZ/46Pvwfbp8Yd8Vvnt8H67UWslb2eEH/Lz89vg98tfxT/P/PgAT4B9QFfA00DQwN7A3iBIUFdQU9CbYObgk+EGIbogwpDtUMjQytDF0Lsw1rDRsZJXxqvWrrocrhHPDOyOwEaERDRGzq91W7109HmkRWRA5tEZnTdaaq2sV1iatPRMlGcWMOhmNjg6Lbor+wPRj1jFnY7xiqmNmWC6sfaznbEd2GXuKY8cp5UzE2sWWxk7G2cXtiZuKd4gvj5/munAruS8TPBNqEuYS/RKPJC4khSW1JuOSo5NP8WR4ibyeFJWUrJSBVIPUgtSRNJu0vWkzfG9+QzqUvia9U0AV/Uz1CXWFW4WjGfYZVRlvM0MzT2ZJZ/Gy+rL1s3dkT+S453y9DrWOta47Vy13c+7oeqf1tRugDTEbujdqbMzfOL7JY9PRzYTNiZt/yDPJK817vSVsS1e+cv6m/LGtHlubCyQK+AXD22y31WxHbedu799hvmP/jk+F7MJrRSZF5UUfilnF174y/ariq4WdsTv7SyxLDu7C7OLtGtrtsPtoqXRpTunYHt897WX0ssKy13uj9l4tX1Zes4+wT7hvpMKnonO/5v5d+z9UxlfeqXKuaq1Wqt5RPXeAfWDwoOPBlhrlmqKa94e4h+7WetS212nXlR/GHM44/LQ+tL73a8bXjQ0KDUUNH4/wjowcDTza02jV2Nik1FTSDDcLm6eORR67+Y3rN50thi21rbTWouPguPD4s2+jvx064X2i+yTjZMt3Wt9Vt1HaCtuh9uz2mY74jpHO8M6BUytOdXfZdrV9b/T9kdNqp6vOyJ4pOUs4m3924VzOudnzqeenL8RdGOuO6n5wcdXF2z0BPf2XvC9duex++WKvU++5K3ZXTl+1uXrqGuNax3XL6+19Fn1tP1j80NZv2d9+w+pG503rm10DywfODjoMXrjleuvyba/b1++svDMwFDJ0dzhyeOQu++7kvaR7L+9n3J9/sOkh+mHhI6lH5Y+VHtf9qPdj64jlyJlR19G+J0FPHoyxxp7/lP7Th/H8p+Sn5ROqE42TZpOnp9ynbj5b/Wz8eerz+emCn6V/rn6h++K7Xxx/6ZtZNTP+kv9y4dfiV/Kvjrxe9rp71n/28ZvkN/NzhW/l3x59x3jX+z7s/cR85gfsh4qPeh+7Pnl/eriQvLDwG/eE8/s3BCkeAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAI0lEQVQYV2M8c+bMfwYgUFJSAlEM9+7dA9O05jOBSboDBgYAtPcYZ1oUA30AAAAASUVORK5CYII=');
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.operation {
|
||||
border: 1px solid #999;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.op-list .operation { /*blue*/
|
||||
color: #3a87ad;
|
||||
background-color: #d9edf7;
|
||||
border-color: #bce8f1;
|
||||
}
|
||||
|
||||
#rec-list .operation { /*green*/
|
||||
color: #468847;
|
||||
background-color: #dff0d8;
|
||||
border-color: #d6e9c6;
|
||||
}
|
||||
|
||||
#controls {
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.textarea-wrapper textarea,
|
||||
.textarea-wrapper div {
|
||||
font-family: Consolas, monospace;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.io-info {
|
||||
font-family: Consolas, monospace;
|
||||
font-weight: normal;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
.arg-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.arg-input {
|
||||
height: 34px;
|
||||
font-size: 15px;
|
||||
line-height: 1.428571429;
|
||||
color: #424242;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 6px 8px;
|
||||
height: 34px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.arg[disabled] {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
textarea.arg {
|
||||
color: #424242;
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
.break {
|
||||
color: #b94a48 !important;
|
||||
background-color: #f2dede !important;
|
||||
border-color: #eed3d7 !important;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.category-title[href='#catFavourites'] {
|
||||
border-bottom-color: #ddd;
|
||||
}
|
||||
|
||||
.category-title[aria-expanded=true] {
|
||||
border-bottom-color: #ddd;
|
||||
}
|
||||
|
||||
.category-title.collapsed {
|
||||
border-bottom-color: #eee;
|
||||
}
|
||||
|
||||
.category-title:hover {
|
||||
color: #3a87ad;
|
||||
}
|
||||
|
||||
#search {
|
||||
border-bottom: 1px solid #e3e3e3;
|
||||
}
|
||||
|
||||
.dropping-file {
|
||||
border: 5px dashed #3a87ad !important;
|
||||
}
|
||||
|
||||
.selected-op {
|
||||
color: #c09853 !important;
|
||||
background-color: #fcf8e3 !important;
|
||||
border-color: #fbeed5 !important;
|
||||
}
|
||||
|
||||
.option-item input[type=number] {
|
||||
font-size: 14px;
|
||||
line-height: 1.428571429;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.favourites-hover {
|
||||
color: #468847;
|
||||
background-color: #dff0d8;
|
||||
border: 2px dashed #468847 !important;
|
||||
padding: 8px 8px 9px 8px;
|
||||
}
|
||||
|
||||
#edit-favourites-list {
|
||||
border: 1px solid #bce8f1;
|
||||
}
|
||||
|
||||
#edit-favourites-list .operation {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#edit-favourites-list .operation:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#save-footer {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.flow-control-op {
|
||||
color: #396f3a !important;
|
||||
background-color: #c7e4ba !important;
|
||||
border-color: #b3dba2 !important;
|
||||
}
|
||||
|
||||
.flow-control-op.break {
|
||||
color: #94312f !important;
|
||||
background-color: #eabfbf !important;
|
||||
border-color: #e2aeb5 !important;
|
||||
}
|
||||
|
||||
#support-modal textarea {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#save-text,
|
||||
#load-text {
|
||||
font-family: Consolas, monospace;
|
||||
}
|
||||
|
||||
button.dropdown-toggle {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #999 !important;
|
||||
background-color: #dfdfdf !important;
|
||||
border-color: #cdcdcd !important;
|
||||
}
|
||||
|
||||
.grey {
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.dark-blue {
|
||||
color: #fff;
|
||||
background-color: #428bca;
|
||||
border-color: #428bca;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #b94a48;
|
||||
background-color: #f2dede;
|
||||
border-color: #eed3d7;
|
||||
}
|
||||
|
||||
.amber {
|
||||
color: #c09853;
|
||||
background-color: #fcf8e3;
|
||||
border-color: #fbeed5;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #468847;
|
||||
background-color: #dff0d8;
|
||||
border-color: #d6e9c6;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #3a87ad;
|
||||
background-color: #d9edf7;
|
||||
border-color: #bce8f1;
|
||||
}
|
410
src/web/html/index.html
Executable file
|
@ -0,0 +1,410 @@
|
|||
<!-- htmlmin:ignore --><!--
|
||||
CyberChef - The Cyber Swiss Army Knife
|
||||
|
||||
@copyright Crown Copyright 2016
|
||||
@license Apache-2.0
|
||||
|
||||
Copyright 2016 Crown Copyright
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- htmlmin:ignore -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CyberChef</title>
|
||||
|
||||
<meta name="copyright" content="Crown Copyright 2016" />
|
||||
<meta name="description" content="The Cyber Swiss Army Knife" />
|
||||
<meta name="keywords" content="base64, hex, decode, encode, encrypt, decrypt, compress, decompress, regex, regular expressions, hash, crypt, hexadecimal, user agent, url, certificate, x.509, parser, JSON, gzip, md5, sha1, aes, des, blowfish, xor" />
|
||||
|
||||
<link rel="icon" type="image/png" href="images/favicon.ico?__inline" />
|
||||
<link href="styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<span id="edit-favourites" class="btn btn-default btn-sm"><img src="images/favourite-16x16.png" /> Edit</span>
|
||||
<div id="alert" class="alert alert-danger">
|
||||
<button type="button" class="close" id="alert-close">×</button>
|
||||
<span id="alert-content"></span>
|
||||
</div>
|
||||
<div id="content-wrapper">
|
||||
<div id="banner" class="green">
|
||||
<a href="cyberchef.htm" style="float: left; margin-left: 10px; margin-right: 80px;" download>Download CyberChef<img src="images/download-24x24.png" /></a>
|
||||
<span id="notice">
|
||||
<script type="text/javascript">
|
||||
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
|
||||
if (navigator.userAgent && navigator.userAgent.match(/MSIE \d\d?\./)) {
|
||||
document.write("Internet Explorer is not supported, please use Firefox or Chrome instead");
|
||||
alert("Internet Explorer is not supported, please use Firefox or Chrome instead");
|
||||
}
|
||||
</script>
|
||||
<noscript>JavaScript is not enabled. Good luck.</noscript>
|
||||
</span>
|
||||
<a href="#" id="support" class="banner-right" data-toggle="modal" data-target="#support-modal">About / Support<img src="images/help-22x22.png" /></a>
|
||||
<a href="#" id="options" class="banner-right">Options<img src="images/settings-22x22.png" /></a>
|
||||
</div>
|
||||
<div id="wrapper">
|
||||
<div id="operations" class="split split-horizontal no-select">
|
||||
<div class="title no-select">Operations</div>
|
||||
<input type="search" class="form-control" id="search" placeholder="Search..." autocomplete="off">
|
||||
<ul class="op-list" id="search-results"></ul>
|
||||
<div class="panel-group no-select" id="categories"></div>
|
||||
</div>
|
||||
|
||||
<div id="recipe" class="split split-horizontal no-select">
|
||||
<div class="title no-select">Recipe</div>
|
||||
<ul id="rec-list" class="no-select"></ul>
|
||||
|
||||
<div id="controls" class="no-select">
|
||||
<div id="operational-controls">
|
||||
<div id="bake-group">
|
||||
<button type="button" class="btn btn-success btn-lg" id="bake">
|
||||
<img src="images/cook_male-32x32.png" />
|
||||
Bake!
|
||||
</button>
|
||||
<label class="btn btn-success btn-lg" id="auto-bake-label">
|
||||
<input type="checkbox" checked="checked" id="auto-bake">
|
||||
<div>Auto Bake</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" style="padding-top: 10px;">
|
||||
<button type="button" class="btn btn-default" id="step"><img src="images/step-16x16.png" /> Step through</button>
|
||||
<button type="button" class="btn btn-default" id="clr-breaks"><img src="images/erase-16x16.png" /> Clear breakpoints</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group-vertical" id="extra-controls">
|
||||
<button type="button" class="btn btn-default" id="save"><img src="images/save-16x16.png" /> Save recipe</button>
|
||||
<button type="button" class="btn btn-default" id="load"><img src="images/open_yellow-16x16.png" /> Load recipe</button>
|
||||
<button type="button" class="btn btn-default" id="clr-recipe"><img src="images/clean-16x16.png" /> Clear recipe</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split split-horizontal" id="IO">
|
||||
<div id="input" class="split no-select">
|
||||
<div class="title no-select">
|
||||
Input
|
||||
<div class="btn-group io-btn-group">
|
||||
<button type="button" class="btn btn-default btn-sm" id="clr-io"><img src="images/recycle-16x16.png" /> Clear I/O</button>
|
||||
<button type="button" class="btn btn-default btn-sm" id="reset-layout"><img src="images/layout-16x16.png" /> Reset layout</button>
|
||||
</div>
|
||||
<div class="io-info" id="input-info"></div>
|
||||
<div class="io-info" id="input-selection-info"></div>
|
||||
</div>
|
||||
<div class="textarea-wrapper no-select">
|
||||
<div id="input-highlighter" class="no-select"></div>
|
||||
<textarea id="input-text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="output" class="split">
|
||||
<div class="title no-select">
|
||||
Output
|
||||
<div class="btn-group io-btn-group">
|
||||
<button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img src="images/save_as-16x16.png" /> Save to file</button>
|
||||
<button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img src="images/switch-16x16.png" /> Move output to input</button>
|
||||
<button type="button" class="btn btn-default btn-sm" id="undo-switch" title="Undo move" disabled="disabled"><img src="images/undo-16x16.png" /> Undo</button>
|
||||
<button type="button" class="btn btn-default btn-sm" id="maximise-output" title="Maximise"><img src="images/maximise-16x16.png" /> Max</button>
|
||||
</div>
|
||||
<div class="io-info" id="output-info"></div>
|
||||
<div class="io-info" id="output-selection-info"></div>
|
||||
</div>
|
||||
<div class="textarea-wrapper">
|
||||
<div id="output-highlighter" class="no-select"></div>
|
||||
<div id="output-html"></div>
|
||||
<textarea id="output-text" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="save-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img class="pull-right" src="images/save-22x22.png" />
|
||||
<h4 class="modal-title">Save recipe</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="save-text">Save your recipe to local storage or copy the following string to load later</label>
|
||||
<textarea class="form-control" id="save-text" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="save-name">Recipe name</label>
|
||||
<input type="text" class="form-control" id="save-name" placeholder="Recipe name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" id="save-footer">
|
||||
<button type="button" class="btn btn-primary" id="save-button" data-dismiss="modal">Save</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Done</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group" id="save-link-group">
|
||||
<label>Data link</label>
|
||||
<div class="save-link-options">
|
||||
<input type="checkbox" id="save-link-recipe-checkbox" checked> Include recipe
|
||||
<input type="checkbox" id="save-link-input-checkbox" checked> Include input
|
||||
</div>
|
||||
<a id="save-link" style="word-wrap: break-word;"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="load-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img class="pull-right" src="images/open_yellow-24x24.png" />
|
||||
<h4 class="modal-title">Load recipe</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="load-name">Load your recipe from local storage or paste it into the box below</label>
|
||||
<select class="form-control" id="load-name"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" id="load-text" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="load-button" data-dismiss="modal">Load</button>
|
||||
<button type="button" class="btn btn-danger" id="load-delete-button">Delete</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="options-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img class="pull-right" src="images/settings-22x22.png" />
|
||||
<h4 class="modal-title">Options</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="options-body">
|
||||
<p style="font-weight: bold">Please note that these options will persist between sessions.</p>
|
||||
<div class="option-item">
|
||||
<input type="checkbox" option="update_url" checked />
|
||||
Update the URL when the input or recipe changes
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="checkbox" option="show_highlighter" checked />
|
||||
Highlight selected bytes in output and input (when possible)
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="checkbox" option="treat_as_utf8" checked />
|
||||
Treat output as UTF-8 if possible
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="checkbox" option="word_wrap" checked />
|
||||
Word wrap the input and output
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="checkbox" option="show_errors" checked />
|
||||
Operation error reporting (recommended)
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="number" option="error_timeout" />
|
||||
Operation error timeout in ms (0 for never)
|
||||
</div>
|
||||
<div class="option-item">
|
||||
<input type="number" option="auto_bake_threshold" />
|
||||
Auto Bake threshold in ms
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" id="reset-options">Reset options to default</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="favourites-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img class="pull-right" src="images/favourite-24x24.png" />
|
||||
<h4 class="modal-title">Edit Favourites</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="options-body">
|
||||
<ul>
|
||||
<li><span style="font-weight: bold">To add:</span> drag the operation over the favourites category</li>
|
||||
<li><span style="font-weight: bold">To reorder:</span> drag up and down in the list below</li>
|
||||
<li><span style="font-weight: bold">To remove:</span> hit the red cross or drag out of the list below</li>
|
||||
</ul>
|
||||
<br>
|
||||
<ul id="edit-favourites-list" class="op-list"></ul>
|
||||
<div class="option-item">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="reset-favourites">Reset favourites to default</button>
|
||||
<button type="button" class="btn btn-success" data-dismiss="modal" id="save-favourites">Save</button>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="support-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<img class="pull-right" src="images/help-22x22.png" />
|
||||
<h4 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<img class="about-img-left" src="images/cyberchef-128x128.png" />
|
||||
<p class="subtext">Compile time: <%= compileTime %></p>
|
||||
<p>© Crown Copyright 2016.</p>
|
||||
<p>Licenced under the Apache Licence, Version 2.0.</p>
|
||||
<br>
|
||||
<br>
|
||||
<div>
|
||||
<ul class='nav nav-tabs' role='tablist'>
|
||||
<li role='presentation' class='active'><a href='#faqs' aria-controls='profile' role='tab' data-toggle='tab'>
|
||||
<img src='images/help-16x16.png' />
|
||||
FAQs
|
||||
</a></li>
|
||||
<li role='presentation'><a href='#report-bug' aria-controls='messages' role='tab' data-toggle='tab'>
|
||||
<img src='images/bug-16x16.png' />
|
||||
Report a bug
|
||||
</a></li>
|
||||
<li role='presentation'><a href='#stats' aria-controls='messages' role='tab' data-toggle='tab'>
|
||||
<img src='images/stats-16x16.png' />
|
||||
Stats
|
||||
</a></li>
|
||||
<li role='presentation'><a href='#about' aria-controls='messages' role='tab' data-toggle='tab'>
|
||||
<img src='images/speech-16x16.png' />
|
||||
About
|
||||
</a></li>
|
||||
</ul>
|
||||
<div class='tab-content'>
|
||||
<div role='tabpanel' class='tab-pane active' id='faqs'>
|
||||
<br>
|
||||
<blockquote>
|
||||
<a data-toggle='collapse' data-target='#faq-examples'>
|
||||
What sort of things can I do with CyberChef?
|
||||
</a>
|
||||
</blockquote>
|
||||
<div class='collapse' id='faq-examples'>
|
||||
<p>There are well over 100 operations in CyberChef allowing you to carry simple and complex tasks easily. Here are some examples:</p>
|
||||
<ul>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22From%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%2Ctrue%5D%7D%5D&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22Translate%20DateTime%20Format%22%2C%22args%22%3A%5B%22Standard%20date%20and%20time%22%2C%22DD%2FMM%2FYYYY%20HH%3Amm%3Ass%22%2C%22UTC%22%2C%22dddd%20Do%20MMMM%20YYYY%20HH%3Amm%3Ass%20Z%20z%22%2C%22Australia%2FQueensland%22%5D%7D%5D&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22Parse%20IPv6%20address%22%2C%22args%22%3A%5B%5D%7D%5D&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy">Parse a Teredo IPv6 address</a></li>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22From%20Hexdump%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22Gunzip%22%2C%22args%22%3A%5B%5D%7D%5D&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu%2Fy7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb%2F3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw">Convert data from a hexdump, then decompress</a></li>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22From%20UNIX%20Timestamp%22%2C%22args%22%3A%5B%22Seconds%20(s)%22%5D%7D%5D&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Display multiple timestamps as full dates</a></li>
|
||||
<li><a href="?recipe=%5B%7B%22op%22%3A%22Fork%22%2C%22args%22%3A%5B%22%5C%5Cn%22%2C%22%5C%5Cn%22%5D%7D%2C%7B%22op%22%3A%22Conditional%20Jump%22%2C%22args%22%3A%5B%221%22%2C%222%22%2C%2210%22%5D%7D%2C%7B%22op%22%3A%22To%20Hex%22%2C%22args%22%3A%5B%22Space%22%5D%7D%2C%7B%22op%22%3A%22Return%22%2C%22args%22%3A%5B%5D%7D%2C%7B%22op%22%3A%22To%20Base64%22%2C%22args%22%3A%5B%22A-Za-z0-9%2B%2F%3D%22%5D%7D%5D&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA">Carry out different operations on data of different types</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<blockquote>
|
||||
<a data-toggle='collapse' data-target='#faq-load-files'>
|
||||
Can I load input directly from files?
|
||||
</a>
|
||||
</blockquote>
|
||||
<div class='collapse' id='faq-load-files'>
|
||||
<p>Yes! Just drag your file over the input box and drop it. The contents of the file will be converted into hexadecimal and the 'From Hex' operation will be added to the beginning of the recipe (if it's not already there). This is so that special characters like carriage returns aren't removed by your browser.</p>
|
||||
<p>Please note that loading large files is likely to cause a crash. There's not a lot that can be done about this - browsers just aren't very good at handling and displaying large amounts of data.</p>
|
||||
</div>
|
||||
<blockquote>
|
||||
<a data-toggle='collapse' data-target='#faq-fork'>
|
||||
How do I run operation X over multiple inputs at once?
|
||||
</a>
|
||||
</blockquote>
|
||||
<div class='collapse' id='faq-fork'>
|
||||
<p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>
|
||||
<p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>
|
||||
<p><a href='?recipe=%5B%7B"op"%3A"Fork"%2C"args"%3A%5B"%5C%5Cn"%2C"%5C%5Cn"%5D%7D%2C%7B"op"%3A"From%20UNIX%20Timestamp"%2C"args"%3A%5B"Seconds%20(s)"%5D%7D%5D&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA%3D%3D'>Click here</a> for an example.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div role='tabpanel' class='tab-pane' id='report-bug'>
|
||||
<br>
|
||||
<p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p>
|
||||
<br>
|
||||
<pre id='report-bug-info'></pre>
|
||||
<br>
|
||||
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a>
|
||||
</div>
|
||||
<div role='tabpanel' class='tab-pane' id='stats'>
|
||||
<br>
|
||||
<p>If you're a nerd like me, you might find statistics really fun! Here's some about the CyberChef code base:</p>
|
||||
<br><pre><%= codebaseStats %></pre>
|
||||
</div>
|
||||
<div role='tabpanel' class='tab-pane' id='about' style="padding: 20px;">
|
||||
<h4>What</h4>
|
||||
<p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p>
|
||||
|
||||
<h4>Why</h4>
|
||||
<p>Digital data comes in all shapes, sizes and formats in the modern world – CyberChef helps to make sense of this data all on one easy-to-use platform.</p>
|
||||
|
||||
<h4>How</h4>
|
||||
<p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p>
|
||||
<p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p>
|
||||
|
||||
<h4>Who</h4>
|
||||
<p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p>
|
||||
|
||||
<h4>Aim</h4>
|
||||
<p>It is hoped that by releasing CyberChef through <a href="https://github.com/gchq/cyberchef">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p>
|
||||
|
||||
<br>
|
||||
<p>There are around 150 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
|
||||
<p>It’s the Cyber Swiss Army Knife.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
<a href="https://github.com/gchq/CyberChef">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="images/fork_me.png" alt="Fork me on GitHub">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="confirm-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="confirm-title"></h4>
|
||||
</div>
|
||||
<div class="modal-body" id="confirm-body"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" id="confirm-yes">
|
||||
<img src="images/thumb_up-16x16.png" />
|
||||
Yes
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" id="confirm-no" data-dismiss="modal">
|
||||
<img src="images/thumb_down-16x16.png" />
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript" src="scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
51
src/web/index.js
Executable file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
var HTMLApp = require("./HTMLApp.js"),
|
||||
Categories = require("../core/config/Categories.js"),
|
||||
OperationConfig = require("../core/config/OperationConfig.js"),
|
||||
CanvasComponents = require("../core/lib/canvascomponents.js");
|
||||
|
||||
/**
|
||||
* Main function used to build the CyberChef web app.
|
||||
*/
|
||||
var main = function() {
|
||||
var defaultFavourites = [
|
||||
"To Base64",
|
||||
"From Base64",
|
||||
"To Hex",
|
||||
"From Hex",
|
||||
"To Hexdump",
|
||||
"From Hexdump",
|
||||
"URL Decode",
|
||||
"Regular expression",
|
||||
"Entropy",
|
||||
"Fork"
|
||||
];
|
||||
|
||||
var defaultOptions = {
|
||||
updateUrl : true,
|
||||
showHighlighter : true,
|
||||
treatAsUtf8 : true,
|
||||
wordWrap : true,
|
||||
showErrors : true,
|
||||
errorTimeout : 4000,
|
||||
autoBakeThreshold : 200,
|
||||
attemptHighlight : true,
|
||||
};
|
||||
|
||||
document.removeEventListener("DOMContentLoaded", main, false);
|
||||
window.app = new HTMLApp(Categories, OperationConfig, defaultFavourites, defaultOptions);
|
||||
window.app.setup();
|
||||
};
|
||||
|
||||
// Fix issues with browsers that don't support console.log()
|
||||
window.console = console || {log: function() {}, error: function() {}};
|
||||
|
||||
window.compileTime = moment.tz(COMPILE_TIME, "DD/MM/YYYY HH:mm:ss z", "UTC").valueOf();
|
||||
window.compileMessage = COMPILE_MSG;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", main, false);
|
50
src/web/static/.htaccess
Executable file
|
@ -0,0 +1,50 @@
|
|||
# Serve up .htm files as binary files rather than text/html.
|
||||
# This allows cyberchef.htm to be downloaded rather than opened in the browser.
|
||||
AddType application/octet-stream .htm
|
||||
|
||||
# Fix Apache bug #45023 where "-gzip" is appended to all ETags, preventing 304 responses
|
||||
<IfModule mod_headers.c>
|
||||
RequestHeader edit "If-None-Match" "^\"(.*)-gzip\"$" "\"$1\""
|
||||
Header edit "ETag" "^\"(.*[^g][^z][^i][^p])\"$" "\"$1-gzip\""
|
||||
</IfModule>
|
||||
|
||||
# Set gzip compression on all resources that support it
|
||||
<IfModule mod_deflate.c>
|
||||
SetOutputFilter DEFLATE
|
||||
</IfModule>
|
||||
|
||||
# Set Expires headers on various resources
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
|
||||
# 10 minutes
|
||||
ExpiresDefault "access plus 600 seconds"
|
||||
|
||||
# 30 days
|
||||
ExpiresByType image/x-icon "access plus 2592000 seconds"
|
||||
ExpiresByType image/jpeg "access plus 2592000 seconds"
|
||||
ExpiresByType image/png "access plus 2592000 seconds"
|
||||
ExpiresByType image/gif "access plus 2592000 seconds"
|
||||
|
||||
# 7 days
|
||||
ExpiresByType text/css "access plus 604800 seconds"
|
||||
ExpiresByType text/javascript "access plus 604800 seconds"
|
||||
ExpiresByType application/javascript "access plus 604800 seconds"
|
||||
ExpiresByType text/html "access plus 604800 seconds"
|
||||
</IfModule>
|
||||
|
||||
# Set Cache-Control headers on various resources
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\\.(ico|jpe?g|png|gif)$">
|
||||
Header set Cache-Control "max-age=2592000, public"
|
||||
</FilesMatch>
|
||||
<FilesMatch "\\.(css)$">
|
||||
Header set Cache-Control "max-age=600, public"
|
||||
</FilesMatch>
|
||||
<FilesMatch "\\.(js)$">
|
||||
Header set Cache-Control "max-age=600, private, must-revalidate"
|
||||
</FilesMatch>
|
||||
<FilesMatch "\\.(x?html?)$">
|
||||
Header set Cache-Control "max-age=600, private, must-revalidate"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
18
src/web/static/ga.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
<!-- Begin Google Analytics -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-85682716-2', 'auto');
|
||||
|
||||
// Specifying location.pathname here overrides the default URL which would include arguments.
|
||||
// This method prevents Google Analytics from logging any recipe or input data in the URL.
|
||||
ga('send', 'pageview', location.pathname);
|
||||
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
||||
|
BIN
src/web/static/images/breakpoint-16x16.png
Executable file
After Width: | Height: | Size: 295 B |
BIN
src/web/static/images/bug-16x16.png
Executable file
After Width: | Height: | Size: 773 B |
BIN
src/web/static/images/clean-16x16.png
Executable file
After Width: | Height: | Size: 702 B |
BIN
src/web/static/images/code-16x16.png
Executable file
After Width: | Height: | Size: 796 B |
BIN
src/web/static/images/cook_female-32x32.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/web/static/images/cook_male-32x32.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/web/static/images/cyberchef-128x128.png
Executable file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/web/static/images/cyberchef-16x16.png
Executable file
After Width: | Height: | Size: 419 B |
BIN
src/web/static/images/cyberchef-256x256.png
Executable file
After Width: | Height: | Size: 17 KiB |
BIN
src/web/static/images/cyberchef-32x32.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/web/static/images/cyberchef-512x512.png
Executable file
After Width: | Height: | Size: 38 KiB |
BIN
src/web/static/images/cyberchef-64x64.png
Executable file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/web/static/images/disable_deselected-16x16.png
Executable file
After Width: | Height: | Size: 746 B |
BIN
src/web/static/images/disable_selected-16x16.png
Executable file
After Width: | Height: | Size: 590 B |
BIN
src/web/static/images/download-24x24.png
Executable file
After Width: | Height: | Size: 905 B |
BIN
src/web/static/images/erase-16x16.png
Executable file
After Width: | Height: | Size: 680 B |
BIN
src/web/static/images/favicon.ico
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/web/static/images/favourite-16x16.png
Executable file
After Width: | Height: | Size: 491 B |
BIN
src/web/static/images/favourite-24x24.png
Executable file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/web/static/images/fork_me.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
src/web/static/images/help-16x16.png
Executable file
After Width: | Height: | Size: 843 B |
BIN
src/web/static/images/help-22x22.png
Executable file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/web/static/images/info-16x16.png
Executable file
After Width: | Height: | Size: 513 B |
BIN
src/web/static/images/layout-16x16.png
Executable file
After Width: | Height: | Size: 334 B |
BIN
src/web/static/images/mail-16x16.png
Executable file
After Width: | Height: | Size: 463 B |
BIN
src/web/static/images/maximise-16x16.png
Executable file
After Width: | Height: | Size: 235 B |
BIN
src/web/static/images/open_yellow-16x16.png
Executable file
After Width: | Height: | Size: 474 B |
BIN
src/web/static/images/open_yellow-24x24.png
Executable file
After Width: | Height: | Size: 719 B |
BIN
src/web/static/images/recycle-16x16.png
Executable file
After Width: | Height: | Size: 585 B |
BIN
src/web/static/images/remove-16x16.png
Executable file
After Width: | Height: | Size: 507 B |
BIN
src/web/static/images/restore-16x16.png
Executable file
After Width: | Height: | Size: 245 B |
BIN
src/web/static/images/save-16x16.png
Executable file
After Width: | Height: | Size: 472 B |
BIN
src/web/static/images/save-22x22.png
Executable file
After Width: | Height: | Size: 695 B |
BIN
src/web/static/images/save_as-16x16.png
Executable file
After Width: | Height: | Size: 642 B |
BIN
src/web/static/images/settings-22x22.png
Executable file
After Width: | Height: | Size: 1 KiB |
BIN
src/web/static/images/speech-16x16.png
Executable file
After Width: | Height: | Size: 360 B |
BIN
src/web/static/images/stats-16x16.png
Executable file
After Width: | Height: | Size: 3.2 KiB |
BIN
src/web/static/images/step-16x16.png
Executable file
After Width: | Height: | Size: 575 B |
BIN
src/web/static/images/switch-16x16.png
Executable file
After Width: | Height: | Size: 472 B |
BIN
src/web/static/images/thumb_down-16x16.png
Executable file
After Width: | Height: | Size: 769 B |
BIN
src/web/static/images/thumb_up-16x16.png
Executable file
After Width: | Height: | Size: 717 B |
BIN
src/web/static/images/undo-16x16.png
Executable file
After Width: | Height: | Size: 553 B |
13
src/web/static/stats.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
128 source files
|
||||
49068 lines
|
||||
1.9M size
|
||||
|
||||
63 JavaScript source files
|
||||
20841 lines
|
||||
788K size
|
||||
|
||||
4.7M uncompressed JavaScript size
|
||||
2.6M compressed JavaScript size
|
||||
|
||||
15 categories
|
||||
177 operations
|