From: Tom Christie Date: Fri, 17 Jan 2020 11:42:51 +0000 (+0000) Subject: Handle redirect with malformed Location headers missing host. (#774) X-Git-Tag: 0.11.1~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1f8fb28d93f796e55f31234dc792a14230b4ff9c;p=thirdparty%2Fhttpx.git Handle redirect with malformed Location headers missing host. (#774) --- diff --git a/httpx/client.py b/httpx/client.py index 590d5d0e..49914787 100644 --- a/httpx/client.py +++ b/httpx/client.py @@ -330,6 +330,11 @@ class BaseClient: url = URL(location, allow_relative=True) + # Handle malformed 'Location' headers that are "absolute" form, have no host. + # See: https://github.com/encode/httpx/issues/771 + if url.scheme and not url.host: + url = url.copy_with(host=request.url.host) + # Facilitate relative 'Location' headers, as allowed by RFC 7231. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') if url.is_relative_url: diff --git a/httpx/models.py b/httpx/models.py index acaaf66d..8d22738b 100644 --- a/httpx/models.py +++ b/httpx/models.py @@ -201,7 +201,7 @@ class URL: or "port" in kwargs ): host = kwargs.pop("host", self.host) - port = kwargs.pop("port", self.port) + port = kwargs.pop("port", None if self.is_relative_url else self.port) username = kwargs.pop("username", self.username) password = kwargs.pop("password", self.password) @@ -216,7 +216,10 @@ class URL: kwargs["authority"] = authority - return URL(self._uri_reference.copy_with(**kwargs).unsplit()) + return URL( + self._uri_reference.copy_with(**kwargs).unsplit(), + allow_relative=self.is_relative_url, + ) def join(self, relative_url: URLTypes) -> "URL": """ diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py index eab44f01..1717d782 100644 --- a/tests/client/test_redirects.py +++ b/tests/client/test_redirects.py @@ -56,6 +56,10 @@ class MockDispatch(AsyncDispatcher): headers = {"location": "/"} return Response(codes.SEE_OTHER, headers=headers, request=request) + elif request.url.path == "/malformed_redirect": + headers = {"location": "https://:443/"} + return Response(codes.SEE_OTHER, headers=headers, request=request) + elif request.url.path == "/no_scheme_redirect": headers = {"location": "//example.org/"} return Response(codes.SEE_OTHER, headers=headers, request=request) @@ -176,6 +180,16 @@ async def test_relative_redirect(): assert len(response.history) == 1 +@pytest.mark.usefixtures("async_environment") +async def test_malformed_redirect(): + # https://github.com/encode/httpx/issues/771 + client = AsyncClient(dispatch=MockDispatch()) + response = await client.get("http://example.org/malformed_redirect") + assert response.status_code == codes.OK + assert response.url == URL("https://example.org/") + assert len(response.history) == 1 + + @pytest.mark.usefixtures("async_environment") async def test_no_scheme_redirect(): client = AsyncClient(dispatch=MockDispatch())