diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 8797b5c..62a0d7c 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 4845257..d967e3a 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 37c9172..69e4b0e 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