I am in the process of the strengthening the Content Security Policy for my site. I still have unsafe-eval
included because there is one application I developed some time ago that uses it, but no other page does. I've tried modifying it using some libraries to get rid of it, but the program fails in some way when I try that.
I use eval
in two places like this:
function derivative(x,y) {
var fxn = document.getElementById('deriv').value;
var result = eval(fxn);
return result;
}
function origfunc(x) {
var original = document.getElementById('original').value;
var result = eval(original);
return result;
}
I'm only doing mathematical operations here, so I was looking for something like eval
that would just do math. For example, there's one library called mathjs
: https://stackoverflow.com/a/16262947/6110631
I tried using math.eval()
(which is now math.evaluate()
), as well as the parse
function. Some parts of the program work while others seem broken. The same thing happens with other libraries, many of which fail completely. It's hard to trace why because I'll see errors inside the library code, but the HTML output on the page is not the same as with eval
, which indicates that the library isn't a suitable drop-in mathematical replacement. The JavaScript Expression Evaluator also has this issue.
One problem is I need to continue supporting the same syntax, which could include:
x*x
4-x+2*y
9.8-0.2*x
x*x*x*x
e
eval
handles all of these without issues. Other libraries often have differing syntax or may not support multiple variables, etc. I'm currently using eval
because the functions operate directly on user input. Part of the challenge comes from the fact that the variables themselves change throughout execution, and some of these libraries don't seem to have access to their values in the interim.
Is there a library or "safer" eval-alternative available that won't break any current inputs, but allows me to stop using eval
itself? In other words, it will return the same results for the same syntax that eval
does? Function()
didn't work for me, either, but that's a no-go anyways since it's also part of unsafe-eval
. This means not just capable of doing all the math, but doing it with the same input syntax, which seems to be tricky (e.g. x*x
instead of something like Math.mult()
).
Here's a console debug using "Nerdamer":
0
euler.js:53
Expression {symbol: Symbol}
euler.js:52 0.09
euler.js:53
Expression {symbol: Symbol}
symbol: Symbol {group: 3, value: "x", multiplier: Frac, power: Frac, imaginary: false, …}
__proto__: Object
euler.js:52 0.36
euler.js:53
Expression {symbol: Symbol}
symbol: Symbol {group: 3, value: "x", multiplier: Frac, power: Frac, imaginary: false, …}
__proto__: Object
Line 52 is using eval
- Line 53 is using the "Nerdamer" library.
52 console.log(eval(original));
53 console.log(nerdamer(original).evaluate());
I'm not interesting in sanitizing the expression before using eval
. I'm not worried about processing user input; I'd like to be able to stop using eval
so I can remove it from my CSP.
Full Code:
I tried to set this up in a jsFiddle but for some reason could not get it to work, there. Here is the minimal HTML needed:
<table>
<tr>
<td><span>f(x) — if known: <span></td>
<td><input id="original" size="30" value="(x*x*x)/3 + 1"></input></td>
</tr>
<tr>
<td><span>f '(x): <span></td>
<td><input id="deriv" size="30" value="x*x"></input></td>
</tr>
<tr>
<td><span>Starting x-value: <span></td>
<td><input id="xinitial" size="30" value="0"></input></td>
</tr>
<tr>
<td><span>Starting y-value: <span></td>
<td><input id="yinitial" size="30" value="1"></input></td>
</tr>
<tr>
<td><span>Final x-value: <span></td>
<td><input id="xfinal" size="30" value="1.8"></input></td>
</tr>
<tr>
<td><span>Step Size: <span></td>
<td><input id="stepsize" size="30" value="0.3"></input></td>
</tr>
</table>
<div id="submitbutton">
<button id="submit">Calculate!</button>
</div>
<div>
<table id="output">
</table>
<div id="actual">
</div>
</div>
Here is the JS:
var func = 0;
var orig = 0;
var x0 = 0;
var y0 = 0;
var xf = 0;
var stepSize = 0;
var e = 2.718281828459045;
var lastX = 0;
var lastY = 0;
function calculate() {
document.getElementById('output').innerHTML = "";
document.getElementById('actual').innerHTML = "";
orig = document.getElementById('original').value;
func = document.getElementById('deriv').value;
stepSize = document.getElementById('stepsize').value;
x0 = document.getElementById('xinitial').value;
y0 = document.getElementById('yinitial').value;
xf = document.getElementById('xfinal').value;
var numIntervals = (xf-x0)/stepSize;
slopes = new Array(numIntervals);
y = new Array(numIntervals);
document.getElementById('output').innerHTML += '<tr><th>Δx</th><th>Slope</th><th>Δy</th><th></th><th>x</th><th></th><th>y</th></tr>';
for (i=0; i<=numIntervals; i++) {
thisX = parseFloat(x0) + parseFloat(i*stepSize);
lastX = thisX;
if (i == 0) {
y[i] = parseFloat(y0);
slopes[i] = derivative(thisX, y[i]);
}
if (i != 0) {
y[i] = parseFloat(y[i-1]) + (parseFloat(slopes[i-1]) * parseFloat(stepSize));
slopes[i] = derivative(thisX, y[i]);
if (i == numIntervals) {
lastY = y[i];
}
}
if (i == 0) {
document.getElementById('output').innerHTML += '<tr>' + '<td>' + parseFloat(stepSize) + '</td><td class="center">' + '—' + '</td><td class="center">' + '—' +'</td><td>' + '(</td><td>' + thisX.toFixed(2) + '</td><td>,</td><td>' + y[i].toFixed(5) + '</td><td>)</td>' + '</tr>';
} else {
document.getElementById('output').innerHTML += '<tr>' + '<td>' + parseFloat(stepSize) + '</td><td>' + parseFloat(slopes[i-1]).toFixed(3) + '</td><td>' + (parseFloat(slopes[i-1]) * parseFloat(stepSize)).toFixed(3) +'</td><td>' + '(</td><td>' + thisX.toFixed(2) + '</td><td>,</td><td>' + y[i].toFixed(5) + '</td><td>)</td>' + '</tr>';
}
}
if (document.getElementById('original').value != "") {
correct = origfunc(lastX);
document.getElementById('actual').innerHTML += '<b>Actual: </b>' + correct.toFixed(3) + '<br>';
document.getElementById('actual').innerHTML += '<b>Error: </b>' + (correct - lastY).toFixed(3) + '<br>';
}
}
function derivative(x,y) {
var fxn = document.getElementById('deriv').value;
//console.log(nerdamer(fxn).evaluate());
//console.log(math.evaluate(fxn));
var result = eval(fxn);
return result;
}
function origfunc(x) {
var original = document.getElementById('original').value;
//console.log(math.evaluate(original));
var result = eval(original);
return result;
}
document.getElementById('submit').onclick = function(){calculate()};
The last 3 lines of expected output in this scenario look like:
0.3 2.250 0.675 ( 1.80 , 2.48500 )
Actual: 2.944
Error: 0.459