24

How do I escape HTML with Jinja2 so that it can be used as a string in JavaScript (jQuery)?

If I were using Django's templating system I could write:

$("#mydiv").append("{{ html_string|escapejs }}");

Django's |escapejs filter would escape things in html_string (eg quotes, special chars) that could break the intended use of this code block, but Jinja2 does not seem to have an equivalent filter (am I wrong here?).

Is there a cleaner solution than copying/pasting the code from Django?

fragilewindows
  • 1,394
  • 1
  • 15
  • 26
meshy
  • 8,470
  • 9
  • 51
  • 73
  • See here: http://jinja.pocoo.org/docs/templates/#escaping – JAR.JAR.beans Sep 09 '12 at 14:16
  • I don't need to escape the jinja tag text itself, I need to ensure that `html_string` doesn't contain any harmful chars. – meshy Sep 09 '12 at 14:26
  • Maybe the |safe filter is what you are after than: http://flask.pocoo.org/docs/templating/#standard-filters – JAR.JAR.beans Sep 09 '12 at 14:30
  • possible duplicate of [What would be the best way to pass a list from python to js using bottle?](http://stackoverflow.com/questions/11427417/what-would-be-the-best-way-to-pass-a-list-from-python-to-js-using-bottle) – Martijn Pieters Sep 09 '12 at 16:15
  • @MartijnPieters The solution is similar, but the question was ultimately meant for pywebkitgtk, not bottle... As the answers in that question relate to that framework, I don't believe this is a duplicate. – meshy Sep 09 '12 at 16:41
  • @meshy: The exact same principle applies though. Incorporate JavaScript literals by using the `json.dumps()` function. – Martijn Pieters Sep 09 '12 at 16:48
  • Jinja has a [builtin `tojson` filter now](http://flask.pocoo.org/docs/0.12/templating/#standard-filters). This seems to dump it out as a JS-escaped string, not a proper JSON object, so you'll need to call `var my_data = JSON.parse({{ my_string|tojson }})` – Hartley Brody Mar 21 '18 at 13:36
  • @MartijnPieters Actually, now (since version 2.9) there are solutions native to jinja2 (tojson filter). – user202729 Dec 02 '18 at 15:53

6 Answers6

21

Jinja2 has nice filter tojson. If you make json from string, it will generate string enclosed in double quotes "". You can safely use it in javascript. And you don't need put quotes around by yourself.

$("#mydiv").append({{ html_string|tojson }});

It also escapes <>& symbols. So it is safe even when sting contains something XSS dangerous like <script>

Alexander C
  • 3,597
  • 1
  • 23
  • 39
15

This is a escapejs filter, based on Django's one, that I wrote for use in Jinja2 templates:

_js_escapes = {
        '\\': '\\u005C',
        '\'': '\\u0027',
        '"': '\\u0022',
        '>': '\\u003E',
        '<': '\\u003C',
        '&': '\\u0026',
        '=': '\\u003D',
        '-': '\\u002D',
        ';': '\\u003B',
        u'\u2028': '\\u2028',
        u'\u2029': '\\u2029'
}
# Escape every ASCII character with a value less than 32.
_js_escapes.update(('%c' % z, '\\u%04X' % z) for z in xrange(32))
def jinja2_escapejs_filter(value):
        retval = []
        for letter in value:
                if _js_escapes.has_key(letter):
                        retval.append(_js_escapes[letter])
                else:
                        retval.append(letter)

        return jinja2.Markup("".join(retval))
JINJA_ENVIRONMENT.filters['escapejs'] = jinja2_escapejs_filter

Example safe usage in a template:

<script type="text/javascript">
<!--
var variableName = "{{ variableName | escapejs }}";
…
//-->
</script>

When variableName is a str or unicode.

Tometzky
  • 22,573
  • 5
  • 59
  • 73
  • It is already required that `{{ variableName | espacejs }}` is in quotes (single or double) so square brackets tricks are not possible. Otherwise even a space could be dangerous. – Tometzky Sep 21 '13 at 07:25
  • 2
    This answer helped me out in this thread. Real mind bender to escape characters inside a string that are going to be printed from Python to HTML which is going to be inside quote tags inside JavaScript which is going to parse as JSON. Thnx for the help! – Matthisk Mar 18 '16 at 14:27
9

I faced a similar problem last year. Not sure whether you're using bottle, but my solution looked something like this.

import json

def escapejs(val):
    return json.dumps(str(val)) # *but see [Important Note] below to be safe

@app.route('/foo')
def foo():
    return bottle.jinja2_template('foo', template_settings={'filters': {'escapejs': escapejs}})

(I wrapped the template_settings dict in a helper function since I used it everywhere, but I kept it simple in this example.)

Unfortunately, it's not as simple as a builtin jinja2 filter, but I was able to live with it happily--especially considering that I had several other custom filters to add, too.

Important Note: Hat tip to @medmunds's for his astute comment below, reminding us that json.dumps is not XSS-safe. IOW, you wouldn't want to use it in a production, internet-facing server. Recommendation is to write a safer json escape routine (or steal django's--sorry OP, I know you were hoping to avoid that) and call that instead of using json.dumps.

ron rothman
  • 17,348
  • 7
  • 41
  • 43
  • Perfect! I'm not using bottle, I'm using pywebkitgtk to build a desktop app, but `json.dumps` is exactly what I needed. Much simpler than I'd hoped! Thank-you! – meshy Sep 09 '12 at 14:44
  • Oh, and thanks for the link to bottle! I've not seen that before, could come in handy ;) – meshy Sep 09 '12 at 14:48
  • 8
    I don't think json.dumps() escapes everything you need to worry about. E.g., as currently written, `escapejs("")` returns `""` -- which seems like it could allow a closing script tag (and anything after it!) to leak into your html. (The Django escapejs filter does unicode escapes on the < and > characters, which avoids the problem.) – medmunds Mar 08 '13 at 20:11
  • 1
    ... yeah, seems like this answer definitely leaves an XSS vulnerability. Khan Academy [has some examples here](https://sites.google.com/a/khanacademy.org/forge/technical/autoescape-in-jinja2-templates) and provides their own escapejs and jsonify filters. Looks like they borrowed [their implementation of escapejs](https://github.com/dsissitka/khan-website/blob/master/templatefilters.py#L112-139) from Django. – medmunds Mar 08 '13 at 20:34
  • @medmunds, thanks for pointing out the XSS vulnerability. You're right; I'll update the entry to point that out. (FWIW, in my case my service is internal, behind firewall, so XSS attack was not a top concern. I'll fix my code nevertheless, just to get in the right habit.) – ron rothman Mar 09 '13 at 05:43
1

I just researched this problem, my solution is to define a filter:

from flask import Flask, Markup
app = Flask(__name__)
app.jinja_env.filters['json'] = lambda v: Markup(json.dumps(v))

and in the template:

<script>
var myvar = {{myvar|json}} ;
</script>

This has the nice feature that myvar can be anything that can be JSON-serialised

Mike Richardson
  • 292
  • 3
  • 12
  • 8
    Don't do this with user-generated content, as users will be able to execute JS. For example when `myvar` = ` – Blaise Jul 15 '16 at 12:14
0

Based on @tometzky here is my Python 3 version:

_js_escapes = {
        '\\': '\\u005C',
        '\'': '\\u0027',
        '"': '\\u0022',
        '>': '\\u003E',
        '<': '\\u003C',
        '&': '\\u0026',
        '=': '\\u003D',
        '-': '\\u002D',
        ';': '\\u003B',
        u'\u2028': '\\u2028',
        u'\u2029': '\\u2029'
}
# Escape every ASCII character with a value less than 32.
_js_escapes.update(('%c' % z, '\\u%04X' % z) for z in range(32))

@register.filter
def escapejs(value):
    return jinja2.Markup("".join(_js_escapes.get(l, l) for l in value))

The usage is exactly the same.

Sebastian Wagner
  • 2,308
  • 2
  • 25
  • 32
-1

You can also use jinja2's autoescape. So, for instance, you could add autoescape to your jinja2 Environment in Python:

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    autoescape=True)

Alternatively, you could use the Autoescape Extension added in Jinja 2.4 to have more control over where the autoescaping is used in the HTML. More information on this here and example (in Google App Engine) here.

Python:

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'])

HTML:

{% autoescape true %}
    <html>
        <body>
            {{ IWillBeEscaped }}
        </body>
    </html>
{% endautoescape %}
Tony Wickham
  • 4,706
  • 3
  • 29
  • 35