Safe eval() alternative in Javascript
Austin Repp
Posted on August 31, 2020
A problem I encountered while creating Discord Bot Studio, was allowing users to enter variables which could be evaluated at runtime. Discord Bot Studio is a visual programming tool, so I felt it was important to offer a familiar variable syntax. Ideally, I wanted a user to be able to type a variable using the following notation, and have it be replaced with that variable's value at runtime:
An example would be if I have an object as follows:
variableObject {
variableName: {
fieldName: "Austin"
}
}
The user should be able to retrieve that value "Austin" with the following syntax:
${variableObject.variableName.fieldName}
Rmember, this is a visual programming tool, so there could be any number of variables in an input string, or there could be none at all. The input is being evaluated at runtime, as it can be dynamic.
The seemingly obvious solution is to use Javascript's eval()
function, to evaluate the variables at runtime. Since DBS creates bots which will eventually be taking untrusted user input, this is not safe to do. Rather than trying to clean any incoming input, I settled on another solution which still allows variables with the dot (.) syntax to be evaluated.
The solution
First I match variables in the input string using regex by looking for the ${} notation I mentioned above.
varRegex = /\${(.*?)}/g;
I trim the excess ${} from the match, and pass the resultant string to the following function, along with the object containing any variables that may be referenced by the user.
// desc = variableObject.variableName.fieldName
/* obj = userVariables {
variableObject {
variableName: {
fieldName: "Austin"
}
}
}
*/
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while (arr.length) {
obj = obj[arr.shift()];
}
return obj;
}
Here obj is the object containing any variables the user input should have access to. Desc is the trimmed match string. Continuing the example from above, desc would equal variableObject.variableName.fieldName. The string is split into an array on the periods. variableObject.variableName.fieldName would be split into
[variableObject, variableName, fieldName]. These array values are then shifted out in order, and used as keys to access the variable-containing object. This function will return just the string "Austin" using the example object from above.
By doing this, I can limit the variables that are available to the user, and also give them access to those variables using normal Javascript syntax. This can be extended if you would like to support variableObject[variableName] as well. This is not a true alternative to eval() for all scenarios, but it works well for indexing into objects at runtime.
Posted on August 31, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 24, 2024
November 22, 2024