From 655773e1c1b75895eda927d5a9d22a3b5b8f572d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 11 Aug 2020 17:18:12 +0100 Subject: [PATCH] Handle URL quoting username and password components. (#1159) * Handle URL quoting username and password components * Tweak userinfo quoting --- httpx/_models.py | 12 ++++++------ tests/models/test_url.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index 8d294e7f23..d6bc46e163 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -7,7 +7,7 @@ import warnings from collections.abc import MutableMapping from http.cookiejar import Cookie, CookieJar -from urllib.parse import parse_qsl, urlencode +from urllib.parse import parse_qsl, quote, unquote, urlencode import chardet import rfc3986 @@ -100,12 +100,12 @@ def userinfo(self) -> str: @property def username(self) -> str: userinfo = self._uri_reference.userinfo or "" - return userinfo.partition(":")[0] + return unquote(userinfo.partition(":")[0]) @property def password(self) -> str: userinfo = self._uri_reference.userinfo or "" - return userinfo.partition(":")[2] + return unquote(userinfo.partition(":")[2]) @property def host(self) -> str: @@ -175,8 +175,8 @@ def copy_with(self, **kwargs: typing.Any) -> "URL": ): host = kwargs.pop("host", self.host) port = kwargs.pop("port", self.port) - username = kwargs.pop("username", self.username) - password = kwargs.pop("password", self.password) + username = quote(kwargs.pop("username", self.username) or "") + password = quote(kwargs.pop("password", self.password) or "") authority = host if port is not None: @@ -193,7 +193,7 @@ def copy_with(self, **kwargs: typing.Any) -> "URL": def join(self, url: URLTypes) -> "URL": """ - Return an absolute URL, using given this URL as the base. + Return an absolute URL, using this URL as the base. """ if self.is_relative_url: return URL(url) diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 19e07575d3..c4975a92e9 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -187,6 +187,18 @@ def test_url_copywith_for_authority(): assert str(new) == "https://username:password@example.net:444" +def test_url_copywith_for_userinfo(): + copy_with_kwargs = { + "username": "tom@example.org", + "password": "abc123@ %", + } + url = URL("https://example.org") + new = url.copy_with(**copy_with_kwargs) + assert str(new) == "https://tom%40example.org:abc123%40%20%25@example.org" + assert new.username == "tom@example.org" + assert new.password == "abc123@ %" + + def test_url_invalid(): with pytest.raises(InvalidURL): URL("https://😇/")