Kaydet (Commit) 845817b0 authored tarafından Tim Graham's avatar Tim Graham

Fixed #24466 -- Added JavaScript escaping in a couple places in the admin.

Thanks Aymeric Augustin and Florian Apolloner for work on the patch.
üst b86abbce
...@@ -1051,9 +1051,8 @@ class ModelAdmin(BaseModelAdmin): ...@@ -1051,9 +1051,8 @@ class ModelAdmin(BaseModelAdmin):
attr = obj._meta.pk.attname attr = obj._meta.pk.attname
value = obj.serializable_value(attr) value = obj.serializable_value(attr)
return SimpleTemplateResponse('admin/popup_response.html', { return SimpleTemplateResponse('admin/popup_response.html', {
'pk_value': escape(pk_value), # for possible backwards-compatibility 'value': value,
'value': escape(value), 'obj': obj,
'obj': escapejs(obj)
}) })
elif "_continue" in request.POST: elif "_continue" in request.POST:
......
...@@ -21,10 +21,10 @@ ...@@ -21,10 +21,10 @@
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({ $("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .inline-related").stackedFormset({
prefix: '{{ inline_admin_formset.formset.prefix }}', prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
deleteText: "{% trans "Remove" %}", deleteText: "{% filter escapejs %}{% trans "Remove" %}{% endfilter %}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}" addText: "{% filter escapejs %}{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}"
}); });
})(django.jQuery); })(django.jQuery);
</script> </script>
...@@ -74,10 +74,10 @@ ...@@ -74,10 +74,10 @@
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({ $("#{{ inline_admin_formset.formset.prefix|escapejs }}-group .tabular.inline-related tbody tr").tabularFormset({
prefix: "{{ inline_admin_formset.formset.prefix }}", prefix: "{{ inline_admin_formset.formset.prefix|escapejs }}",
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|capfirst as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}", addText: "{% filter escapejs %}{% blocktrans with inline_admin_formset.opts.verbose_name|capfirst as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}{% endfilter %}",
deleteText: "{% trans 'Remove' %}" deleteText: "{% filter escapejs %}{% trans 'Remove' %}{% endfilter %}"
}); });
})(django.jQuery); })(django.jQuery);
</script> </script>
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
{% if action == 'change' %} {% if action == 'change' %}
opener.dismissChangeRelatedObjectPopup(window, "{{ value }}", "{{ obj }}", "{{ new_value }}"); opener.dismissChangeRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}", "{{ new_value|escapejs }}");
{% elif action == 'delete' %} {% elif action == 'delete' %}
opener.dismissDeleteRelatedObjectPopup(window, "{{ value }}"); opener.dismissDeleteRelatedObjectPopup(window, "{{ value|escapejs }}");
{% else %} {% else %}
opener.dismissAddRelatedObjectPopup(window, "{{ value }}", "{{ obj }}"); opener.dismissAddRelatedObjectPopup(window, "{{ value|escapejs }}", "{{ obj|escapejs }}");
{% endif %} {% endif %}
</script> </script>
</body> </body>
......
...@@ -15,7 +15,7 @@ from django.template.loader import render_to_string ...@@ -15,7 +15,7 @@ from django.template.loader import render_to_string
from django.utils import six from django.utils import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import ( from django.utils.html import (
escape, format_html, format_html_join, smart_urlquote, escape, escapejs, format_html, format_html_join, smart_urlquote,
) )
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import Truncator from django.utils.text import Truncator
...@@ -50,7 +50,7 @@ class FilteredSelectMultiple(forms.SelectMultiple): ...@@ -50,7 +50,7 @@ class FilteredSelectMultiple(forms.SelectMultiple):
# TODO: "id_" is hard-coded here. This should instead use the correct # TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically. # API to determine the ID dynamically.
output.append('SelectFilter.init("id_%s", "%s", %s); });</script>\n' output.append('SelectFilter.init("id_%s", "%s", %s); });</script>\n'
% (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked))) % (name, escapejs(self.verbose_name), int(self.is_stacked)))
return mark_safe(''.join(output)) return mark_safe(''.join(output))
......
from __future__ import unicode_literals
from django.template.loader import render_to_string
from django.test import SimpleTestCase
class TestTemplates(SimpleTestCase):
def test_javascript_escaping(self):
context = {
'inline_admin_formset': {
'formset': {'prefix': 'my-prefix'},
'opts': {'verbose_name': 'verbose name\\'},
},
}
output = render_to_string('admin/edit_inline/stacked.html', context)
self.assertIn('prefix: "my\\u002Dprefix",', output)
self.assertIn('addText: "Add another Verbose name\\u005C"', output)
output = render_to_string('admin/edit_inline/tabular.html', context)
self.assertIn('prefix: "my\\u002Dprefix",', output)
self.assertIn('addText: "Add another Verbose name\\u005C"', output)
...@@ -78,7 +78,7 @@ class TestInline(TestDataMixin, TestCase): ...@@ -78,7 +78,7 @@ class TestInline(TestDataMixin, TestCase):
# The heading for the m2m inline block uses the right text # The heading for the m2m inline block uses the right text
self.assertContains(response, '<h2>Author-book relationships</h2>') self.assertContains(response, '<h2>Author-book relationships</h2>')
# The "add another" label is correct # The "add another" label is correct
self.assertContains(response, 'Add another Author-book relationship') self.assertContains(response, 'Add another Author\\u002Dbook relationship')
# The '+' is dropped from the autogenerated form prefix (Author_books+) # The '+' is dropped from the autogenerated form prefix (Author_books+)
self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"')
...@@ -524,7 +524,7 @@ class TestInlinePermissions(TestCase): ...@@ -524,7 +524,7 @@ class TestInlinePermissions(TestCase):
response = self.client.get(reverse('admin:admin_inlines_author_add')) response = self.client.get(reverse('admin:admin_inlines_author_add'))
# No change permission on books, so no inline # No change permission on books, so no inline
self.assertNotContains(response, '<h2>Author-book relationships</h2>') self.assertNotContains(response, '<h2>Author-book relationships</h2>')
self.assertNotContains(response, 'Add another Author-Book Relationship') self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
def test_inline_add_fk_noperm(self): def test_inline_add_fk_noperm(self):
...@@ -538,7 +538,7 @@ class TestInlinePermissions(TestCase): ...@@ -538,7 +538,7 @@ class TestInlinePermissions(TestCase):
response = self.client.get(self.author_change_url) response = self.client.get(self.author_change_url)
# No change permission on books, so no inline # No change permission on books, so no inline
self.assertNotContains(response, '<h2>Author-book relationships</h2>') self.assertNotContains(response, '<h2>Author-book relationships</h2>')
self.assertNotContains(response, 'Add another Author-Book Relationship') self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
def test_inline_change_fk_noperm(self): def test_inline_change_fk_noperm(self):
...@@ -554,7 +554,7 @@ class TestInlinePermissions(TestCase): ...@@ -554,7 +554,7 @@ class TestInlinePermissions(TestCase):
response = self.client.get(reverse('admin:admin_inlines_author_add')) response = self.client.get(reverse('admin:admin_inlines_author_add'))
# No change permission on Books, so no inline # No change permission on Books, so no inline
self.assertNotContains(response, '<h2>Author-book relationships</h2>') self.assertNotContains(response, '<h2>Author-book relationships</h2>')
self.assertNotContains(response, 'Add another Author-Book Relationship') self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
def test_inline_add_fk_add_perm(self): def test_inline_add_fk_add_perm(self):
...@@ -573,7 +573,7 @@ class TestInlinePermissions(TestCase): ...@@ -573,7 +573,7 @@ class TestInlinePermissions(TestCase):
response = self.client.get(self.author_change_url) response = self.client.get(self.author_change_url)
# No change permission on books, so no inline # No change permission on books, so no inline
self.assertNotContains(response, '<h2>Author-book relationships</h2>') self.assertNotContains(response, '<h2>Author-book relationships</h2>')
self.assertNotContains(response, 'Add another Author-Book Relationship') self.assertNotContains(response, 'Add another Author\\u002DBook Relationship')
self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"')
self.assertNotContains(response, 'id="id_Author_books-0-DELETE"') self.assertNotContains(response, 'id="id_Author_books-0-DELETE"')
...@@ -583,7 +583,7 @@ class TestInlinePermissions(TestCase): ...@@ -583,7 +583,7 @@ class TestInlinePermissions(TestCase):
response = self.client.get(self.author_change_url) response = self.client.get(self.author_change_url)
# We have change perm on books, so we can add/change/delete inlines # We have change perm on books, so we can add/change/delete inlines
self.assertContains(response, '<h2>Author-book relationships</h2>') self.assertContains(response, '<h2>Author-book relationships</h2>')
self.assertContains(response, 'Add another Author-book relationship') self.assertContains(response, 'Add another Author\\u002Dbook relationship')
self.assertContains(response, '<input type="hidden" id="id_Author_books-TOTAL_FORMS" ' self.assertContains(response, '<input type="hidden" id="id_Author_books-TOTAL_FORMS" '
'value="4" name="Author_books-TOTAL_FORMS" />', html=True) 'value="4" name="Author_books-TOTAL_FORMS" />', html=True)
self.assertContains(response, '<input type="hidden" id="id_Author_books-0-id" ' self.assertContains(response, '<input type="hidden" id="id_Author_books-0-id" '
......
...@@ -24,6 +24,7 @@ from django.core.checks import Error ...@@ -24,6 +24,7 @@ from django.core.checks import Error
from django.core.files import temp as tempfile from django.core.files import temp as tempfile
from django.core.urlresolvers import NoReverseMatch, resolve, reverse from django.core.urlresolvers import NoReverseMatch, resolve, reverse
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.template.loader import render_to_string
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import ( from django.test import (
TestCase, modify_settings, override_settings, skipUnlessDBFeature, TestCase, modify_settings, override_settings, skipUnlessDBFeature,
...@@ -3490,6 +3491,30 @@ action)</option> ...@@ -3490,6 +3491,30 @@ action)</option>
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.template_name, 'admin/popup_response.html') self.assertEqual(response.template_name, 'admin/popup_response.html')
def test_popup_template_escaping(self):
context = {
'new_value': 'new_value\\',
'obj': 'obj\\',
'value': 'value\\',
}
output = render_to_string('admin/popup_response.html', context)
self.assertIn(
'opener.dismissAddRelatedObjectPopup(window, "value\\u005C", "obj\\u005C");', output
)
context['action'] = 'change'
output = render_to_string('admin/popup_response.html', context)
self.assertIn(
'opener.dismissChangeRelatedObjectPopup(window, '
'"value\\u005C", "obj\\u005C", "new_value\\u005C");', output
)
context['action'] = 'delete'
output = render_to_string('admin/popup_response.html', context)
self.assertIn(
'opener.dismissDeleteRelatedObjectPopup(window, "value\\u005C");', output
)
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'], @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
ROOT_URLCONF="admin_views.urls") ROOT_URLCONF="admin_views.urls")
......
...@@ -264,17 +264,23 @@ class AdminForeignKeyRawIdWidget(TestDataMixin, DjangoTestCase): ...@@ -264,17 +264,23 @@ class AdminForeignKeyRawIdWidget(TestDataMixin, DjangoTestCase):
class FilteredSelectMultipleWidgetTest(DjangoTestCase): class FilteredSelectMultipleWidgetTest(DjangoTestCase):
def test_render(self): def test_render(self):
w = widgets.FilteredSelectMultiple('test', False) # Backslash in verbose_name to ensure it is JavaScript escaped.
w = widgets.FilteredSelectMultiple('test\\', False)
self.assertHTMLEqual( self.assertHTMLEqual(
w.render('test', 'test'), w.render('test', 'test'),
'<select multiple="multiple" name="test" class="selectfilter">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 0); });</script>\n' '<select multiple="multiple" name="test" class="selectfilter">\n</select>'
'<script type="text/javascript">addEvent(window, "load", function(e) '
'{SelectFilter.init("id_test", "test\\u005C", 0); });</script>\n'
) )
def test_stacked_render(self): def test_stacked_render(self):
w = widgets.FilteredSelectMultiple('test', True) # Backslash in verbose_name to ensure it is JavaScript escaped.
w = widgets.FilteredSelectMultiple('test\\', True)
self.assertHTMLEqual( self.assertHTMLEqual(
w.render('test', 'test'), w.render('test', 'test'),
'<select multiple="multiple" name="test" class="selectfilterstacked">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 1); });</script>\n' '<select multiple="multiple" name="test" class="selectfilterstacked">\n</select>'
'<script type="text/javascript">addEvent(window, "load", function(e) '
'{SelectFilter.init("id_test", "test\\u005C", 1); });</script>\n'
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment