lib.Uri: Fix local path edge cases #84

Merged
Jan Lindemann merged 2 commits from jan/fix/20260905-lib-uri-fix-path-join-for-empty-authority into master 2026-09-05 14:10:53 +02:00 AGit
2 changed files with 29 additions and 1 deletions

View file

@ -18,6 +18,11 @@ class Uri:
if self.__p.path.startswith('~'):
return self.path
if scheme:
if self.hostname is None and not self.path.startswith('/'):
# -- No authority and a relative path: '://' would
# re-parse the first path segment as a host
ret += f'{self.protocol}:'
else:
ret += f'{self.protocol}://'
if credentials and self.username:
ret += self.username
@ -156,6 +161,12 @@ class Uri:
ret.__password = None
if not path:
return ret
if ret.__string.endswith('://'):
# -- Empty authority: the trailing '/' is part of the '://'
# separator, so a leading slash in path must be kept to stay
# an absolute path
ret.__string += path if path.startswith('/') else '/' + path
return ret
if ret.__string[-1] == '/':
if path[0] == '/':
ret.__string += path[1:]

View file

@ -21,6 +21,23 @@ assert '<hidden>' in u.safe_full_with_username # safe version hides password
u = Uri('/local/path')
assert u.scheme == 'file'
assert u.protocol == 'file'
assert u.full == 'file:///local/path'
# Relative paths default to file, too
u = Uri('../../local/path')
assert u.scheme == 'file'
assert u.protocol == 'file'
assert u.path == '../../local/path'
# full keeps relative paths relative: '://' would re-parse the
# first segment as a host
assert u.full == 'file:../../local/path'
assert Uri(u.full).path == '../../local/path'
# An explicit file: scheme with a relative path round-trips, too
u = Uri('file:../../local/path')
assert u.path == '../../local/path'
assert u.full == 'file:../../local/path'
# Pimp returns existing Uri unchanged
u1 = Uri('ssh://host/path')