diff --git a/httpx/_urlparse.py b/httpx/_urlparse.py index bf190fd560..34244d0124 100644 --- a/httpx/_urlparse.py +++ b/httpx/_urlparse.py @@ -166,9 +166,9 @@ class ParseResult(typing.NamedTuple): @property def authority(self) -> str: + # Do not include the userinfo here, to avoid leaking credentials return "".join( [ - f"{self.userinfo}@" if self.userinfo else "", f"[{self.host}]" if ":" in self.host else self.host, f":{self.port}" if self.port is not None else "", ] diff --git a/httpx/_urls.py b/httpx/_urls.py index 147a8fa333..e7e488101e 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -375,30 +375,7 @@ def __str__(self) -> str: return str(self._uri_reference) def __repr__(self) -> str: - scheme, userinfo, host, port, path, query, fragment = self._uri_reference - - if ":" in userinfo: - # Mask any password component. - userinfo = f'{userinfo.split(":")[0]}:[secure]' - - authority = "".join( - [ - f"{userinfo}@" if userinfo else "", - f"[{host}]" if ":" in host else host, - f":{port}" if port is not None else "", - ] - ) - url = "".join( - [ - f"{self.scheme}:" if scheme else "", - f"//{authority}" if authority else "", - path, - f"?{query}" if query is not None else "", - f"#{fragment}" if fragment is not None else "", - ] - ) - - return f"{self.__class__.__name__}({url!r})" + return f"{self.__class__.__name__}({str(self._uri_reference)!r})" @property def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: nocover diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 7638b8bd68..166629dc42 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -302,7 +302,7 @@ async def test_auth_disable_per_request() -> None: def test_auth_hidden_url() -> None: url = "http://example-username:example-password@example.org/" - expected = "URL('http://example-username:[secure]@example.org/')" + expected = "URL('http://example.org/')" assert url == httpx.URL(url) assert expected == repr(httpx.URL(url)) diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py index 06c28e1e30..f470319ad8 100644 --- a/tests/models/test_responses.py +++ b/tests/models/test_responses.py @@ -89,7 +89,8 @@ def test_response_json(): def test_raise_for_status(): - request = httpx.Request("GET", "https://example.org") + # The credentials here should be removed from all exception strings below + request = httpx.Request("GET", "https://user:password@example.org") # 2xx status codes are not an error. response = httpx.Response(200, request=request) diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 03072e8f5c..4c2119907f 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -591,7 +591,10 @@ def test_url_copywith_authority_subcomponents(): } url = httpx.URL("https://example.org") new = url.copy_with(**copy_with_kwargs) - assert str(new) == "https://username:password@example.net:444" + assert str(new) == "https://example.net:444" + assert new.username == "username" + assert new.password == "password" + assert new.userinfo == b"username:password" def test_url_copywith_netloc(): @@ -610,7 +613,7 @@ def test_url_copywith_userinfo_subcomponents(): } url = httpx.URL("https://example.org") new = url.copy_with(**copy_with_kwargs) - assert str(new) == "https://tom%40example.org:abc123%40%20%@example.org" + assert str(new) == "https://example.org" assert new.username == "tom@example.org" assert new.password == "abc123@ %" assert new.userinfo == b"tom%40example.org:abc123%40%20%" diff --git a/tests/test_utils.py b/tests/test_utils.py index f9c215f65a..2bd47e0810 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -65,6 +65,26 @@ def test_logging_request(server, caplog): ] +def test_logging_request_with_auth(server, caplog): + caplog.set_level(logging.INFO) + with httpx.Client() as client: + credentials = { + "username": "user", + "password": "abc123%", + } + url = server.url.copy_with(**credentials) + response = client.get(url) + assert response.status_code == 200 + + assert caplog.record_tuples == [ + ( + "httpx", + logging.INFO, + 'HTTP Request: GET http://127.0.0.1:8000/ "HTTP/1.1 200 OK"', + ) + ] + + def test_logging_redirect_chain(server, caplog): caplog.set_level(logging.INFO) with httpx.Client(follow_redirects=True) as client: