diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index e67b822d..c957782e 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -271,7 +271,7 @@ class FtpFs(AbstractedFS): vp = join(self.cwd, path).lstrip("/") try: - self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], []) + self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False) except Exception as ex: raise FSE(str(ex)) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index fb226913..a8a316aa 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1152,18 +1152,9 @@ class HttpCli(object): dst = self.headers["destination"] dst = re.sub("^https?://[^/]+", "", dst).lstrip() dst = unquotep(dst) - if not self._mv(self.vpath, dst): + if not self._mv(self.vpath, dst.lstrip("/")): return False - # up2k only cares about files and removes all empty folders; - # clients naturally expect empty folders to survive a rename - vn, rem = self.asrv.vfs.get(dst, self.uname, False, False) - dabs = vn.canonical(rem) - try: - bos.makedirs(dabs) - except: - pass - return True def _applesan(self) -> bool: @@ -3054,7 +3045,7 @@ class HttpCli(object): ret = self.gen_tree(top, dst) if self.is_vproxied: parents = self.args.R.split("/") - for parent in parents[::-1]: + for parent in reversed(parents): ret = {"k%s" % (parent,): ret, "a": []} zs = json.dumps(ret) @@ -3193,7 +3184,9 @@ class HttpCli(object): nlim = int(self.uparam.get("lim") or 0) lim = [nlim, nlim] if nlim else [] - x = self.conn.hsrv.broker.ask("up2k.handle_rm", self.uname, self.ip, req, lim) + x = self.conn.hsrv.broker.ask( + "up2k.handle_rm", self.uname, self.ip, req, lim, False + ) self.loud_reply(x.get()) return True @@ -3210,7 +3203,7 @@ class HttpCli(object): # x-www-form-urlencoded (url query part) uses # either + or %20 for 0x20 so handle both dst = unquotep(dst.replace("+", " ")) - return self._mv(self.vpath, dst) + return self._mv(self.vpath, dst.lstrip("/")) def _mv(self, vsrc: str, vdst: str) -> bool: if not self.can_move: diff --git a/copyparty/smbd.py b/copyparty/smbd.py index ac5cdab4..d0ee688d 100644 --- a/copyparty/smbd.py +++ b/copyparty/smbd.py @@ -261,7 +261,7 @@ class SMB(object): yeet("blocked delete (no-del-acc): " + vpath) vpath = vpath.replace("\\", "/").lstrip("/") - self.hub.up2k.handle_rm(LEELOO_DALLAS, "1.7.6.2", [vpath], []) + self.hub.up2k.handle_rm(LEELOO_DALLAS, "1.7.6.2", [vpath], [], False) def _utime(self, vpath: str, times: tuple[float, float]) -> None: if not self.args.smbw: diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 0e6c74a3..a291c647 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -384,7 +384,7 @@ class Up2k(object): if vp: fvp = "%s/%s" % (vp, fvp) - self._handle_rm(LEELOO_DALLAS, "", fvp, []) + self._handle_rm(LEELOO_DALLAS, "", fvp, [], True) nrm += 1 if nrm: @@ -2897,7 +2897,9 @@ class Up2k(object): except: pass - def handle_rm(self, uname: str, ip: str, vpaths: list[str], lim: list[int]) -> str: + def handle_rm( + self, uname: str, ip: str, vpaths: list[str], lim: list[int], rm_up: bool + ) -> str: n_files = 0 ok = {} ng = {} @@ -2906,7 +2908,7 @@ class Up2k(object): self.log("hit delete limit of {} files".format(lim[1]), 3) break - a, b, c = self._handle_rm(uname, ip, vp, lim) + a, b, c = self._handle_rm(uname, ip, vp, lim, rm_up) n_files += a for k in b: ok[k] = 1 @@ -2920,7 +2922,7 @@ class Up2k(object): return "deleted {} files (and {}/{} folders)".format(n_files, iok, iok + ing) def _handle_rm( - self, uname: str, ip: str, vpath: str, lim: list[int] + self, uname: str, ip: str, vpath: str, lim: list[int], rm_up: bool ) -> tuple[int, list[str], list[str]]: self.db_act = time.time() try: @@ -3027,16 +3029,22 @@ class Up2k(object): if xad: runhook(self.log, xad, abspath, vpath, "", uname, 0, 0, ip, 0, "") - ok: list[str] = [] - ng: list[str] = [] if is_dir: ok, ng = rmdirs(self.log_func, scandir, True, atop, 1) + else: + ok = ng = [] - ok2, ng2 = rmdirs_up(os.path.dirname(atop), ptop) + if rm_up: + ok2, ng2 = rmdirs_up(os.path.dirname(atop), ptop) + else: + ok2 = ng2 = [] return n_files, ok + ok2, ng + ng2 def handle_mv(self, uname: str, svp: str, dvp: str) -> str: + if svp == dvp or dvp.startswith(svp + "/"): + raise Pebkac(400, "mv: cannot move parent into subfolder") + svn, srem = self.asrv.vfs.get(svp, uname, True, False, True) svn, srem = svn.get_dbv(srem) sabs = svn.canonical(srem, False) @@ -3090,8 +3098,21 @@ class Up2k(object): curs.clear() - rmdirs(self.log_func, scandir, True, sabs, 1) - rmdirs_up(os.path.dirname(sabs), svn.realpath) + rm_ok, rm_ng = rmdirs(self.log_func, scandir, True, sabs, 1) + + for zsl in (rm_ok, rm_ng): + for ap in reversed(zsl): + if not ap.startswith(sabs): + raise Pebkac(500, "mv_d: bug at {}, top {}".format(ap, sabs)) + + rem = ap[len(sabs) :].replace(os.sep, "/").lstrip("/") + vp = vjoin(dvp, rem) + try: + dvn, drem = self.asrv.vfs.get(vp, uname, False, True) + bos.mkdir(dvn.canonical(drem)) + except: + pass + return "k" def _mv_file( diff --git a/copyparty/util.py b/copyparty/util.py index 94371441..5247a70e 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2270,7 +2270,7 @@ def rmdirs( dirs = [os.path.join(top, x) for x in dirs] ok = [] ng = [] - for d in dirs[::-1]: + for d in reversed(dirs): a, b = rmdirs(logger, scandir, lstat, d, depth + 1) ok += a ng += b