fix: make expression evaluation in active effects more secure
This commit is contained in:
parent
19ba312a44
commit
20ea70d96a
8 changed files with 1222 additions and 1 deletions
90
spec/expression-evaluation/evaluator.spec.ts
Normal file
90
spec/expression-evaluation/evaluator.spec.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { defaultEvaluator, Evaluator, mathEvaluator } from "../../src/expression-evaluation/evaluator";
|
||||
|
||||
describe("Evaluator", () => {
|
||||
it("evaluates expressions that only use identifiers according to the given predicate", () => {
|
||||
// given
|
||||
const expression = "typeof 'foo' === 'string' ? 42 : null";
|
||||
|
||||
// when
|
||||
const result = defaultEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(result).toEqual(42);
|
||||
});
|
||||
|
||||
it("fails to evaluate expressions that contain identifiers that are not allowed by the predicate", () => {
|
||||
// given
|
||||
const expression = "typeof 'foo' === 'string' ? 42 : function (){}";
|
||||
|
||||
// when
|
||||
const evaluate = () => defaultEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(evaluate).toThrowError("'function' is not an allowed identifier.");
|
||||
});
|
||||
|
||||
it("fails to evaluate expressions that contain invalid tokens", () => {
|
||||
// given
|
||||
const expression = "1;";
|
||||
|
||||
// when
|
||||
const evaluate = () => defaultEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(evaluate).toThrowError("Invalid or unexpected token (1)");
|
||||
});
|
||||
|
||||
it("fails to evaluate expressions that contain arrow functions", () => {
|
||||
// given
|
||||
const expression = "(() => 1)()";
|
||||
|
||||
// when
|
||||
const evaluate = () => defaultEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(evaluate).toThrowError("Invalid or unexpected token (4)");
|
||||
});
|
||||
|
||||
it("makes the given context available", () => {
|
||||
// given
|
||||
const context = { floor: Math.floor };
|
||||
const evaluator = new Evaluator({ context });
|
||||
const expression = "floor(0.5)";
|
||||
|
||||
// when
|
||||
const result = evaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(result).toEqual(0);
|
||||
});
|
||||
|
||||
describe("mathEvaluator", () => {
|
||||
it("makes the given context available", () => {
|
||||
// given
|
||||
const expression = "sqrt(sin(PI))";
|
||||
|
||||
// when
|
||||
const result = mathEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(result).toEqual(Math.sqrt(Math.sin(Math.PI)));
|
||||
});
|
||||
|
||||
it("does not give acces to the function constructor", () => {
|
||||
// given
|
||||
const expression = "sqrt.constructor";
|
||||
|
||||
// when
|
||||
const evaluate = () => mathEvaluator.evaluate(expression);
|
||||
|
||||
// then
|
||||
expect(evaluate).toThrowError("'constructor' is not an allowed identifier.");
|
||||
});
|
||||
});
|
||||
});
|
602
spec/expression-evaluation/lexer.spec.ts
Normal file
602
spec/expression-evaluation/lexer.spec.ts
Normal file
|
@ -0,0 +1,602 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { Lexer } from "../../src/expression-evaluation/lexer";
|
||||
|
||||
import type { Token } from "../../src/expression-evaluation/grammar";
|
||||
|
||||
describe("Lexer", () => {
|
||||
const singleOperatorTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: "+",
|
||||
expected: [
|
||||
{ type: "+", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "-",
|
||||
expected: [
|
||||
{ type: "-", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "*",
|
||||
expected: [
|
||||
{ type: "*", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "**",
|
||||
expected: [
|
||||
{ type: "**", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "/",
|
||||
expected: [
|
||||
{ type: "/", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "%",
|
||||
expected: [
|
||||
{ type: "%", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "===",
|
||||
expected: [
|
||||
{ type: "===", pos: 0 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "!==",
|
||||
expected: [
|
||||
{ type: "!==", pos: 0 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "==",
|
||||
expected: [
|
||||
{ type: "==", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "<",
|
||||
expected: [
|
||||
{ type: "<", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "<=",
|
||||
expected: [
|
||||
{ type: "<=", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ">",
|
||||
expected: [
|
||||
{ type: ">", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ">=",
|
||||
expected: [
|
||||
{ type: ">=", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "&&",
|
||||
expected: [
|
||||
{ type: "&&", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "||",
|
||||
expected: [
|
||||
{ type: "||", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "&",
|
||||
expected: [
|
||||
{ type: "&", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "|",
|
||||
expected: [
|
||||
{ type: "|", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "<<",
|
||||
expected: [
|
||||
{ type: "<<", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ">>>",
|
||||
expected: [
|
||||
{ type: ">>>", pos: 0 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ".",
|
||||
expected: [
|
||||
{ type: ".", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "?.",
|
||||
expected: [
|
||||
{ type: "?.", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "??",
|
||||
expected: [
|
||||
{ type: "??", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "?",
|
||||
expected: [
|
||||
{ type: "?", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ":",
|
||||
expected: [
|
||||
{ type: ":", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "(",
|
||||
expected: [
|
||||
{ type: "(", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ")",
|
||||
expected: [
|
||||
{ type: ")", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "[",
|
||||
expected: [
|
||||
{ type: "[", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "]",
|
||||
expected: [
|
||||
{ type: "]", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ",",
|
||||
expected: [
|
||||
{ type: ",", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "{",
|
||||
expected: [
|
||||
{ type: "{", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "}",
|
||||
expected: [
|
||||
{ type: "}", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const singleNumberTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: "1",
|
||||
expected: [
|
||||
{ type: "number", symbol: "1", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "42",
|
||||
expected: [
|
||||
{ type: "number", symbol: "42", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "42.9",
|
||||
expected: [
|
||||
{ type: "number", symbol: "42.9", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ".9",
|
||||
expected: [
|
||||
{ type: "number", symbol: ".9", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "1_1",
|
||||
expected: [
|
||||
{ type: "number", symbol: "1_1", pos: 0 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "10_1",
|
||||
expected: [
|
||||
{ type: "number", symbol: "10_1", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "1_1_1",
|
||||
expected: [
|
||||
{ type: "number", symbol: "1_1_1", pos: 0 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: ".1_1",
|
||||
expected: [
|
||||
{ type: "number", symbol: ".1_1", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const invalidNumberTestCases: { input: string; expected: Token[] }[] = [
|
||||
{ input: "1.1.1", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "1__1", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "1_", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "1._1", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "0_1", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "00_1", expected: [{ type: "invalid", pos: 0 }] },
|
||||
];
|
||||
|
||||
const singleIdentifierTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: "foo",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "foo", pos: 0 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "_foo",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "_foo", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "$foo",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "$foo", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "foo1",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "foo1", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "_foo1_",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "_foo1_", pos: 0 },
|
||||
{ type: "eof", pos: 6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "μ",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "μ", pos: 0 },
|
||||
{ type: "eof", pos: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "._1",
|
||||
expected: [
|
||||
{ type: ".", pos: 0 },
|
||||
{ type: "iden", symbol: "_1", pos: 1 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "true",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "true", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "false",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "false", pos: 0 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "null",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "null", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "undefined",
|
||||
expected: [
|
||||
{ type: "iden", symbol: "undefined", pos: 0 },
|
||||
{ type: "eof", pos: 9 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const invalidIdentifierTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: "1foo",
|
||||
expected: [
|
||||
{ type: "number", symbol: "1", pos: 0 },
|
||||
{ type: "iden", symbol: "foo", pos: 1 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{ input: "↓", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: '"', expected: [{ type: "invalid", pos: 0 }] },
|
||||
];
|
||||
|
||||
const singleStringTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: '""',
|
||||
expected: [
|
||||
{ type: "string", symbol: '""', pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: '"foo"',
|
||||
expected: [
|
||||
{ type: "string", symbol: '"foo"', pos: 0 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: '"\\""',
|
||||
expected: [
|
||||
{ type: "string", symbol: '"\\""', pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: '"\\\'"',
|
||||
expected: [
|
||||
{ type: "string", symbol: '"\\\'"', pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "''",
|
||||
expected: [
|
||||
{ type: "string", symbol: "''", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "'foo'",
|
||||
expected: [
|
||||
{ type: "string", symbol: "'foo'", pos: 0 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "'\\''",
|
||||
expected: [
|
||||
{ type: "string", symbol: "'\\''", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "'\\\"'",
|
||||
expected: [
|
||||
{ type: "string", symbol: "'\\\"'", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "``",
|
||||
expected: [
|
||||
{ type: "string", symbol: "``", pos: 0 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "`foo`",
|
||||
expected: [
|
||||
{ type: "string", symbol: "`foo`", pos: 0 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "`\\``",
|
||||
expected: [
|
||||
{ type: "string", symbol: "`\\``", pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: '`\\"`',
|
||||
expected: [
|
||||
{ type: "string", symbol: '`\\"`', pos: 0 },
|
||||
{ type: "eof", pos: 4 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const invalidStringTestCases: { input: string; expected: Token[] }[] = [
|
||||
{ input: '"', expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: '"\\"', expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "'", expected: [{ type: "invalid", pos: 0 }] },
|
||||
{ input: "'\\'", expected: [{ type: "invalid", pos: 0 }] },
|
||||
];
|
||||
|
||||
const whiteSpaceTestCases: { input: string; expected: Token[] }[] = [
|
||||
{ input: " ", expected: [{ type: "eof", pos: 1 }] },
|
||||
{ input: " ", expected: [{ type: "eof", pos: 3 }] },
|
||||
{ input: "\n", expected: [{ type: "eof", pos: 1 }] },
|
||||
{ input: " \n", expected: [{ type: "eof", pos: 2 }] },
|
||||
{ input: " ", expected: [{ type: "eof", pos: 1 }] },
|
||||
];
|
||||
|
||||
const complicatedTermTestCases: { input: string; expected: Token[] }[] = [
|
||||
{
|
||||
input: "5x",
|
||||
expected: [
|
||||
{ type: "number", symbol: "5", pos: 0 },
|
||||
{ type: "iden", symbol: "x", pos: 1 },
|
||||
{ type: "eof", pos: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "5*x",
|
||||
expected: [
|
||||
{ type: "number", symbol: "5", pos: 0 },
|
||||
{ type: "*", pos: 1 },
|
||||
{ type: "iden", symbol: "x", pos: 2 },
|
||||
{ type: "eof", pos: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "5 * x",
|
||||
expected: [
|
||||
{ type: "number", symbol: "5", pos: 0 },
|
||||
{ type: "*", pos: 2 },
|
||||
{ type: "iden", symbol: "x", pos: 4 },
|
||||
{ type: "eof", pos: 5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "(5 * 5 + 2) / 1.2 === 'foo'",
|
||||
expected: [
|
||||
{ type: "(", pos: 0 },
|
||||
{ type: "number", symbol: "5", pos: 1 },
|
||||
{ type: "*", pos: 3 },
|
||||
{ type: "number", symbol: "5", pos: 5 },
|
||||
{ type: "+", pos: 7 },
|
||||
{ type: "number", symbol: "2", pos: 9 },
|
||||
{ type: ")", pos: 10 },
|
||||
{ type: "/", pos: 12 },
|
||||
{ type: "number", symbol: "1.2", pos: 14 },
|
||||
{ type: "===", pos: 18 },
|
||||
{ type: "string", symbol: "'foo'", pos: 22 },
|
||||
{ type: "eof", pos: 27 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "(() => {console.log('foo'); return 1;})()",
|
||||
expected: [
|
||||
{ type: "(", pos: 0 },
|
||||
{ type: "(", pos: 1 },
|
||||
{ type: ")", pos: 2 },
|
||||
{ type: "invalid", pos: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "(function() {console.log('foo'); return 1;})()",
|
||||
expected: [
|
||||
{ type: "(", pos: 0 },
|
||||
{ type: "iden", symbol: "function", pos: 1 },
|
||||
{ type: "(", pos: 9 },
|
||||
{ type: ")", pos: 10 },
|
||||
{ type: "{", pos: 12 },
|
||||
{ type: "iden", symbol: "console", pos: 13 },
|
||||
{ type: ".", pos: 20 },
|
||||
{ type: "iden", symbol: "log", pos: 21 },
|
||||
{ type: "(", pos: 24 },
|
||||
{ type: "string", symbol: "'foo'", pos: 25 },
|
||||
{ type: ")", pos: 30 },
|
||||
{ type: "invalid", pos: 31 },
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "'ranged' === 'ranged'",
|
||||
expected: [
|
||||
{ type: "string", symbol: "'ranged'", pos: 0 },
|
||||
{ type: "===", pos: 9 },
|
||||
{ type: "string", symbol: "'ranged'", pos: 13 },
|
||||
{ type: "eof", pos: 21 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it.each([
|
||||
...singleOperatorTestCases,
|
||||
...singleNumberTestCases,
|
||||
...invalidNumberTestCases,
|
||||
...singleIdentifierTestCases,
|
||||
...invalidIdentifierTestCases,
|
||||
...singleStringTestCases,
|
||||
...invalidStringTestCases,
|
||||
...whiteSpaceTestCases,
|
||||
...complicatedTermTestCases,
|
||||
])("lexes $input correctly", ({ input, expected }) => {
|
||||
// when
|
||||
const result = consume(new Lexer(input));
|
||||
|
||||
// then
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
function consume<T>(iterable: Iterable<T>): T[] {
|
||||
const result: T[] = [];
|
||||
for (const value of iterable) {
|
||||
result.push(value);
|
||||
}
|
||||
return result;
|
||||
}
|
126
spec/expression-evaluation/validator.spec.ts
Normal file
126
spec/expression-evaluation/validator.spec.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { literals, safeOperators } from "../../src/expression-evaluation/grammar";
|
||||
import { Validator } from "../../src/expression-evaluation/validator";
|
||||
|
||||
describe("Validator", () => {
|
||||
it("allows identifier according to the given predicate", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => identifier === "true";
|
||||
const validator = new Validator(predicate);
|
||||
const input = "true";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).not.toThrow();
|
||||
});
|
||||
|
||||
it("disallows identifier according to the given predicate", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => identifier === "false";
|
||||
const validator = new Validator(predicate);
|
||||
const input = "true";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).toThrowError("'true' is not an allowed identifier");
|
||||
});
|
||||
|
||||
it("allows multiple identifiers according to the given predicate", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => identifier === "true" || identifier === "null";
|
||||
const validator = new Validator(predicate);
|
||||
const input = "true null";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).not.toThrow();
|
||||
});
|
||||
|
||||
it("allows multiple identifiers in a more complex expression according to the given rule", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => identifier === "true" || identifier === "null";
|
||||
const validator = new Validator(predicate);
|
||||
const input = "true === null";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).not.toThrow();
|
||||
});
|
||||
|
||||
it("mentions the first not allowed identifier in the thrown errror", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => identifier === "true" || identifier === "null";
|
||||
const validator = new Validator(predicate);
|
||||
const input = "true === null && undefined === false";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).toThrowError("'undefined' is not an allowed identifier.");
|
||||
});
|
||||
|
||||
it("disallows invalid invalid tokens", () => {
|
||||
// given
|
||||
const validator = new Validator();
|
||||
const input = ";";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).toThrowError("Invalid or unexpected token (0)");
|
||||
});
|
||||
|
||||
it("allows a complicated valid expression", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) =>
|
||||
[...safeOperators, ...literals, "floor", "random"].includes(identifier);
|
||||
const validator = new Validator(predicate);
|
||||
const input = "typeof (floor(random() * 5) / 2) === 'number' ? 42 : 'foo'";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).not.toThrow();
|
||||
});
|
||||
|
||||
it("disallows a complicated expression if it contains a disallowed identifier", () => {
|
||||
// given
|
||||
const predicate = (identifier: string) => [...safeOperators, ...literals, "ceil"].includes(identifier);
|
||||
const validator = new Validator(predicate);
|
||||
const input = "ceil.constructor('alert(1); return 1;')()";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).toThrowError("'constructor' is not an allowed identifier.");
|
||||
});
|
||||
|
||||
it("disallows arrow functions", () => {
|
||||
// given
|
||||
const validator = new Validator();
|
||||
const input = "() => {}";
|
||||
|
||||
// when
|
||||
const validate = () => validator.validate(input);
|
||||
|
||||
// then
|
||||
expect(validate).toThrowError("Invalid or unexpected token (3)");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue