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')