2013-11-24 15:43:24 +00:00
|
|
|
|
import importlib
|
2010-03-08 17:01:21 +00:00
|
|
|
|
import inspect
|
2011-05-24 21:33:32 +00:00
|
|
|
|
import os
|
2012-10-20 12:50:44 +00:00
|
|
|
|
import re
|
2011-02-01 14:18:07 +00:00
|
|
|
|
import sys
|
2015-02-21 18:18:54 +00:00
|
|
|
|
import tempfile
|
2018-12-06 12:58:29 +00:00
|
|
|
|
import threading
|
2017-01-07 11:11:46 +00:00
|
|
|
|
from io import StringIO
|
2017-04-01 20:44:33 +00:00
|
|
|
|
from pathlib import Path
|
2019-04-05 12:38:35 +00:00
|
|
|
|
from unittest import mock
|
2010-03-08 17:01:21 +00:00
|
|
|
|
|
2012-09-15 19:07:31 +00:00
|
|
|
|
from django.core import mail
|
2009-03-31 17:10:06 +00:00
|
|
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
2015-04-19 13:30:01 +00:00
|
|
|
|
from django.db import DatabaseError, connection
|
2021-06-20 18:16:33 +00:00
|
|
|
|
from django.http import Http404, HttpRequest, HttpResponse
|
2017-04-01 20:44:33 +00:00
|
|
|
|
from django.shortcuts import render
|
2015-04-24 19:35:30 +00:00
|
|
|
|
from django.template import TemplateDoesNotExist
|
2015-04-17 21:38:20 +00:00
|
|
|
|
from django.test import RequestFactory, SimpleTestCase, override_settings
|
2018-04-28 13:20:27 +00:00
|
|
|
|
from django.test.utils import LoggingCaptureMixin
|
2018-12-07 22:52:28 +00:00
|
|
|
|
from django.urls import path, reverse
|
2019-08-09 20:40:25 +00:00
|
|
|
|
from django.urls.converters import IntConverter
|
2015-03-07 12:18:04 +00:00
|
|
|
|
from django.utils.functional import SimpleLazyObject
|
2020-01-09 09:00:07 +00:00
|
|
|
|
from django.utils.regex_helper import _lazy_re_compile
|
2017-08-02 19:15:49 +00:00
|
|
|
|
from django.utils.safestring import mark_safe
|
2015-04-19 13:30:01 +00:00
|
|
|
|
from django.views.debug import (
|
2020-06-09 19:55:49 +00:00
|
|
|
|
CallableSettingWrapper, ExceptionCycleWarning, ExceptionReporter,
|
|
|
|
|
|
Path as DebugPath, SafeExceptionReporterFilter, default_urlconf,
|
2020-01-09 09:00:07 +00:00
|
|
|
|
get_default_exception_reporter_filter, technical_404_response,
|
|
|
|
|
|
technical_500_response,
|
2015-04-19 13:30:01 +00:00
|
|
|
|
)
|
2019-12-09 11:12:17 +00:00
|
|
|
|
from django.views.decorators.debug import (
|
|
|
|
|
|
sensitive_post_parameters, sensitive_variables,
|
|
|
|
|
|
)
|
2010-03-08 15:20:29 +00:00
|
|
|
|
|
2015-01-28 12:35:27 +00:00
|
|
|
|
from ..views import (
|
2016-10-27 20:58:41 +00:00
|
|
|
|
custom_exception_reporter_filter_view, index_page,
|
|
|
|
|
|
multivalue_dict_key_error, non_sensitive_view, paranoid_view,
|
|
|
|
|
|
sensitive_args_function_caller, sensitive_kwargs_function_caller,
|
|
|
|
|
|
sensitive_method_view, sensitive_view,
|
2015-01-28 12:35:27 +00:00
|
|
|
|
)
|
2009-03-31 17:10:06 +00:00
|
|
|
|
|
2011-02-01 14:18:07 +00:00
|
|
|
|
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class User:
|
2015-10-02 14:44:35 +00:00
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return 'jacob'
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-27 20:58:41 +00:00
|
|
|
|
class WithoutEmptyPathUrls:
|
2018-12-07 22:52:28 +00:00
|
|
|
|
urlpatterns = [path('url/', index_page, name='url')]
|
2016-10-27 20:58:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class CallableSettingWrapperTests(SimpleTestCase):
|
2014-07-26 10:06:48 +00:00
|
|
|
|
""" Unittests for CallableSettingWrapper
|
|
|
|
|
|
"""
|
|
|
|
|
|
def test_repr(self):
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class WrappedCallable:
|
2014-07-26 10:06:48 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return "repr from the wrapped callable"
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
actual = repr(CallableSettingWrapper(WrappedCallable()))
|
|
|
|
|
|
self.assertEqual(actual, "repr from the wrapped callable")
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-02-08 14:52:39 +00:00
|
|
|
|
@override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls')
|
2018-04-29 09:02:51 +00:00
|
|
|
|
class DebugViewTests(SimpleTestCase):
|
2011-04-27 15:47:16 +00:00
|
|
|
|
|
2009-03-31 17:10:06 +00:00
|
|
|
|
def test_files(self):
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/raises/')
|
2011-03-03 15:04:39 +00:00
|
|
|
|
self.assertEqual(response.status_code, 500)
|
2009-03-31 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
|
data = {
|
2012-08-15 15:41:09 +00:00
|
|
|
|
'file_data.txt': SimpleUploadedFile('file_data.txt', b'haha'),
|
2009-03-31 17:10:06 +00:00
|
|
|
|
}
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.post('/raises/', data)
|
2012-04-14 13:35:25 +00:00
|
|
|
|
self.assertContains(response, 'file_data.txt', status_code=500)
|
|
|
|
|
|
self.assertNotContains(response, 'haha', status_code=500)
|
2010-03-05 18:53:07 +00:00
|
|
|
|
|
2014-06-24 04:13:34 +00:00
|
|
|
|
def test_400(self):
|
2016-10-27 07:53:39 +00:00
|
|
|
|
# When DEBUG=True, technical_500_template() is called.
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.security', 'WARNING'):
|
|
|
|
|
|
response = self.client.get('/raises400/')
|
2014-06-24 04:13:34 +00:00
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=400)
|
|
|
|
|
|
|
2020-09-07 11:33:47 +00:00
|
|
|
|
def test_400_bad_request(self):
|
|
|
|
|
|
# When DEBUG=True, technical_500_template() is called.
|
|
|
|
|
|
with self.assertLogs('django.request', 'WARNING') as cm:
|
|
|
|
|
|
response = self.client.get('/raises400_bad_request/')
|
|
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=400)
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
cm.records[0].getMessage(),
|
|
|
|
|
|
'Malformed request syntax: /raises400_bad_request/',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2014-12-17 21:10:57 +00:00
|
|
|
|
# Ensure no 403.html template exists to test the default case.
|
|
|
|
|
|
@override_settings(TEMPLATES=[{
|
|
|
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
|
|
|
|
}])
|
2011-08-12 14:15:31 +00:00
|
|
|
|
def test_403(self):
|
2014-12-17 21:10:57 +00:00
|
|
|
|
response = self.client.get('/raises403/')
|
|
|
|
|
|
self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
|
|
|
|
|
|
|
|
|
|
|
|
# Set up a test 403.html template.
|
|
|
|
|
|
@override_settings(TEMPLATES=[{
|
|
|
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
|
|
|
|
'OPTIONS': {
|
|
|
|
|
|
'loaders': [
|
|
|
|
|
|
('django.template.loaders.locmem.Loader', {
|
2015-04-21 19:54:00 +00:00
|
|
|
|
'403.html': 'This is a test template for a 403 error ({{ exception }}).',
|
2014-12-17 21:10:57 +00:00
|
|
|
|
}),
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
}])
|
2011-08-12 14:15:31 +00:00
|
|
|
|
def test_403_template(self):
|
2014-12-17 21:10:57 +00:00
|
|
|
|
response = self.client.get('/raises403/')
|
|
|
|
|
|
self.assertContains(response, 'test template', status_code=403)
|
2015-04-21 19:54:00 +00:00
|
|
|
|
self.assertContains(response, '(Insufficient Permissions).', status_code=403)
|
2011-08-12 14:15:31 +00:00
|
|
|
|
|
2010-03-05 18:53:07 +00:00
|
|
|
|
def test_404(self):
|
2014-01-14 15:43:27 +00:00
|
|
|
|
response = self.client.get('/raises404/')
|
2021-04-27 06:40:52 +00:00
|
|
|
|
self.assertNotContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<pre class="exception_value">',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
)
|
2020-07-07 23:23:54 +00:00
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<p>The current path, <code>not-in-urls</code>, didn’t match any '
|
|
|
|
|
|
'of these.</p>',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2014-02-15 13:41:01 +00:00
|
|
|
|
|
|
|
|
|
|
def test_404_not_in_urls(self):
|
|
|
|
|
|
response = self.client.get('/not-in-urls')
|
2014-06-16 07:39:18 +00:00
|
|
|
|
self.assertNotContains(response, "Raised by:", status_code=404)
|
2021-04-27 06:40:52 +00:00
|
|
|
|
self.assertNotContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<pre class="exception_value">',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
)
|
2016-10-20 17:29:04 +00:00
|
|
|
|
self.assertContains(response, "Django tried these URL patterns", status_code=404)
|
2020-07-07 23:23:54 +00:00
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<p>The current path, <code>not-in-urls</code>, didn’t match any '
|
|
|
|
|
|
'of these.</p>',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2016-10-20 17:29:04 +00:00
|
|
|
|
# Pattern and view name of a RegexURLPattern appear.
|
|
|
|
|
|
self.assertContains(response, r"^regex-post/(?P<pk>[0-9]+)/$", status_code=404)
|
|
|
|
|
|
self.assertContains(response, "[name='regex-post']", status_code=404)
|
|
|
|
|
|
# Pattern and view name of a RoutePattern appear.
|
|
|
|
|
|
self.assertContains(response, r"path-post/<int:pk>/", status_code=404)
|
|
|
|
|
|
self.assertContains(response, "[name='path-post']", status_code=404)
|
2014-02-15 13:41:01 +00:00
|
|
|
|
|
2016-10-27 20:58:41 +00:00
|
|
|
|
@override_settings(ROOT_URLCONF=WithoutEmptyPathUrls)
|
|
|
|
|
|
def test_404_empty_path_not_in_urls(self):
|
|
|
|
|
|
response = self.client.get('/')
|
2020-07-07 23:23:54 +00:00
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<p>The empty path didn’t match any of these.</p>',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2016-10-27 20:58:41 +00:00
|
|
|
|
|
2014-06-16 07:39:18 +00:00
|
|
|
|
def test_technical_404(self):
|
2018-12-07 22:52:28 +00:00
|
|
|
|
response = self.client.get('/technical404/')
|
2021-04-13 07:15:04 +00:00
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<pre class="exception_value">Testing technical 404.</pre>',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2014-06-16 07:39:18 +00:00
|
|
|
|
self.assertContains(response, "Raised by:", status_code=404)
|
|
|
|
|
|
self.assertContains(response, "view_tests.views.technical404", status_code=404)
|
2020-07-07 23:23:54 +00:00
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<p>The current path, <code>technical404/</code>, matched the '
|
|
|
|
|
|
'last one.</p>',
|
|
|
|
|
|
status_code=404,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2014-06-16 07:39:18 +00:00
|
|
|
|
|
|
|
|
|
|
def test_classbased_technical_404(self):
|
2018-12-07 22:52:28 +00:00
|
|
|
|
response = self.client.get('/classbased404/')
|
2014-06-16 07:39:18 +00:00
|
|
|
|
self.assertContains(response, "Raised by:", status_code=404)
|
|
|
|
|
|
self.assertContains(response, "view_tests.views.Http404View", status_code=404)
|
|
|
|
|
|
|
2012-10-20 12:50:44 +00:00
|
|
|
|
def test_non_l10ned_numeric_ids(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Numeric IDs and fancy traceback context blocks line numbers shouldn't be localized.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True, USE_L10N=True):
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/raises500/')
|
2012-10-20 12:50:44 +00:00
|
|
|
|
# We look for a HTML fragment of the form
|
|
|
|
|
|
# '<div class="context" id="c38123208">', not '<div class="context" id="c38,123,208"'
|
|
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=500)
|
|
|
|
|
|
match = re.search(b'<div class="context" id="(?P<id>[^"]+)">', response.content)
|
2014-10-28 10:02:56 +00:00
|
|
|
|
self.assertIsNotNone(match)
|
2020-05-10 20:03:39 +00:00
|
|
|
|
id_repr = match['id']
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.assertFalse(
|
|
|
|
|
|
re.search(b'[^c0-9]', id_repr),
|
2017-02-17 21:04:45 +00:00
|
|
|
|
"Numeric IDs in debug response HTML page shouldn't be localized (value: %s)." % id_repr.decode()
|
2016-04-08 02:04:45 +00:00
|
|
|
|
)
|
2012-10-20 12:50:44 +00:00
|
|
|
|
|
2010-03-08 17:01:21 +00:00
|
|
|
|
def test_template_exceptions(self):
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.client.get(reverse('template_exception'))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
raising_loc = inspect.trace()[-1][-2][0].strip()
|
|
|
|
|
|
self.assertNotEqual(
|
|
|
|
|
|
raising_loc.find("raise Exception('boom')"), -1,
|
|
|
|
|
|
"Failed to find 'raise Exception' in last frame of "
|
|
|
|
|
|
"traceback, instead found: %s" % raising_loc
|
|
|
|
|
|
)
|
2011-01-20 22:33:58 +00:00
|
|
|
|
|
|
|
|
|
|
def test_template_loader_postmortem(self):
|
2013-02-24 15:41:10 +00:00
|
|
|
|
"""Tests for not existing file"""
|
|
|
|
|
|
template_name = "notfound.html"
|
2015-02-21 18:18:54 +00:00
|
|
|
|
with tempfile.NamedTemporaryFile(prefix=template_name) as tmpfile:
|
|
|
|
|
|
tempdir = os.path.dirname(tmpfile.name)
|
2013-02-24 15:41:10 +00:00
|
|
|
|
template_path = os.path.join(tempdir, template_name)
|
2014-12-17 21:51:42 +00:00
|
|
|
|
with override_settings(TEMPLATES=[{
|
|
|
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
|
|
|
|
'DIRS': [tempdir],
|
2018-04-29 09:02:51 +00:00
|
|
|
|
}]), self.assertLogs('django.request', 'ERROR'):
|
2013-02-24 15:41:10 +00:00
|
|
|
|
response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
|
2015-03-05 15:52:22 +00:00
|
|
|
|
self.assertContains(response, "%s (Source does not exist)" % template_path, status_code=500, count=2)
|
2016-07-23 00:38:35 +00:00
|
|
|
|
# Assert as HTML.
|
|
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<li><code>django.template.loaders.filesystem.Loader</code>: '
|
|
|
|
|
|
'%s (Source does not exist)</li>' % os.path.join(tempdir, 'notfound.html'),
|
|
|
|
|
|
status_code=500,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
2013-02-24 15:41:10 +00:00
|
|
|
|
|
2013-09-06 20:50:18 +00:00
|
|
|
|
def test_no_template_source_loaders(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Make sure if you don't specify a template, the debug view doesn't blow up.
|
|
|
|
|
|
"""
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
|
|
self.client.get('/render_no_template/')
|
2013-09-06 20:50:18 +00:00
|
|
|
|
|
2013-12-07 02:21:58 +00:00
|
|
|
|
@override_settings(ROOT_URLCONF='view_tests.default_urls')
|
|
|
|
|
|
def test_default_urlconf_template(self):
|
|
|
|
|
|
"""
|
2020-10-01 11:07:57 +00:00
|
|
|
|
Make sure that the default URLconf template is shown instead of the
|
|
|
|
|
|
technical 404 page, if the user has not altered their URLconf yet.
|
2013-12-07 02:21:58 +00:00
|
|
|
|
"""
|
|
|
|
|
|
response = self.client.get('/')
|
|
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
2020-09-28 23:43:19 +00:00
|
|
|
|
"<h1>The install worked successfully! Congratulations!</h1>"
|
2013-12-07 02:21:58 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(ROOT_URLCONF='view_tests.regression_21530_urls')
|
|
|
|
|
|
def test_regression_21530(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Regression test for bug #21530.
|
|
|
|
|
|
|
|
|
|
|
|
If the admin app include is replaced with exactly one url
|
|
|
|
|
|
pattern, then the technical 404 template should be displayed.
|
|
|
|
|
|
|
|
|
|
|
|
The bug here was that an AttributeError caused a 500 response.
|
|
|
|
|
|
"""
|
|
|
|
|
|
response = self.client.get('/')
|
|
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
"Page not found <span>(404)</span>",
|
|
|
|
|
|
status_code=404
|
|
|
|
|
|
)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
|
2019-04-05 12:45:41 +00:00
|
|
|
|
def test_template_encoding(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The templates are loaded directly, not via a template loader, and
|
|
|
|
|
|
should be opened as utf-8 charset as is the default specified on
|
|
|
|
|
|
template engines.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with mock.patch.object(DebugPath, 'open') as m:
|
|
|
|
|
|
default_urlconf(None)
|
|
|
|
|
|
m.assert_called_once_with(encoding='utf-8')
|
|
|
|
|
|
m.reset_mock()
|
|
|
|
|
|
technical_404_response(mock.MagicMock(), mock.Mock())
|
|
|
|
|
|
m.assert_called_once_with(encoding='utf-8')
|
|
|
|
|
|
|
2019-08-09 20:40:25 +00:00
|
|
|
|
def test_technical_404_converter_raise_404(self):
|
|
|
|
|
|
with mock.patch.object(IntConverter, 'to_python', side_effect=Http404):
|
|
|
|
|
|
response = self.client.get('/path-post/1/')
|
|
|
|
|
|
self.assertContains(response, 'Page not found', status_code=404)
|
|
|
|
|
|
|
2019-09-07 17:08:12 +00:00
|
|
|
|
def test_exception_reporter_from_request(self):
|
2020-02-20 04:32:23 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/custom_reporter_class_view/')
|
2019-09-07 17:08:12 +00:00
|
|
|
|
self.assertContains(response, 'custom traceback text', status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(DEFAULT_EXCEPTION_REPORTER='view_tests.views.CustomExceptionReporter')
|
|
|
|
|
|
def test_exception_reporter_from_settings(self):
|
2020-02-20 04:32:23 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/raises500/')
|
2019-09-07 17:08:12 +00:00
|
|
|
|
self.assertContains(response, 'custom traceback text', status_code=500)
|
|
|
|
|
|
|
2020-10-15 09:44:02 +00:00
|
|
|
|
@override_settings(DEFAULT_EXCEPTION_REPORTER='view_tests.views.TemplateOverrideExceptionReporter')
|
|
|
|
|
|
def test_template_override_exception_reporter(self):
|
|
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/raises500/')
|
|
|
|
|
|
self.assertContains(
|
|
|
|
|
|
response,
|
|
|
|
|
|
'<h1>Oh no, an error occurred!</h1>',
|
|
|
|
|
|
status_code=500,
|
|
|
|
|
|
html=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get('/raises500/', HTTP_ACCEPT='text/plain')
|
|
|
|
|
|
self.assertContains(response, 'Oh dear, an error occurred!', status_code=500)
|
|
|
|
|
|
|
2013-12-07 12:06:28 +00:00
|
|
|
|
|
2015-04-19 13:30:01 +00:00
|
|
|
|
class DebugViewQueriesAllowedTests(SimpleTestCase):
|
|
|
|
|
|
# May need a query to initialize MySQL connection
|
2018-07-12 04:12:20 +00:00
|
|
|
|
databases = {'default'}
|
2015-04-19 13:30:01 +00:00
|
|
|
|
|
|
|
|
|
|
def test_handle_db_exception(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Ensure the debug view works when a database exception is raised by
|
|
|
|
|
|
performing an invalid query and passing the exception to the debug view.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with connection.cursor() as cursor:
|
|
|
|
|
|
try:
|
|
|
|
|
|
cursor.execute('INVALID SQL')
|
|
|
|
|
|
except DatabaseError:
|
|
|
|
|
|
exc_info = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
rf = RequestFactory()
|
|
|
|
|
|
response = technical_500_response(rf.get('/'), *exc_info)
|
|
|
|
|
|
self.assertContains(response, 'OperationalError at /', status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-01-11 09:06:42 +00:00
|
|
|
|
@override_settings(
|
|
|
|
|
|
DEBUG=True,
|
2016-02-08 14:52:39 +00:00
|
|
|
|
ROOT_URLCONF='view_tests.urls',
|
2015-01-11 09:06:42 +00:00
|
|
|
|
# No template directories are configured, so no templates will be found.
|
|
|
|
|
|
TEMPLATES=[{
|
|
|
|
|
|
'BACKEND': 'django.template.backends.dummy.TemplateStrings',
|
|
|
|
|
|
}],
|
|
|
|
|
|
)
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class NonDjangoTemplatesDebugViewTests(SimpleTestCase):
|
2015-01-11 09:06:42 +00:00
|
|
|
|
|
|
|
|
|
|
def test_400(self):
|
2016-10-28 03:53:15 +00:00
|
|
|
|
# When DEBUG=True, technical_500_template() is called.
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.security', 'WARNING'):
|
|
|
|
|
|
response = self.client.get('/raises400/')
|
2018-04-28 13:20:27 +00:00
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=400)
|
2015-01-11 09:06:42 +00:00
|
|
|
|
|
2020-09-07 11:33:47 +00:00
|
|
|
|
def test_400_bad_request(self):
|
|
|
|
|
|
# When DEBUG=True, technical_500_template() is called.
|
|
|
|
|
|
with self.assertLogs('django.request', 'WARNING') as cm:
|
|
|
|
|
|
response = self.client.get('/raises400_bad_request/')
|
|
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=400)
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
cm.records[0].getMessage(),
|
|
|
|
|
|
'Malformed request syntax: /raises400_bad_request/',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2015-01-11 09:06:42 +00:00
|
|
|
|
def test_403(self):
|
|
|
|
|
|
response = self.client.get('/raises403/')
|
|
|
|
|
|
self.assertContains(response, '<h1>403 Forbidden</h1>', status_code=403)
|
|
|
|
|
|
|
|
|
|
|
|
def test_404(self):
|
|
|
|
|
|
response = self.client.get('/raises404/')
|
|
|
|
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
|
|
|
|
|
|
|
|
def test_template_not_found_error(self):
|
|
|
|
|
|
# Raises a TemplateDoesNotExist exception and shows the debug view.
|
|
|
|
|
|
url = reverse('raises_template_does_not_exist', kwargs={"path": "notfound.html"})
|
2018-04-29 09:02:51 +00:00
|
|
|
|
with self.assertLogs('django.request', 'ERROR'):
|
|
|
|
|
|
response = self.client.get(url)
|
2015-01-11 09:06:42 +00:00
|
|
|
|
self.assertContains(response, '<div class="context" id="', status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class ExceptionReporterTests(SimpleTestCase):
|
2011-02-01 14:18:07 +00:00
|
|
|
|
rf = RequestFactory()
|
|
|
|
|
|
|
|
|
|
|
|
def test_request_and_exception(self):
|
|
|
|
|
|
"A simple exception report can be generated"
|
|
|
|
|
|
try:
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
2015-10-02 14:44:35 +00:00
|
|
|
|
request.user = User()
|
2011-02-01 15:13:56 +00:00
|
|
|
|
raise ValueError("Can't find my keys")
|
|
|
|
|
|
except ValueError:
|
2011-02-01 14:18:07 +00:00
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>ValueError at /test_view/</h1>', html)
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">Can't find my keys</pre>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<th>Request Method:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Request URL:</th>', html)
|
2015-10-02 14:44:35 +00:00
|
|
|
|
self.assertIn('<h3 id="user-info">USER</h3>', html)
|
|
|
|
|
|
self.assertIn('<p>jacob</p>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
2017-04-15 13:21:35 +00:00
|
|
|
|
self.assertIn('<p>No POST data</p>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
|
|
|
|
|
|
def test_no_request(self):
|
|
|
|
|
|
"An exception report can be generated without request"
|
|
|
|
|
|
try:
|
2011-02-01 15:13:56 +00:00
|
|
|
|
raise ValueError("Can't find my keys")
|
|
|
|
|
|
except ValueError:
|
2011-02-01 14:18:07 +00:00
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>ValueError</h1>', html)
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">Can't find my keys</pre>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertNotIn('<th>Request Method:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Request URL:</th>', html)
|
2015-10-02 14:44:35 +00:00
|
|
|
|
self.assertNotIn('<h3 id="user-info">USER</h3>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertIn('<p>Request data not supplied</p>', html)
|
|
|
|
|
|
|
2020-10-09 12:15:09 +00:00
|
|
|
|
def test_sharing_traceback(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise ValueError('Oops')
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'<form action="https://dpaste.com/" name="pasteform" '
|
|
|
|
|
|
'id="pasteform" method="post">',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2013-05-18 16:26:34 +00:00
|
|
|
|
def test_eol_support(self):
|
2016-10-27 07:53:39 +00:00
|
|
|
|
"""The ExceptionReporter supports Unix, Windows and Macintosh EOL markers"""
|
2017-06-01 23:08:59 +00:00
|
|
|
|
LINES = ['print %d' % i for i in range(1, 6)]
|
2013-05-18 16:26:34 +00:00
|
|
|
|
reporter = ExceptionReporter(None, None, None, None)
|
|
|
|
|
|
|
2013-02-24 15:41:10 +00:00
|
|
|
|
for newline in ['\n', '\r\n', '\r']:
|
2015-02-21 18:18:54 +00:00
|
|
|
|
fd, filename = tempfile.mkstemp(text=False)
|
2018-02-07 19:20:04 +00:00
|
|
|
|
os.write(fd, (newline.join(LINES) + newline).encode())
|
2013-05-18 16:26:34 +00:00
|
|
|
|
os.close(fd)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter._get_lines_from_file(filename, 3, 2),
|
|
|
|
|
|
(1, LINES[1:3], LINES[3], LINES[4:])
|
|
|
|
|
|
)
|
|
|
|
|
|
finally:
|
|
|
|
|
|
os.unlink(filename)
|
|
|
|
|
|
|
2011-02-01 14:18:07 +00:00
|
|
|
|
def test_no_exception(self):
|
|
|
|
|
|
"An exception report can be generated for just a request"
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>Report at /test_view/</h1>', html)
|
2013-04-23 18:17:04 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">No exception message supplied</pre>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<th>Request Method:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Request URL:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
|
|
|
|
|
|
2020-07-13 06:01:59 +00:00
|
|
|
|
def test_suppressed_context(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise RuntimeError("Can't find my keys")
|
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
|
raise ValueError("Can't find my keys") from None
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML('<h1>ValueError</h1>', html)
|
|
|
|
|
|
self.assertIn('<pre class="exception_value">Can't find my keys</pre>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertIn('<p>Request data not supplied</p>', html)
|
|
|
|
|
|
self.assertNotIn('During handling of the above exception', html)
|
|
|
|
|
|
|
2020-10-12 07:06:35 +00:00
|
|
|
|
def test_innermost_exception_without_traceback(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise RuntimeError('Oops')
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
new_exc = RuntimeError('My context')
|
|
|
|
|
|
exc.__context__ = new_exc
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
frames = reporter.get_traceback_frames()
|
2020-11-03 21:16:05 +00:00
|
|
|
|
self.assertEqual(len(frames), 2)
|
2020-10-12 07:06:35 +00:00
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML('<h1>RuntimeError</h1>', html)
|
|
|
|
|
|
self.assertIn('<pre class="exception_value">Oops</pre>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertIn('<p>Request data not supplied</p>', html)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'During handling of the above exception (My context), another '
|
|
|
|
|
|
'exception occurred',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
2020-11-03 21:16:05 +00:00
|
|
|
|
self.assertInHTML('<li class="frame user">None</li>', html)
|
|
|
|
|
|
self.assertIn('Traceback (most recent call last):\n None', html)
|
|
|
|
|
|
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('Exception Type: RuntimeError', text)
|
|
|
|
|
|
self.assertIn('Exception Value: Oops', text)
|
|
|
|
|
|
self.assertIn('Traceback (most recent call last):\n None', text)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'During handling of the above exception (My context), another '
|
|
|
|
|
|
'exception occurred',
|
|
|
|
|
|
text,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_mid_stack_exception_without_traceback(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise RuntimeError('Inner Oops')
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
new_exc = RuntimeError('My context')
|
|
|
|
|
|
new_exc.__context__ = exc
|
|
|
|
|
|
raise RuntimeError('Oops') from new_exc
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML('<h1>RuntimeError</h1>', html)
|
|
|
|
|
|
self.assertIn('<pre class="exception_value">Oops</pre>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Exception Value:</th>', html)
|
|
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
|
|
|
|
|
self.assertInHTML('<li class="frame user">Traceback: None</li>', html)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'During handling of the above exception (Inner Oops), another '
|
|
|
|
|
|
'exception occurred:\n Traceback: None',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('Exception Type: RuntimeError', text)
|
|
|
|
|
|
self.assertIn('Exception Value: Oops', text)
|
|
|
|
|
|
self.assertIn('Traceback (most recent call last):', text)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'During handling of the above exception (Inner Oops), another '
|
|
|
|
|
|
'exception occurred:\n Traceback: None',
|
|
|
|
|
|
text,
|
|
|
|
|
|
)
|
2020-10-12 07:06:35 +00:00
|
|
|
|
|
2016-12-01 10:38:01 +00:00
|
|
|
|
def test_reporting_of_nested_exceptions(self):
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
try:
|
|
|
|
|
|
try:
|
2017-08-10 01:12:37 +00:00
|
|
|
|
raise AttributeError(mark_safe('<p>Top level</p>'))
|
2016-12-01 10:38:01 +00:00
|
|
|
|
except AttributeError as explicit:
|
|
|
|
|
|
try:
|
2017-08-10 01:12:37 +00:00
|
|
|
|
raise ValueError(mark_safe('<p>Second exception</p>')) from explicit
|
2016-12-01 10:38:01 +00:00
|
|
|
|
except ValueError:
|
2017-08-02 19:56:47 +00:00
|
|
|
|
raise IndexError(mark_safe('<p>Final exception</p>'))
|
2016-12-01 10:38:01 +00:00
|
|
|
|
except Exception:
|
|
|
|
|
|
# Custom exception handler, just pass it into ExceptionReporter
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
explicit_exc = 'The above exception ({0}) was the direct cause of the following exception:'
|
|
|
|
|
|
implicit_exc = 'During handling of the above exception ({0}), another exception occurred:'
|
|
|
|
|
|
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
# Both messages are twice on page -- one rendered as html,
|
|
|
|
|
|
# one as plain text (for pastebin)
|
2017-08-10 01:12:37 +00:00
|
|
|
|
self.assertEqual(2, html.count(explicit_exc.format('<p>Top level</p>')))
|
|
|
|
|
|
self.assertEqual(2, html.count(implicit_exc.format('<p>Second exception</p>')))
|
2017-08-02 19:56:47 +00:00
|
|
|
|
self.assertEqual(10, html.count('<p>Final exception</p>'))
|
2016-12-01 10:38:01 +00:00
|
|
|
|
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
2017-08-10 01:12:37 +00:00
|
|
|
|
self.assertIn(explicit_exc.format('<p>Top level</p>'), text)
|
|
|
|
|
|
self.assertIn(implicit_exc.format('<p>Second exception</p>'), text)
|
2017-08-02 19:56:47 +00:00
|
|
|
|
self.assertEqual(3, text.count('<p>Final exception</p>'))
|
2016-12-01 10:38:01 +00:00
|
|
|
|
|
2017-08-10 01:01:33 +00:00
|
|
|
|
def test_reporting_frames_without_source(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
source = "def funcName():\n raise Error('Whoops')\nfuncName()"
|
|
|
|
|
|
namespace = {}
|
|
|
|
|
|
code = compile(source, 'generated', 'exec')
|
|
|
|
|
|
exec(code, namespace)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
frames = reporter.get_traceback_frames()
|
|
|
|
|
|
last_frame = frames[-1]
|
|
|
|
|
|
self.assertEqual(last_frame['context_line'], '<source code not available>')
|
|
|
|
|
|
self.assertEqual(last_frame['filename'], 'generated')
|
|
|
|
|
|
self.assertEqual(last_frame['function'], 'funcName')
|
|
|
|
|
|
self.assertEqual(last_frame['lineno'], 2)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2019-09-17 10:07:47 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'<span class="fname">generated</span>, line 2, in funcName',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'<code class="fname">generated</code>, line 2, in funcName',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
2019-07-26 01:44:27 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'"generated", line 2, in funcName\n'
|
|
|
|
|
|
' <source code not available>',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
2017-08-10 01:01:33 +00:00
|
|
|
|
text = reporter.get_traceback_text()
|
2019-07-26 01:44:27 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'"generated", line 2, in funcName\n'
|
|
|
|
|
|
' <source code not available>',
|
|
|
|
|
|
text,
|
|
|
|
|
|
)
|
2017-08-10 01:01:33 +00:00
|
|
|
|
|
2019-10-08 22:00:57 +00:00
|
|
|
|
def test_reporting_frames_source_not_match(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
source = "def funcName():\n raise Error('Whoops')\nfuncName()"
|
|
|
|
|
|
namespace = {}
|
|
|
|
|
|
code = compile(source, 'generated', 'exec')
|
|
|
|
|
|
exec(code, namespace)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
with mock.patch(
|
|
|
|
|
|
'django.views.debug.ExceptionReporter._get_source',
|
|
|
|
|
|
return_value=['wrong source'],
|
|
|
|
|
|
):
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
frames = reporter.get_traceback_frames()
|
|
|
|
|
|
last_frame = frames[-1]
|
|
|
|
|
|
self.assertEqual(last_frame['context_line'], '<source code not available>')
|
|
|
|
|
|
self.assertEqual(last_frame['filename'], 'generated')
|
|
|
|
|
|
self.assertEqual(last_frame['function'], 'funcName')
|
|
|
|
|
|
self.assertEqual(last_frame['lineno'], 2)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2019-09-17 10:07:47 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'<span class="fname">generated</span>, line 2, in funcName',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'<code class="fname">generated</code>, line 2, in funcName',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
2019-10-08 22:00:57 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'"generated", line 2, in funcName\n'
|
|
|
|
|
|
' <source code not available>',
|
|
|
|
|
|
html,
|
|
|
|
|
|
)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'"generated", line 2, in funcName\n'
|
|
|
|
|
|
' <source code not available>',
|
|
|
|
|
|
text,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2018-12-06 12:58:29 +00:00
|
|
|
|
def test_reporting_frames_for_cyclic_reference(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
def test_func():
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise RuntimeError('outer') from RuntimeError('inner')
|
|
|
|
|
|
except RuntimeError as exc:
|
|
|
|
|
|
raise exc.__cause__
|
|
|
|
|
|
test_func()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
|
|
|
|
|
|
def generate_traceback_frames(*args, **kwargs):
|
|
|
|
|
|
nonlocal tb_frames
|
|
|
|
|
|
tb_frames = reporter.get_traceback_frames()
|
|
|
|
|
|
|
|
|
|
|
|
tb_frames = None
|
|
|
|
|
|
tb_generator = threading.Thread(target=generate_traceback_frames, daemon=True)
|
2020-06-09 19:55:49 +00:00
|
|
|
|
msg = (
|
|
|
|
|
|
"Cycle in the exception chain detected: exception 'inner' "
|
|
|
|
|
|
"encountered again."
|
|
|
|
|
|
)
|
|
|
|
|
|
with self.assertWarnsMessage(ExceptionCycleWarning, msg):
|
|
|
|
|
|
tb_generator.start()
|
2018-12-06 12:58:29 +00:00
|
|
|
|
tb_generator.join(timeout=5)
|
|
|
|
|
|
if tb_generator.is_alive():
|
|
|
|
|
|
# tb_generator is a daemon that runs until the main thread/process
|
|
|
|
|
|
# exits. This is resource heavy when running the full test suite.
|
|
|
|
|
|
# Setting the following values to None makes
|
|
|
|
|
|
# reporter.get_traceback_frames() exit early.
|
|
|
|
|
|
exc_value.__traceback__ = exc_value.__context__ = exc_value.__cause__ = None
|
|
|
|
|
|
tb_generator.join()
|
|
|
|
|
|
self.fail('Cyclic reference in Exception Reporter.get_traceback_frames()')
|
|
|
|
|
|
if tb_frames is None:
|
|
|
|
|
|
# can happen if the thread generating traceback got killed
|
|
|
|
|
|
# or exception while generating the traceback
|
|
|
|
|
|
self.fail('Traceback generation failed')
|
|
|
|
|
|
last_frame = tb_frames[-1]
|
|
|
|
|
|
self.assertIn('raise exc.__cause__', last_frame['context_line'])
|
|
|
|
|
|
self.assertEqual(last_frame['filename'], __file__)
|
|
|
|
|
|
self.assertEqual(last_frame['function'], 'test_func')
|
|
|
|
|
|
|
2011-02-01 14:18:07 +00:00
|
|
|
|
def test_request_and_message(self):
|
|
|
|
|
|
"A message can be provided in addition to a request"
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>Report at /test_view/</h1>', html)
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">I'm a little teapot</pre>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<th>Request Method:</th>', html)
|
|
|
|
|
|
self.assertIn('<th>Request URL:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
2020-11-03 21:16:05 +00:00
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertNotIn('<p>Request data not supplied</p>', html)
|
|
|
|
|
|
|
|
|
|
|
|
def test_message_only(self):
|
|
|
|
|
|
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>Report</h1>', html)
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">I'm a little teapot</pre>', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertNotIn('<th>Request Method:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Request URL:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Type:</th>', html)
|
|
|
|
|
|
self.assertNotIn('<th>Exception Value:</th>', html)
|
2020-11-03 21:16:05 +00:00
|
|
|
|
self.assertIn('<h2>Traceback ', html)
|
2011-02-01 14:18:07 +00:00
|
|
|
|
self.assertIn('<h2>Request information</h2>', html)
|
|
|
|
|
|
self.assertIn('<p>Request data not supplied</p>', html)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
2014-08-13 13:32:14 +00:00
|
|
|
|
def test_non_utf8_values_handling(self):
|
|
|
|
|
|
"Non-UTF-8 exceptions/values should not make the output generation choke."
|
|
|
|
|
|
try:
|
|
|
|
|
|
class NonUtf8Output(Exception):
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return b'EXC\xe9EXC'
|
|
|
|
|
|
somevar = b'VAL\xe9VAL' # NOQA
|
|
|
|
|
|
raise NonUtf8Output()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertIn('VAL\\xe9VAL', html)
|
|
|
|
|
|
self.assertIn('EXC\\xe9EXC', html)
|
|
|
|
|
|
|
2017-08-02 19:56:47 +00:00
|
|
|
|
def test_local_variable_escaping(self):
|
|
|
|
|
|
"""Safe strings in local variables are escaped."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
local = mark_safe('<p>Local variable</p>')
|
|
|
|
|
|
raise ValueError(local)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
html = ExceptionReporter(None, exc_type, exc_value, tb).get_traceback_html()
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertIn('<td class="code"><pre>'<p>Local variable</p>'</pre></td>', html)
|
2017-08-02 19:56:47 +00:00
|
|
|
|
|
2014-08-13 13:32:14 +00:00
|
|
|
|
def test_unprintable_values_handling(self):
|
|
|
|
|
|
"Unprintable values should not make the output generation choke."
|
|
|
|
|
|
try:
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class OomOutput:
|
2014-08-13 13:32:14 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
raise MemoryError('OOM')
|
|
|
|
|
|
oomvalue = OomOutput() # NOQA
|
|
|
|
|
|
raise ValueError()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertIn('<td class="code"><pre>Error in formatting', html)
|
|
|
|
|
|
|
|
|
|
|
|
def test_too_large_values_handling(self):
|
|
|
|
|
|
"Large values should not create a large HTML."
|
2014-08-26 20:41:42 +00:00
|
|
|
|
large = 256 * 1024
|
2014-08-13 13:32:14 +00:00
|
|
|
|
repr_of_str_adds = len(repr(''))
|
|
|
|
|
|
try:
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class LargeOutput:
|
2014-08-13 13:32:14 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return repr('A' * large)
|
|
|
|
|
|
largevalue = LargeOutput() # NOQA
|
|
|
|
|
|
raise ValueError()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2014-08-26 20:41:42 +00:00
|
|
|
|
self.assertEqual(len(html) // 1024 // 128, 0) # still fit in 128Kb
|
2014-08-13 13:32:14 +00:00
|
|
|
|
self.assertIn('<trimmed %d bytes string>' % (large + repr_of_str_adds,), html)
|
|
|
|
|
|
|
2017-07-31 22:30:18 +00:00
|
|
|
|
def test_encoding_error(self):
|
2017-08-02 19:15:49 +00:00
|
|
|
|
"""
|
|
|
|
|
|
A UnicodeError displays a portion of the problematic string. HTML in
|
|
|
|
|
|
safe strings is escaped.
|
|
|
|
|
|
"""
|
2017-07-31 22:30:18 +00:00
|
|
|
|
try:
|
2017-08-02 19:15:49 +00:00
|
|
|
|
mark_safe('abcdefghijkl<p>mnὀp</p>qrstuwxyz').encode('ascii')
|
2017-07-31 22:30:18 +00:00
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertIn('<h2>Unicode error hint</h2>', html)
|
|
|
|
|
|
self.assertIn('The string that could not be encoded/decoded was: ', html)
|
2017-08-02 19:15:49 +00:00
|
|
|
|
self.assertIn('<strong><p>mnὀp</p></strong>', html)
|
2017-07-31 22:30:18 +00:00
|
|
|
|
|
2013-11-20 17:50:36 +00:00
|
|
|
|
def test_unfrozen_importlib(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
importlib is not a frozen app, but its loader thinks it's frozen which
|
2016-12-01 10:38:01 +00:00
|
|
|
|
results in an ImportError. Refs #21443.
|
2013-11-20 17:50:36 +00:00
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
importlib.import_module('abc.def.invalid.name')
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2019-01-18 15:04:29 +00:00
|
|
|
|
self.assertInHTML('<h1>ModuleNotFoundError at /test_view/</h1>', html)
|
2013-11-20 17:50:36 +00:00
|
|
|
|
|
2015-03-07 12:18:04 +00:00
|
|
|
|
def test_ignore_traceback_evaluation_exceptions(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Don't trip over exceptions generated by crafted objects when
|
|
|
|
|
|
evaluating them while cleansing (#24455).
|
|
|
|
|
|
"""
|
|
|
|
|
|
class BrokenEvaluation(Exception):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def broken_setup():
|
|
|
|
|
|
raise BrokenEvaluation
|
|
|
|
|
|
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
broken_lazy = SimpleLazyObject(broken_setup)
|
|
|
|
|
|
try:
|
|
|
|
|
|
bool(broken_lazy)
|
|
|
|
|
|
except BrokenEvaluation:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"BrokenEvaluation",
|
2016-06-28 15:21:26 +00:00
|
|
|
|
ExceptionReporter(request, exc_type, exc_value, tb).get_traceback_html(),
|
2015-03-07 12:18:04 +00:00
|
|
|
|
"Evaluation exception reason not mentioned in traceback"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2015-09-03 18:52:49 +00:00
|
|
|
|
@override_settings(ALLOWED_HOSTS='example.com')
|
|
|
|
|
|
def test_disallowed_host(self):
|
|
|
|
|
|
"An exception report can be generated even for a disallowed host."
|
|
|
|
|
|
request = self.rf.get('/', HTTP_HOST='evil.com')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertIn("http://evil.com/", html)
|
|
|
|
|
|
|
2016-09-07 11:09:45 +00:00
|
|
|
|
def test_request_with_items_key(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
An exception report can be generated for requests with 'items' in
|
|
|
|
|
|
request GET, POST, FILES, or COOKIES QueryDicts.
|
|
|
|
|
|
"""
|
2019-04-24 11:30:34 +00:00
|
|
|
|
value = '<td>items</td><td class="code"><pre>'Oops'</pre></td>'
|
2016-09-07 11:09:45 +00:00
|
|
|
|
# GET
|
|
|
|
|
|
request = self.rf.get('/test_view/?items=Oops')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML(value, html)
|
|
|
|
|
|
# POST
|
|
|
|
|
|
request = self.rf.post('/test_view/', data={'items': 'Oops'})
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML(value, html)
|
|
|
|
|
|
# FILES
|
2017-01-07 11:11:46 +00:00
|
|
|
|
fp = StringIO('filecontent')
|
2016-09-07 11:09:45 +00:00
|
|
|
|
request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp})
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
|
|
|
|
|
self.assertInHTML(
|
|
|
|
|
|
'<td>items</td><td class="code"><pre><InMemoryUploadedFile: '
|
|
|
|
|
|
'items (application/octet-stream)></pre></td>',
|
|
|
|
|
|
html
|
|
|
|
|
|
)
|
2019-06-25 09:12:45 +00:00
|
|
|
|
# COOKIES
|
2016-09-07 11:09:45 +00:00
|
|
|
|
rf = RequestFactory()
|
|
|
|
|
|
rf.cookies['items'] = 'Oops'
|
|
|
|
|
|
request = rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2019-04-24 11:30:34 +00:00
|
|
|
|
self.assertInHTML('<td>items</td><td class="code"><pre>'Oops'</pre></td>', html)
|
2016-09-07 11:09:45 +00:00
|
|
|
|
|
2016-12-03 18:51:32 +00:00
|
|
|
|
def test_exception_fetching_user(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The error page can be rendered if the current user can't be retrieved
|
|
|
|
|
|
(such as when the database is unavailable).
|
|
|
|
|
|
"""
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class ExceptionUser:
|
2016-12-03 18:51:32 +00:00
|
|
|
|
def __str__(self):
|
|
|
|
|
|
raise Exception()
|
|
|
|
|
|
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
request.user = ExceptionUser()
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise ValueError('Oops')
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
html = reporter.get_traceback_html()
|
2017-04-01 15:31:54 +00:00
|
|
|
|
self.assertInHTML('<h1>ValueError at /test_view/</h1>', html)
|
2016-12-03 18:51:32 +00:00
|
|
|
|
self.assertIn('<pre class="exception_value">Oops</pre>', html)
|
|
|
|
|
|
self.assertIn('<h3 id="user-info">USER</h3>', html)
|
|
|
|
|
|
self.assertIn('<p>[unable to retrieve the current user]</p>', html)
|
|
|
|
|
|
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('USER: [unable to retrieve the current user]', text)
|
|
|
|
|
|
|
2019-04-05 12:38:35 +00:00
|
|
|
|
def test_template_encoding(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The templates are loaded directly, not via a template loader, and
|
|
|
|
|
|
should be opened as utf-8 charset as is the default specified on
|
|
|
|
|
|
template engines.
|
|
|
|
|
|
"""
|
|
|
|
|
|
reporter = ExceptionReporter(None, None, None, None)
|
|
|
|
|
|
with mock.patch.object(DebugPath, 'open') as m:
|
|
|
|
|
|
reporter.get_traceback_html()
|
|
|
|
|
|
m.assert_called_once_with(encoding='utf-8')
|
|
|
|
|
|
m.reset_mock()
|
|
|
|
|
|
reporter.get_traceback_text()
|
|
|
|
|
|
m.assert_called_once_with(encoding='utf-8')
|
|
|
|
|
|
|
2021-04-29 12:35:11 +00:00
|
|
|
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
|
|
|
|
|
def test_get_raw_insecure_uri(self):
|
|
|
|
|
|
factory = RequestFactory(HTTP_HOST='evil.com')
|
|
|
|
|
|
tests = [
|
|
|
|
|
|
('////absolute-uri', 'http://evil.com//absolute-uri'),
|
|
|
|
|
|
('/?foo=bar', 'http://evil.com/?foo=bar'),
|
|
|
|
|
|
('/path/with:colons', 'http://evil.com/path/with:colons'),
|
|
|
|
|
|
]
|
|
|
|
|
|
for url, expected in tests:
|
|
|
|
|
|
with self.subTest(url=url):
|
|
|
|
|
|
request = factory.get(url)
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
self.assertEqual(reporter._get_raw_insecure_uri(), expected)
|
|
|
|
|
|
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class PlainTextReportTests(SimpleTestCase):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
rf = RequestFactory()
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
def test_request_and_exception(self):
|
|
|
|
|
|
"A simple exception report can be generated"
|
|
|
|
|
|
try:
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
2015-10-02 14:44:35 +00:00
|
|
|
|
request.user = User()
|
2011-10-02 02:53:58 +00:00
|
|
|
|
raise ValueError("Can't find my keys")
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('ValueError at /test_view/', text)
|
|
|
|
|
|
self.assertIn("Can't find my keys", text)
|
|
|
|
|
|
self.assertIn('Request Method:', text)
|
|
|
|
|
|
self.assertIn('Request URL:', text)
|
2015-10-02 14:44:35 +00:00
|
|
|
|
self.assertIn('USER: jacob', text)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
self.assertIn('Exception Type:', text)
|
|
|
|
|
|
self.assertIn('Exception Value:', text)
|
2019-07-26 01:44:27 +00:00
|
|
|
|
self.assertIn('Traceback (most recent call last):', text)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
self.assertIn('Request information:', text)
|
|
|
|
|
|
self.assertNotIn('Request data not supplied', text)
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_request(self):
|
|
|
|
|
|
"An exception report can be generated without request"
|
|
|
|
|
|
try:
|
|
|
|
|
|
raise ValueError("Can't find my keys")
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(None, exc_type, exc_value, tb)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('ValueError', text)
|
|
|
|
|
|
self.assertIn("Can't find my keys", text)
|
|
|
|
|
|
self.assertNotIn('Request Method:', text)
|
|
|
|
|
|
self.assertNotIn('Request URL:', text)
|
2015-10-02 14:44:35 +00:00
|
|
|
|
self.assertNotIn('USER:', text)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
self.assertIn('Exception Type:', text)
|
|
|
|
|
|
self.assertIn('Exception Value:', text)
|
2019-07-26 01:44:27 +00:00
|
|
|
|
self.assertIn('Traceback (most recent call last):', text)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
self.assertIn('Request data not supplied', text)
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_exception(self):
|
|
|
|
|
|
"An exception report can be generated for just a request"
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
reporter.get_traceback_text()
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
def test_request_and_message(self):
|
|
|
|
|
|
"A message can be provided in addition to a request"
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
reporter.get_traceback_text()
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2017-04-01 20:44:33 +00:00
|
|
|
|
@override_settings(DEBUG=True)
|
|
|
|
|
|
def test_template_exception(self):
|
|
|
|
|
|
request = self.rf.get('/test_view/')
|
|
|
|
|
|
try:
|
|
|
|
|
|
render(request, 'debug/template_error.html')
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
exc_type, exc_value, tb = sys.exc_info()
|
|
|
|
|
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
2019-11-04 08:59:34 +00:00
|
|
|
|
templ_path = Path(Path(__file__).parents[1], 'templates', 'debug', 'template_error.html')
|
2017-04-01 20:44:33 +00:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'Template error:\n'
|
|
|
|
|
|
'In template %(path)s, error at line 2\n'
|
|
|
|
|
|
' \'cycle\' tag requires at least two arguments\n'
|
|
|
|
|
|
' 1 : Template with error:\n'
|
|
|
|
|
|
' 2 : {%% cycle %%} \n'
|
|
|
|
|
|
' 3 : ' % {'path': templ_path},
|
|
|
|
|
|
text
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2016-09-07 11:09:45 +00:00
|
|
|
|
def test_request_with_items_key(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
An exception report can be generated for requests with 'items' in
|
|
|
|
|
|
request GET, POST, FILES, or COOKIES QueryDicts.
|
|
|
|
|
|
"""
|
|
|
|
|
|
# GET
|
|
|
|
|
|
request = self.rf.get('/test_view/?items=Oops')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
2016-12-01 10:38:01 +00:00
|
|
|
|
self.assertIn("items = 'Oops'", text)
|
2016-09-07 11:09:45 +00:00
|
|
|
|
# POST
|
|
|
|
|
|
request = self.rf.post('/test_view/', data={'items': 'Oops'})
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
2016-12-01 10:38:01 +00:00
|
|
|
|
self.assertIn("items = 'Oops'", text)
|
2016-09-07 11:09:45 +00:00
|
|
|
|
# FILES
|
2017-01-07 11:11:46 +00:00
|
|
|
|
fp = StringIO('filecontent')
|
2016-09-07 11:09:45 +00:00
|
|
|
|
request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp})
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn('items = <InMemoryUploadedFile:', text)
|
2019-06-25 09:12:45 +00:00
|
|
|
|
# COOKIES
|
2016-09-07 11:09:45 +00:00
|
|
|
|
rf = RequestFactory()
|
|
|
|
|
|
rf.cookies['items'] = 'Oops'
|
|
|
|
|
|
request = rf.get('/test_view/')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn("items = 'Oops'", text)
|
|
|
|
|
|
|
2011-10-02 02:53:58 +00:00
|
|
|
|
def test_message_only(self):
|
|
|
|
|
|
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
reporter.get_traceback_text()
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2015-09-03 18:52:49 +00:00
|
|
|
|
@override_settings(ALLOWED_HOSTS='example.com')
|
|
|
|
|
|
def test_disallowed_host(self):
|
|
|
|
|
|
"An exception report can be generated even for a disallowed host."
|
|
|
|
|
|
request = self.rf.get('/', HTTP_HOST='evil.com')
|
|
|
|
|
|
reporter = ExceptionReporter(request, None, None, None)
|
|
|
|
|
|
text = reporter.get_traceback_text()
|
|
|
|
|
|
self.assertIn("http://evil.com/", text)
|
|
|
|
|
|
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class ExceptionReportTestMixin:
|
2011-10-02 02:53:58 +00:00
|
|
|
|
# Mixin used in the ExceptionReporterFilterTests and
|
|
|
|
|
|
# AjaxResponseExceptionReporterFilter tests below
|
2019-01-02 23:18:19 +00:00
|
|
|
|
breakfast_data = {
|
|
|
|
|
|
'sausage-key': 'sausage-value',
|
|
|
|
|
|
'baked-beans-key': 'baked-beans-value',
|
|
|
|
|
|
'hash-brown-key': 'hash-brown-value',
|
|
|
|
|
|
'bacon-key': 'bacon-value',
|
|
|
|
|
|
}
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def verify_unsafe_response(self, view, check_for_vars=True,
|
|
|
|
|
|
check_for_POST_params=True):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Asserts that potentially sensitive info are displayed in the response.
|
|
|
|
|
|
"""
|
|
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
|
|
|
|
|
response = view(request)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
if check_for_vars:
|
|
|
|
|
|
# All variables are shown.
|
|
|
|
|
|
self.assertContains(response, 'cooked_eggs', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'scrambled', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'sauce', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'worcestershire', status_code=500)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
if check_for_POST_params:
|
|
|
|
|
|
for k, v in self.breakfast_data.items():
|
|
|
|
|
|
# All POST parameters are shown.
|
|
|
|
|
|
self.assertContains(response, k, status_code=500)
|
|
|
|
|
|
self.assertContains(response, v, status_code=500)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def verify_safe_response(self, view, check_for_vars=True,
|
|
|
|
|
|
check_for_POST_params=True):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Asserts that certain sensitive info are not displayed in the response.
|
|
|
|
|
|
"""
|
|
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
|
|
|
|
|
response = view(request)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
if check_for_vars:
|
|
|
|
|
|
# Non-sensitive variable's name and value are shown.
|
|
|
|
|
|
self.assertContains(response, 'cooked_eggs', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'scrambled', status_code=500)
|
|
|
|
|
|
# Sensitive variable's name is shown but not its value.
|
|
|
|
|
|
self.assertContains(response, 'sauce', status_code=500)
|
|
|
|
|
|
self.assertNotContains(response, 'worcestershire', status_code=500)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
if check_for_POST_params:
|
2017-12-06 22:17:59 +00:00
|
|
|
|
for k in self.breakfast_data:
|
2012-06-04 00:33:09 +00:00
|
|
|
|
# All POST parameters' names are shown.
|
|
|
|
|
|
self.assertContains(response, k, status_code=500)
|
|
|
|
|
|
# Non-sensitive POST parameters' values are shown.
|
|
|
|
|
|
self.assertContains(response, 'baked-beans-value', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'hash-brown-value', status_code=500)
|
|
|
|
|
|
# Sensitive POST parameters' values are not shown.
|
|
|
|
|
|
self.assertNotContains(response, 'sausage-value', status_code=500)
|
|
|
|
|
|
self.assertNotContains(response, 'bacon-value', status_code=500)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def verify_paranoid_response(self, view, check_for_vars=True,
|
|
|
|
|
|
check_for_POST_params=True):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Asserts that no variables or POST parameters are displayed in the response.
|
|
|
|
|
|
"""
|
|
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
|
|
|
|
|
response = view(request)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
if check_for_vars:
|
|
|
|
|
|
# Show variable names but not their values.
|
|
|
|
|
|
self.assertContains(response, 'cooked_eggs', status_code=500)
|
|
|
|
|
|
self.assertNotContains(response, 'scrambled', status_code=500)
|
|
|
|
|
|
self.assertContains(response, 'sauce', status_code=500)
|
|
|
|
|
|
self.assertNotContains(response, 'worcestershire', status_code=500)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
if check_for_POST_params:
|
|
|
|
|
|
for k, v in self.breakfast_data.items():
|
|
|
|
|
|
# All POST parameters' names are shown.
|
|
|
|
|
|
self.assertContains(response, k, status_code=500)
|
|
|
|
|
|
# No POST parameters' values are shown.
|
|
|
|
|
|
self.assertNotContains(response, v, status_code=500)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def verify_unsafe_email(self, view, check_for_POST_params=True):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Asserts that potentially sensitive info are displayed in the email report.
|
|
|
|
|
|
"""
|
2015-01-21 16:55:57 +00:00
|
|
|
|
with self.settings(ADMINS=[('Admin', 'admin@fattie-breakie.com')]):
|
2013-11-02 21:34:05 +00:00
|
|
|
|
mail.outbox = [] # Empty outbox
|
2011-06-08 22:18:46 +00:00
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
view(request)
|
2011-06-14 10:35:13 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
email = mail.outbox[0]
|
2012-12-31 17:34:08 +00:00
|
|
|
|
|
2011-06-08 22:18:46 +00:00
|
|
|
|
# Frames vars are never shown in plain text email reports.
|
2017-01-24 11:22:42 +00:00
|
|
|
|
body_plain = str(email.body)
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertNotIn('cooked_eggs', body_plain)
|
|
|
|
|
|
self.assertNotIn('scrambled', body_plain)
|
|
|
|
|
|
self.assertNotIn('sauce', body_plain)
|
|
|
|
|
|
self.assertNotIn('worcestershire', body_plain)
|
|
|
|
|
|
|
|
|
|
|
|
# Frames vars are shown in html email reports.
|
2017-01-24 11:22:42 +00:00
|
|
|
|
body_html = str(email.alternatives[0][0])
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertIn('cooked_eggs', body_html)
|
|
|
|
|
|
self.assertIn('scrambled', body_html)
|
|
|
|
|
|
self.assertIn('sauce', body_html)
|
|
|
|
|
|
self.assertIn('worcestershire', body_html)
|
|
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
if check_for_POST_params:
|
|
|
|
|
|
for k, v in self.breakfast_data.items():
|
|
|
|
|
|
# All POST parameters are shown.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertIn(k, body_plain)
|
|
|
|
|
|
self.assertIn(v, body_plain)
|
|
|
|
|
|
self.assertIn(k, body_html)
|
|
|
|
|
|
self.assertIn(v, body_html)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def verify_safe_email(self, view, check_for_POST_params=True):
|
2011-06-08 22:18:46 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Asserts that certain sensitive info are not displayed in the email report.
|
|
|
|
|
|
"""
|
2015-01-21 16:55:57 +00:00
|
|
|
|
with self.settings(ADMINS=[('Admin', 'admin@fattie-breakie.com')]):
|
2013-11-02 21:34:05 +00:00
|
|
|
|
mail.outbox = [] # Empty outbox
|
2011-06-08 22:18:46 +00:00
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
view(request)
|
2011-06-14 10:35:13 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
email = mail.outbox[0]
|
2012-12-31 17:34:08 +00:00
|
|
|
|
|
2011-06-08 22:18:46 +00:00
|
|
|
|
# Frames vars are never shown in plain text email reports.
|
2017-01-24 11:22:42 +00:00
|
|
|
|
body_plain = str(email.body)
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertNotIn('cooked_eggs', body_plain)
|
|
|
|
|
|
self.assertNotIn('scrambled', body_plain)
|
|
|
|
|
|
self.assertNotIn('sauce', body_plain)
|
|
|
|
|
|
self.assertNotIn('worcestershire', body_plain)
|
|
|
|
|
|
|
|
|
|
|
|
# Frames vars are shown in html email reports.
|
2017-01-24 11:22:42 +00:00
|
|
|
|
body_html = str(email.alternatives[0][0])
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertIn('cooked_eggs', body_html)
|
|
|
|
|
|
self.assertIn('scrambled', body_html)
|
|
|
|
|
|
self.assertIn('sauce', body_html)
|
|
|
|
|
|
self.assertNotIn('worcestershire', body_html)
|
|
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
if check_for_POST_params:
|
2017-12-06 22:17:59 +00:00
|
|
|
|
for k in self.breakfast_data:
|
2012-06-04 00:33:09 +00:00
|
|
|
|
# All POST parameters' names are shown.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertIn(k, body_plain)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
# Non-sensitive POST parameters' values are shown.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertIn('baked-beans-value', body_plain)
|
|
|
|
|
|
self.assertIn('hash-brown-value', body_plain)
|
|
|
|
|
|
self.assertIn('baked-beans-value', body_html)
|
|
|
|
|
|
self.assertIn('hash-brown-value', body_html)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
# Sensitive POST parameters' values are not shown.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
self.assertNotIn('sausage-value', body_plain)
|
|
|
|
|
|
self.assertNotIn('bacon-value', body_plain)
|
|
|
|
|
|
self.assertNotIn('sausage-value', body_html)
|
|
|
|
|
|
self.assertNotIn('bacon-value', body_html)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
|
|
|
|
|
def verify_paranoid_email(self, view):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Asserts that no variables or POST parameters are displayed in the email report.
|
|
|
|
|
|
"""
|
2015-01-21 16:55:57 +00:00
|
|
|
|
with self.settings(ADMINS=[('Admin', 'admin@fattie-breakie.com')]):
|
2013-11-02 21:34:05 +00:00
|
|
|
|
mail.outbox = [] # Empty outbox
|
2011-06-08 22:18:46 +00:00
|
|
|
|
request = self.rf.post('/some_url/', self.breakfast_data)
|
2013-09-07 22:35:55 +00:00
|
|
|
|
view(request)
|
2011-06-14 10:35:13 +00:00
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
email = mail.outbox[0]
|
|
|
|
|
|
# Frames vars are never shown in plain text email reports.
|
2017-01-24 11:22:42 +00:00
|
|
|
|
body = str(email.body)
|
2012-12-08 10:13:52 +00:00
|
|
|
|
self.assertNotIn('cooked_eggs', body)
|
|
|
|
|
|
self.assertNotIn('scrambled', body)
|
|
|
|
|
|
self.assertNotIn('sauce', body)
|
|
|
|
|
|
self.assertNotIn('worcestershire', body)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
for k, v in self.breakfast_data.items():
|
|
|
|
|
|
# All POST parameters' names are shown.
|
2012-12-08 10:13:52 +00:00
|
|
|
|
self.assertIn(k, body)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
# No POST parameters' values are shown.
|
2012-12-08 10:13:52 +00:00
|
|
|
|
self.assertNotIn(v, body)
|
2011-06-08 22:18:46 +00:00
|
|
|
|
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2014-04-05 06:04:46 +00:00
|
|
|
|
@override_settings(ROOT_URLCONF='view_tests.urls')
|
2015-04-17 21:38:20 +00:00
|
|
|
|
class ExceptionReporterFilterTests(ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase):
|
2011-10-02 02:53:58 +00:00
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive information can be filtered out of error reports (#14614).
|
2011-10-02 02:53:58 +00:00
|
|
|
|
"""
|
|
|
|
|
|
rf = RequestFactory()
|
|
|
|
|
|
|
2011-06-08 22:18:46 +00:00
|
|
|
|
def test_non_sensitive_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Everything (request info and frame variables) can bee seen
|
2011-06-08 22:18:46 +00:00
|
|
|
|
in the default error reports for non-sensitive requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(non_sensitive_view)
|
|
|
|
|
|
self.verify_unsafe_email(non_sensitive_view)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_unsafe_response(non_sensitive_view)
|
|
|
|
|
|
self.verify_unsafe_email(non_sensitive_view)
|
|
|
|
|
|
|
|
|
|
|
|
def test_sensitive_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive POST parameters and frame variables cannot be
|
2011-06-08 22:18:46 +00:00
|
|
|
|
seen in the default error reports for sensitive requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(sensitive_view)
|
|
|
|
|
|
self.verify_unsafe_email(sensitive_view)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_safe_response(sensitive_view)
|
|
|
|
|
|
self.verify_safe_email(sensitive_view)
|
|
|
|
|
|
|
|
|
|
|
|
def test_paranoid_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
No POST parameters and frame variables can be seen in the
|
2011-06-08 22:18:46 +00:00
|
|
|
|
default error reports for "paranoid" requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(paranoid_view)
|
|
|
|
|
|
self.verify_unsafe_email(paranoid_view)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_paranoid_response(paranoid_view)
|
|
|
|
|
|
self.verify_paranoid_email(paranoid_view)
|
|
|
|
|
|
|
2013-09-17 11:46:38 +00:00
|
|
|
|
def test_multivalue_dict_key_error(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
#21098 -- Sensitive POST parameters cannot be seen in the
|
2013-09-17 11:46:38 +00:00
|
|
|
|
error reports for if request.POST['nonexistent_key'] throws an error.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(multivalue_dict_key_error)
|
|
|
|
|
|
self.verify_unsafe_email(multivalue_dict_key_error)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_safe_response(multivalue_dict_key_error)
|
|
|
|
|
|
self.verify_safe_email(multivalue_dict_key_error)
|
|
|
|
|
|
|
2011-06-08 22:18:46 +00:00
|
|
|
|
def test_custom_exception_reporter_filter(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
It's possible to assign an exception reporter filter to
|
2011-06-08 22:18:46 +00:00
|
|
|
|
the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(custom_exception_reporter_filter_view)
|
|
|
|
|
|
self.verify_unsafe_email(custom_exception_reporter_filter_view)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_unsafe_response(custom_exception_reporter_filter_view)
|
|
|
|
|
|
self.verify_unsafe_email(custom_exception_reporter_filter_view)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2012-06-04 00:33:09 +00:00
|
|
|
|
def test_sensitive_method(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
The sensitive_variables decorator works with object methods.
|
2012-06-04 00:33:09 +00:00
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
2016-10-27 07:53:39 +00:00
|
|
|
|
self.verify_unsafe_response(sensitive_method_view, check_for_POST_params=False)
|
|
|
|
|
|
self.verify_unsafe_email(sensitive_method_view, check_for_POST_params=False)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
2016-10-27 07:53:39 +00:00
|
|
|
|
self.verify_safe_response(sensitive_method_view, check_for_POST_params=False)
|
|
|
|
|
|
self.verify_safe_email(sensitive_method_view, check_for_POST_params=False)
|
2012-06-04 00:33:09 +00:00
|
|
|
|
|
2012-12-31 17:34:08 +00:00
|
|
|
|
def test_sensitive_function_arguments(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive variables don't leak in the sensitive_variables decorator's
|
|
|
|
|
|
frame, when those variables are passed as arguments to the decorated
|
|
|
|
|
|
function.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(sensitive_args_function_caller)
|
|
|
|
|
|
self.verify_unsafe_email(sensitive_args_function_caller)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_safe_response(sensitive_args_function_caller, check_for_POST_params=False)
|
|
|
|
|
|
self.verify_safe_email(sensitive_args_function_caller, check_for_POST_params=False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_sensitive_function_keyword_arguments(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive variables don't leak in the sensitive_variables decorator's
|
|
|
|
|
|
frame, when those variables are passed as keyword arguments to the
|
|
|
|
|
|
decorated function.
|
2012-12-31 17:34:08 +00:00
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(sensitive_kwargs_function_caller)
|
|
|
|
|
|
self.verify_unsafe_email(sensitive_kwargs_function_caller)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_safe_response(sensitive_kwargs_function_caller, check_for_POST_params=False)
|
|
|
|
|
|
self.verify_safe_email(sensitive_kwargs_function_caller, check_for_POST_params=False)
|
|
|
|
|
|
|
2013-10-29 22:06:59 +00:00
|
|
|
|
def test_callable_settings(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Callable settings should not be evaluated in the debug page (#21345).
|
|
|
|
|
|
"""
|
|
|
|
|
|
def callable_setting():
|
|
|
|
|
|
return "This should not be displayed"
|
|
|
|
|
|
with self.settings(DEBUG=True, FOOBAR=callable_setting):
|
2014-01-14 15:43:27 +00:00
|
|
|
|
response = self.client.get('/raises500/')
|
2013-10-29 22:06:59 +00:00
|
|
|
|
self.assertNotContains(response, "This should not be displayed", status_code=500)
|
2014-07-26 10:01:44 +00:00
|
|
|
|
|
|
|
|
|
|
def test_callable_settings_forbidding_to_set_attributes(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Callable settings which forbid to set attributes should not break
|
|
|
|
|
|
the debug page (#23070).
|
|
|
|
|
|
"""
|
2017-01-19 07:39:46 +00:00
|
|
|
|
class CallableSettingWithSlots:
|
2014-07-26 10:01:44 +00:00
|
|
|
|
__slots__ = []
|
|
|
|
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
|
|
return "This should not be displayed"
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=True, WITH_SLOTS=CallableSettingWithSlots()):
|
|
|
|
|
|
response = self.client.get('/raises500/')
|
|
|
|
|
|
self.assertNotContains(response, "This should not be displayed", status_code=500)
|
2013-10-29 22:06:59 +00:00
|
|
|
|
|
2013-10-29 22:31:53 +00:00
|
|
|
|
def test_dict_setting_with_non_str_key(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
A dict setting containing a non-string key should not break the
|
|
|
|
|
|
debug page (#12744).
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True, FOOBAR={42: None}):
|
2014-01-14 15:43:27 +00:00
|
|
|
|
response = self.client.get('/raises500/')
|
2013-10-29 22:31:53 +00:00
|
|
|
|
self.assertContains(response, 'FOOBAR', status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
def test_sensitive_settings(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The debug page should not show some sensitive settings
|
|
|
|
|
|
(password, secret key, ...).
|
|
|
|
|
|
"""
|
|
|
|
|
|
sensitive_settings = [
|
|
|
|
|
|
'SECRET_KEY',
|
|
|
|
|
|
'PASSWORD',
|
|
|
|
|
|
'API_KEY',
|
|
|
|
|
|
'AUTH_TOKEN',
|
|
|
|
|
|
]
|
|
|
|
|
|
for setting in sensitive_settings:
|
|
|
|
|
|
with self.settings(DEBUG=True, **{setting: "should not be displayed"}):
|
2014-01-14 15:43:27 +00:00
|
|
|
|
response = self.client.get('/raises500/')
|
2013-10-29 22:31:53 +00:00
|
|
|
|
self.assertNotContains(response, 'should not be displayed', status_code=500)
|
|
|
|
|
|
|
|
|
|
|
|
def test_settings_with_sensitive_keys(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The debug page should filter out some sensitive information found in
|
|
|
|
|
|
dict settings.
|
|
|
|
|
|
"""
|
|
|
|
|
|
sensitive_settings = [
|
|
|
|
|
|
'SECRET_KEY',
|
|
|
|
|
|
'PASSWORD',
|
|
|
|
|
|
'API_KEY',
|
|
|
|
|
|
'AUTH_TOKEN',
|
|
|
|
|
|
]
|
|
|
|
|
|
for setting in sensitive_settings:
|
|
|
|
|
|
FOOBAR = {
|
|
|
|
|
|
setting: "should not be displayed",
|
|
|
|
|
|
'recursive': {setting: "should not be displayed"},
|
|
|
|
|
|
}
|
|
|
|
|
|
with self.settings(DEBUG=True, FOOBAR=FOOBAR):
|
2014-01-14 15:43:27 +00:00
|
|
|
|
response = self.client.get('/raises500/')
|
2013-10-29 22:31:53 +00:00
|
|
|
|
self.assertNotContains(response, 'should not be displayed', status_code=500)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2020-01-09 09:00:07 +00:00
|
|
|
|
def test_cleanse_setting_basic(self):
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
self.assertEqual(reporter_filter.cleanse_setting('TEST', 'TEST'), 'TEST')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('PASSWORD', 'super_secret'),
|
|
|
|
|
|
reporter_filter.cleansed_substitute,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_cleanse_setting_ignore_case(self):
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('password', 'super_secret'),
|
|
|
|
|
|
reporter_filter.cleansed_substitute,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_cleanse_setting_recurses_in_dictionary(self):
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
initial = {'login': 'cooper', 'password': 'secret'}
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('SETTING_NAME', initial),
|
|
|
|
|
|
{'login': 'cooper', 'password': reporter_filter.cleansed_substitute},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-08-25 12:33:05 +00:00
|
|
|
|
def test_cleanse_setting_recurses_in_dictionary_with_non_string_key(self):
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
initial = {('localhost', 8000): {'login': 'cooper', 'password': 'secret'}}
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('SETTING_NAME', initial),
|
|
|
|
|
|
{
|
|
|
|
|
|
('localhost', 8000): {
|
|
|
|
|
|
'login': 'cooper',
|
|
|
|
|
|
'password': reporter_filter.cleansed_substitute,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-04-11 01:25:23 +00:00
|
|
|
|
def test_cleanse_setting_recurses_in_list_tuples(self):
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
initial = [
|
|
|
|
|
|
{
|
|
|
|
|
|
'login': 'cooper',
|
|
|
|
|
|
'password': 'secret',
|
|
|
|
|
|
'apps': (
|
|
|
|
|
|
{'name': 'app1', 'api_key': 'a06b-c462cffae87a'},
|
|
|
|
|
|
{'name': 'app2', 'api_key': 'a9f4-f152e97ad808'},
|
|
|
|
|
|
),
|
|
|
|
|
|
'tokens': ['98b37c57-ec62-4e39', '8690ef7d-8004-4916'],
|
|
|
|
|
|
},
|
|
|
|
|
|
{'SECRET_KEY': 'c4d77c62-6196-4f17-a06b-c462cffae87a'},
|
|
|
|
|
|
]
|
|
|
|
|
|
cleansed = [
|
|
|
|
|
|
{
|
|
|
|
|
|
'login': 'cooper',
|
|
|
|
|
|
'password': reporter_filter.cleansed_substitute,
|
|
|
|
|
|
'apps': (
|
|
|
|
|
|
{'name': 'app1', 'api_key': reporter_filter.cleansed_substitute},
|
|
|
|
|
|
{'name': 'app2', 'api_key': reporter_filter.cleansed_substitute},
|
|
|
|
|
|
),
|
|
|
|
|
|
'tokens': reporter_filter.cleansed_substitute,
|
|
|
|
|
|
},
|
|
|
|
|
|
{'SECRET_KEY': reporter_filter.cleansed_substitute},
|
|
|
|
|
|
]
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('SETTING_NAME', initial),
|
|
|
|
|
|
cleansed,
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('SETTING_NAME', tuple(initial)),
|
|
|
|
|
|
tuple(cleansed),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-01-09 10:37:19 +00:00
|
|
|
|
def test_request_meta_filtering(self):
|
|
|
|
|
|
request = self.rf.get('/', HTTP_SECRET_HEADER='super_secret')
|
|
|
|
|
|
reporter_filter = SafeExceptionReporterFilter()
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.get_safe_request_meta(request)['HTTP_SECRET_HEADER'],
|
|
|
|
|
|
reporter_filter.cleansed_substitute,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_exception_report_uses_meta_filtering(self):
|
|
|
|
|
|
response = self.client.get('/raises500/', HTTP_SECRET_HEADER='super_secret')
|
|
|
|
|
|
self.assertNotIn(b'super_secret', response.content)
|
|
|
|
|
|
response = self.client.get(
|
|
|
|
|
|
'/raises500/',
|
|
|
|
|
|
HTTP_SECRET_HEADER='super_secret',
|
2019-12-15 14:30:35 +00:00
|
|
|
|
HTTP_ACCEPT='application/json',
|
2020-01-09 10:37:19 +00:00
|
|
|
|
)
|
|
|
|
|
|
self.assertNotIn(b'super_secret', response.content)
|
|
|
|
|
|
|
2020-01-09 09:00:07 +00:00
|
|
|
|
|
|
|
|
|
|
class CustomExceptionReporterFilter(SafeExceptionReporterFilter):
|
|
|
|
|
|
cleansed_substitute = 'XXXXXXXXXXXXXXXXXXXX'
|
|
|
|
|
|
hidden_settings = _lazy_re_compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE|DATABASE_URL', flags=re.I)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(
|
|
|
|
|
|
ROOT_URLCONF='view_tests.urls',
|
|
|
|
|
|
DEFAULT_EXCEPTION_REPORTER_FILTER='%s.CustomExceptionReporterFilter' % __name__,
|
|
|
|
|
|
)
|
|
|
|
|
|
class CustomExceptionReporterFilterTests(SimpleTestCase):
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
get_default_exception_reporter_filter.cache_clear()
|
|
|
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
|
get_default_exception_reporter_filter.cache_clear()
|
|
|
|
|
|
|
|
|
|
|
|
def test_setting_allows_custom_subclass(self):
|
|
|
|
|
|
self.assertIsInstance(
|
|
|
|
|
|
get_default_exception_reporter_filter(),
|
|
|
|
|
|
CustomExceptionReporterFilter,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_cleansed_substitute_override(self):
|
|
|
|
|
|
reporter_filter = get_default_exception_reporter_filter()
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('password', 'super_secret'),
|
|
|
|
|
|
reporter_filter.cleansed_substitute,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_hidden_settings_override(self):
|
|
|
|
|
|
reporter_filter = get_default_exception_reporter_filter()
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
reporter_filter.cleanse_setting('database_url', 'super_secret'),
|
|
|
|
|
|
reporter_filter.cleansed_substitute,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2013-11-02 21:34:05 +00:00
|
|
|
|
|
2019-12-15 14:30:35 +00:00
|
|
|
|
class NonHTMLResponseExceptionReporterFilter(ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase):
|
2011-10-02 02:53:58 +00:00
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive information can be filtered out of error reports.
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
2019-12-15 14:30:35 +00:00
|
|
|
|
The plain text 500 debug-only error page is served when it has been
|
|
|
|
|
|
detected the request doesn't accept HTML content. Don't check for
|
|
|
|
|
|
(non)existence of frames vars in the traceback information section of the
|
|
|
|
|
|
response content because they're not included in these error pages.
|
2011-10-02 02:53:58 +00:00
|
|
|
|
Refs #14614.
|
|
|
|
|
|
"""
|
2019-12-15 14:30:35 +00:00
|
|
|
|
rf = RequestFactory(HTTP_ACCEPT='application/json')
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
def test_non_sensitive_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Request info can bee seen in the default error reports for
|
2011-10-02 02:53:58 +00:00
|
|
|
|
non-sensitive requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_sensitive_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
Sensitive POST parameters cannot be seen in the default
|
2011-10-02 02:53:58 +00:00
|
|
|
|
error reports for sensitive requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(sensitive_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_safe_response(sensitive_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_paranoid_request(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
No POST parameters can be seen in the default error reports
|
2011-10-02 02:53:58 +00:00
|
|
|
|
for "paranoid" requests.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
|
|
|
|
|
self.verify_unsafe_response(paranoid_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
|
|
|
|
|
self.verify_paranoid_response(paranoid_view, check_for_vars=False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_exception_reporter_filter(self):
|
|
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
|
It's possible to assign an exception reporter filter to
|
2011-10-02 02:53:58 +00:00
|
|
|
|
the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
|
|
|
|
|
|
"""
|
|
|
|
|
|
with self.settings(DEBUG=True):
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.verify_unsafe_response(custom_exception_reporter_filter_view, check_for_vars=False)
|
2011-10-02 02:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
with self.settings(DEBUG=False):
|
2016-04-08 02:04:45 +00:00
|
|
|
|
self.verify_unsafe_response(custom_exception_reporter_filter_view, check_for_vars=False)
|
2016-02-11 12:23:18 +00:00
|
|
|
|
|
2017-06-05 18:43:40 +00:00
|
|
|
|
@override_settings(DEBUG=True, ROOT_URLCONF='view_tests.urls')
|
2019-12-15 14:30:35 +00:00
|
|
|
|
def test_non_html_response_encoding(self):
|
|
|
|
|
|
response = self.client.get('/raises500/', HTTP_ACCEPT='application/json')
|
2020-07-14 11:32:24 +00:00
|
|
|
|
self.assertEqual(response.headers['Content-Type'], 'text/plain; charset=utf-8')
|
2017-06-05 18:43:40 +00:00
|
|
|
|
|
2016-02-11 12:23:18 +00:00
|
|
|
|
|
2019-12-09 11:12:17 +00:00
|
|
|
|
class DecoratorsTests(SimpleTestCase):
|
|
|
|
|
|
def test_sensitive_variables_not_called(self):
|
|
|
|
|
|
msg = (
|
|
|
|
|
|
'sensitive_variables() must be called to use it as a decorator, '
|
|
|
|
|
|
'e.g., use @sensitive_variables(), not @sensitive_variables.'
|
|
|
|
|
|
)
|
|
|
|
|
|
with self.assertRaisesMessage(TypeError, msg):
|
|
|
|
|
|
@sensitive_variables
|
|
|
|
|
|
def test_func(password):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_sensitive_post_parameters_not_called(self):
|
|
|
|
|
|
msg = (
|
|
|
|
|
|
'sensitive_post_parameters() must be called to use it as a '
|
|
|
|
|
|
'decorator, e.g., use @sensitive_post_parameters(), not '
|
|
|
|
|
|
'@sensitive_post_parameters.'
|
|
|
|
|
|
)
|
|
|
|
|
|
with self.assertRaisesMessage(TypeError, msg):
|
|
|
|
|
|
@sensitive_post_parameters
|
|
|
|
|
|
def test_func(request):
|
|
|
|
|
|
return index_page(request)
|
2021-06-20 18:16:33 +00:00
|
|
|
|
|
|
|
|
|
|
def test_sensitive_post_parameters_http_request(self):
|
|
|
|
|
|
class MyClass:
|
|
|
|
|
|
@sensitive_post_parameters()
|
|
|
|
|
|
def a_view(self, request):
|
|
|
|
|
|
return HttpResponse()
|
|
|
|
|
|
|
|
|
|
|
|
msg = (
|
|
|
|
|
|
"sensitive_post_parameters didn't receive an HttpRequest object. "
|
|
|
|
|
|
"If you are decorating a classmethod, make sure to use "
|
|
|
|
|
|
"@method_decorator."
|
|
|
|
|
|
)
|
|
|
|
|
|
with self.assertRaisesMessage(TypeError, msg):
|
|
|
|
|
|
MyClass().a_view(HttpRequest())
|