mirror of
https://github.com/django/django.git
synced 2025-06-05 11:39:13 +00:00
Fixed #17419 -- Added json_tag template filter.
This commit is contained in:
parent
ef2512b2ff
commit
8c709d79cb
@ -12,8 +12,8 @@ from django.utils import formats
|
|||||||
from django.utils.dateformat import format, time_format
|
from django.utils.dateformat import format, time_format
|
||||||
from django.utils.encoding import iri_to_uri
|
from django.utils.encoding import iri_to_uri
|
||||||
from django.utils.html import (
|
from django.utils.html import (
|
||||||
avoid_wrapping, conditional_escape, escape, escapejs, linebreaks,
|
avoid_wrapping, conditional_escape, escape, escapejs,
|
||||||
strip_tags, urlize as _urlize,
|
json_script as _json_script, linebreaks, strip_tags, urlize as _urlize,
|
||||||
)
|
)
|
||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
from django.utils.text import (
|
from django.utils.text import (
|
||||||
@ -82,6 +82,15 @@ def escapejs_filter(value):
|
|||||||
return escapejs(value)
|
return escapejs(value)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(is_safe=True)
|
||||||
|
def json_script(value, element_id):
|
||||||
|
"""
|
||||||
|
Output value JSON-encoded, wrapped in a <script type="application/json">
|
||||||
|
tag.
|
||||||
|
"""
|
||||||
|
return _json_script(value, element_id)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def floatformat(text, arg=-1):
|
def floatformat(text, arg=-1):
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""HTML utilities suitable for global use."""
|
"""HTML utilities suitable for global use."""
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from urllib.parse import (
|
from urllib.parse import (
|
||||||
@ -77,6 +78,27 @@ def escapejs(value):
|
|||||||
return mark_safe(str(value).translate(_js_escapes))
|
return mark_safe(str(value).translate(_js_escapes))
|
||||||
|
|
||||||
|
|
||||||
|
_json_script_escapes = {
|
||||||
|
ord('>'): '\\u003E',
|
||||||
|
ord('<'): '\\u003C',
|
||||||
|
ord('&'): '\\u0026',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def json_script(value, element_id):
|
||||||
|
"""
|
||||||
|
Escape all the HTML/XML special characters with their unicode escapes, so
|
||||||
|
value is safe to be output anywhere except for inside a tag attribute. Wrap
|
||||||
|
the escaped JSON in a script tag.
|
||||||
|
"""
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
json_str = json.dumps(value, cls=DjangoJSONEncoder).translate(_json_script_escapes)
|
||||||
|
return format_html(
|
||||||
|
'<script id="{}" type="application/json">{}</script>',
|
||||||
|
element_id, mark_safe(json_str)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def conditional_escape(text):
|
def conditional_escape(text):
|
||||||
"""
|
"""
|
||||||
Similar to escape(), except that it doesn't operate on pre-escaped strings.
|
Similar to escape(), except that it doesn't operate on pre-escaped strings.
|
||||||
|
@ -1782,6 +1782,46 @@ For example::
|
|||||||
If ``value`` is the list ``['a', 'b', 'c']``, the output will be the string
|
If ``value`` is the list ``['a', 'b', 'c']``, the output will be the string
|
||||||
``"a // b // c"``.
|
``"a // b // c"``.
|
||||||
|
|
||||||
|
.. templatefilter:: json_script
|
||||||
|
|
||||||
|
``json_script``
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
|
||||||
|
Safely outputs a Python object as JSON, wrapped in a ``<script>`` tag, ready
|
||||||
|
for use with JavaScript.
|
||||||
|
|
||||||
|
**Argument:** HTML "id" of the ``<script>`` tag.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
{{ value|json_script:"hello-data" }}
|
||||||
|
|
||||||
|
If ``value`` is a the dictionary ``{'hello': 'world'}``, the output will be:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<script id="hello-data" type="application/json">{"hello": "world"}</script>
|
||||||
|
|
||||||
|
The resulting data can be accessed in JavaScript like this:
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
var el = document.getElementById('hello-data');
|
||||||
|
var value = JSON.parse(el.textContent || el.innerText);
|
||||||
|
|
||||||
|
XSS attacks are mitigated by escaping the characters "<", ">" and "&". For
|
||||||
|
example if ``value`` is ``{'hello': 'world</script>&'}``, the output is:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<script id="hello-data" type="application/json">{"hello": "world\\u003C/script\\u003E\\u0026amp;"}</script>
|
||||||
|
|
||||||
|
This is compatible with a strict Content Security Policy that prohibits in-page
|
||||||
|
script execution. It also maintains a clean separation between passive data and
|
||||||
|
executable code.
|
||||||
|
|
||||||
.. templatefilter:: last
|
.. templatefilter:: last
|
||||||
|
|
||||||
``last``
|
``last``
|
||||||
|
@ -205,7 +205,8 @@ Signals
|
|||||||
Templates
|
Templates
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The new :tfilter:`json_script` filter safely outputs a Python object as JSON,
|
||||||
|
wrapped in a ``<script>`` tag, ready for use with JavaScript.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
~~~~~
|
~~~~~
|
||||||
|
19
tests/template_tests/filter_tests/test_json_script.py
Normal file
19
tests/template_tests/filter_tests/test_json_script.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
|
class JsonScriptTests(SimpleTestCase):
|
||||||
|
|
||||||
|
@setup({'json-tag01': '{{ value|json_script:"test_id" }}'})
|
||||||
|
def test_basic(self):
|
||||||
|
output = self.engine.render_to_string(
|
||||||
|
'json-tag01',
|
||||||
|
{'value': {'a': 'testing\r\njson \'string" <b>escaping</b>'}}
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
output,
|
||||||
|
'<script id="test_id" type="application/json">'
|
||||||
|
'{"a": "testing\\r\\njson \'string\\" \\u003Cb\\u003Eescaping\\u003C/b\\u003E"}'
|
||||||
|
'</script>'
|
||||||
|
)
|
@ -4,8 +4,8 @@ from datetime import datetime
|
|||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.utils.functional import lazystr
|
from django.utils.functional import lazystr
|
||||||
from django.utils.html import (
|
from django.utils.html import (
|
||||||
conditional_escape, escape, escapejs, format_html, html_safe, linebreaks,
|
conditional_escape, escape, escapejs, format_html, html_safe, json_script,
|
||||||
smart_urlquote, strip_spaces_between_tags, strip_tags,
|
linebreaks, smart_urlquote, strip_spaces_between_tags, strip_tags,
|
||||||
)
|
)
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
@ -147,6 +147,28 @@ class TestUtilsHtml(SimpleTestCase):
|
|||||||
self.check_output(escapejs, value, output)
|
self.check_output(escapejs, value, output)
|
||||||
self.check_output(escapejs, lazystr(value), output)
|
self.check_output(escapejs, lazystr(value), output)
|
||||||
|
|
||||||
|
def test_json_script(self):
|
||||||
|
tests = (
|
||||||
|
# "<", ">" and "&" are quoted inside JSON strings
|
||||||
|
(('&<>', '<script id="test_id" type="application/json">"\\u0026\\u003C\\u003E"</script>')),
|
||||||
|
# "<", ">" and "&" are quoted inside JSON objects
|
||||||
|
(
|
||||||
|
{'a': '<script>test&ing</script>'},
|
||||||
|
'<script id="test_id" type="application/json">'
|
||||||
|
'{"a": "\\u003Cscript\\u003Etest\\u0026ing\\u003C/script\\u003E"}</script>'
|
||||||
|
),
|
||||||
|
# Lazy strings are quoted
|
||||||
|
(lazystr('&<>'), '<script id="test_id" type="application/json">"\\u0026\\u003C\\u003E"</script>'),
|
||||||
|
(
|
||||||
|
{'a': lazystr('<script>test&ing</script>')},
|
||||||
|
'<script id="test_id" type="application/json">'
|
||||||
|
'{"a": "\\u003Cscript\\u003Etest\\u0026ing\\u003C/script\\u003E"}</script>'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for arg, expected in tests:
|
||||||
|
with self.subTest(arg=arg):
|
||||||
|
self.assertEqual(json_script(arg, 'test_id'), expected)
|
||||||
|
|
||||||
def test_smart_urlquote(self):
|
def test_smart_urlquote(self):
|
||||||
items = (
|
items = (
|
||||||
('http://öäü.com/', 'http://xn--4ca9at.com/'),
|
('http://öäü.com/', 'http://xn--4ca9at.com/'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user