From 335f4ac03d95a1873c3be7739de8fb3ba6b15a9e Mon Sep 17 00:00:00 2001 From: Karen Tracey <kmtracey@gmail.com> Date: Thu, 6 Nov 2008 19:53:36 +0000 Subject: [PATCH] [1.0.X] Fixed #6160, #9111 -- Consistently apply conditional_escape to form errors and labels when outputing them as HTML. [9365] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@9366 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/views.py | 6 +++--- django/forms/forms.py | 8 ++++---- django/forms/util.py | 2 +- tests/regressiontests/forms/forms.py | 18 +++++++++++++----- tests/regressiontests/forms/util.py | 7 +++++++ 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index cf17b827e7..ec98cf07f7 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -16,7 +16,7 @@ class PasswordResetTest(TestCase): response = self.client.get('/password_reset/') self.assertEquals(response.status_code, 200) response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) - self.assertContains(response, "That e-mail address doesn't have an associated user account") + self.assertContains(response, "That e-mail address doesn't have an associated user account") self.assertEquals(len(mail.outbox), 0) def test_email_found(self): @@ -87,7 +87,7 @@ class PasswordResetTest(TestCase): response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2':' x'}) self.assertEquals(response.status_code, 200) - self.assert_("The two password fields didn't match" in response.content) + self.assert_("The two password fields didn't match" in response.content) class ChangePasswordTest(TestCase): @@ -147,7 +147,7 @@ class ChangePasswordTest(TestCase): } ) self.assertEquals(response.status_code, 200) - self.assert_("The two password fields didn't match." in response.content) + self.assert_("The two password fields didn't match." in response.content) def test_password_change_succeeds(self): self.login() diff --git a/django/forms/forms.py b/django/forms/forms.py index 3a6182611e..e28479fb8b 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -5,7 +5,7 @@ Form classes from copy import deepcopy from django.utils.datastructures import SortedDict -from django.utils.html import escape +from django.utils.html import conditional_escape from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode from django.utils.safestring import mark_safe @@ -140,7 +140,7 @@ class BaseForm(StrAndUnicode): output, hidden_fields = [], [] for name, field in self.fields.items(): bf = BoundField(self, field, name) - bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable. + bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) @@ -149,7 +149,7 @@ class BaseForm(StrAndUnicode): if errors_on_separate_row and bf_errors: output.append(error_row % force_unicode(bf_errors)) if bf.label: - label = escape(force_unicode(bf.label)) + label = conditional_escape(force_unicode(bf.label)) # Only add the suffix if the label does not end in # punctuation. if self.label_suffix: @@ -395,7 +395,7 @@ class BoundField(StrAndUnicode): If attrs are given, they're used as HTML attributes on the <label> tag. """ - contents = contents or escape(self.label) + contents = contents or conditional_escape(self.label) widget = self.field.widget id_ = widget.attrs.get('id') or self.auto_id if id_: diff --git a/django/forms/util.py b/django/forms/util.py index ea936277b0..b9b88a61e6 100644 --- a/django/forms/util.py +++ b/django/forms/util.py @@ -39,7 +39,7 @@ class ErrorList(list, StrAndUnicode): def as_ul(self): if not self: return u'' return mark_safe(u'<ul class="errorlist">%s</ul>' - % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self])) + % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self])) def as_text(self): if not self: return u'' diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index 76132b273f..6bee94e579 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -593,17 +593,25 @@ u'Yesterday' u'Yesterday' Validation errors are HTML-escaped when output as HTML. +>>> from django.utils.safestring import mark_safe >>> class EscapingForm(Form): -... special_name = CharField() +... special_name = CharField(label="<em>Special</em> Field") +... special_safe_name = CharField(label=mark_safe("<em>Special</em> Field")) ... def clean_special_name(self): ... raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name']) +... def clean_special_safe_name(self): +... raise ValidationError(mark_safe("'<b>%s</b>' is a safe string" % self.cleaned_data['special_safe_name'])) ->>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False) +>>> f = EscapingForm({'special_name': "Nothing to escape", 'special_safe_name': "Nothing to escape"}, auto_id=False) >>> print f -<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Nothing to escape'</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr> ->>> f = EscapingForm({'special_name': "Should escape < & > and <script>alert('xss')</script>"}, auto_id=False) +<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>Something's wrong with 'Nothing to escape'</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr> +<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>'<b>Nothing to escape</b>' is a safe string</li></ul><input type="text" name="special_safe_name" value="Nothing to escape" /></td></tr> +>>> f = EscapingForm( +... {'special_name': "Should escape < & > and <script>alert('xss')</script>", +... 'special_safe_name': "<i>Do not escape</i>"}, auto_id=False) >>> print f -<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'</li></ul><input type="text" name="special_name" value="Should escape < & > and <script>alert('xss')</script>" /></td></tr> +<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'</li></ul><input type="text" name="special_name" value="Should escape < & > and <script>alert('xss')</script>" /></td></tr> +<tr><th><em>Special</em> Field:</th><td><ul class="errorlist"><li>'<b><i>Do not escape</i></b>' is a safe string</li></ul><input type="text" name="special_safe_name" value="<i>Do not escape</i>" /></td></tr> """ + \ r""" # [This concatenation is to keep the string below the jython's 32K limit]. diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py index 68c082c114..845ddeaadb 100644 --- a/tests/regressiontests/forms/util.py +++ b/tests/regressiontests/forms/util.py @@ -49,4 +49,11 @@ u'' # Can take a non-string. >>> print ValidationError(VeryBadError()).messages <ul class="errorlist"><li>A very bad error.</li></ul> + +# Escapes non-safe input but not input marked safe. +>>> example = 'Example of link: <a href="http://www.example.com/">example</a>' +>>> print ValidationError(example).messages +<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul> +>>> print ValidationError(mark_safe(example)).messages +<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul> """