I have two arrays - one that contains booleans, and the other operators:
to_eval = [true, true, false, false]
ops=['&&', '||', '&&']
Out of this I'd like to build an expression
result = true && true || false && false
Which should evaluate to true based on precedence rules
If I iterate over the arrays evaluating as I go, the result is false.
I could build a string and use Eval or Function - but from what I have been reading this can cause security issues in a web application.
Is there a way in Javascript to build out an expression without evaluating it till it is fully formed so that precedence rules are observed without using Eval or Function?
CodePudding user response:
You can create a mapping functions for the trusted logic operators safely with Array#reduce
:
But the way this evaluates from left to right without precedences, so
true && true || false && false
will evaludatefalse
. If you want more accurate result like how JavaScript parse things, it would be easier to useeval
with proper sanitization.
const to_eval = [true, true, false, false];
const ops = ['&&', '||', '&&'];
const ops_map = {
'&&': (a, b) => a && b,
'||': (a, b) => a || b
};
const result = to_eval.reduce((acc, cur, i) => ops_map[ops[i-1]](acc, cur));
console.log(result);
With proper santization, you can safely use eval
:
const evaluate = (to_eval, ops = []) => {
const expression = to_eval.reduce((acc, cur, i) => {
let left = i === 1 ? !!acc : acc;
let op = {'&&': '&&', '||': '||'}[ops[i-1]];
if(typeof op !== 'string') {
op = '||'; // fallback
}
let right = !!cur;
return `${left} ${op} ${right}`
});
const result = eval(expression);
console.log(expression, ' => ', result);
return result;
}
const to_eval = [true, true, false, false]
const ops = ['&&', '||', '&&'];
evaluate(to_eval, ops);
evaluate([true], []);
evaluate(['alert(1)', 0, false], ['alert(2)', 'hasOwnProperty']);
CodePudding user response:
You can safely call eval
, if it is certain that your two arrays have the expected values (booleans and expected operators). So just add some code to verify the two given inputs.
You can do as follows:
function evaluate(bools, ops) {
// Verify the given arguments are as expected
if (!ops.every(op => ["&&", "||"].includes(op))) throw "invalid operator";
if (!bools.every(bool => typeof bool === "boolean")) throw "invalid operand";
if (bools.length !== ops.length 1) throw "length mismatch";
return eval(bools.map((bool, i) => bool " " (ops[i] ?? "")).join(" "));
}
let to_eval = [true, true, false, false];
let ops = ['&&', '||', '&&'];
let result = evaluate(to_eval, ops);
console.log(result);
CodePudding user response:
You could use eval if you sanitize the input (IE, verify that all the tokens are from a very specific list of allowed tokens).
function safeEval(expression) {
const allowed = ['true', 'false', '&&', '||'];
if (expression.split(/\s /).every(token => allowed.includes(token))) {
return eval(expression);
} else {
throw Error('Encountered forbidden token.');
}
}
console.log(safeEval('true && true || false && false'));
console.log(safeEval('undefined'));
This is far from the most efficient code you could write, but it's simple and gets the job done, and adding support for other operators and parentheses is trivial.
Alternatively, you could evaluate the operators yourself, in the order of precedence you desire:
const expression = [true, '&&', true, '||', false, '&&', false];
for (let i = 0; i < expression.length; i) {
if (expression[i] === '&&') {
const left = expression[i - 1];
const right = expression[i 1];
expression.splice(i - 1, 3, left && right);
}
}
console.log(expression);
for (let i = 0; i < expression.length; i) {
if (expression[i] === '||') {
const left = expression[i - 1];
const right = expression[i 1];
expression.splice(i - 1, 3, left || right);
}
}
console.log(expression);
You probably would want to make this code more robust and less repetitive if you were to add support for many operators, but that should get you started at least.