Operations can now: 1) return their progress directly. 2) throw an error. 3) (ADDED) return a promise: + that resolves to their progress. + that rejects an error message (like throwing but asynchronous). For an example see the new operation "Wait" (Flow Control) Added a flow control operation "Wait", which waits for the number of milliseconds passed in as its argument. It is a fairly useless operation but it does demonstrate how asynchronous operations now work. A recipe like: ``` Fork Wait (1000ms) ``` will only wait for 1000ms (each wait runs at the same time as each other). I have not looked into performance implications yet, also this code is probably more complicated than it needs to be (would love help on this).
134 lines
4.5 KiB
JavaScript
Executable File
134 lines
4.5 KiB
JavaScript
Executable File
/**
|
|
* The main controller for CyberChef.
|
|
*
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @copyright Crown Copyright 2016
|
|
* @license Apache-2.0
|
|
*
|
|
* @class
|
|
*/
|
|
var Chef = function() {
|
|
this.dish = new Dish();
|
|
};
|
|
|
|
|
|
/**
|
|
* Runs the recipe over the input.
|
|
*
|
|
* @param {string} inputText - The input data as a string
|
|
* @param {Object[]} recipeConfig - The recipe configuration object
|
|
* @param {Object} options - The options object storing various user choices
|
|
* @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
|
|
* @param {number} progress - The position in the recipe to start from
|
|
* @param {number} [step] - The number of operations to execute
|
|
*
|
|
* @returns {Object} response
|
|
* @returns {string} response.result - The output of the recipe
|
|
* @returns {string} response.type - The data type of the result
|
|
* @returns {number} response.progress - The position that we have got to in the recipe
|
|
* @returns {number} response.options - The app options object (which may have been changed)
|
|
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
|
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
|
*/
|
|
Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step) {
|
|
var startTime = new Date().getTime(),
|
|
recipe = new Recipe(recipeConfig),
|
|
containsFc = recipe.containsFlowControl(),
|
|
chef = this;
|
|
|
|
// Reset attemptHighlight flag
|
|
if (options.hasOwnProperty("attemptHighlight")) {
|
|
options.attemptHighlight = true;
|
|
}
|
|
|
|
if (containsFc) options.attemptHighlight = false;
|
|
|
|
// Clean up progress
|
|
if (progress >= recipeConfig.length) {
|
|
progress = 0;
|
|
}
|
|
|
|
if (step) {
|
|
// Unset breakpoint on this step
|
|
recipe.setBreakpoint(progress, false);
|
|
// Set breakpoint on next step
|
|
recipe.setBreakpoint(progress + 1, true);
|
|
}
|
|
|
|
// If stepping with flow control, we have to start from the beginning
|
|
// but still want to skip all previous breakpoints
|
|
if (progress > 0 && containsFc) {
|
|
recipe.removeBreaksUpTo(progress);
|
|
progress = 0;
|
|
}
|
|
|
|
// If starting from scratch, load data
|
|
if (progress === 0) {
|
|
chef.dish.set(inputText, Dish.STRING);
|
|
}
|
|
|
|
var ret = {
|
|
options: options,
|
|
error: false,
|
|
};
|
|
|
|
return new Promise(function(resolve) {
|
|
recipe.execute(chef.dish, progress)
|
|
.then(function(progress) {
|
|
ret.result = chef.dish.type === Dish.HTML ?
|
|
chef.dish.get(Dish.HTML) :
|
|
chef.dish.get(Dish.STRING);
|
|
ret.type = Dish.enumLookup(chef.dish.type);
|
|
|
|
ret.duration = new Date().getTime() - startTime;
|
|
ret.progress = progress;
|
|
|
|
resolve(ret);
|
|
})
|
|
.catch(function(err) {
|
|
ret.result = chef.dish.type === Dish.HTML ?
|
|
chef.dish.get(Dish.HTML) :
|
|
chef.dish.get(Dish.STRING);
|
|
ret.type = Dish.enumLookup(chef.dish.type);
|
|
|
|
ret.duration = new Date().getTime() - startTime;
|
|
ret.progress = err.progress;
|
|
ret.error = err;
|
|
|
|
// Resolve not reject: we are packaging the error as a value.
|
|
resolve(ret);
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* @param {Object[]} recipeConfig - The recipe configuration object
|
|
* @returns {number} The time it took to run the silent bake in milliseconds.
|
|
*/
|
|
Chef.prototype.silentBake = function(recipeConfig) {
|
|
var startTime = new Date().getTime(),
|
|
recipe = new Recipe(recipeConfig),
|
|
dish = new Dish("", Dish.STRING);
|
|
|
|
try {
|
|
recipe.execute(dish);
|
|
} catch (err) {
|
|
// Suppress all errors
|
|
}
|
|
return new Date().getTime() - startTime;
|
|
};
|