]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Patch `copy_with` (#2185)
authorAlan Li <61896187+lebr0nli@users.noreply.github.com>
Tue, 3 May 2022 10:33:13 +0000 (18:33 +0800)
committerGitHub <noreply@github.com>
Tue, 3 May 2022 10:33:13 +0000 (11:33 +0100)
* Patch `copy_with`

* Add a new test for `copy_with`

httpx/_urls.py
tests/models/test_url.py

index 70486bc9e480c03452711cdbec9ebc0a99aaaece..f6788e5568168a748974888f2232315894d5e251 100644 (file)
@@ -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))
index cd099bd931ada0eb7f953fee6a583e0a3044ff89..a088fc2a1031e5cbb32ddc0310aa2f038c7dab9d 100644 (file)
@@ -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://😇/")