-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
Copy pathChef.mjs
executable file
·191 lines (163 loc) · 6.04 KB
/
Chef.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* @author n1474335 [[email protected]]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Dish from "./Dish.mjs";
import Recipe from "./Recipe.mjs";
import log from "loglevel";
import { isWorkerEnvironment } from "./Utils.mjs";
/**
* The main controller for CyberChef.
*/
class Chef {
/**
* Chef constructor
*/
constructor() {
this.dish = new Dish();
}
/**
* Runs the recipe over the input.
*
* @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
* @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
*
* @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.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)
*/
async bake(input, recipeConfig, options) {
log.debug("Chef baking");
const startTime = Date.now(),
recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(),
notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8;
let error = false,
progress = 0;
if (containsFc && isWorkerEnvironment()) self.setOption("attemptHighlight", false);
// Load data
const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
this.dish.set(input, type);
try {
progress = await recipe.execute(this.dish, progress);
} catch (err) {
log.error(err);
error = {
displayStr: err.displayStr,
};
progress = err.progress;
}
// Create a raw version of the dish, unpresented
const rawDish = this.dish.clone();
// Present the raw result
await recipe.present(this.dish);
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType =
this.dish.type === Dish.HTML ?
Dish.HTML :
this.dish.size > threshold ?
Dish.ARRAY_BUFFER :
Dish.STRING;
return {
dish: rawDish,
result: await this.dish.get(returnType, notUTF8),
type: Dish.enumLookup(this.dish.type),
progress: progress,
duration: Date.now() - startTime,
error: error
};
}
/**
* 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.
*/
silentBake(recipeConfig) {
log.debug("Running silent bake");
const startTime = Date.now(),
recipe = new Recipe(recipeConfig),
dish = new Dish();
try {
recipe.execute(dish);
} catch (err) {
// Suppress all errors
}
return Date.now() - startTime;
}
/**
* Calculates highlight offsets if possible.
*
* @param {Object[]} recipeConfig
* @param {string} direction
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
* @returns {Object}
*/
async calculateHighlights(recipeConfig, direction, pos) {
const recipe = new Recipe(recipeConfig);
const highlights = await recipe.generateHighlightList();
if (!highlights) return false;
for (let i = 0; i < highlights.length; i++) {
// Remove multiple highlights before processing again
pos = [pos[0]];
const func = direction === "forward" ? highlights[i].f : highlights[i].b;
if (typeof func == "function") {
try {
pos = func(pos, highlights[i].args);
} catch (err) {
// Throw away highlighting errors
pos = [];
}
}
}
return {
pos: pos,
direction: direction
};
}
/**
* Translates the dish to a specified type and returns it.
*
* @param {Dish} dish
* @param {string} type
* @returns {Dish}
*/
async getDishAs(dish, type) {
const newDish = new Dish(dish);
return await newDish.get(type);
}
/**
* Gets the title of a dish and returns it
*
* @param {Dish} dish
* @param {number} [maxLength=100]
* @returns {string}
*/
async getDishTitle(dish, maxLength=100) {
const newDish = new Dish(dish);
return await newDish.getTitle(maxLength);
}
}
export default Chef;