10

Similar to python: make a variable equal an operator (+,/,*,-)

I've got a bit of code where the user can pick a type of comparison to be run, and a value to compare against. I'm curious to know if there's any way in Javascript to turn that user provided comparison value into an actual comparison, allowing me to do something like:

if (user_val user_comparison other_val) {
    do_something();
}

Instead of having to do something like:

if (user_comparison = '<') {
    if (user_val < other_val) {
        do_something();
    }
else if (user_comparison = '<=') {
    if (user_val <= other_val) {
        do_something();
    }
....etc

Note that should any of the comparisons be matched, the same code will be executed.

Community
  • 1
  • 1
lightstrike
  • 954
  • 2
  • 15
  • 31

2 Answers2

17

No that is not possible. But you can structure your code in a better way. For example you can have a lookup table:

var operator_table = {
    '>': function(a, b) { return a > b; },
    '<': function(a, b) { return a < b; }
    // ...
};

and later:

if(operator_table[user_comparison](user_val, other_val)) {
    // do something
}

Of course you should also handle the case when user_comparison does not exist in the table.

These also gives you a better control over allowed and not allowed operators.

Here is a DEMO create by @Jesse.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 2
    Here's a jsFiddle demonstrating this: http://jsfiddle.net/jonypawks/Cq8Hd/ – Jesse May 14 '12 at 21:31
  • 1
    Who knows? That's a nice, elegant solution that works way better than a switch. I use a similar technique for switching between views in my app. – Brendan Delumpa May 14 '12 at 23:25
  • That's quite smart! Thanks so much, I didn't realize you could do something like that - attach a function as a dictionary value – lightstrike May 15 '12 at 14:02
  • @lightstrike: In JavaScript, functions are first class citizens, that means you can treat them like any other value (string, number, etc). – Felix Kling May 15 '12 at 14:52
7

Assuming that you are checking the user provided operands and operators properly to ensure that they contain the data you want instead of other javascript executable code, you can concatenate the two operands with the operator in between and feed it to eval() to get it executed.

Now, eval() is dangerous because it can execute any JavaScript code. The user can feed executable and possibly malicious JavaScript code as the operator and eval() would evaluate it. Therefore, when you do the concatenation, you should do it after validating that the operand is safe. To stress this point, I'll write one of the most important tenets of computer security in large fonts:

All input is evil until proven otherwise.

Also, note that eval() calls the JavaScript interpreter to interpret, compile and execute your code. This is slow. While you may not notice any observable performance issue if you are just using eval() once in a while, you may notice performance issues if you are calling eval() very frequently, say, on every keyevent.

Considering these drawbacks of eval(), you might want to go for a neater solution like the one posted by Felix Kling. However, it is also possible to solve this problem using eval() in a safe manner as shown below:

function compare(a, op, b)
{
  // Check that we have two numbers and an operator fed as a string.
  if (typeof a != 'number' || typeof b != 'number' || typeof op != 'string')
    return

  // Make sure that the string doesn't contain any executable code by checking
  // it against a whitelist of allowed comparison operators.
  if (['<', '>', '<=', '>=', '==', '!='].indexOf(op) == -1)
    return

  // If we have reached here, we are sure that a and b are two integers and
  // op contains a valid comparison operator. It is now safe to concatenate
  // them and make a JavaScript executable code.
  if (eval(a + op + b))
    doSomething();
}

Note that validating the input against a whitelist is almost always a better idea than validating it against a blacklist. See https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#White_List_Input_Validation for a brief discussion on it.

Here is a demonstration of this solution: http://jsfiddle.net/YrQ4C/ (Code also reproduced below):

function doSomething()
{
  alert('done something!')
}

function compare(a, op, b)
{
  if (typeof a != 'number' || typeof b != 'number' || typeof op != 'string')
    return

  if (['<', '>', '<=', '>=', '==', '!='].indexOf(op) == -1)
    return

  if (eval(a + op + b))
    doSomething();
}

// Positive test cases
compare(2, '<', 3)
compare(2, '<=', 3)

// Negative test cases
compare(2, '>', 3)
compare(2, '>=', 3)

// Attack tests
compare('alert(', '"attack!"', ')')

// Edit: Adding a new attack test case given by Jesse
// in the comments below. This function prevents this
// attack successfully because the whitelist validation
// for the second argument would fail.
compare(1, ';console.log("executed code");2==', 2)

Edit: Demo with Jesse's test case included: http://jsfiddle.net/99eP2/

Susam Pal
  • 32,765
  • 12
  • 81
  • 103
  • If any of these values is provided by the user, he could execute anything... – Felix Kling May 14 '12 at 21:31
  • @Jesse could you please explain why you should not use eval? – Esen May 14 '12 at 21:34
  • @FelixKling I have mentioned in my response: `Assuming that you are checking the user provided operands and operators properly to ensure that they contain the data you want instead of other javascript executable code ...` – Susam Pal May 14 '12 at 21:36
  • Uh sorry :-/ Anyways, you might have to wrap the expression in parenthesis (`eval('(' + a + op + b + ')')`), I'm not sure... – Felix Kling May 14 '12 at 21:39
  • Because even with the checking you're doing there, it's not safe. Consider passing through the arguments `1`, `;console.log("unexpected executed code");2==`, and `2`. The console.log statement would be executed and then it would continue without your code being able to tell. You could do more rigorous checking, but it's not worth trying to secure `eval`, there is almost always a better approach. And the few exceptions most JavaScript developers will never encounter, so writing off `eval` completely is much easier. – Jesse May 14 '12 at 21:54
  • 1
    @Jesse Here is a demo where I have fed the arguments you have specified: http://jsfiddle.net/99eP2/ . console.log statement doesn't get executed. I think you missed the fact that my function is also checking the operator argument against a whitelist of operators. The second argument you have given won't match this whitelist, and thus the function would return without doing anything. – Susam Pal May 14 '12 at 22:15
  • You've also edited the post significantly since I commented. You initially had a bare `eval` which is a huge red flag. I still think just avoiding the `eval` and using an object lookup is easier and lets you skip all the validation and worry associated. – Jesse May 14 '12 at 23:18
  • 1
    @Jesse Of course, I have been improving my answer and filling in more details after my first post, but my demo URL ( jsfiddle.net/YrQ4C ) contained the safe compare() function from the very beginning. The assumption of input validation was also explicitly mentioned in the post from the very beginning. I agree that using an object lookup is safer, cleaner and more maintainable. But my point is that this little problem can be solved using eval() also in a safe manner while completing satisfying the OP's requirements. – Susam Pal May 14 '12 at 23:51