Merge pull request #10172 from ThomasWaldmann/reject-unc-paths-10164

Location: reject UNC paths everywhere early with a hint, fixes #10164
This commit is contained in:
TW
2026-08-21 21:04:17 +02:00
committed by GitHub
3 changed files with 24 additions and 12 deletions
@@ -12,6 +12,10 @@ expanded by your shell).
Note: You may also prepend ``file://`` to a filesystem path to use URL style.
Note: UNC paths (``//server/share/path``, ``\\server\share\path``) are not
supported — mount the share (on Windows: map it to a drive letter, e.g.
``net use X: \\server\share``) and use the mounted path instead.
**Remote repositories** accessed via SSH user@host (REST http over stdio):
``rest://user@host:port//abs/path/to/repo`` — absolute path
+10 -6
View File
@@ -724,9 +724,9 @@ class Location:
BORGSTORE_SCHEMES = ("sftp", "http", "https", "s3", "b2", "rclone")
# path may contain any chars. to avoid ambiguities with other regexes, it must not start with
# "//", a "scheme://" or one of the borgstore "scheme:" specifiers (all of which are matched
# a "scheme://" or one of the borgstore "scheme:" specifiers (all of which are matched
# before local_re in _parse). the borgstore scheme list is sourced from BORGSTORE_SCHEMES.
local_path_re = r"(?!(//|(?:ssh|file)://|(?:" + "|".join(BORGSTORE_SCHEMES) + r"):))" r"(?P<path>.+)"
local_path_re = r"(?!((?:ssh|file)://|(?:" + "|".join(BORGSTORE_SCHEMES) + r"):))" r"(?P<path>.+)"
# abs_path must start with a slash (or drive letter on Windows).
abs_path_re = r"(?P<path>[A-Za-z]:/.+)" if is_win32 else r"(?P<path>/.+)"
@@ -796,11 +796,15 @@ class Location:
self.raw = text # as given by user, might contain placeholders
self.processed = replace_placeholders(self.raw, overrides) # after placeholder replacement
valid = self._parse(self.processed)
if valid:
self.valid = True
else:
if not self._parse(self.processed):
raise ValueError('Invalid location format: "%s"' % self.processed)
if self.proto == "file" and self.path.startswith("//"):
# UNC path (//server/share/..., \\server\share\...): not supported, fail early with a hint, see #10164.
raise ValueError(
f'UNC paths are not supported: "{self.processed}". '
"Mount the share (Windows: net use X: \\\\server\\share) and use the mounted path instead."
)
self.valid = True
def _parse(self, text):
m = self.ssh_re.match(text)
+10 -6
View File
@@ -264,13 +264,17 @@ class TestLocationWithoutEnv:
repr(Location(url)) == f"Location(proto='file', user=None, pass=None, host=None, port=None, path='{path}')"
)
@pytest.mark.skipif(is_win32, reason="still broken")
def test_smb(self, monkeypatch):
def test_unc_rejected(self, monkeypatch):
# UNC paths are not supported (#3141), the parser rejects all spellings early, see #10164.
monkeypatch.delenv("BORG_REPO", raising=False)
assert (
repr(Location("file:////server/share/path"))
== "Location(proto='file', user=None, pass=None, host=None, port=None, path='//server/share/path')"
)
with pytest.raises(ValueError, match="UNC paths are not supported"):
Location("//server/share/repo")
# on win32, file:/// must be followed by a drive letter, so this is invalid even earlier.
with pytest.raises(ValueError, match="Invalid location" if is_win32 else "UNC paths are not supported"):
Location("file:////server/share/repo")
if is_win32:
with pytest.raises(ValueError, match="UNC paths are not supported"):
Location(r"\\server\share\repo")
def test_folder(self, monkeypatch):
monkeypatch.delenv("BORG_REPO", raising=False)