diff --git a/django/http/request.py b/django/http/request.py
index b50392abd2..15f1c4614e 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -68,8 +68,11 @@ class HttpRequest(object):
'<%s: %s %r>' % (self.__class__.__name__, self.method, force_str(self.get_full_path()))
)
- def get_host(self):
- """Returns the HTTP host using the environment or request headers."""
+ def _get_raw_host(self):
+ """
+ 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.
if settings.USE_X_FORWARDED_HOST and (
'HTTP_X_FORWARDED_HOST' in self.META):
@@ -82,6 +85,11 @@ class HttpRequest(object):
server_port = self.get_port()
if server_port != ('443' if self.is_secure() else '80'):
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
if settings.DEBUG:
@@ -138,6 +146,17 @@ class HttpRequest(object):
raise
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):
"""
Builds an absolute URI from the location and the variables available in
diff --git a/django/views/debug.py b/django/views/debug.py
index 4f96ac320c..55357b3344 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -669,7 +669,7 @@ TECHNICAL_500_TEMPLATE = ("""
Request URL: |
- {{ request.build_absolute_uri|escape }} |
+ {{ request.get_raw_uri|escape }} |
{% endif %}
@@ -849,7 +849,7 @@ Environment:
{% if request %}
Request Method: {{ request.META.REQUEST_METHOD }}
-Request URL: {{ request.build_absolute_uri|escape }}
+Request URL: {{ request.get_raw_uri|escape }}
{% endif %}
Django Version: {{ django_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' %}
{% if request %}
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 }}
Python Executable: {{ sys_executable }}
Python Version: {{ sys_version_info }}
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index 0b4abbfef6..9c39bb2d59 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -352,6 +352,25 @@ class AdminEmailHandlerTest(SimpleTestCase):
self.assertEqual(len(mail.outbox), 1)
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):
"""
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index 8a0b32d1c0..3a3d7e7733 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -502,6 +502,18 @@ class RequestsTests(SimpleTestCase):
with self.assertRaises(UnreadablePostError):
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):
poisoned_hosts = [
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 4f3a9d6161..b4e7f9c7fa 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -439,6 +439,14 @@ class ExceptionReporterTests(SimpleTestCase):
"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):
rf = RequestFactory()
@@ -495,6 +503,14 @@ class PlainTextReportTests(SimpleTestCase):
reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
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):