We have a server-side rendered HTML page which includes an external JavaScript file. To trigger the code in that file, we want to call a function and pass it some dynamic data (in the form of JSON):
<script src="/static/foo/bar.js"></script>
<script>
foo.bar.init({biz: 42, qux: "quux"});
</script>
We're rendering this from a Nunjucks template, and passing the JSON object as a value data
in the context. This can contain arbitrary data, including user-provided content.
This is safe but does not work, because <>&
are being escaped (thanks to Nunjucks autoescaping):
foo.bar.init({{ data | dump }});
This works, but is not safe, because a string in the JSON might contain the text </script>
:
foo.bar.init({{ data | dump | safe }});
How to convince Nunjucks to render this JSON so it can be interpreted safely and correctly? It sounds like it should be a solved problem, but I can't find a premade solution anywhere.
Doing this for now:
/**
* Returns a JSON stringified version of the value, safe for inclusion in an
* inline <script> tag. The optional argument 'spaces' can be used for
* pretty-printing.
*
* Output is NOT safe for inclusion in HTML! If that's what you need, use the
* built-in 'dump' filter instead.
*/
env.addFilter('json', function (value, spaces) {
if (value instanceof nunjucks.runtime.SafeString) {
value = value.toString()
}
const jsonString = JSON.stringify(value, null, spaces).replace(/</g, '\\u003c')
return nunjucks.runtime.markSafe(jsonString)
})
Usage:
<script src="/static/foo/bar.js"></script>
<script>
foo.bar.init({{ data | json }});
</script>
Better solutions still welcome.