Fixed #25099 -- Fixed crash in AdminEmailHandler on DisallowedHost.
This commit is contained in:
parent
4c0447b2ae
commit
cf29b6b561
|
@ -68,8 +68,11 @@ class HttpRequest(object):
|
||||||
'<%s: %s %r>' % (self.__class__.__name__, self.method, force_str(self.get_full_path()))
|
'<%s: %s %r>' % (self.__class__.__name__, self.method, force_str(self.get_full_path()))
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_host(self):
|
def _get_raw_host(self):
|
||||||
"""Returns the HTTP host using the environment or request headers."""
|
"""
|
||||||
|
Return the HTTP host using the environment or request headers. Skip
|
||||||
|
allowed hosts protection, so may return an insecure host.
|
||||||
|
"""
|
||||||
# We try three options, in order of decreasing preference.
|
# We try three options, in order of decreasing preference.
|
||||||
if settings.USE_X_FORWARDED_HOST and (
|
if settings.USE_X_FORWARDED_HOST and (
|
||||||
'HTTP_X_FORWARDED_HOST' in self.META):
|
'HTTP_X_FORWARDED_HOST' in self.META):
|
||||||
|
@ -82,6 +85,11 @@ class HttpRequest(object):
|
||||||
server_port = self.get_port()
|
server_port = self.get_port()
|
||||||
if server_port != ('443' if self.is_secure() else '80'):
|
if server_port != ('443' if self.is_secure() else '80'):
|
||||||
host = '%s:%s' % (host, server_port)
|
host = '%s:%s' % (host, server_port)
|
||||||
|
return host
|
||||||
|
|
||||||
|
def get_host(self):
|
||||||
|
"""Return the HTTP host using the environment or request headers."""
|
||||||
|
host = self._get_raw_host()
|
||||||
|
|
||||||
# There is no hostname validation when DEBUG=True
|
# There is no hostname validation when DEBUG=True
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
@ -138,6 +146,17 @@ class HttpRequest(object):
|
||||||
raise
|
raise
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def get_raw_uri(self):
|
||||||
|
"""
|
||||||
|
Return an absolute URI from variables available in this request. Skip
|
||||||
|
allowed hosts protection, so may return insecure URI.
|
||||||
|
"""
|
||||||
|
return '{scheme}://{host}{path}'.format(
|
||||||
|
scheme=self.scheme,
|
||||||
|
host=self._get_raw_host(),
|
||||||
|
path=self.get_full_path(),
|
||||||
|
)
|
||||||
|
|
||||||
def build_absolute_uri(self, location=None):
|
def build_absolute_uri(self, location=None):
|
||||||
"""
|
"""
|
||||||
Builds an absolute URI from the location and the variables available in
|
Builds an absolute URI from the location and the variables available in
|
||||||
|
|
|
@ -669,7 +669,7 @@ TECHNICAL_500_TEMPLATE = ("""
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Request URL:</th>
|
<th>Request URL:</th>
|
||||||
<td>{{ request.build_absolute_uri|escape }}</td>
|
<td>{{ request.get_raw_uri|escape }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -849,7 +849,7 @@ Environment:
|
||||||
|
|
||||||
{% if request %}
|
{% if request %}
|
||||||
Request Method: {{ request.META.REQUEST_METHOD }}
|
Request Method: {{ request.META.REQUEST_METHOD }}
|
||||||
Request URL: {{ request.build_absolute_uri|escape }}
|
Request URL: {{ request.get_raw_uri|escape }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
Django Version: {{ django_version_info }}
|
Django Version: {{ django_version_info }}
|
||||||
Python Version: {{ sys_version_info }}
|
Python Version: {{ sys_version_info }}
|
||||||
|
@ -1048,7 +1048,7 @@ TECHNICAL_500_TEXT_TEMPLATE = ("""{% firstof exception_type 'Report' %}{% if req
|
||||||
{% firstof exception_value 'No exception message supplied' %}
|
{% firstof exception_value 'No exception message supplied' %}
|
||||||
{% if request %}
|
{% if request %}
|
||||||
Request Method: {{ request.META.REQUEST_METHOD }}
|
Request Method: {{ request.META.REQUEST_METHOD }}
|
||||||
Request URL: {{ request.build_absolute_uri }}{% endif %}
|
Request URL: {{ request.get_raw_uri }}{% endif %}
|
||||||
Django Version: {{ django_version_info }}
|
Django Version: {{ django_version_info }}
|
||||||
Python Executable: {{ sys_executable }}
|
Python Executable: {{ sys_executable }}
|
||||||
Python Version: {{ sys_version_info }}
|
Python Version: {{ sys_version_info }}
|
||||||
|
|
|
@ -352,6 +352,25 @@ class AdminEmailHandlerTest(SimpleTestCase):
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual(mail.outbox[0].to, ['manager@example.com'])
|
self.assertEqual(mail.outbox[0].to, ['manager@example.com'])
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS='example.com')
|
||||||
|
def test_disallowed_host_doesnt_crash(self):
|
||||||
|
admin_email_handler = self.get_admin_email_handler(self.logger)
|
||||||
|
old_include_html = admin_email_handler.include_html
|
||||||
|
|
||||||
|
# Text email
|
||||||
|
admin_email_handler.include_html = False
|
||||||
|
try:
|
||||||
|
self.client.get('/', HTTP_HOST='evil.com')
|
||||||
|
finally:
|
||||||
|
admin_email_handler.include_html = old_include_html
|
||||||
|
|
||||||
|
# HTML email
|
||||||
|
admin_email_handler.include_html = True
|
||||||
|
try:
|
||||||
|
self.client.get('/', HTTP_HOST='evil.com')
|
||||||
|
finally:
|
||||||
|
admin_email_handler.include_html = old_include_html
|
||||||
|
|
||||||
|
|
||||||
class SettingsConfigTest(AdminScriptTestCase):
|
class SettingsConfigTest(AdminScriptTestCase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -502,6 +502,18 @@ class RequestsTests(SimpleTestCase):
|
||||||
with self.assertRaises(UnreadablePostError):
|
with self.assertRaises(UnreadablePostError):
|
||||||
request.FILES
|
request.FILES
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
||||||
|
def test_get_raw_uri(self):
|
||||||
|
factory = RequestFactory(HTTP_HOST='evil.com')
|
||||||
|
request = factory.get('////absolute-uri')
|
||||||
|
self.assertEqual(request.get_raw_uri(), 'http://evil.com//absolute-uri')
|
||||||
|
|
||||||
|
request = factory.get('/?foo=bar')
|
||||||
|
self.assertEqual(request.get_raw_uri(), 'http://evil.com/?foo=bar')
|
||||||
|
|
||||||
|
request = factory.get('/path/with:colons')
|
||||||
|
self.assertEqual(request.get_raw_uri(), 'http://evil.com/path/with:colons')
|
||||||
|
|
||||||
|
|
||||||
class HostValidationTests(SimpleTestCase):
|
class HostValidationTests(SimpleTestCase):
|
||||||
poisoned_hosts = [
|
poisoned_hosts = [
|
||||||
|
|
|
@ -439,6 +439,14 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||||
"Evaluation exception reason not mentioned in traceback"
|
"Evaluation exception reason not mentioned in traceback"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
|
||||||
class PlainTextReportTests(SimpleTestCase):
|
class PlainTextReportTests(SimpleTestCase):
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
|
@ -495,6 +503,14 @@ class PlainTextReportTests(SimpleTestCase):
|
||||||
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
|
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
|
||||||
reporter.get_traceback_text()
|
reporter.get_traceback_text()
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
|
||||||
class ExceptionReportTestMixin(object):
|
class ExceptionReportTestMixin(object):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue