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:
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)
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":
"""
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)
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())