4

Eval function of javascript is not working with locker service. it is working without locker service enabled. I have attached both screenshots as well as the code. The SFDC release version is Winter 17. Is someone else facing the same issue?

DemoTestApp.app

<aura:application >
   <c:DemoTest />
</aura:application>

DemoTest.cmp

<aura:component >
   <ui:inputText aura:id="txtValue1" label="Enter First Value " value=""/>
   <ui:inputText aura:id="txtValue2" label="Enter Second Value " value=""/>
   <button aura:id="btnSum" onclick="{!c.doSum}">Sum</button>
   Sum:<ui:outputText aura:id="outputValue" value=""/> 
</aura:component>

DemoTestController.js

({
doSum : function(component, event, helper) {
    var val1=component.find("txtValue1").get("v.value");
    var val2=component.find("txtValue2").get("v.value");
    var sumOutlbl=component.find("outputValue");
    console.log("***eval function***");
    console.log(eval);
    //adding two values by using eval function
    var sumVal=eval("parseInt(val1)+parseInt(val2)");
    console.log("Sum of two value: "+sumVal);
    sumOutlbl.set("v.value",sumVal);
}
})

Console log output: Locker service is disabled enter image description here

Console log output: Locker service is enabled enter image description here

7

The Eval function of Javascript has many security vulnerabilities like XSS and hence locker blocks it .You cannot use eval with locker service

The danger of eval() is that an attacker may be able to manipulate data that is eventually run through eval() in other ways. If the eval()'d string comes from an HTTP connection, the attacker may perform a MITM attack and modify the string. If the string comes from external storage, the attacker may have manipulated the data in that storage location. Etc

Here is a reference from the actual docs

Also here is a reference to lightning security requirements and clearly states eval() is not allowed .

  • 3
    This includes all forms of unsafe-eval: eval(stringOfCode), new Function(stringOfCode), setTimeout(stringOfCode), etc. This, along with strict mode compliance, are "laws of physics" for Lightning Components and our security policies do not allow us to alter this even if we wanted to. These rules apply to any javascript used in Lightning - code you author and any libraries/frameworks you use. The good news is that this is not just a Lightning thing - these are industry best practices and many libraries/frameworks have long been compliant with both unsafe-inline and strict mode. – Doug Chasman Nov 11 '16 at 14:53
  • 1
    ... Libraries such as D3 version 4 and HighCharts version 5 have recently been released and are now fully CSP/strict mode (and Locker) savvy. NOTE: You will see the last phase of enforcement using Content Security Policy in Spring'17 - so even if you attempt to try other forms of unsafe-eval that strict mode does not eliminate CSP blocks the rest so you need to insure you are not using the other forms of unsafe-eval. – Doug Chasman Nov 11 '16 at 14:57
  • @DougChasman You mentioned somewhere of a website where you have all the functions marked in green that are implemented and supported in locker .Can you please point me to that ? – Mohith Shrivastava Nov 11 '16 at 15:00
  • 2
    documentation.auraframework.org and navigate to the Locker Service API Viewer. – Doug Chasman Nov 11 '16 at 15:42
  • @MohithShrivastava Thanks for the replay. We will try some alternate way for this. – Gopalakrishna Boddepalli Nov 14 '16 at 16:14
2

You could try writing a component to do some the expression evaluation instead of eval.

Something of this sort; which I used to evaluate expressions dynamically (I had a requirement where the user needed to add fields to a component dynamically (non config) and create dependencies between them by having some fields populated based on the value in another field).

Basically - for integers - convert string input to postfix and evaluates; for string(if, else) - split and evaluate for boolean (true/false) - split and evaluate

The if/else only works one level deep; but worked for what I needed it to do.

Expression_EvalCmp.cmp

<aura:component >
<!-- PUBLIC USER DEFINED ATTRIBUTES -->

<!-- INTERNAL ATTRIBUTES -->

<!-- HANDLERS AND EVENTS-->

<!-- PUBLIC METHODS -->
<aura:method name="evalExpression" action="{!c.evalExpression}" description="Evaluates String Expression and Returns Result">
    <aura:attribute name="baExpressionType" type="String" description="Output Type: Acceptable Values are : 'text','number','checkbox'" />
    <aura:attribute name="baExpressionInput" type="String" description="String Expression to be Evaluated" />
</aura:method>
<aura:component >

Expression_EvalCmpController.js

({
evalExpression : function(component, event, helper) {
    var params = event.getParam("arguments");
    var result;
    if(params){
        var baExpressionType = params.baExpressionType;
        var baExpressionInput = params.baExpressionInput;
        //'Boolean','Integer','String','double'
        switch(baExpressionType){
            case "checkbox":
                result = helper.parseBooleanExpression(baExpressionInput);
                break;
            case "number":
                result = helper.parseIntegerExpression(baExpressionInput);
                break;
            case "text":
                result = helper.parseStringExpression(baExpressionInput);
                break;              
            }
            return(result);
    }
},
})

Expression_EvalCmpHelper.js

({
parseIntegerExpression: function(input) {
    var postFixExpr = this.infixToPostfixExpr(input);
    var postFixArr = postFixExpr.split(" ");
    var exprResult = this.evaluatePostfix(postFixArr);
    return exprResult;
},

parseBooleanExpression: function(input){
    var exprResult;
    input = input.replace(/\s+/g, ""); 
    input = input.split(/([\=\>\<\&\|])/);
    var cndtn1 = input[0];
    var exprn =  input[1];
    var cndtn2 = input[2];
    switch(exprn){
        case "=":
                if(cndtn1 == cndtn2)
                    exprResult = true;
                else
                    exprResult = false;
            break;
        case "<":
                if(parseInt(cndtn1) < parseInt(cndtn2))
                    exprResult = true;
                else
                    exprResult = false;
            break;
        case ">":
            if(parseInt(cndtn1) > parseInt(cndtn2))
                    exprResult = true;
                else
                    exprResult = false;
            break;
        case "&":
            if(parseInt(cndtn1) && parseInt(cndtn2))
                exprResult = true;
            else
                exprResult = false;
            break;
        case "|":
            if(parseInt(cndtn1) || parseInt(cndtn2))
                exprResult = true;
            else
                exprResult = false;
            break
    }
    return exprResult;
},

parseStringExpression: function(input){
    var exprResult;
    input = input.replace(/\s+/g, "");
    input = input.split(/([\?])/);
    var boolExpr = input[0];
    var ifResults = input[2].split(/([\,])/);
    var exprEval = this.parseBooleanExpression(boolExpr);
    if(exprEval == true)
        exprResult = ifResults[0].replace(/([_])/g," ");
    else if (exprEval == false)
        exprResult = ifResults[2].replace(/([_])/g," ");
    else
        exprResult = "INVALID INPUT";
    return exprResult;    
},

// Uses Shunting Yard Algorithm to Convert infix to postfix expression
infixToPostfixExpr: function(infix) {
    var outputQueue = "";
    var operatorStack = [];
    var operators = {
        "^": {
            precedence: 4,
            associativity: "Right"
        },
        "/": {
            precedence: 3,
            associativity: "Left"
        },
        "*": {
            precedence: 3,
            associativity: "Left"
        },
        "+": {
            precedence: 2,
            associativity: "Left"
        },
        "-": {
            precedence: 2,
            associativity: "Left"
        }
    };
    infix = infix.replace(/\s+/g, "");
    infix = infix.split(/([\+\-\*\/\^\(\)])/);
    infix = this.cleanArray(infix);
    for (var i = 0; i < infix.length; i++) {
        var token = infix[i];
        if (this.isStrNumber(token)) {
            outputQueue += token + " ";
        } else if ("^*/+-".indexOf(token) !== -1) {
            var o1 = token;
            var o2 = operatorStack[operatorStack.length - 1];
            while ("^*/+-".indexOf(o2) !== -1 && ((operators[o1].associativity === "Left" && operators[o1].precedence <= operators[o2].precedence) || (operators[o1].associativity === "Right" && operators[o1].precedence < operators[o2].precedence))) {
                outputQueue += operatorStack.pop() + " ";
                o2 = operatorStack[operatorStack.length - 1];
            }
            operatorStack.push(o1);
        } else if (token === "(") {
            operatorStack.push(token);
        } else if (token === ")") {
            while (operatorStack[operatorStack.length - 1] !== "(") {
                outputQueue += operatorStack.pop() + " ";
            }
            operatorStack.pop();
        }
    }
    while (operatorStack.length > 0) {
        outputQueue += operatorStack.pop() + " ";
    }
    return outputQueue;
},

isStrNumber: function(strInp) {
    return !isNaN(parseFloat(strInp)) && isFinite(strInp);
},

cleanArray: function(strArray) {
    for (var i = 0; i < strArray.length; i++) {
        if (strArray[i] === "") {
            strArray.splice(i, 1);
        }
    }
    return strArray;
},

//evaluates postfix string
evaluatePostfix: function(strPostfix) {
    var returnValue = 0;
    var stack = [];
    strPostfix = this.cleanArray(strPostfix);
    for (var i = 0; i < strPostfix.length; i++) {
        if ("^*/+-".indexOf(strPostfix[i]) == -1) {
            stack.push(strPostfix[i]);
        } else {
            var a = parseInt(stack.pop());
            var b = parseInt(stack.pop());
            switch (strPostfix[i]) {
                case "+":
                    stack.push(a + b);
                    break;
                case "-":
                    stack.push(b - a);
                    break;
                case "*":
                    stack.push(a * b);
                    break;
                case "/":
                    stack.push(b / a);
                    break;
                case "^":
                    stack.push(Math.pow(b, a));
                    break;
            }
        }
    }
    returnValue = parseInt(stack.pop());
    return returnValue;
},


})

Usage : Add the component to the markup of any component where you need to evaluate a string expression and invoke the component method in your controller or helper as
var calcCmp = component.find("ExpressionEvalCmp");
calcValue = calcCmp.evalExpression(calcType, calcExpr);

Where

calcType can be checkbox(boolean), number(integer) and text(if/else)

calcExpr can be
"2=3" (boolean)
"[2]+[3]+([4]/[5]-[6])" (integer)
"[2] > 0 ? Two Is Greater , Two Is Lesser"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.