diff --git a/CHANGES/6494.bugfix.rst b/CHANGES/6494.bugfix.rst new file mode 100644 index 00000000000..3827644f0d1 --- /dev/null +++ b/CHANGES/6494.bugfix.rst @@ -0,0 +1 @@ +Added support for URL credentials with empty (zero-length) username, e.g. ``https://:password@host`` -- by :user:`shuckc` diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3f4a257a678..b542cae7021 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -77,6 +77,7 @@ Chih-Yuan Chen Chris AtLee Chris Laws Chris Moore +Chris Shucksmith Christopher Schmitt Claudiu Popa Colin Dunklau diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 3bba912f1d1..1cb0ca151ed 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -338,8 +338,8 @@ def update_host(self, url: URL) -> None: # basic auth info username, password = url.user, url.password - if username: - self.auth = helpers.BasicAuth(username, password or "") + if username or password: + self.auth = helpers.BasicAuth(username or "", password or "") def update_version(self, version: Union[http.HttpVersion, str]) -> None: """Convert request version to two elements tuple. diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 2be5977c490..9a9252a27f2 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -162,9 +162,9 @@ def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth" """Create BasicAuth from url.""" if not isinstance(url, URL): raise TypeError("url should be yarl.URL instance") - if url.user is None: + if url.user is None and url.password is None: return None - return cls(url.user, url.password or "", encoding=encoding) + return cls(url.user or "", url.password or "", encoding=encoding) def encode(self) -> str: """Encode credentials.""" diff --git a/tests/test_client_request.py b/tests/test_client_request.py index a17c979c651..ecd02895e94 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -448,6 +448,13 @@ def test_basic_auth_from_url(make_request: _RequestMaker) -> None: assert "python.org" == req.host +def test_basic_auth_no_user_from_url(make_request: _RequestMaker) -> None: + req = make_request("get", "http://:1234@python.org") + assert "AUTHORIZATION" in req.headers + assert "Basic OjEyMzQ=" == req.headers["AUTHORIZATION"] + assert "python.org" == req.host + + def test_basic_auth_from_url_overridden(make_request: _RequestMaker) -> None: req = make_request( "get", "http://garbage@python.org", auth=aiohttp.BasicAuth("nkim", "1234") diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 37963167c07..b6c580e51fd 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -208,6 +208,14 @@ def test_basic_auth_from_url() -> None: assert auth.password == "pass" +def test_basic_auth_no_user_from_url() -> None: + url = URL("http://:pass@example.com") + auth = helpers.BasicAuth.from_url(url) + assert auth is not None + assert auth.login == "" + assert auth.password == "pass" + + def test_basic_auth_from_not_url() -> None: with pytest.raises(TypeError): helpers.BasicAuth.from_url("http://user:pass@example.com") # type: ignore[arg-type]