Skip to content

Commit c536710

Browse files
committed
Display errors containing HTML with RejectRaw (issue2550847)
In general outputting un-escaped HTML in a message to the user is an unsafe operation, which is why error message are escaped by default. In some cases though it is desirable for a detector to include HTML within an error message. For these cases where HTML is required the RejectRaw exception can be used within the detector.
1 parent e2c83cf commit c536710

File tree

5 files changed

+41
-21
lines changed

5 files changed

+41
-21
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Features:
5757
(similar to RFC 2822), e.g. +0200 for CEST or -0500 for EST. This also
5858
works in the XMLRPC interface. For examples see roundup.date.Date.
5959
(Ralf Schlatterbeck)
60+
- Add RejectRaw exception to allow unescaped HTML error messages to be
61+
displayed to the user (thanks Ezio Melotti for the initial patch)
62+
(John Kristensen)
6063

6164
Fixed:
6265

doc/customizing.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,13 @@ To use, simply add at the top of your auditor::
928928

929929
And then when your rejection criteria have been detected, simply::
930930

931-
raise Reject
931+
raise Reject('Description of error')
932+
933+
Error messages raised with ``Reject`` automatically have any HTML content
934+
escaped before being displayed to the user. To display an error message to the
935+
user without performing any HTML escaping the ``RejectRaw`` should be used. All
936+
security implications should be carefully considering before using
937+
``RejectRaw``.
932938

933939

934940
Generating email from Roundup

roundup/cgi/actions.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from roundup import hyperdb, token, date, password
44
from roundup.actions import Action as BaseAction
55
from roundup.i18n import _
6-
import roundup.exceptions
76
from roundup.cgi import exceptions, templating
87
from roundup.mailgw import uidFromAddress
8+
from roundup.exceptions import Reject, RejectRaw
99
from roundup.anypy import io_, urllib_
1010

1111
__all__ = ['Action', 'ShowAction', 'RetireAction', 'SearchAction',
@@ -106,7 +106,7 @@ def handle(self):
106106
"""Retire the context item."""
107107
# ensure modification comes via POST
108108
if self.client.env['REQUEST_METHOD'] != 'POST':
109-
raise roundup.exceptions.Reject(self._('Invalid request'))
109+
raise Reject(self._('Invalid request'))
110110

111111
# if we want to view the index template now, then unset the itemid
112112
# context info (a special-case for retire actions on the index page)
@@ -285,7 +285,7 @@ def handle(self):
285285
"""
286286
# ensure modification comes via POST
287287
if self.client.env['REQUEST_METHOD'] != 'POST':
288-
raise roundup.exceptions.Reject(self._('Invalid request'))
288+
raise Reject(self._('Invalid request'))
289289

290290
# figure the properties list for the class
291291
cl = self.db.classes[self.classname]
@@ -606,7 +606,7 @@ def handle(self):
606606
"""
607607
# ensure modification comes via POST
608608
if self.client.env['REQUEST_METHOD'] != 'POST':
609-
raise roundup.exceptions.Reject(self._('Invalid request'))
609+
raise Reject(self._('Invalid request'))
610610

611611
user_activity = self.lastUserActivity()
612612
if user_activity:
@@ -620,10 +620,10 @@ def handle(self):
620620
# handle the props
621621
try:
622622
message = self._editnodes(props, links)
623-
except (ValueError, KeyError, IndexError,
624-
roundup.exceptions.Reject), message:
623+
except (ValueError, KeyError, IndexError, Reject) as message:
624+
escape = not isinstance(message, RejectRaw)
625625
self.client.add_error_message(
626-
self._('Edit Error: %s') % str(message))
626+
self._('Edit Error: %s') % str(message), escape=escape)
627627
return
628628

629629
# commit now that all the tricky stuff is done
@@ -652,7 +652,7 @@ def handle(self):
652652
'''
653653
# ensure modification comes via POST
654654
if self.client.env['REQUEST_METHOD'] != 'POST':
655-
raise roundup.exceptions.Reject(self._('Invalid request'))
655+
raise Reject(self._('Invalid request'))
656656

657657
# parse the props from the form
658658
try:
@@ -666,10 +666,11 @@ def handle(self):
666666
try:
667667
# when it hits the None element, it'll set self.nodeid
668668
messages = self._editnodes(props, links)
669-
except (ValueError, KeyError, IndexError,
670-
roundup.exceptions.Reject), message:
669+
except (ValueError, KeyError, IndexError, Reject) as message:
670+
escape = not isinstance(message, RejectRaw)
671671
# these errors might just be indicative of user dumbness
672-
self.client.add_error_message(_('Error: %s') % str(message))
672+
self.client.add_error_message(_('Error: %s') % str(message),
673+
escape=escape)
673674
return
674675

675676
# commit now that all the tricky stuff is done
@@ -833,7 +834,7 @@ def handle(self):
833834
"""
834835
# ensure modification comes via POST
835836
if self.client.env['REQUEST_METHOD'] != 'POST':
836-
raise roundup.exceptions.Reject(self._('Invalid request'))
837+
raise Reject(self._('Invalid request'))
837838

838839
# parse the props from the form
839840
try:
@@ -849,10 +850,11 @@ def handle(self):
849850
try:
850851
# when it hits the None element, it'll set self.nodeid
851852
messages = self._editnodes(props, links)
852-
except (ValueError, KeyError, IndexError,
853-
roundup.exceptions.Reject), message:
853+
except (ValueError, KeyError, IndexError, Reject) as message:
854+
escape = not isinstance(message, RejectRaw)
854855
# these errors might just be indicative of user dumbness
855-
self.client.add_error_message(_('Error: %s') % str(message))
856+
self.client.add_error_message(_('Error: %s') % str(message),
857+
escape=escape)
856858
return
857859

858860
# fix up the initial roles
@@ -957,7 +959,7 @@ def handle(self):
957959
"""
958960
# ensure modification comes via POST
959961
if self.client.env['REQUEST_METHOD'] != 'POST':
960-
raise roundup.exceptions.Reject(self._('Invalid request'))
962+
raise Reject(self._('Invalid request'))
961963

962964
# we need the username at a minimum
963965
if '__login_name' not in self.form:

roundup/cgi/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from roundup import roundupdb, date, hyperdb, password
1717
from roundup.cgi import templating, cgitb, TranslationService
1818
from roundup.cgi.actions import *
19-
from roundup.exceptions import *
19+
from roundup.exceptions import LoginError, Reject, RejectRaw, Unauthorised
2020
from roundup.cgi.exceptions import *
2121
from roundup.cgi.form_parser import FormParser
2222
from roundup.mailer import Mailer, MessageSendError, encode_quopri
@@ -1274,9 +1274,9 @@ def handle_action(self):
12741274
return getattr(self, action_klass)()
12751275
else:
12761276
return action_klass(self).execute()
1277-
1278-
except (ValueError, Reject), err:
1279-
self.add_error_message(str(err))
1277+
except (ValueError, Reject) as err:
1278+
escape = not isinstance(err, RejectRaw)
1279+
self.add_error_message(str(err), escape=escape)
12801280

12811281
def get_action_class(self, action_name):
12821282
if (hasattr(self.instance, 'cgi_actions') and

roundup/exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ class Reject(Exception):
2121
"""
2222
pass
2323

24+
25+
class RejectRaw(Reject):
26+
"""
27+
Performs the same function as Reject, except HTML in the message is not
28+
escaped when displayed to the user.
29+
"""
30+
pass
31+
32+
2433
class UsageError(ValueError):
2534
pass
2635

0 commit comments

Comments
 (0)