Skip to content

Commit d5878c9

Browse files
Merge pull request #131 from ThomasWaldmann/url-unquote
posixfs, s3, sftp: URL-unquote
2 parents cd77b47 + bd26416 commit d5878c9

File tree

5 files changed

+34
-7
lines changed

5 files changed

+34
-7
lines changed

src/borgstore/backends/posixfs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import re
77
import sys
8+
from urllib.parse import unquote
89
from pathlib import Path
910
import shutil
1011
import stat
@@ -36,13 +37,14 @@ def get_file_backend(url):
3637
file:// # only empty host part is supported.
3738
(?P<path>(/.*)) # path must be an absolute path. 3rd slash is separator AND part of the path.
3839
"""
40+
# the path or drive_and_path could be URL-quoted and thus must be URL-unquoted
3941
if sys.platform in ("win32", "msys", "cygwin"):
4042
m = re.match(windows_file_regex, url, re.VERBOSE)
4143
if m:
42-
return PosixFS(path=m["drive_and_path"])
44+
return PosixFS(path=unquote(m["drive_and_path"]))
4345
m = re.match(file_regex, url, re.VERBOSE)
4446
if m:
45-
return PosixFS(path=m["path"])
47+
return PosixFS(path=unquote(m["path"]))
4648

4749

4850
class PosixFS(BackendBase):

src/borgstore/backends/rclone.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def get_rclone_backend(url):
6262
"""
6363
m = re.match(rclone_regex, url, re.VERBOSE)
6464
if m:
65+
# no URL-unquote here, we just pass through the rclone remote spec "as is"
6566
return Rclone(path=m["path"])
6667

6768

src/borgstore/backends/s3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ def get_s3_backend(url: str):
6868
schema = m["schema"]
6969
hostname = m["hostname"]
7070
port = m["port"]
71-
bucket = m["bucket"]
72-
path = m["path"]
71+
bucket = m["bucket"] # no unquote: all valid bucket characters are URL-safe
72+
path = urllib.parse.unquote(m["path"])
7373

7474
endpoint_url = None
7575
if schema and hostname:

src/borgstore/backends/sftp.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
from pathlib import Path
6+
from urllib.parse import unquote
67
import random
78
import re
89
import stat
@@ -45,7 +46,12 @@ def get_sftp_backend(url):
4546
"""
4647
m = re.match(sftp_regex, url, re.VERBOSE)
4748
if m:
48-
return Sftp(username=m["username"], hostname=m["hostname"], port=int(m["port"] or "0"), path=m["path"])
49+
return Sftp(
50+
username=unquote(m["username"]) if m["username"] else None,
51+
hostname=m["hostname"],
52+
port=int(m["port"] or "0"),
53+
path=unquote(m["path"]),
54+
)
4955

5056

5157
class Sftp(BackendBase):

tests/test_backends.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ def get_backend_from_fixture(tested_backends, request):
167167
return request.getfixturevalue(tested_backends)
168168

169169

170-
POSIX_ABS_TESTCASES = [("file:///absolute/path", "/absolute/path")]
171-
WINDOWS_ABS_TESTCASES = [("file:///C:/absolute/path", "C:/absolute/path")]
170+
POSIX_ABS_TESTCASES = [("file:///absolute/path", "/absolute/path"), ("file:///absolute/my%20path", "/absolute/my path")]
171+
WINDOWS_ABS_TESTCASES = [
172+
("file:///C:/absolute/path", "C:/absolute/path"),
173+
("file:///C:/absolute/my%20path", "C:/absolute/my path"),
174+
]
172175

173176

174177
@pytest.mark.parametrize("url,path", WINDOWS_ABS_TESTCASES if os.name == "nt" else POSIX_ABS_TESTCASES)
@@ -210,6 +213,8 @@ def test_invalid_or_remote_file_url(url):
210213
("sftp://username@hostname:2222//abs/path", "username", "hostname", 2222, "/abs/path"),
211214
("sftp://username@hostname//abs/path", "username", "hostname", 0, "/abs/path"),
212215
("sftp://hostname//abs/path", None, "hostname", 0, "/abs/path"),
216+
("sftp://username@hostname/rel/my%20path", "username", "hostname", 0, "rel/my path"),
217+
("sftp://username@hostname//abs/my%20path", "username", "hostname", 0, "/abs/my path"),
213218
],
214219
)
215220
def test_sftp_url(url, username, hostname, port, path):
@@ -221,6 +226,19 @@ def test_sftp_url(url, username, hostname, port, path):
221226
assert backend.base_path == path
222227

223228

229+
@pytest.mark.parametrize(
230+
"url,bucket,path", [("s3:/bucket/path", "bucket", "path"), ("s3:/bucket/my%20path", "bucket", "my path")]
231+
)
232+
def test_s3_url(url, bucket, path):
233+
from unittest.mock import patch, MagicMock
234+
235+
with patch("borgstore.backends.s3.boto3", MagicMock()):
236+
backend = get_s3_backend(url)
237+
assert isinstance(backend, S3)
238+
assert backend.bucket == bucket
239+
assert backend.base_path == path + "/"
240+
241+
224242
def test_flat(tested_backends, request):
225243
with get_backend_from_fixture(tested_backends, request) as backend:
226244
k0, v0 = key(0), b"value0"

0 commit comments

Comments
 (0)