aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES2
-rw-r--r--docs/api.rst12
-rw-r--r--jinja2/defaults.py6
-rw-r--r--jinja2/filters.py36
-rw-r--r--jinja2/utils.py31
-rw-r--r--tests/test_filters.py13
6 files changed, 97 insertions, 3 deletions
diff --git a/CHANGES b/CHANGES
index 62ca6b08..5f69137d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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
-------------
diff --git a/docs/api.rst b/docs/api.rst
index 107acd65..8bf0fdfe 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -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
---------
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
index bdb538d5..90ccb65f 100644
--- a/jinja2/defaults.py
+++ b/jinja2/defaults.py
@@ -41,8 +41,10 @@ DEFAULT_NAMESPACE = {
# default policies
DEFAULT_POLICIES = {
- 'urlize.rel': 'noopener',
- 'urlize.target': None,
+ 'urlize.rel': 'noopener',
+ 'urlize.target': None,
+ 'json.dumps_function': None,
+ 'json.dumps_kwargs': {'sort_keys': True},
}
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 33e0ff14..05c2fc45 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -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,
}
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 96b13521..38e5edb2 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -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."""
diff --git a/tests/test_filters.py b/tests/test_filters.py
index ba57136d..1a8a1640 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -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'