diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 4201cfe..e0bee53 100644
a
|
b
|
def force_escape(value):
|
414 | 414 | """ |
415 | 415 | return escape(value) |
416 | 416 | |
| 417 | @register.filter(is_safe=True) |
| 418 | @stringfilter |
| 419 | def nonraising_force_escape(value): |
| 420 | """ |
| 421 | Escapes a string's HTML. This returns a new string containing the escaped |
| 422 | characters, or in case of exception in force_escape() |
| 423 | (in particular MemoryError), a unicode string notifying |
| 424 | about the exception. Never raises any exception derived from Exception. |
| 425 | """ |
| 426 | try: |
| 427 | return force_escape(value) |
| 428 | except Exception, e: |
| 429 | return u"cannot render value due to exception in force_escape(): %s" % e.__class__.__name__ |
| 430 | |
417 | 431 | @register.filter("linebreaks", is_safe=True, needs_autoescape=True) |
418 | 432 | @stringfilter |
419 | 433 | def linebreaks_filter(value, autoescape=None): |
diff --git a/django/views/debug.py b/django/views/debug.py
index 10c07e8..a738f4f 100644
a
|
b
|
from django.core.exceptions import ImproperlyConfigured
|
11 | 11 | from django.http import (HttpResponse, HttpResponseServerError, |
12 | 12 | HttpResponseNotFound, HttpRequest, build_request_repr) |
13 | 13 | from django.template import Template, Context, TemplateDoesNotExist |
14 | | from django.template.defaultfilters import force_escape, pprint |
| 14 | from django.template.defaultfilters import nonraising_force_escape, pprint |
15 | 15 | from django.utils.html import escape |
16 | 16 | from django.utils.encoding import force_bytes, smart_text |
17 | 17 | from django.utils.module_loading import import_by_path |
… |
… |
class ExceptionReporter(object):
|
246 | 246 | frames = self.get_traceback_frames() |
247 | 247 | for i, frame in enumerate(frames): |
248 | 248 | if 'vars' in frame: |
249 | | frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']] |
| 249 | frame['vars'] = [(k, nonraising_force_escape(pprint(v))) |
| 250 | for k, v in frame['vars']] |
250 | 251 | frames[i] = frame |
251 | 252 | |
252 | 253 | unicode_hint = '' |
… |
… |
TECHNICAL_500_TEMPLATE = """
|
584 | 585 | <body> |
585 | 586 | <div id="summary"> |
586 | 587 | <h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1> |
587 | | <pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception supplied{% endif %}</pre> |
| 588 | <pre class="exception_value">{% if exception_value %}{{ exception_value|nonraising_force_escape }}{% else %}No exception supplied{% endif %}</pre> |
588 | 589 | <table class="meta"> |
589 | 590 | {% if request %} |
590 | 591 | <tr> |
… |
… |
TECHNICAL_500_TEMPLATE = """
|
609 | 610 | {% if exception_type and exception_value %} |
610 | 611 | <tr> |
611 | 612 | <th>Exception Value:</th> |
612 | | <td><pre>{{ exception_value|force_escape }}</pre></td> |
| 613 | <td><pre>{{ exception_value|nonraising_force_escape }}</pre></td> |
613 | 614 | </tr> |
614 | 615 | {% endif %} |
615 | 616 | {% if lastframe %} |
… |
… |
TECHNICAL_500_TEMPLATE = """
|
639 | 640 | {% if unicode_hint %} |
640 | 641 | <div id="unicode-hint"> |
641 | 642 | <h2>Unicode error hint</h2> |
642 | | <p>The string that could not be encoded/decoded was: <strong>{{ unicode_hint|force_escape }}</strong></p> |
| 643 | <p>The string that could not be encoded/decoded was: <strong>{{ unicode_hint|nonraising_force_escape }}</strong></p> |
643 | 644 | </div> |
644 | 645 | {% endif %} |
645 | 646 | {% if template_does_not_exist %} |
… |
… |
TECHNICAL_500_TEMPLATE = """
|
717 | 718 | <tbody> |
718 | 719 | {% for var in frame.vars|dictsort:"0" %} |
719 | 720 | <tr> |
720 | | <td>{{ var.0|force_escape }}</td> |
| 721 | <td>{{ var.0|nonraising_force_escape }}</td> |
721 | 722 | <td class="code"><pre>{{ var.1 }}</pre></td> |
722 | 723 | </tr> |
723 | 724 | {% endfor %} |
… |
… |
Traceback:
|
770 | 771 | {% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line|escape }}{% endif %} |
771 | 772 | {% endfor %} |
772 | 773 | Exception Type: {{ exception_type|escape }}{% if request %} at {{ request.path_info|escape }}{% endif %} |
773 | | Exception Value: {{ exception_value|force_escape }} |
| 774 | Exception Value: {{ exception_value|nonraising_force_escape }} |
774 | 775 | </textarea> |
775 | 776 | <br><br> |
776 | 777 | <input type="submit" value="Share this traceback on a public Web site"> |
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index a84b419..708d9e3 100644
a
|
b
|
from django.test.utils import (override_settings, setup_test_template_loader,
|
16 | 16 | restore_template_loaders) |
17 | 17 | from django.utils.encoding import force_text, force_bytes |
18 | 18 | from django.views.debug import ExceptionReporter |
| 19 | from django.template import defaultfilters |
19 | 20 | |
20 | 21 | from .. import BrokenException, except_args |
21 | 22 | from ..views import (sensitive_view, non_sensitive_view, paranoid_view, |
… |
… |
class ExceptionReporterTests(TestCase):
|
105 | 106 | self.assertIn('<h2>Request information</h2>', html) |
106 | 107 | self.assertNotIn('<p>Request data not supplied</p>', html) |
107 | 108 | |
| 109 | def test_request_and_exception_when_escaping_raises(self): |
| 110 | "Complete rendering of exception report if escaping raises exception" |
| 111 | # specifically if it raises MemoryError |
| 112 | |
| 113 | try: |
| 114 | request = self.rf.get('/test_view/') |
| 115 | raise ValueError("Can't find my keys") |
| 116 | except ValueError: |
| 117 | exc_type, exc_value, tb = sys.exc_info() |
| 118 | |
| 119 | def mock_escape_that_raises(*args, **kwargs): |
| 120 | raise MemoryError() |
| 121 | |
| 122 | orig_escape = defaultfilters.escape |
| 123 | try: |
| 124 | defaultfilters.escape = mock_escape_that_raises |
| 125 | reporter = ExceptionReporter(request, exc_type, exc_value, tb) |
| 126 | html = reporter.get_traceback_html() |
| 127 | self.assertIn('<h1>ValueError at /test_view/</h1>', html) |
| 128 | self.assertIn('<pre class="exception_value">cannot render value due to exception in force_escape(): MemoryError</pre>', html) |
| 129 | finally: |
| 130 | defaultfilters.escape = orig_escape |
| 131 | |
108 | 132 | def test_no_request(self): |
109 | 133 | "An exception report can be generated without request" |
110 | 134 | try: |