Skip to content

Commit 63acb6a

Browse files
committed
fix(uri): 🐛 treat file:// URIs with non-local authority as remote
1 parent 2762f4b commit 63acb6a

1 file changed

Lines changed: 29 additions & 5 deletions

File tree

rocrate_validator/utils/uri.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ class URI:
114114
"hdfs",
115115
)
116116

117+
# ``file://`` authorities that denote the local machine (RFC 8089 §2):
118+
# an empty authority (``file:///path``) or the special ``localhost`` host.
119+
LOCAL_FILE_AUTHORITIES = ("", "localhost")
120+
117121
# Backwards-compatible alias kept for callers that still inspect it.
118122
REMOTE_SUPPORTED_SCHEMA = SUPPORTED_ROCRATE_SCHEMES[:-1] # http, https, ftp
119123

@@ -123,12 +127,20 @@ def __init__(self, uri: Union[str, Path]):
123127
self._uri = uri = str(uri)
124128
try:
125129
# Inputs that are not external references are assumed to be local
126-
# paths, so the ``file://`` scheme is added explicitly. The
130+
# paths, so the ``file:`` scheme is added explicitly. The
127131
# detection covers both authority-based schemes (``http://``,
128132
# ``scp://``) and scheme-only ones (``urn:``, ``doi:``), as
129133
# defined by RFC 3986.
134+
#
135+
# The authority-less ``file:`` form (no ``//``) is used on purpose:
136+
# ``file://data/x`` would parse ``data`` as the authority (host),
137+
# while ``file:data/x`` keeps ``data/x`` as the path with an empty
138+
# authority. This way a local path never gains a spurious host and
139+
# the authority remains a reliable signal to tell a local file
140+
# (``file:///path``) from a remote one (``file://host/path``,
141+
# RFC 8089).
130142
if not is_external_reference(uri):
131-
uri = f"file://{uri}"
143+
uri = f"file:{uri}"
132144
# parse the value to extract the scheme
133145
self._parse_result = urlparse(uri)
134146
if not self.scheme:
@@ -181,11 +193,23 @@ def as_path(self) -> Path:
181193
return Path(self._uri)
182194

183195
def is_remote_resource(self) -> bool:
184-
"""Return True for any well-formed URI whose scheme is not `file`."""
185-
return bool(self.scheme) and self.scheme != "file"
196+
"""
197+
Return True for any well-formed URI that points to a non-local resource.
198+
199+
Schemes other than ``file`` (``http``, ``scp``, ``s3``, ...) are always
200+
remote. A ``file://`` URI is remote when it carries an explicit,
201+
non-local authority (host) — e.g. ``file://hostname/path`` per
202+
RFC 8089: the referenced file lives on another machine and is therefore
203+
not part of the local RO-Crate payload.
204+
"""
205+
if not self.scheme:
206+
return False
207+
if self.scheme == "file":
208+
return self.get_netloc().lower() not in self.LOCAL_FILE_AUTHORITIES
209+
return True
186210

187211
def is_local_resource(self) -> bool:
188-
return self.scheme == "file"
212+
return self.scheme == "file" and self.get_netloc().lower() in self.LOCAL_FILE_AUTHORITIES
189213

190214
def is_natively_checkable(self) -> bool:
191215
"""Return True if availability can be verified via a native request."""

0 commit comments

Comments
 (0)