diff --git a/django/views/templates/technical_500.html b/django/views/templates/technical_500.html index 7801289b78..9d65346847 100644 --- a/django/views/templates/technical_500.html +++ b/django/views/templates/technical_500.html @@ -190,7 +190,7 @@

Error during template rendering

In template {{ template_info.name }}, error at line {{ template_info.line }}

-

{{ template_info.message }}

+

{{ template_info.message|force_escape }}

{% for source_line in template_info.source_lines %} @@ -316,7 +316,7 @@ Using engine {{ entry.backend.name }}: {% endif %}{% endif %}{% if template_info %} Template error: In template {{ template_info.name }}, error at line {{ template_info.line }} - {{ template_info.message }} + {{ template_info.message|force_escape }} {% for source_line in template_info.source_lines %}{% if source_line.0 == template_info.line %} {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}{% else %} {{ source_line.0 }} : {{ source_line.1 }}{% endif %}{% endfor %}{% endif %} Traceback (most recent call last):{% for frame in frames %} diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index 591013be36..8eda91ec35 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -7,7 +7,7 @@ import tempfile import threading from io import StringIO from pathlib import Path -from unittest import mock +from unittest import mock, skipIf from django.core import mail from django.core.files.uploadedfile import SimpleUploadedFile @@ -263,6 +263,27 @@ class DebugViewTests(SimpleTestCase): "traceback, instead found: %s" % raising_loc ) + @skipIf( + sys.platform == 'win32', + 'Raises OSError instead of TemplateDoesNotExist on Windows.', + ) + def test_safestring_in_exception(self): + with self.assertLogs('django.request', 'ERROR'): + response = self.client.get('/safestring_exception/') + self.assertNotContains( + response, + '', + status_code=500, + html=True, + ) + self.assertContains( + response, + '<script>alert(1);</script>', + count=3, + status_code=500, + html=True, + ) + def test_template_loader_postmortem(self): """Tests for not existing file""" template_name = "notfound.html" diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py index 7a20ff40fa..159f353ee6 100644 --- a/tests/view_tests/urls.py +++ b/tests/view_tests/urls.py @@ -59,6 +59,11 @@ urlpatterns += i18n_patterns( ) urlpatterns += [ + path( + 'safestring_exception/', + views.safestring_in_template_exception, + name='safestring_exception', + ), path('template_exception/', views.template_exception, name='template_exception'), path( 'raises_template_does_not_exist/', diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py index 97695ef493..8cf9ab4dde 100644 --- a/tests/view_tests/views.py +++ b/tests/view_tests/views.py @@ -9,7 +9,7 @@ from django.core.exceptions import ( ) from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import render -from django.template import TemplateDoesNotExist +from django.template import Context, Template, TemplateDoesNotExist from django.urls import get_resolver from django.views import View from django.views.debug import ( @@ -89,6 +89,18 @@ def template_exception(request): return render(request, 'debug/template_exception.html') +def safestring_in_template_exception(request): + """ + Trigger an exception in the template machinery which causes a SafeString + to be inserted as args[0] of the Exception. + """ + template = Template('{% extends "" %}') + try: + template.render(Context()) + except Exception: + return technical_500_response(request, *sys.exc_info()) + + def jsi18n(request): return render(request, 'jsi18n.html')