diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index ddf57c71..2fc613ca 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -552,15 +552,14 @@ class VFS(object): return self._get_dbv(vrem) shv, srem = src - return shv, vjoin(srem, vrem) + return shv._get_dbv(vjoin(srem, vrem)) def _get_dbv(self, vrem: str) -> tuple["VFS", str]: dbv = self.dbv if not dbv: return self, vrem - tv = [self.vpath[len(dbv.vpath) :].lstrip("/"), vrem] - vrem = "/".join([x for x in tv if x]) + vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem) return dbv, vrem def canonical(self, rem: str, resolve: bool = True) -> str: diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index ac21a84b..4a496ab6 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -105,6 +105,7 @@ from .util import ( unquotep, vjoin, vol_san, + vroots, vsplit, wrename, wunlink, @@ -1202,9 +1203,6 @@ class HttpCli(object): if "stack" in self.uparam: return self.tx_stack() - if "ups" in self.uparam: - return self.tx_ups() - if "setck" in self.uparam: return self.setck() @@ -1220,6 +1218,10 @@ class HttpCli(object): if "h" in self.uparam: return self.tx_mounts() + if "ups" in self.uparam: + # vpath is used for share translation + return self.tx_ups() + if "rss" in self.uparam: return self.tx_rss() @@ -2408,6 +2410,15 @@ class HttpCli(object): if "purl" in ret: ret["purl"] = self.args.SR + ret["purl"] + if self.args.shr and self.vpath.startswith(self.args.shr1): + # strip common suffix (uploader's folder structure) + vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem)) + if not ret["purl"].startswith(vp_vfs): + t = "share-mapping failed; req=[%s] dbv=[%s] vrem=[%s] n1=[%s] n2=[%s] purl=[%s]" + zt = (self.vpath, dbv.vpath, vrem, vp_req, vp_vfs, ret["purl"]) + raise Pebkac(500, t % zt) + ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :] + ret = json.dumps(ret) self.log(ret) self.reply(ret.encode("utf-8"), mime="application/json") @@ -2500,7 +2511,11 @@ class HttpCli(object): chashes.append(siblings[n : n + clen]) vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True) - ptop = (vfs.dbv or vfs).realpath + ptop = vfs.get_dbv("")[0].realpath + # if this is a share, then get_dbv has been overridden to return + # the dbv (which does not exist as a property). And its realpath + # could point into the middle of its origin vfs node, meaning it + # is not necessarily registered with up2k, so get_dbv is crucial broker = self.conn.hsrv.broker x = broker.ask("up2k.handle_chunks", ptop, wark, chashes) @@ -4456,7 +4471,7 @@ class HttpCli(object): rvol=rvol, wvol=wvol, avol=avol, - in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]), + in_shr=self.args.shr and self.vpath.startswith(self.args.shr1), vstate=vstate, ups=ups, scanning=vs["scanning"], @@ -4520,7 +4535,7 @@ class HttpCli(object): t = t.format(self.args.SR) qv = quotep(self.vpaths) + self.ourlq() - in_shr = self.args.shr and self.vpath.startswith(self.args.shr[1:]) + in_shr = self.args.shr and self.vpath.startswith(self.args.shr1) html = self.j2s("splash", this=self, qvpath=qv, in_shr=in_shr, msg=t) self.reply(html.encode("utf-8"), status=rc) return True @@ -4683,6 +4698,11 @@ class HttpCli(object): lm = "ups [{}]".format(filt) self.log(lm) + if self.args.shr and self.vpath.startswith(self.args.shr1): + shr_dbv, shr_vrem = self.vn.get_dbv(self.rem) + else: + shr_dbv = None + ret: list[dict[str, Any]] = [] t0 = time.time() lim = time.time() - self.args.unpost @@ -4703,7 +4723,12 @@ class HttpCli(object): else: allvols = list(self.asrv.vfs.all_vols.values()) - allvols = [x for x in allvols if "e2d" in x.flags] + allvols = [ + x + for x in allvols + if "e2d" in x.flags + and ("*" in x.axs.uwrite or self.uname in x.axs.uwrite or x == shr_dbv) + ] for vol in allvols: cur = idx.get_cur(vol) @@ -4753,6 +4778,16 @@ class HttpCli(object): ret = ret[:2000] + if shr_dbv: + # translate vpaths from share-target to share-url + # to satisfy access checks + assert shr_vrem.split # type: ignore # !rm + vp_shr, vp_vfs = vroots(self.vpath, vjoin(shr_dbv.vpath, shr_vrem)) + for v in ret: + vp = v["vp"] + if vp.startswith(vp_vfs): + v["vp"] = vp_shr + vp[len(vp_vfs) :] + if self.is_vproxied: for v in ret: v["vp"] = self.args.SR + v["vp"] @@ -4882,7 +4917,7 @@ class HttpCli(object): if m: raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],)) - if vp.startswith(self.args.shr[1:]): + if vp.startswith(self.args.shr1): raise Pebkac(400, "yo dawg...") cur = idx.get_shr() diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 518f1686..4ad5d6e5 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -230,6 +230,7 @@ class SvcHub(object): if not self.args.no_ses: self.setup_session_db() + args.shr1 = "" if args.shr: self.setup_share_db() @@ -460,6 +461,7 @@ class SvcHub(object): raise Exception(t) al.shr = "/%s/" % (al.shr,) + al.shr1 = al.shr[1:] create = True modified = False diff --git a/copyparty/up2k.py b/copyparty/up2k.py index ebcac6a2..47048b99 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -3907,11 +3907,9 @@ class Up2k(object): if unpost: raise Pebkac(400, "cannot unpost folders") elif stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode): - dbv, vrem = self.asrv.vfs.get(vpath, uname, *permsets[0]) - dbv, vrem = dbv.get_dbv(vrem) - voldir = vsplit(vrem)[0] + voldir = vsplit(rem)[0] vpath_dir = vsplit(vpath)[0] - g = [(dbv, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore + g = [(vn, voldir, vpath_dir, adir, [(fn, 0)], [], {})] # type: ignore else: self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop)) return 0, [], [] @@ -3938,7 +3936,10 @@ class Up2k(object): volpath = ("%s/%s" % (vrem, fn)).strip("/") vpath = ("%s/%s" % (dbv.vpath, volpath)).strip("/") self.log("rm %s\n %s" % (vpath, abspath)) - _ = dbv.get(volpath, uname, *permsets[0]) + if not unpost: + # recursion-only sanchk + _ = dbv.get(volpath, uname, *permsets[0]) + if xbd: if not runhook( self.log, diff --git a/copyparty/util.py b/copyparty/util.py index d9c9d2e7..a22abf90 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2204,6 +2204,23 @@ def unquotep(txt: str) -> str: return w8dec(unq2) +def vroots(vp1: str, vp2: str) -> tuple[str, str]: + """ + input("q/w/e/r","a/s/d/e/r") output("/q/w/","/a/s/d/") + """ + while vp1 and vp2: + zt1 = vp1.rsplit("/", 1) if "/" in vp1 else ("", vp1) + zt2 = vp2.rsplit("/", 1) if "/" in vp2 else ("", vp2) + if zt1[1] != zt2[1]: + break + vp1 = zt1[0] + vp2 = zt2[0] + return ( + "/%s/" % (vp1,) if vp1 else "/", + "/%s/" % (vp2,) if vp2 else "/", + ) + + def vsplit(vpath: str) -> tuple[str, str]: if "/" not in vpath: return "", vpath diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 517870db..d40d9e37 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -4510,9 +4510,12 @@ var fileman = (function () { '