Commit cf29b6b5 authored by Vlastimil Zíma's avatar Vlastimil Zíma Committed by Tim Graham
Browse files

Fixed #25099 -- Fixed crash in AdminEmailHandler on DisallowedHost.

parent 4c0447b2
Loading
Loading
Loading
Loading
+21 −2
Original line number Diff line number Diff line
@@ -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
+3 −3
Original line number Diff line number Diff line
@@ -669,7 +669,7 @@ TECHNICAL_500_TEMPLATE = ("""
    </tr>
    <tr>
      <th>Request URL:</th>
      <td>{{ request.build_absolute_uri|escape }}</td>
      <td>{{ request.get_raw_uri|escape }}</td>
    </tr>
{% endif %}
    <tr>
@@ -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 }}
+19 −0
Original line number Diff line number Diff line
@@ -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):
    """
+12 −0
Original line number Diff line number Diff line
@@ -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 = [
+16 −0
Original line number Diff line number Diff line
@@ -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):