mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 17:12:13 -06:00
WebDAV: support COPY, KDE-Dolphin (#136):
* add support for the COPY verb * COPY/MOVE: add overwrite support; default is True according to rfc (only applies to single files for now) * COPY/MOVE/MKCOL: return 401 as necessary for clients which rechallenge frequently such as KDE Dolphin (KIO/6.10) * MOVE: support webdav:// Destination prefix as used by KDE Dolphin (KIO/6.10) * MOVE: vproxy support
This commit is contained in:
parent
2f6707825a
commit
62ee7f6980
|
@ -191,7 +191,7 @@ class HttpCli(object):
|
||||||
self.is_vproxied = False
|
self.is_vproxied = False
|
||||||
self.in_hdr_recv = True
|
self.in_hdr_recv = True
|
||||||
self.headers: dict[str, str] = {}
|
self.headers: dict[str, str] = {}
|
||||||
self.mode = " "
|
self.mode = " " # http verb
|
||||||
self.req = " "
|
self.req = " "
|
||||||
self.http_ver = ""
|
self.http_ver = ""
|
||||||
self.hint = ""
|
self.hint = ""
|
||||||
|
@ -731,10 +731,10 @@ class HttpCli(object):
|
||||||
return self.handle_unlock() and self.keepalive
|
return self.handle_unlock() and self.keepalive
|
||||||
elif self.mode == "MKCOL":
|
elif self.mode == "MKCOL":
|
||||||
return self.handle_mkcol() and self.keepalive
|
return self.handle_mkcol() and self.keepalive
|
||||||
elif self.mode == "MOVE":
|
elif self.mode in ("MOVE", "COPY"):
|
||||||
return self.handle_move() and self.keepalive
|
return self.handle_cpmv() and self.keepalive
|
||||||
else:
|
else:
|
||||||
raise Pebkac(400, 'invalid HTTP mode "{0}"'.format(self.mode))
|
raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode))
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if not isinstance(ex, Pebkac):
|
if not isinstance(ex, Pebkac):
|
||||||
|
@ -1776,6 +1776,12 @@ class HttpCli(object):
|
||||||
if "%" in self.req:
|
if "%" in self.req:
|
||||||
self.log(" `-- %r" % (self.vpath,))
|
self.log(" `-- %r" % (self.vpath,))
|
||||||
|
|
||||||
|
if self.args.no_dav:
|
||||||
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
||||||
|
|
||||||
|
if not self.can_write:
|
||||||
|
raise Pebkac(401, "authenticate")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._mkdir(self.vpath, True)
|
return self._mkdir(self.vpath, True)
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
|
@ -1785,14 +1791,35 @@ class HttpCli(object):
|
||||||
self.reply(b"", ex.code)
|
self.reply(b"", ex.code)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handle_move(self) -> bool:
|
def handle_cpmv(self) -> bool:
|
||||||
dst = self.headers["destination"]
|
dst = self.headers["destination"]
|
||||||
dst = re.sub("^https?://[^/]+", "", dst).lstrip()
|
|
||||||
dst = unquotep(dst)
|
|
||||||
if not self._mv(self.vpath, dst.lstrip("/")):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
# dolphin (KIO/6.10) "webdav://127.0.0.1:3923/a/b.txt"
|
||||||
|
dst = re.sub("^[a-zA-Z]+://[^/]+", "", dst).lstrip()
|
||||||
|
|
||||||
|
if self.is_vproxied and dst.startswith(self.args.SRS):
|
||||||
|
dst = dst[len(self.args.RS) :]
|
||||||
|
|
||||||
|
if self.do_log:
|
||||||
|
self.log("%s %s --//> %s @%s" % (self.mode, self.req, dst, self.uname))
|
||||||
|
if "%" in self.req:
|
||||||
|
self.log(" `-- %r" % (self.vpath,))
|
||||||
|
|
||||||
|
if self.args.no_dav:
|
||||||
|
raise Pebkac(405, "WebDAV is disabled in server config")
|
||||||
|
|
||||||
|
dst = unquotep(dst)
|
||||||
|
|
||||||
|
# overwrite=True is default; rfc4918 9.8.4
|
||||||
|
overwrite = self.headers.get("overwrite", "").lower() != "f"
|
||||||
|
|
||||||
|
try:
|
||||||
|
fun = self._cp if self.mode == "COPY" else self._mv
|
||||||
|
return fun(self.vpath, dst.lstrip("/"), overwrite)
|
||||||
|
except Pebkac as ex:
|
||||||
|
if ex.code == 403:
|
||||||
|
ex.code = 401
|
||||||
|
raise
|
||||||
|
|
||||||
def _applesan(self) -> bool:
|
def _applesan(self) -> bool:
|
||||||
if self.args.dav_mac or "Darwin/" not in self.ua:
|
if self.args.dav_mac or "Darwin/" not in self.ua:
|
||||||
|
@ -5441,6 +5468,8 @@ class HttpCli(object):
|
||||||
|
|
||||||
def handle_rm(self, req: list[str]) -> bool:
|
def handle_rm(self, req: list[str]) -> bool:
|
||||||
if not req and not self.can_delete:
|
if not req and not self.can_delete:
|
||||||
|
if self.mode == "DELETE" and self.uname == "*":
|
||||||
|
raise Pebkac(401, "authenticate") # webdav
|
||||||
raise Pebkac(403, "'delete' not allowed for user " + self.uname)
|
raise Pebkac(403, "'delete' not allowed for user " + self.uname)
|
||||||
|
|
||||||
if self.args.no_del:
|
if self.args.no_del:
|
||||||
|
@ -5475,14 +5504,22 @@ class HttpCli(object):
|
||||||
if not dst:
|
if not dst:
|
||||||
raise Pebkac(400, "need dst vpath")
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
return self._mv(self.vpath, dst.lstrip("/"))
|
return self._mv(self.vpath, dst.lstrip("/"), False)
|
||||||
|
|
||||||
def _mv(self, vsrc: str, vdst: str) -> bool:
|
def _mv(self, vsrc: str, vdst: str, overwrite: bool) -> bool:
|
||||||
if self.args.no_mv:
|
if self.args.no_mv:
|
||||||
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
raise Pebkac(403, "the rename/move feature is disabled in server config")
|
||||||
|
|
||||||
self.asrv.vfs.get(vsrc, self.uname, True, False, True)
|
# `handle_cpmv` will catch 403 from these and raise 401
|
||||||
self.asrv.vfs.get(vdst, self.uname, False, True)
|
svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False, True)
|
||||||
|
dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
dabs = dvn.canonical(drem)
|
||||||
|
if bos.path.exists(dabs):
|
||||||
|
self.log("overwriting %s" % (dabs,))
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
|
||||||
self.loud_reply(x.get(), status=201)
|
self.loud_reply(x.get(), status=201)
|
||||||
|
@ -5498,14 +5535,21 @@ class HttpCli(object):
|
||||||
if not dst:
|
if not dst:
|
||||||
raise Pebkac(400, "need dst vpath")
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
return self._cp(self.vpath, dst.lstrip("/"))
|
return self._cp(self.vpath, dst.lstrip("/"), False)
|
||||||
|
|
||||||
def _cp(self, vsrc: str, vdst: str) -> bool:
|
def _cp(self, vsrc: str, vdst: str, overwrite: bool) -> bool:
|
||||||
if self.args.no_cp:
|
if self.args.no_cp:
|
||||||
raise Pebkac(403, "the copy feature is disabled in server config")
|
raise Pebkac(403, "the copy feature is disabled in server config")
|
||||||
|
|
||||||
self.asrv.vfs.get(vsrc, self.uname, True, False)
|
svn, srem = self.asrv.vfs.get(vsrc, self.uname, True, False)
|
||||||
self.asrv.vfs.get(vdst, self.uname, False, True)
|
dvn, drem = self.asrv.vfs.get(vdst, self.uname, False, True)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
dabs = dvn.canonical(drem)
|
||||||
|
if bos.path.exists(dabs):
|
||||||
|
self.log("overwriting %s" % (dabs,))
|
||||||
|
self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
|
||||||
|
wunlink(self.log, dabs, dvn.flags)
|
||||||
|
|
||||||
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
|
x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
|
||||||
self.loud_reply(x.get(), status=201)
|
self.loud_reply(x.get(), status=201)
|
||||||
|
|
Loading…
Reference in a new issue