Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely render JSON into an inline <script> using Nunjucks?

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.

like image 988
Thomas Avatar asked Sep 26 '17 12:09

Thomas


1 Answers

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.

like image 179
Thomas Avatar answered Nov 15 '22 12:11

Thomas