Before we get started…
Prefer script-src-elem over script-src for better cross-browser support of this solution.
I have a variation on this solution using embedded workers with blob URLs at https://gist.github.com/dfkaye/14e5cc5dbe5bb38a9d80f25f54061c7f.
You want to do this:
Function(`return ${expr}`)
But your Content-Security-Policy header prevents the main thread from running eval() or Function() by default; for example:
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' 'strict-dynamic' 'nonce-4AEemGb0xJptoIGFP3Nd';"
>
Notably, the script-src policy does not contain 'unsafe-eval' — in effect, your site does not trust any code, whether yours or a third party's, to evaluate code safely.
- Create a web worker (we'll define its contents in a moment) and subscribe to its
messageevent:
var worker = new Worker("./worker.js");
worker.addEventListener('message', function(e) {
console.info('worker completed');
// event.data contains the stringified JSON response
var data = JSON.parse(e.data);
// do things in the main thread using data...
console.log(JSON.stringify(data, null, 2));
});
- Define the functionality you want inside the 'worker.js' file. The following pretends to assign a new value in
data, specifically,data[cellid] = value, and then return the modifieddata,id, andvalue.
function evaluate(e) {
console.info('worker evaluating');
console.log(e.data);
var data = JSON.parse(e.data.data || '{}');
var item = JSON.parse(e.data.item || '{}');
var id = item.id;
var value = item.value;
// Replace cellId (A1, PP12, etc.) with data[cellid] value.
var expr = value.substring(1).trim().replace(/[A-Z]+[\d]+/g, function(cellid) {
var item = data[cellid] || { value: '' };
var test = Number(item.value);
// Quote the value if it can't be coerced to a Number.
return test !== test ? '"' + item.value + '"' : (item.value || 0);
});
var computedValue = value;
try {
computedValue = Function(`return ${expr};`).call(null);
} catch (e) {
console.error({
error: "Error evaluating " + value,
expr,
computedValue
});
}
self.postMessage(JSON.stringify({
id: id,
value: computedValue
}));
}
- Subscribe to the message event inside the worker
self.addEventListener('message', evaluate);
- Back in the main thread, create a function that calls
worker.postMesage()— we'll assume the function uses fields from thedataanditemarguments, then makes that call:
function useWorker(data, item) {
worker.postMessage({
data: JSON.stringify(data),
item: JSON.stringify(item)
});
}
- Call that function
var data = {};
var item = { id: "test", value: "something" };
useWorker(data, item);
You should see the "worker completed" message in the console along with formatted JSON data:
{
"id": "test",
"value": "something"
}
-
The worker must reside in its own file - it cannot be created from a
Blobor data schema URI - more on that here → https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Content_security_policy. -
Content Security Policy's
script-srcmust include 'self' or 'strict-dynamic' (recommend including both for browsers that do not support 'strict-dynamic') - more about the script-src directive here → https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
- Do not use
script-src— Usescript-src-elemfor best cross-browser support.
The script-src directive is not carefully supported across all browsers to enable workers launched from trusted scripts to run eval().
This past week (2020 new year's) I found that only Firefox <= 71 runs eval() in a worker launched from code in a script-src directive when defined in the <meta> tag in the page itself. Edge ignored it completely and ran the worker. Chrome complained that the worker's CSP did not allow 'unsafe-eval'.
Moving that directive to the server and adding the response header there resulted in ALL browsers failing to execute the worker.
Changing script-src to script-src-elem in both server and <meta> tag versions resulted in successful execution across all browsers.
- There is also a
worker-srcdirective, but…
According to this, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src, you can specify a CSP header for worker scripts.
I had no success using this directive on server response headers or in the <meta> tag.