Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/hyperlink/_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,10 +1064,20 @@ def __init__(
def get_decoded_url(self, lazy=False):
# type: (bool) -> DecodedURL
try:
return self._decoded_url
decoded_url = self._decoded_url
except AttributeError:
self._decoded_url = DecodedURL(self, lazy=lazy) # type: DecodedURL
return self._decoded_url
return self._decoded_url
if not lazy:
# A cached wrapper may have been created without validation.
(
decoded_url.host,
decoded_url.userinfo,
decoded_url.path,
decoded_url.query,
decoded_url.fragment,
)
return decoded_url

@property
def scheme(self):
Expand Down
35 changes: 35 additions & 0 deletions src/hyperlink/test/test_decoded_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@ def test_passthroughs(self):
assert not (durl == 1)
assert durl != 1

def test_eager_decoding_validates_lazy_cache(self):
# type: () -> None
"""A cached lazy wrapper must not bypass eager decoding errors."""
urls = [
"https://example.com/%FF",
"https://example.com/?%FF=value",
"https://example.com/?key=%FF",
"https://example.com/#%FF",
"https://%FF@example.com/",
"https://user:%FF@example.com/",
]
for text in urls:
url = URL.from_text(text)
self.assertRaises(UnicodeDecodeError, url.get_decoded_url)
lazy_url = url.get_decoded_url(lazy=True)
# Repeated eager attempts must still fail after a failed decode.
self.assertRaises(UnicodeDecodeError, url.get_decoded_url)
self.assertRaises(UnicodeDecodeError, url.get_decoded_url)
self.assertTrue(url.get_decoded_url(lazy=True) is lazy_url)

def test_eager_decoding_populates_lazy_cache(self):
# type: () -> None
"""Eager access validates the cached wrapper without replacing it."""
url = URL.from_text(TOTAL_URL)
lazy_url = url.get_decoded_url(lazy=True)
self.assertFalse(hasattr(lazy_url, "_path"))
self.assertTrue(url.get_decoded_url() is lazy_url)
self.assertTrue(hasattr(lazy_url, "_path"))
self.assertTrue(hasattr(lazy_url, "_query"))
self.assertTrue(hasattr(lazy_url, "_fragment"))
self.assertTrue(hasattr(lazy_url, "_userinfo"))
self.assertTrue(url.get_decoded_url() is lazy_url)
self.assertTrue(url.get_decoded_url(lazy=True) is lazy_url)
self.assertEqual(lazy_url.path, DecodedURL(url).path)

def test_repr(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
Expand Down