Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to Evil Eval - relational operators

As a form of input validation I need to coerce a string like '9>6' to evaluate to a boolean value.

Apart from evaluating the string I can't seem to find a workaround .

I've always heard about the evilness of eval (especially since I'm validating a form input), about the fact that it could evaluate any script and performance issues.

but....

Are there any alternative in my case (dealing with relational operators)?

var arr = ['<9', '>2'];

var check = function (a) {

    return arr.every(function (x) {
            var string = '';

            string += a + x;

            try {
                return eval(string);
            } catch (e) {
                return false;
            }
        });
    };

console.log(check('3'))
like image 850
maioman Avatar asked Sep 04 '15 11:09

maioman


People also ask

What can I use instead of eval?

An alternative to eval is Function() . Just like eval() , Function() takes some expression as a string for execution, except, rather than outputting the result directly, it returns an anonymous function to you that you can call. `Function() is a faster and more secure alternative to eval().

Why eval () is the evil?

eval() is evil if running on the server using input submitted by a client that was not created by the developer or that was not sanitized by the developer. eval() is not evil if running on the client, even if using unsanitized input crafted by the client.

Is eval function bad?

It is a possible security risk, it has a different scope of execution, and is quite inefficient, as it creates an entirely new scripting environment for the execution of the code. See here for some more info: eval. It is quite useful, though, and used with moderation can add a lot of good functionality.


2 Answers

I wouldn't say eval is inherently evil, I only say that it has its advantages and disadvantages. In my practice I experienced very few cases where its advantages were numerous.

The two main problems with the eval:

  1. It needs runtime interpretation, i.e. string processing and interpreting from the javascript engine. It is slow. Normal javascript runs in a tokenized form.
  2. If you use eval, you also start a game where you want to block inserting any harmful code, without decreseased functionality, and the hackers trying to find a way through your blocks. And your only possibility to know you didn't lose that your site weren't cracked until now. You don't have any possibility to know that you won.

But: eval runs mainly on the browser side, or processes user input data (created by the same user), or server data which was generated by your server-side.

The solution in your case is obvious: use hard OO things, for example functors.

function comparator(lighter, num) {
  return function(x) {
    return lighter ? (x<num) : (x>num);
  }
}

var arr = [ comparator(true, '9'), comparator(false, '2') ];

var check = function(a) {
  return arr.every(function(comp) { return comp(a); });
}

console.log(check('3'));

On the first spot it is much more complex as your version, but it is only because you are accustomed to eval and aren't to tricky functor solutions. Actually, the complexity of the solutions are imho similar.

like image 197
peterh Avatar answered Oct 04 '22 08:10

peterh


I can see three things to improve:

  • Instead of stringifying the a value and concatenating it to your expression, you should just refer to the a variable directly so that you can compare to the true form of the value. Otherwise you might get odd behaviour.

    return eval("a " + x);
    
  • Since you are probably using check multiple times, you should not call eval every time (or even multiple times per call). You can compose the strings into one large && expression, and even better you can compile the conditions to single functions beforehand so that you don't have to call eval from within check at all.

  • You should consider using the Function constructor instead of eval.

With those, your code might look like this:

var arr = ['<9', '>2'];

var check = new Function("a", "return "+arr.map(function(x) {
    return "a"+x;
}).join(" && ")+";");
console.log(check.toString()); // function anonymous(a) { return a<9 && a>2; }
console.log(check('3'));

or this:

var arr = ['<9', '>2'];
var fns = arr.map(function(x) {
    return new Function("a", "return a "+x+";");
});
function check(a) {
    return fns.every(function(f) {
        return f(a);
    });
}
console.log(check('3'));

Of course I am expecting that arr is not controlled by the user (or worse, another than the current user); if you expect your users to input those conditions you should make sure to whitelist them explicitly, or maybe use a more sophisticated expression grammar/parser/evaluator right away.
Unless that is the case, eval (or for that matter, Function) is not exactly evil, it just does shorten (and possible simplify) your code. You could've written out full-blown functions (like Nit and peterh suggested) just as well.

like image 24
Bergi Avatar answered Oct 04 '22 09:10

Bergi