Skip to content

Commit

Permalink
Formally disallow flowcontrol operations from being used in bake recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
d98762625 committed Jun 5, 2020
1 parent 9392089 commit 53e6983
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 74 deletions.
8 changes: 7 additions & 1 deletion src/node/NodeRecipe.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,22 @@ class NodeRecipe {
* @param {String | Function | Object} ing
*/
_validateIngredient(ing) {
// CASE operation name given. Find operation and validate
if (typeof ing === "string") {
const op = operations.find((op) => {
return sanitise(op.opName) === sanitise(ing);
});
if (op) {
return op;
return this._validateIngredient(op);
} else {
throw new TypeError(`Couldn't find an operation with name '${ing}'.`);
}
// CASE operation given. Check its a chef operation and check its not flowcontrol
} else if (typeof ing === "function") {
if (ing.flowControl) {
throw new TypeError(`flowControl operations like ${ing.opName} are not currently allowed in recipes for chef.bake`);
}

if (operations.includes(ing)) {
return ing;
} else {
Expand Down
37 changes: 17 additions & 20 deletions src/node/api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export function _wrap(OpClass) {
// Check to see if class's run function is async.
const opInstance = new OpClass();
const isAsync = opInstance.run.constructor.name === "AsyncFunction";
const isFlowControl = opInstance.flowControl;

let wrapped;

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

// SPECIAL CASE for Magic.
if (opInstance.flowControl) {
// SPECIAL CASE for Magic. Other flowControl operations will
// not work because the opList is not passed through.
if (isFlowControl) {
opInstance.ingValues = transformedArgs;

const state = {
Expand Down Expand Up @@ -241,6 +243,8 @@ export function _wrap(OpClass) {
// used in chef.help
wrapped.opName = OpClass.name;
wrapped.args = createArgInfo(opInstance);
// Used in NodeRecipe to check for flowControl ops
wrapped.flowControl = isFlowControl;

return wrapped;
}
Expand Down Expand Up @@ -315,25 +319,18 @@ export function help(input) {


/**
* bake [Wrapped] - Perform an array of operations on some input.
* @returns {Function}
* bake
*
* @param {*} input - some input for a recipe.
* @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig -
* An operation, operation name, or an array of either.
* @returns {NodeDish} of the result
* @throws {TypeError} if invalid recipe given.
*/
export function bake() {

/**
* bake
*
* @param {*} input - some input for a recipe.
* @param {String | Function | String[] | Function[] | [String | Function]} recipeConfig -
* An operation, operation name, or an array of either.
* @returns {SyncDish} of the result
* @throws {TypeError} if invalid recipe given.
*/
return function(input, recipeConfig) {
const recipe = new NodeRecipe(recipeConfig);
const dish = ensureIsDish(input);
return recipe.execute(dish);
};
export function bake(input, recipeConfig) {
const recipe = new NodeRecipe(recipeConfig);
const dish = ensureIsDish(input);
return recipe.execute(dish);
}


Expand Down
5 changes: 2 additions & 3 deletions src/node/config/scripts/generateNodeIndex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ Object.keys(operations).forEach((op) => {

code += `];
const prebaked = bake(operations);
chef.bake = prebaked;
chef.bake = bake;
export default chef;
// Operations as top level exports.
Expand All @@ -114,7 +113,7 @@ Object.keys(operations).forEach((op) => {
});

code += " NodeDish as Dish,\n";
code += " prebaked as bake,\n";
code += " bake,\n";
code += " help,\n";
code += " OperationError,\n";
code += " ExcludedOperationError,\n";
Expand Down
105 changes: 55 additions & 50 deletions tests/node/tests/nodeApi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import assert from "assert";
import it from "../assertionHandler.mjs";
import chef from "../../../src/node/index.mjs";
import OperationError from "../../../src/core/errors/OperationError.mjs";
import { OperationError, ExcludedOperationError } from "../../../src/core/errors/index.mjs";
import NodeDish from "../../../src/node/NodeDish.mjs";

import { toBase32} from "../../../src/node/index.mjs";
import { toBase32, magic} from "../../../src/node/index.mjs";
import TestRegister from "../../lib/TestRegister.mjs";

TestRegister.addApiTests([
Expand Down Expand Up @@ -181,22 +181,17 @@ TestRegister.addApiTests([
}),

it("chef.bake: should complain if recipe isnt a valid object", () => {
try {
chef.bake("some input", 3264);
} catch (e) {
assert.strictEqual(e.name, "TypeError");
assert.strictEqual(e.message, "Recipe can only contain function names or functions");
}
assert.throws(() => chef.bake("some input", 3264), {
name: "TypeError",
message: "Recipe can only contain function names or functions"
});
}),

it("chef.bake: Should complain if string op is invalid", () => {
try {
chef.bake("some input", "not a valid operation");
assert.fail("Shouldn't be hit");
} catch (e) {
assert.strictEqual(e.name, "TypeError");
assert.strictEqual(e.message, "Couldn't find an operation with name 'not a valid operation'.");
}
assert.throws(() => chef.bake("some input", "not a valid operation"), {
name: "TypeError",
message: "Couldn't find an operation with name 'not a valid operation'."
});
}),

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

it("chef.bake: Should complain if an invalid operation is inputted", () => {
try {
chef.bake("https://google.com/search?q=help", () => {});
assert.fail("Shouldn't be hit");
} catch (e) {
assert.strictEqual(e.name, "TypeError");
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
}
assert.throws(() => chef.bake("https://google.com/search?q=help", () => {}), {
name: "TypeError",
message: "Inputted function not a Chef operation."
});
}),

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

it("should complain if an invalid operation is inputted as part of array", () => {
try {
chef.bake("something", [() => {}]);
} catch (e) {
assert.strictEqual(e.name, "TypeError");
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
}
assert.throws(() => chef.bake("something", [() => {}]), {
name: "TypeError",
message: "Inputted function not a Chef operation."
});
}),

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

it("chef.bake: should error if op in JSON is not chef op", () => {
try {
chef.bake("some input", {
op: () => {},
args: ["Colon"],
});
} catch (e) {
assert.strictEqual(e.name, "TypeError");
assert.strictEqual(e.message, "Inputted function not a Chef operation.");
}
assert.throws(() => chef.bake("some input", {
op: () => {},
args: ["Colon"],
}), {
name: "TypeError",
message: "Inputted function not a Chef operation."
});
}),

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

it("Excluded operations: throw a sensible error when you try and call one", () => {
try {
chef.fork();
} catch (e) {
assert.strictEqual(e.type, "ExcludedOperationError");
assert.strictEqual(e.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
}
it("chef.bake: cannot accept flowControl operations in recipe", () => {
assert.throws(() => chef.bake("some input", "magic"), {
name: "TypeError",
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
});
assert.throws(() => chef.bake("some input", magic), {
name: "TypeError",
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
});
assert.throws(() => chef.bake("some input", ["to base 64", "magic"]), {
name: "TypeError",
message: "flowControl operations like Magic are not currently allowed in recipes for chef.bake"
});
}),

it("Excluded operations: throw a sensible error when you try and call one", () => {
try {
chef.renderImage();
} catch (e) {
assert.strictEqual(e.type, "ExcludedOperationError");
assert.strictEqual(e.message, "Sorry, the RenderImage operation is not available in the Node.js version of CyberChef.");
}
assert.throws(chef.fork,
(err) => {
assert(err instanceof ExcludedOperationError);
assert.deepEqual(err.message, "Sorry, the Fork operation is not available in the Node.js version of CyberChef.");
return true;
},
"Unexpected error type"
);
assert.throws(chef.javaScriptBeautify,
(err) => {
assert(err instanceof ExcludedOperationError);
assert.deepEqual(err.message, "Sorry, the JavaScriptBeautify operation is not available in the Node.js version of CyberChef.");
return true;
},
"Unexpected error type"
);
}),

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

]);

0 comments on commit 53e6983

Please sign in to comment.