From: Alan Li <61896187+lebr0nli@users.noreply.github.com> Date: Tue, 3 May 2022 10:33:13 +0000 (+0800) Subject: Patch `copy_with` (#2185) X-Git-Tag: 0.23.0~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e9b0c85dd4f4e4469c57c4b38e5101fd12081b5c;p=thirdparty%2Fhttpx.git Patch `copy_with` (#2185) * Patch `copy_with` * Add a new test for `copy_with` --- diff --git a/httpx/_urls.py b/httpx/_urls.py index 70486bc9..f6788e55 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -484,7 +484,11 @@ class URL: # \_/ \______________/\_________/ \_________/ \__/ # | | | | | # scheme authority path query fragment - return URL(self._uri_reference.copy_with(**kwargs).unsplit()) + new_url = URL(self) + new_url._uri_reference = self._uri_reference.copy_with(**kwargs) + if new_url.is_absolute_url: + new_url._uri_reference = new_url._uri_reference.normalize() + return URL(new_url) def copy_set_param(self, key: str, value: typing.Any = None) -> "URL": return self.copy_with(params=self.params.set(key, value)) diff --git a/tests/models/test_url.py b/tests/models/test_url.py index cd099bd9..a088fc2a 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -308,6 +308,55 @@ def test_url_copywith_raw_path(): assert url.raw_path == b"/some/path?a=123" +def test_url_copywith_security(): + """ + Prevent unexpected changes on URL after calling copy_with (CVE-2021-41945) + """ + url = httpx.URL("https://u:p@[invalid!]//evilHost/path?t=w#tw") + original_scheme = url.scheme + original_userinfo = url.userinfo + original_netloc = url.netloc + original_raw_path = url.raw_path + original_query = url.query + original_fragment = url.fragment + url = url.copy_with() + assert url.scheme == original_scheme + assert url.userinfo == original_userinfo + assert url.netloc == original_netloc + assert url.raw_path == original_raw_path + assert url.query == original_query + assert url.fragment == original_fragment + + url = httpx.URL("https://u:p@[invalid!]//evilHost/path?t=w#tw") + original_scheme = url.scheme + original_netloc = url.netloc + original_raw_path = url.raw_path + original_query = url.query + original_fragment = url.fragment + url = url.copy_with(userinfo=b"") + assert url.scheme == original_scheme + assert url.userinfo == b"" + assert url.netloc == original_netloc + assert url.raw_path == original_raw_path + assert url.query == original_query + assert url.fragment == original_fragment + + url = httpx.URL("https://example.com/path?t=w#tw") + original_userinfo = url.userinfo + original_netloc = url.netloc + original_raw_path = url.raw_path + original_query = url.query + original_fragment = url.fragment + bad = "https://xxxx:xxxx@xxxxxxx/xxxxx/xxx?x=x#xxxxx" + url = url.copy_with(scheme=bad) + assert url.scheme == bad + assert url.userinfo == original_userinfo + assert url.netloc == original_netloc + assert url.raw_path == original_raw_path + assert url.query == original_query + assert url.fragment == original_fragment + + def test_url_invalid(): with pytest.raises(httpx.InvalidURL): httpx.URL("https://😇/")