8

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
  • 924
  • 2
  • 14
  • 29

3 Answers3

16

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
  • 756,363
  • 169
  • 1,062
  • 1,111
  • 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
6

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
  • 30,040
  • 10
  • 77
  • 99
  • 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
-2

Since @Susam Pal code is not working. I am posting a working version

<html>
  <head>
   <script>
       function CompareSomething(val1, compareString, val2) {  
           eval('if(' + val1 + ' ' + compareString + ' ' + val2 + '){conditionPassed();}else{conditionFailed();}'); 
  }
  function compare(a, op, b) { 
      if (eval(a + op + b))
          conditionPassed();
      else
         conditionFailed();
  }
  function conditionPassed() {
      alert('condition passed');
  }
  function conditionFailed() {
      alert('condition failed');
  }
    </script>
  </head> 
<body>
a:<input id='txt1' type="text" />&nbsp;op:<input id='txt2' type="text" />&nbsp;b:<input id='txt3' type="text" /><br/>
<button id='compare'  onclick='CompareSomething(document.getElementById("txt1").value,document.getElementById("txt2").value,document.getElementById("txt3").value)'>Compare Esen Method</button><br/>
<button id='compare'  onclick='Compare(document.getElementById("txt1").value,document.getElementById("txt2").value,document.getElementById("txt3").value)'>Compare Susam Method</button>
  </body>
 </html>
Esen
  • 947
  • 1
  • 19
  • 46
  • 1
    I've tested my code ( http://jsfiddle.net/YrQ4C/ ) in Firefox, Chrome on Linux as well as Windows, as well as IE8, and it works on all three browsers. Which browser did you use when my code didn't work for you? What error did you get in the JavaScript console? – Susam Pal May 14 '12 at 22:08
  • you have edited your answer. your earlier version had this code. Try it out and tell me if this works in all browser. If I have seen your edit, I wouldn't have posted updated version. If that made you annoyed and reduce a vote then thank you very much. function compare(a, b, op) { if (eval(a + op + b)) do_something() } – Esen May 15 '12 at 13:01
  • 1
    If you check my current code it has the code you mention `if (eval(a + op + b)) doSomething();`. This is the same code that was present in my first incomplete answer. Later, I added only added the security checks to make sure that the solution is complete. You still haven't told me which browser failed to execute `if (eval(a + op + b)) doSomething();`. This code in my current solution executes in all three browsers. – Susam Pal May 15 '12 at 13:42
  • @SusamPal for simplicity sake I have added your version and my version into a simple html page. Test this against any browser and tell me if this still works for you. Either I am missing something or you missing something. By the way your code doesn't work in IE, Firefox, Chrome – Esen May 15 '12 at 14:56
  • 2
    This was my first code that I posted without the additional security checks: http://jsfiddle.net/BCdcv/ . This must be the code you are referring to when you say that it doesn't work. However, this code works fine for me on Firefox, Chrome as well as IE. :) I would ask the same question once again. What do you mean by "doesn't work"? Do you get any errors in the JavaScript console? Please provide the full details of "doesn't work". – Susam Pal May 15 '12 at 15:27
  • Its my bad, I called your compare method with a capital "C" Compare instead of compare that made the confusion. – Esen May 15 '12 at 15:49