From 34eb888deb36402f395f49287b6fdfa859c10c82 Mon Sep 17 00:00:00 2001 From: Octavio Galland Date: Thu, 27 Aug 2026 10:39:47 -0300 Subject: [PATCH 1/2] don't lower case-sensitive user info embedded in URLs --- cachecontrol/controller.py | 3 ++- tests/test_cache_control.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cachecontrol/controller.py b/cachecontrol/controller.py index 03b2218..4a99a6e 100644 --- a/cachecontrol/controller.py +++ b/cachecontrol/controller.py @@ -70,7 +70,8 @@ def _urlnorm(cls, uri: str) -> str: raise Exception("Only absolute URIs are allowed. uri = %s" % uri) scheme = scheme.lower() - authority = authority.lower() + userinfo, separator, host = authority.rpartition("@") + authority = f"{userinfo}{separator}{host.lower()}" if not path: path = "/" diff --git a/tests/test_cache_control.py b/tests/test_cache_control.py index 9d3d4d8..1c30330 100644 --- a/tests/test_cache_control.py +++ b/tests/test_cache_control.py @@ -322,3 +322,11 @@ def test_cached_request_with_malformed_date_not_returned(self): self.c.cache = DictCache({self.url: resp}) assert not self.req({}) + + +class TestCacheControllerUrlNormalization: + def test_cache_url_preserves_userinfo_case(self): + url = "HTTP://Alice:Secret@EXAMPLE.COM/resource" + expected_url = "http://Alice:Secret@example.com/resource" + + assert CacheController.cache_url(url) == expected_url From 9f3617214b053fb52113586951f6dd1797a1c335 Mon Sep 17 00:00:00 2001 From: Octavio Galland Date: Thu, 27 Aug 2026 13:58:00 -0300 Subject: [PATCH 2/2] streamline user info case test --- tests/test_cache_control.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/test_cache_control.py b/tests/test_cache_control.py index 1c30330..6260ff7 100644 --- a/tests/test_cache_control.py +++ b/tests/test_cache_control.py @@ -127,6 +127,21 @@ def test_no_cache_with_vary_star(self, cc): assert not cc.cache.set.called + def test_cache_response_preserves_userinfo_case(self): + cache = DictCache({}) + cc = CacheController(cache) + url = "http://Alice:Secret@localhost/" + req = DummyRequest(url=url, headers={}) + resp = DummyResponse(status=200, headers={"ETag": "xyz"}) + cc.cache_response(req, resp, b"") + + req = DummyRequest( + url="http://alice:secret@localhost/", headers={"if-match": "xyz"} + ) + result = cc.conditional_headers(req) + + assert not result + def test_update_cached_response_no_local_cache(self): """ If the local cache doesn't have the given URL, just reuse the response @@ -322,11 +337,3 @@ def test_cached_request_with_malformed_date_not_returned(self): self.c.cache = DictCache({self.url: resp}) assert not self.req({}) - - -class TestCacheControllerUrlNormalization: - def test_cache_url_preserves_userinfo_case(self): - url = "HTTP://Alice:Secret@EXAMPLE.COM/resource" - expected_url = "http://Alice:Secret@example.com/resource" - - assert CacheController.cache_url(url) == expected_url