diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index d1f02be843b..be4c6f8e7f9 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -19,7 +19,7 @@ from django.shortcuts import get_object_or_404, render_to_response from django.utils.decorators import method_decorator from django.utils.datastructures import SortedDict from django.utils.functional import update_wrapper -from django.utils.html import escape +from django.utils.html import escape, escapejs from django.utils.safestring import mark_safe from django.utils.functional import curry from django.utils.text import capfirst, get_text_list @@ -717,7 +717,7 @@ class ModelAdmin(BaseModelAdmin): if "_popup" in request.POST: return HttpResponse('' % \ # escape() calls force_unicode. - (escape(pk_value), escape(obj))) + (escape(pk_value), escapejs(obj))) elif "_addanother" in request.POST: self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) return HttpResponseRedirect(request.path) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index d59ad2d461c..1e00effea09 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -64,29 +64,10 @@ def capfirst(value): capfirst.is_safe=True capfirst = stringfilter(capfirst) -_base_js_escapes = ( - ('\\', r'\u005C'), - ('\'', r'\u0027'), - ('"', r'\u0022'), - ('>', r'\u003E'), - ('<', r'\u003C'), - ('&', r'\u0026'), - ('=', r'\u003D'), - ('-', r'\u002D'), - (';', r'\u003B'), - (u'\u2028', r'\u2028'), - (u'\u2029', r'\u2029') -) - -# Escape every ASCII character with a value less than 32. -_js_escapes = (_base_js_escapes + - tuple([('%c' % z, '\\u%04X' % z) for z in range(32)])) - def escapejs(value): """Hex encodes characters for use in JavaScript strings.""" - for bad, good in _js_escapes: - value = value.replace(bad, good) - return value + from django.utils.html import escapejs + return escapejs(value) escapejs = stringfilter(escapejs) def fix_ampersands(value): @@ -745,7 +726,6 @@ timesince.is_safe = False def timeuntil(value, arg=None): """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" from django.utils.timesince import timeuntil - from datetime import datetime if not value: return u'' try: diff --git a/django/utils/html.py b/django/utils/html.py index 951b3f2a592..094bc6660da 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -34,6 +34,31 @@ def escape(html): return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, unicode) +_base_js_escapes = ( + ('\\', r'\u005C'), + ('\'', r'\u0027'), + ('"', r'\u0022'), + ('>', r'\u003E'), + ('<', r'\u003C'), + ('&', r'\u0026'), + ('=', r'\u003D'), + ('-', r'\u002D'), + (';', r'\u003B'), + (u'\u2028', r'\u2028'), + (u'\u2029', r'\u2029') +) + +# Escape every ASCII character with a value less than 32. +_js_escapes = (_base_js_escapes + + tuple([('%c' % z, '\\u%04X' % z) for z in range(32)])) + +def escapejs(value): + """Hex encodes characters for use in JavaScript strings.""" + for bad, good in _js_escapes: + value = mark_safe(force_unicode(value).replace(bad, good)) + return value +escapejs = allow_lazy(escapejs, unicode) + def conditional_escape(html): """ Similar to escape(), except that it doesn't operate on pre-escaped strings. diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index eb35b418609..90abfa8dbb7 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -107,6 +107,22 @@ class AdminViewBasicTest(TestCase): response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data) self.assertEqual(response.status_code, 302) # redirect somewhere + def testPopupAddPost(self): + """ + Ensure http response from a popup is properly escaped. + """ + post_data = { + '_popup': u'1', + 'title': u'title with a new\nline', + 'content': u'some content', + 'date_0': u'2010-09-10', + 'date_1': u'14:55:39', + } + response = self.client.post('/test_admin/%s/admin_views/article/add/' % self.urlbit, post_data) + self.failUnlessEqual(response.status_code, 200) + self.assertContains(response, 'dismissAddAnotherPopup') + self.assertContains(response, 'title with a new\u000Aline') + # Post data for edit inline inline_post_data = { "name": u"Test section", diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index a9b0d337f2a..3acb218cd18 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -109,3 +109,15 @@ class TestUtilsHtml(unittest.TestCase): ) for value, output in items: self.check_output(f, value, output) + + def test_escapejs(self): + f = html.escapejs + items = ( + (u'"double quotes" and \'single quotes\'', u'\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027'), + (ur'\ : backslashes, too', u'\\u005C : backslashes, too'), + (u'and lots of whitespace: \r\n\t\v\f\b', u'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008'), + (ur'', u'\\u003Cscript\\u003Eand this\\u003C/script\\u003E'), + (u'paragraph separator:\u2029and line separator:\u2028', u'paragraph separator:\\u2029and line separator:\\u2028'), + ) + for value, output in items: + self.check_output(f, value, output)