Skip to content

Commit 53e6983

Browse files
committed
Formally disallow flowcontrol operations from being used in bake recipes
1 parent 9392089 commit 53e6983

4 files changed

Lines changed: 81 additions & 74 deletions

File tree

src/node/NodeRecipe.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,22 @@ class NodeRecipe {
2828
* @param {String | Function | Object} ing
2929
*/
3030
_validateIngredient(ing) {
31+
// CASE operation name given. Find operation and validate
3132
if (typeof ing === "string") {
3233
const op = operations.find((op) => {
3334
return sanitise(op.opName) === sanitise(ing);
3435
});
3536
if (op) {
36-
return op;
37+
return this._validateIngredient(op);
3738
} else {
3839
throw new TypeError(`Couldn't find an operation with name '${ing}'.`);
3940
}
41+
// CASE operation given. Check its a chef operation and check its not flowcontrol
4042
} else if (typeof ing === "function") {
43+
if (ing.flowControl) {
44+
throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake`);
45+
}
46+
4147
if (operations.includes(ing)) {
4248
return ing;
4349
} else {

src/node/api.mjs

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export function _wrap(OpClass) {
177177
// Check to see if class's run function is async.
178178
const opInstance = new OpClass();
179179
const isAsync = opInstance.run.constructor.name === "AsyncFunction";
180+
const isFlowControl = opInstance.flowControl;
180181

181182
let wrapped;
182183

@@ -192,8 +193,9 @@ export function _wrap(OpClass) {
192193
wrapped = async (input, args=null) => {
193194
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
194195

195-
// SPECIAL CASE for Magic.
196-
if (opInstance.flowControl) {
196+
// SPECIAL CASE for Magic. Other flowControl operations will
197+
// not work because the opList is not passed through.
198+
if (isFlowControl) {
197199
opInstance.ingValues = transformedArgs;
198200

199201
const state = {
@@ -241,6 +243,8 @@ export function _wrap(OpClass) {
241243
// used in chef.help
242244
wrapped.opName = OpClass.name;
243245
wrapped.args = createArgInfo(opInstance);
246+
// Used in NodeRecipe to check for flowControl ops
247+
wrapped.flowControl = isFlowControl;
244248

245249
return wrapped;
246250
}
@@ -315,25 +319,18 @@ export function help(input) {
315319

316320

317321
/**
318-
* bake [Wrapped] - Perform an array of operations on some input.
319-
* @returns {Function}
322+
* bake
323+
*
324+
* @param {*} input - some input for a recipe.
325+
* @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig -
326+
* An operation, operation name, or an array of either.
327+
* @returns {NodeDish} of the result
328+
* @throws {TypeError} if invalid recipe given.
320329
*/
321-
export function bake() {
322-
323-
/**
324-
* bake
325-
*
326-
* @param {*} input - some input for a recipe.
327-
* @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig -
328-
* An operation, operation name, or an array of either.
329-
* @returns {SyncDish} of the result
330-
* @throws {TypeError} if invalid recipe given.
331-
*/
332-
return function(input, recipeConfig) {
333-
const recipe = new NodeRecipe(recipeConfig);
334-
const dish = ensureIsDish(input);
335-
return recipe.execute(dish);
336-
};
330+
export function bake(input, recipeConfig) {
331+
const recipe = new NodeRecipe(recipeConfig);
332+
const dish = ensureIsDish(input);
333+
return recipe.execute(dish);
337334
}
338335

339336

src/node/config/scripts/generateNodeIndex.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ Object.keys(operations).forEach((op) => {
100100

101101
code += `];
102102
103-
const prebaked = bake(operations);
104-
chef.bake = prebaked;
103+
chef.bake = bake;
105104
export default chef;
106105
107106
// Operations as top level exports.
@@ -114,7 +113,7 @@ Object.keys(operations).forEach((op) => {
114113
});
115114

116115
code += " NodeDish as Dish,\n";
117-
code += " prebaked as bake,\n";
116+
code += " bake,\n";
118117
code += " help,\n";
119118
code += " OperationError,\n";
120119
code += " ExcludedOperationError,\n";

tests/node/tests/nodeApi.mjs

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import assert from "assert";
1414
import it from "../assertionHandler.mjs";
1515
import chef from "../../../src/node/index.mjs";
16-
import OperationError from "../../../src/core/errors/OperationError.mjs";
16+
import { OperationError, ExcludedOperationError } from "../../../src/core/errors/index.mjs";
1717
import NodeDish from "../../../src/node/NodeDish.mjs";
1818

19-
import { toBase32} from "../../../src/node/index.mjs";
19+
import { toBase32, magic} from "../../../src/node/index.mjs";
2020
import TestRegister from "../../lib/TestRegister.mjs";
2121

2222
TestRegister.addApiTests([
@@ -181,22 +181,17 @@ TestRegister.addApiTests([
181181
}),
182182

183183
it("chef.bake: should complain if recipe isnt a valid object", () => {
184-
try {
185-
chef.bake("some input", 3264);
186-
} catch (e) {
187-
assert.strictEqual(e.name, "TypeError");
188-
assert.strictEqual(e.message, "Recipe can only contain function names or functions");
189-
}
184+
assert.throws(() => chef.bake("some input", 3264), {
185+
name: "TypeError",
186+
message: "Recipe can only contain function names or functions"
187+
});
190188
}),
191189

192190
it("chef.bake: Should complain if string op is invalid", () => {
193-
try {
194-
chef.bake("some input", "not a valid operation");
195-
assert.fail("Shouldn't be hit");
196-
} catch (e) {
197-
assert.strictEqual(e.name, "TypeError");
198-
assert.strictEqual(e.message, "Couldn't find an operation with name 'not a valid operation'.");
199-
}
191+
assert.throws(() => chef.bake("some input", "not a valid operation"), {
192+
name: "TypeError",
193+
message: "Couldn't find an operation with name 'not a valid operation'."
194+
});
200195
}),
201196

202197
it("chef.bake: Should take an input and an operation and perform it", () => {
@@ -205,13 +200,10 @@ TestRegister.addApiTests([
205200
}),
206201

207202
it("chef.bake: Should complain if an invalid operation is inputted", () => {
208-
try {
209-
chef.bake("https://google.com/search?q=help", () => {});
210-
assert.fail("Shouldn't be hit");
211-
} catch (e) {
212-
assert.strictEqual(e.name, "TypeError");
213-
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
214-
}
203+
assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), {
204+
name: "TypeError",
205+
message: "Inputted function not a Chef operation."
206+
});
215207
}),
216208

217209
it("chef.bake: accepts an array of operation names and performs them all in order", () => {
@@ -241,12 +233,10 @@ TestRegister.addApiTests([
241233
}),
242234

243235
it("should complain if an invalid operation is inputted as part of array", () => {
244-
try {
245-
chef.bake("something", [() => {}]);
246-
} catch (e) {
247-
assert.strictEqual(e.name, "TypeError");
248-
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
249-
}
236+
assert.throws(() => chef.bake("something", [() => {}]), {
237+
name: "TypeError",
238+
message: "Inputted function not a Chef operation."
239+
});
250240
}),
251241

252242
it("chef.bake: should take single JSON object describing op and args OBJ", () => {
@@ -275,15 +265,13 @@ TestRegister.addApiTests([
275265
}),
276266

277267
it("chef.bake: should error if op in JSON is not chef op", () => {
278-
try {
279-
chef.bake("some input", {
280-
op: () => {},
281-
args: ["Colon"],
282-
});
283-
} catch (e) {
284-
assert.strictEqual(e.name, "TypeError");
285-
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
286-
}
268+
assert.throws(() => chef.bake("some input", {
269+
op: () => {},
270+
args: ["Colon"],
271+
}), {
272+
name: "TypeError",
273+
message: "Inputted function not a Chef operation."
274+
});
287275
}),
288276

289277
it("chef.bake: should take multiple ops in JSON object form, some ops by string", () => {
@@ -357,22 +345,38 @@ TestRegister.addApiTests([
357345
assert.strictEqual(result.toString(), "begin_something_aaaaaaaaaaaaaa_end_something");
358346
}),
359347

360-
it("Excluded operations: throw a sensible error when you try and call one", () => {
361-
try {
362-
chef.fork();
363-
} catch (e) {
364-
assert.strictEqual(e.type, "ExcludedOperationError");
365-
assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
366-
}
348+
it("chef.bake: cannot accept flowControl operations in recipe", () => {
349+
assert.throws(() => chef.bake("some input", "magic"), {
350+
name: "TypeError",
351+
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
352+
});
353+
assert.throws(() => chef.bake("some input", magic), {
354+
name: "TypeError",
355+
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
356+
});
357+
assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), {
358+
name: "TypeError",
359+
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
360+
});
367361
}),
368362

369363
it("Excluded operations: throw a sensible error when you try and call one", () => {
370-
try {
371-
chef.renderImage();
372-
} catch (e) {
373-
assert.strictEqual(e.type, "ExcludedOperationError");
374-
assert.strictEqual(e.message, "Sorry, the RenderImage operation is not available in the Node.js version of CyberChef.");
375-
}
364+
assert.throws(chef.fork,
365+
(err) => {
366+
assert(err instanceof ExcludedOperationError);
367+
assert.deepEqual(err.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
368+
return true;
369+
},
370+
"Unexpected error type"
371+
);
372+
assert.throws(chef.javaScriptBeautify,
373+
(err) => {
374+
assert(err instanceof ExcludedOperationError);
375+
assert.deepEqual(err.message, "Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef.");
376+
return true;
377+
},
378+
"Unexpected error type"
379+
);
376380
}),
377381

378382
it("Operation arguments: should be accessible from operation object if op has array arg", () => {
@@ -405,4 +409,5 @@ TestRegister.addApiTests([
405409
assert.equal(chef.convertDistance.args.inputUnits.options[0], "Nanometres (nm)");
406410
assert.equal(chef.defangURL.args.process.options[1], "Only full URLs");
407411
}),
412+
408413
]);

0 commit comments

Comments
 (0)