Skip to content

Commit 22a1964

Browse files
authored
added quote to urls, adjusted tests to cover this (#194)
Ref #193 The same as in #192 but for all other DAV methods. Tests were expanded to catch this case. Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent f29e3bb commit 22a1964

File tree

4 files changed

+96
-46
lines changed

4 files changed

+96
-46
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77
### Fixed
88

99
- files: proper url encoding of special chars in `mkdir` and `delete` methods. #191 Thanks to @tobenary
10+
- files: proper url encoding of special chars in all other `DAV` methods. #194
1011

1112
## [0.7.1 - 2022-12-21]
1213

nc_py_api/files/files.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def find(self, req: list, path: str | FsNode = "") -> list[FsNode]:
8686
def download(self, path: str | FsNode) -> bytes:
8787
"""Downloads and returns the content of a file."""
8888
path = path.user_path if isinstance(path, FsNode) else path
89-
response = self._session.adapter_dav.get(dav_get_obj_path(self._session.user, path))
89+
response = self._session.adapter_dav.get(quote(dav_get_obj_path(self._session.user, path)))
9090
check_error(response, f"download: user={self._session.user}, path={path}")
9191
return response.content
9292

@@ -138,7 +138,7 @@ def upload(self, path: str | FsNode, content: bytes | str) -> FsNode:
138138
"""
139139
path = path.user_path if isinstance(path, FsNode) else path
140140
full_path = dav_get_obj_path(self._session.user, path)
141-
response = self._session.adapter_dav.put(full_path, content=content)
141+
response = self._session.adapter_dav.put(quote(full_path), content=content)
142142
check_error(response, f"upload: user={self._session.user}, path={path}, size={len(content)}")
143143
return FsNode(full_path.strip("/"), **etag_fileid_from_response(response))
144144

@@ -219,11 +219,11 @@ def move(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=False)
219219
full_dest_path = dav_get_obj_path(
220220
self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
221221
)
222-
dest = self._session.cfg.dav_endpoint + full_dest_path
222+
dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
223223
headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
224224
response = self._session.adapter_dav.request(
225225
"MOVE",
226-
dav_get_obj_path(self._session.user, path_src),
226+
quote(dav_get_obj_path(self._session.user, path_src)),
227227
headers=headers,
228228
)
229229
check_error(response, f"move: user={self._session.user}, src={path_src}, dest={dest}, {overwrite}")
@@ -241,11 +241,11 @@ def copy(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=False)
241241
full_dest_path = dav_get_obj_path(
242242
self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
243243
)
244-
dest = self._session.cfg.dav_endpoint + full_dest_path
244+
dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
245245
headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
246246
response = self._session.adapter_dav.request(
247247
"COPY",
248-
dav_get_obj_path(self._session.user, path_src),
248+
quote(dav_get_obj_path(self._session.user, path_src)),
249249
headers=headers,
250250
)
251251
check_error(response, f"copy: user={self._session.user}, src={path_src}, dest={dest}, {overwrite}")
@@ -277,7 +277,7 @@ def setfav(self, path: str | FsNode, value: int | bool) -> None:
277277
path = path.user_path if isinstance(path, FsNode) else path
278278
root = build_setfav_req(value)
279279
webdav_response = self._session.adapter_dav.request(
280-
"PROPPATCH", dav_get_obj_path(self._session.user, path), content=element_tree_as_str(root)
280+
"PROPPATCH", quote(dav_get_obj_path(self._session.user, path)), content=element_tree_as_str(root)
281281
)
282282
check_error(webdav_response, f"setfav: path={path}, value={value}")
283283

@@ -301,7 +301,7 @@ def trashbin_restore(self, path: str | FsNode) -> None:
301301
headers = Headers({"Destination": dest}, encoding="utf-8")
302302
response = self._session.adapter_dav.request(
303303
"MOVE",
304-
f"/trashbin/{self._session.user}/{path}",
304+
quote(f"/trashbin/{self._session.user}/{path}"),
305305
headers=headers,
306306
)
307307
check_error(response, f"trashbin_restore: user={self._session.user}, src={path}, dest={dest}")
@@ -313,7 +313,7 @@ def trashbin_delete(self, path: str | FsNode, not_fail=False) -> None:
313313
:param not_fail: if set to ``True`` and the object is not found, it does not raise an exception.
314314
"""
315315
path = path.user_path if isinstance(path, FsNode) else path
316-
response = self._session.adapter_dav.delete(f"/trashbin/{self._session.user}/{path}")
316+
response = self._session.adapter_dav.delete(quote(f"/trashbin/{self._session.user}/{path}"))
317317
if response.status_code == 404 and not_fail:
318318
return
319319
check_error(response)
@@ -431,7 +431,7 @@ def _listdir(
431431
root, dav_path = build_listdir_req(user, path, properties, prop_type)
432432
webdav_response = self._session.adapter_dav.request(
433433
"PROPFIND",
434-
dav_path,
434+
quote(dav_path),
435435
content=element_tree_as_str(root),
436436
headers={"Depth": "infinity" if depth == -1 else str(depth)},
437437
)
@@ -440,16 +440,17 @@ def _listdir(
440440
)
441441

442442
def __download2stream(self, path: str, fp, **kwargs) -> None:
443-
with self._session.adapter_dav.stream("GET", dav_get_obj_path(self._session.user, path)) as response:
443+
with self._session.adapter_dav.stream("GET", quote(dav_get_obj_path(self._session.user, path))) as response:
444444
check_error(response, f"download_stream: user={self._session.user}, path={path}")
445445
for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
446446
fp.write(data_chunk)
447447

448448
def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
449-
_dav_path = dav_get_obj_path(self._session.user, "nc-py-api-" + random_string(56), root_path="/uploads")
449+
_tmp_path = "nc-py-api-" + random_string(56)
450+
_dav_path = quote(dav_get_obj_path(self._session.user, _tmp_path, root_path="/uploads"))
450451
_v2 = bool(self._session.cfg.options.upload_chunk_v2 and chunk_size >= 5 * 1024 * 1024)
451452
full_path = dav_get_obj_path(self._session.user, path)
452-
headers = Headers({"Destination": self._session.cfg.dav_endpoint + full_path}, encoding="utf-8")
453+
headers = Headers({"Destination": self._session.cfg.dav_endpoint + quote(full_path)}, encoding="utf-8")
453454
if _v2:
454455
response = self._session.adapter_dav.request("MKCOL", _dav_path, headers=headers)
455456
else:
@@ -548,7 +549,7 @@ async def find(self, req: list, path: str | FsNode = "") -> list[FsNode]:
548549
async def download(self, path: str | FsNode) -> bytes:
549550
"""Downloads and returns the content of a file."""
550551
path = path.user_path if isinstance(path, FsNode) else path
551-
response = await self._session.adapter_dav.get(dav_get_obj_path(await self._session.user, path))
552+
response = await self._session.adapter_dav.get(quote(dav_get_obj_path(await self._session.user, path)))
552553
check_error(response, f"download: user={await self._session.user}, path={path}")
553554
return response.content
554555

@@ -602,7 +603,7 @@ async def upload(self, path: str | FsNode, content: bytes | str) -> FsNode:
602603
"""
603604
path = path.user_path if isinstance(path, FsNode) else path
604605
full_path = dav_get_obj_path(await self._session.user, path)
605-
response = await self._session.adapter_dav.put(full_path, content=content)
606+
response = await self._session.adapter_dav.put(quote(full_path), content=content)
606607
check_error(response, f"upload: user={await self._session.user}, path={path}, size={len(content)}")
607608
return FsNode(full_path.strip("/"), **etag_fileid_from_response(response))
608609

@@ -683,11 +684,11 @@ async def move(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=
683684
full_dest_path = dav_get_obj_path(
684685
await self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
685686
)
686-
dest = self._session.cfg.dav_endpoint + full_dest_path
687+
dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
687688
headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
688689
response = await self._session.adapter_dav.request(
689690
"MOVE",
690-
dav_get_obj_path(await self._session.user, path_src),
691+
quote(dav_get_obj_path(await self._session.user, path_src)),
691692
headers=headers,
692693
)
693694
check_error(response, f"move: user={await self._session.user}, src={path_src}, dest={dest}, {overwrite}")
@@ -705,11 +706,11 @@ async def copy(self, path_src: str | FsNode, path_dest: str | FsNode, overwrite=
705706
full_dest_path = dav_get_obj_path(
706707
await self._session.user, path_dest.user_path if isinstance(path_dest, FsNode) else path_dest
707708
)
708-
dest = self._session.cfg.dav_endpoint + full_dest_path
709+
dest = self._session.cfg.dav_endpoint + quote(full_dest_path)
709710
headers = Headers({"Destination": dest, "Overwrite": "T" if overwrite else "F"}, encoding="utf-8")
710711
response = await self._session.adapter_dav.request(
711712
"COPY",
712-
dav_get_obj_path(await self._session.user, path_src),
713+
quote(dav_get_obj_path(await self._session.user, path_src)),
713714
headers=headers,
714715
)
715716
check_error(response, f"copy: user={await self._session.user}, src={path_src}, dest={dest}, {overwrite}")
@@ -741,7 +742,7 @@ async def setfav(self, path: str | FsNode, value: int | bool) -> None:
741742
path = path.user_path if isinstance(path, FsNode) else path
742743
root = build_setfav_req(value)
743744
webdav_response = await self._session.adapter_dav.request(
744-
"PROPPATCH", dav_get_obj_path(await self._session.user, path), content=element_tree_as_str(root)
745+
"PROPPATCH", quote(dav_get_obj_path(await self._session.user, path)), content=element_tree_as_str(root)
745746
)
746747
check_error(webdav_response, f"setfav: path={path}, value={value}")
747748

@@ -770,7 +771,7 @@ async def trashbin_restore(self, path: str | FsNode) -> None:
770771
headers = Headers({"Destination": dest}, encoding="utf-8")
771772
response = await self._session.adapter_dav.request(
772773
"MOVE",
773-
f"/trashbin/{await self._session.user}/{path}",
774+
quote(f"/trashbin/{await self._session.user}/{path}"),
774775
headers=headers,
775776
)
776777
check_error(response, f"trashbin_restore: user={await self._session.user}, src={path}, dest={dest}")
@@ -782,7 +783,7 @@ async def trashbin_delete(self, path: str | FsNode, not_fail=False) -> None:
782783
:param not_fail: if set to ``True`` and the object is not found, it does not raise an exception.
783784
"""
784785
path = path.user_path if isinstance(path, FsNode) else path
785-
response = await self._session.adapter_dav.delete(f"/trashbin/{await self._session.user}/{path}")
786+
response = await self._session.adapter_dav.delete(quote(f"/trashbin/{await self._session.user}/{path}"))
786787
if response.status_code == 404 and not_fail:
787788
return
788789
check_error(response)
@@ -900,7 +901,7 @@ async def _listdir(
900901
root, dav_path = build_listdir_req(user, path, properties, prop_type)
901902
webdav_response = await self._session.adapter_dav.request(
902903
"PROPFIND",
903-
dav_path,
904+
quote(dav_path),
904905
content=element_tree_as_str(root),
905906
headers={"Depth": "infinity" if depth == -1 else str(depth)},
906907
)
@@ -910,17 +911,18 @@ async def _listdir(
910911

911912
async def __download2stream(self, path: str, fp, **kwargs) -> None:
912913
async with self._session.adapter_dav.stream(
913-
"GET", dav_get_obj_path(await self._session.user, path)
914+
"GET", quote(dav_get_obj_path(await self._session.user, path))
914915
) as response:
915916
check_error(response, f"download_stream: user={await self._session.user}, path={path}")
916917
async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
917918
fp.write(data_chunk)
918919

919920
async def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
920-
_dav_path = dav_get_obj_path(await self._session.user, "nc-py-api-" + random_string(56), root_path="/uploads")
921+
_tmp_path = "nc-py-api-" + random_string(56)
922+
_dav_path = quote(dav_get_obj_path(await self._session.user, _tmp_path, root_path="/uploads"))
921923
_v2 = bool(self._session.cfg.options.upload_chunk_v2 and chunk_size >= 5 * 1024 * 1024)
922924
full_path = dav_get_obj_path(await self._session.user, path)
923-
headers = Headers({"Destination": self._session.cfg.dav_endpoint + full_path}, encoding="utf-8")
925+
headers = Headers({"Destination": self._session.cfg.dav_endpoint + quote(full_path)}, encoding="utf-8")
924926
if _v2:
925927
response = await self._session.adapter_dav.request("MKCOL", _dav_path, headers=headers)
926928
else:

tests/actual_tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def init_filesystem_for_user(nc_any, rand_bytes):
3939
/test_12345_text.txt
4040
/test_generated_image.png **Favorite**
4141
/test_dir_tmp
42+
/test_###_dir
4243
"""
4344
clean_filesystem_for_user(nc_any)
4445
im = BytesIO()
@@ -48,6 +49,7 @@ def init_filesystem_for_user(nc_any, rand_bytes):
4849
nc_any.files.makedirs("/test_dir/subdir")
4950
nc_any.files.mkdir("/test_dir/test_empty_child_dir/")
5051
nc_any.files.mkdir("/test_dir_tmp")
52+
nc_any.files.mkdir("/test_###_dir")
5153

5254
def init_folder(folder: str = ""):
5355
nc_any.files.upload(path.join(folder, "test_empty_text.txt"), content=b"")
@@ -72,6 +74,7 @@ def clean_filesystem_for_user(nc_any):
7274
"test_64_bytes.bin",
7375
"test_12345_text.txt",
7476
"test_generated_image.png",
77+
"test_###_dir",
7578
]
7679
for i in clean_up_list:
7780
nc_any.files.delete(i, not_fail=True)

0 commit comments

Comments
 (0)