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)