Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to make a comparison operator a variable? [duplicate]

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.

like image 575
lightstrike Avatar asked May 14 '12 21:05

lightstrike


2 Answers

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.

like image 173
Felix Kling Avatar answered Nov 13 '22 10:11

Felix Kling


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/

like image 27
Susam Pal Avatar answered Nov 13 '22 09:11

Susam Pal