From 01dd1f4652e10b1f5b3a44084130bc078bcb7229 Mon Sep 17 00:00:00 2001 From: lin Date: Wed, 4 Nov 2026 13:03:40 +0800 Subject: [PATCH] Preserve empty relative paths during normalization --- src/hyperlink/_url.py | 5 +++-- src/hyperlink/test/test_decoded_url.py | 14 ++++++++++++++ src/hyperlink/test/test_url.py | 12 ++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 8797b5cc..62a0d7c1 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1469,7 +1469,8 @@ def normalize( * Uppercase remaining percent-encoded octets (`RFC 3986 2.1`_) * Convert scheme and host casing to lowercase (`RFC 3986 3.2.2`_) * Resolve any "." and ".." references in the path (`RFC 3986 6.2.2.3`_) - * Ensure an ending slash on URLs with an empty path (`RFC 3986 6.2.3`_) + * Ensure an ending slash on URLs with a host and an empty path + (`RFC 3986 6.2.3`_) * Encode any stray percent signs (`%`) in percent-encoded fields (path, query, fragment, userinfo) (`RFC 3986 2.4`_) @@ -1515,7 +1516,7 @@ def _dec_unres(target): kw["path"] = [ _dec_unres(p) for p in _resolve_dot_segments(self.path) ] - else: + elif self.host: kw["path"] = (u"",) if query: kw["query"] = [ diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 48452579..d967e3ab 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -15,6 +15,20 @@ class TestURL(HyperlinkTestCase): + def test_normalize_empty_relative_path(self): + # type: () -> None + base = DecodedURL.from_text("http://example.com/a/b?old=1") + for text in ("", "?x=1", "#section", "?x=1#section"): + reference = DecodedURL.from_text(text) + normalized = reference.normalize() + assert normalized.path == () + assert not normalized.rooted + assert normalized.to_text() == text + assert base.click(normalized.to_text()) == base.click(text) + assert base.encoded_url.click(normalized.encoded_url) == ( + base.encoded_url.click(reference.encoded_url) + ) + def test_durl_basic(self): # type: () -> None bdurl = DecodedURL.from_text(BASIC_URL) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 37c91726..69e4b0e0 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1461,6 +1461,18 @@ def test_normalize(self): == "ftp://%25:%25@/%25%25%25/%25a%25b?%25=%25%25#%25" ) + def test_normalize_empty_relative_path(self): + # type: () -> None + base = URL.from_text("http://example.com/a/b?old=1") + for text in ("", "?x=1", "#section", "?x=1#section"): + reference = URL.from_text(text) + normalized = reference.normalize() + assert normalized.path == () + assert not normalized.rooted + assert normalized.to_text() == text + assert base.click(normalized) == base.click(reference) + assert normalized.normalize() == normalized + def test_str(self): # type: () -> None