Skip to content

Commit 78d6aff

Browse files
committed
avoid xss when using js_reverse_inline
1 parent 0714391 commit 78d6aff

File tree

5 files changed

+28
-2
lines changed

5 files changed

+28
-2
lines changed

README.rst

+4
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ or, if you are using Django > 1.5
184184
Usage as template tag
185185
_____________________
186186

187+
You can place the js_reverse JavaScript inline into your templates,
188+
however use of inline JavaScript is not recommended, because it
189+
will make it impossible to deploy a secure Content Security Policy.
190+
See `django-csp <https://django-csp.readthedocs.io/>`__
187191

188192
::
189193

django_js_reverse/core.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.conf import settings
99
from django.core.exceptions import ImproperlyConfigured
1010
from django.template import loader
11+
from django.utils.safestring import mark_safe
1112
from django.utils.encoding import force_text
1213

1314
from . import rjsmin
@@ -120,6 +121,16 @@ def generate_json(default_urlresolver, script_prefix=None):
120121
}
121122

122123

124+
def _safe_json(obj):
125+
return mark_safe(
126+
json
127+
.dumps(obj)
128+
.replace('>', '\\u003E')
129+
.replace('<', '\\u003C')
130+
.replace('&', '\\u0026')
131+
)
132+
133+
123134
def generate_js(default_urlresolver):
124135
js_var_name = getattr(settings, 'JS_REVERSE_JS_VAR_NAME', JS_VAR_NAME)
125136
if not JS_IDENTIFIER_RE.match(js_var_name.upper()):
@@ -147,7 +158,7 @@ def generate_js(default_urlresolver):
147158

148159
data = generate_json(default_urlresolver, script_prefix)
149160
js_content = loader.render_to_string('django_js_reverse/urls_js.tpl', {
150-
'data': json.dumps(data),
161+
'data': _safe_json(data),
151162
'js_name': '.'.join([js_global_object_name, js_var_name]),
152163
})
153164

django_js_reverse/templates/django_js_reverse/urls_js.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{ js_name }} = (function () {
22
"use strict";
3-
var data = {{ data|safe }};
3+
var data = {{ data }};
44
function factory(d) {
55
var url_patterns = d.urls;
66
var url_prefix = d.prefix;

django_js_reverse/tests/test_urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# test urls
2222
url(r'^test_no_url_args/$', dummy_view, name='test_no_url_args'),
23+
url(r'^test_script/$', dummy_view, name='</script><script>console.log(&amp;)</script><!--'),
2324
url(r'^test_one_url_args/(?P<arg_one>[-\w]+)/$', dummy_view, name='test_one_url_args'),
2425
url(r'^test_two_url_args/(?P<arg_one>[-\w]+)-(?P<arg_two>[-\w]+)/$', dummy_view, name='test_two_url_args'),
2526
url(r'^test_optional_url_arg/(?:1_(?P<arg_one>[-\w]+)-)?2_(?P<arg_two>[-\w]+)/$', dummy_view,

django_js_reverse/tests/unit_tests.py

+10
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ def test_script_prefix(self):
265265

266266
@override_settings(
267267
ROOT_URLCONF='django_js_reverse.tests.test_urls',
268+
268269
TEMPLATE_CONTEXT_PROCESSORS=['django.core.context_processors.request'],
269270
)
270271
class JSReverseTemplateTagTest(AbstractJSReverseTestCase, TestCase):
@@ -288,6 +289,15 @@ def test_tpl_tag_without_request_in_context(self):
288289
js_from_view = smart_str(self.client.post('/jsreverse/').content)
289290
self.assertEqual(js_from_tag, js_from_view)
290291

292+
def test_tpl_tag_escape_entities(self):
293+
context_instance = Context()
294+
tpl = Template('{% load js_reverse %}{% js_reverse_inline %}')
295+
js = tpl.render(context_instance)
296+
self.assertIn(
297+
'\\u003C/script\\u003E\\u003Cscript\\u003Econsole.log(\\u0026amp;)'
298+
'\\u003C/script\\u003E\\u003C!--',
299+
js,
300+
)
291301

292302
if __name__ == '__main__':
293303
unittest.main()

0 commit comments

Comments
 (0)