Ported tojson filter. Fixes #458

This commit is contained in:
Armin Ronacher 2017-01-06 21:33:51 +01:00
parent ffe0caa1f0
commit e71a130607
6 changed files with 97 additions and 3 deletions

View File

@ -25,6 +25,8 @@ Version 2.9
the string is barely truncated at all.
- Change the logic for macro autoescaping to be based on the runtime
autoescaping information at call time instead of macro define time.
- Ported a modified version of the `tojson` filter from Flask to Jinja2
and hooked it up with the new policy framework.
Version 2.8.2
-------------

View File

@ -565,6 +565,18 @@ Example::
The default target that is issued for links from the `urlize` filter
if no other target is defined by the call explicitly.
``json.dumps_function``:
If this is set to a value other than `None` then the `tojson` filter
will dump with this function instead of the default one. Note that
this function should accept arbitrary extra arguments which might be
passed in the future from the filter. Currently the only argument
that might be passed is `indent`. The default dump function is
``json.dumps``.
``json.dumps_kwargs``:
Keyword arguments to be passed to the dump function. The default is
``{'sort_keys': True}``.
Utilities
---------

View File

@ -43,6 +43,8 @@ DEFAULT_NAMESPACE = {
DEFAULT_POLICIES = {
'urlize.rel': 'noopener',
'urlize.target': None,
'json.dumps_function': None,
'json.dumps_kwargs': {'sort_keys': True},
}

View File

@ -15,7 +15,7 @@ from random import choice
from itertools import groupby
from collections import namedtuple
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
unicode_urlencode
unicode_urlencode, htmlsafe_json_dumps
from jinja2.runtime import Undefined
from jinja2.exceptions import FilterArgumentError
from jinja2._compat import imap, string_types, text_type, iteritems
@ -916,6 +916,39 @@ def do_rejectattr(*args, **kwargs):
return select_or_reject(args, kwargs, lambda x: not x, True)
@evalcontextfilter
def do_tojson(eval_ctx, value, indent=None):
"""Dumps a structure to JSON so that it's safe to use in ``<script>``
tags. It accepts the same arguments and returns a JSON string. Note that
this is available in templates through the ``|tojson`` filter which will
also mark the result as safe. Due to how this function escapes certain
characters this is safe even if used outside of ``<script>`` tags.
The following characters are escaped in strings:
- ``<``
- ``>``
- ``&``
- ``'``
This makes it safe to embed such strings in any place in HTML with the
notable exception of double quoted attributes. In that case single
quote your attributes or HTML escape it in addition.
The indent parameter can be used to enable pretty printing. Set it to
the number of spaces that the structures should be indented with.
.. versionadded:: 2.9
"""
policies = eval_ctx.environment.policies
dumper = policies['json.dumps_function']
options = policies['json.dumps_kwargs']
if indent is not None:
options = dict(options)
options['indent'] = indent
return htmlsafe_json_dumps(value, dumper=dumper, **options)
def prepare_map(args, kwargs):
context = args[0]
seq = args[1]
@ -1021,4 +1054,5 @@ FILTERS = {
'wordcount': do_wordcount,
'wordwrap': do_wordwrap,
'xmlattr': do_xmlattr,
'tojson': do_tojson,
}

View File

@ -9,6 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
import re
import json
import errno
from collections import deque
from threading import Lock
@ -37,6 +38,8 @@ internal_code = set()
concat = u''.join
_slash_escape = '\\/' not in json.dumps('/')
def contextfunction(f):
"""This decorator can be used to mark a function or method context callable.
@ -485,6 +488,34 @@ except ImportError:
pass
def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
"""Works exactly like :func:`dumps` but is safe for use in ``<script>``
tags. It accepts the same arguments and returns a JSON string. Note that
this is available in templates through the ``|tojson`` filter which will
also mark the result as safe. Due to how this function escapes certain
characters this is safe even if used outside of ``<script>`` tags.
The following characters are escaped in strings:
- ``<``
- ``>``
- ``&``
- ``'``
This makes it safe to embed such strings in any place in HTML with the
notable exception of double quoted attributes. In that case single
quote your attributes or HTML escape it in addition.
"""
if dumper is None:
dumper = json.dumps
rv = dumper(obj, **kwargs) \
.replace(u'<', u'\\u003c') \
.replace(u'>', u'\\u003e') \
.replace(u'&', u'\\u0026') \
.replace(u"'", u'\\u0027')
return rv
@implements_iterator
class Cycler(object):
"""A cycle helper for templates."""

View File

@ -576,3 +576,16 @@ class TestFilter(object):
tmpl = env.from_string('{{ users|rejectattr("id", "odd")|'
'map(attribute="name")|join("|") }}')
assert tmpl.render(users=users) == 'jane'
def test_json_dump(self):
env = Environment(autoescape=True)
t = env.from_string('{{ x|tojson }}')
assert t.render(x={'foo': 'bar'}) == '{&#34;foo&#34;: &#34;bar&#34;}'
assert t.render(x='"bar\'') == '&#34;\&#34;bar\u0027&#34;'
def my_dumps(value, **options):
assert options == {'foo': 'bar'}
return '42'
env.policies['json.dumps_function'] = my_dumps
env.policies['json.dumps_kwargs'] = {'foo': 'bar'}
assert t.render(x=23) == '42'