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
6 changes: 5 additions & 1 deletion docs/source/release-notes/unreleased.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
2.x.y - 202z-aa-bb
------------------

- *Add Items here*
- Preserve the leading slash required by RFC 3986 when removing dot segments
from rootless paths, fixing resolution against bases such as
``scheme:foo/bar``. See `issue #84`_.

.. links below here

.. _issue #84: https://github.com/python-hyper/rfc3986/issues/84
11 changes: 5 additions & 6 deletions src/rfc3986/normalizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,12 @@ def remove_dot_segments(s: str) -> str:
# element
elif output:
output.pop()
# Preserve the slash that replaces '/..' when the last segment
# is removed, even if the original path was rootless.
if not output:
output.append("")

# If the path starts with '/' and the output is empty or the first string
# is non-empty
if s.startswith("/") and (not output or output[0]):
output.insert(0, "")

# If the path starts with '/.' or '/..' ensure we add one more empty
# If the path ends with '/.' or '/..' ensure we add one more empty
# string to add a trailing '/'
if s.endswith(("/.", "/..")):
output.append("")
Expand Down
9 changes: 9 additions & 0 deletions tests/test_normalizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ def test_normalize_percent_characters():
("//a/./b/../b/%63/%7Bfoo%7D", "//a/b/%63/%7Bfoo%7D"),
("mid/content=5/../6", "mid/6"),
("/a/b/c/./../../g", "/a/g"),
("foo/../baz", "/baz"),
("foo/..", "/"),
("foo/../", "/"),
("foo/bar/../../baz", "/baz"),
("foo/../../baz", "/baz"),
("foo/..//baz", "//baz"),
("foo//../baz", "foo/baz"),
("../", ""),
("../../baz", "baz"),
]


Expand Down
16 changes: 16 additions & 0 deletions tests/test_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,22 @@ def test_uris_with_no_authority_with_query_only_are_absolute(


class TestURIReferencesResolve:
@pytest.mark.parametrize(
["relative", "expected"],
[
("../baz", "scheme:/baz"),
("..", "scheme:/"),
("../../baz", "scheme:/baz"),
("../baz?query#fragment", "scheme:/baz?query#fragment"),
("baz", "scheme:foo/baz"),
],
)
def test_resolve_with_rootless_base(self, relative, expected):
base = URIReference.from_string("scheme:foo/bar")
reference = URIReference.from_string(relative)

assert reference.resolve_with(base).unsplit() == expected

def test_with_basic_and_relative_uris(self, basic_uri, relative_uri):
R = URIReference.from_string(relative_uri)
B = URIReference.from_string(basic_uri)
Expand Down