From fff7291dcf9ef82a2df0f2cb5df60056e71e5840 Mon Sep 17 00:00:00 2001 From: ed Date: Thu, 23 Oct 2025 21:44:28 +0000 Subject: [PATCH] show `h` vols in ls and tree, and compensate with some optimizations --- copyparty/authsrv.py | 56 +++++++++++++++++++++++++------------------- copyparty/ftpd.py | 3 ++- copyparty/httpcli.py | 49 ++++++++++++++++++++++++-------------- tests/test_vfs.py | 6 ++--- 4 files changed, 69 insertions(+), 45 deletions(-) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 6bcb7d6e..041d3324 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -393,11 +393,15 @@ class VFS(object): axs: AXS, flags: dict[str, Any], ) -> None: + nss: set[str] = set() self.log = log self.realpath = realpath # absolute path on host filesystem self.vpath = vpath # absolute path in the virtual filesystem self.vpath0 = vpath0 # original vpath (before idp expansion) self.axs = axs + self.uaxs: dict[ + str, tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool] + ] = {} self.flags = flags # config options self.root = self self.dev = 0 # st_dev @@ -555,29 +559,19 @@ class VFS(object): def can_access( self, vpath: str, uname: str - ) -> tuple[bool, bool, bool, bool, bool, bool, bool, bool]: - """can Read,Write,Move,Delete,Get,Upget,Admin,Dot""" + ) -> tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]: + """can Read,Write,Move,Delete,Get,Upget,Html,Admin,Dot""" + # NOTE: only used by get_perms, which is only used by hooks; the lowest of fruits if vpath: vn, _ = self._find(undot(vpath)) else: vn = self - c = vn.axs - return ( - uname in c.uread, - uname in c.uwrite, - uname in c.umove, - uname in c.udel, - uname in c.uget, - uname in c.upget, - uname in c.uadmin, - uname in c.udot, - ) - # skip uhtml because it's rarely needed + return vn.uaxs[uname] def get_perms(self, vpath: str, uname: str) -> str: zbl = self.can_access(vpath, uname) - ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok) + ret = "".join(ch for ch, ok in zip("rwmdgGha.", zbl) if ok) if "rwmd" in ret and "a." in ret: ret += "A" return ret @@ -772,20 +766,17 @@ class VFS(object): virt_vis[name] = vn2 continue - ok = False - zx = vn2.axs - axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget] + u_has = vn2.uaxs.get(uname) or [False] * 9 for pset in permsets: ok = True - for req, lst in zip(pset, axs): - if req and uname not in lst: + for req, zb in zip(pset, u_has): + if req and not zb: ok = False + break if ok: + virt_vis[name] = vn2 break - if ok: - virt_vis[name] = vn2 - if ".hist" in abspath: p = abspath.replace("\\", "/") if WINDOWS else abspath if p.endswith("/.hist"): @@ -1995,6 +1986,23 @@ class AuthSrv(object): umap[usr].sort() setattr(vfs, "a" + perm, umap) + for vol in vfs.all_nodes.values(): + za = vol.axs + vol.uaxs = { + un: ( + un in za.uread, + un in za.uwrite, + un in za.umove, + un in za.udel, + un in za.uget, + un in za.upget, + un in za.uhtml, + un in za.uadmin, + un in za.udot, + ) + for un in unames + } + all_users = {} missing_users = {} associated_users = {} @@ -3436,7 +3444,7 @@ class AuthSrv(object): raise Exception("volume not found: " + zs) self.log(str({"users": users, "vols": vols, "flags": flags})) - t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) uadmin({})" + t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) html({}) uadmin({})" for k, zv in self.vfs.all_vols.items(): vc = zv.axs vs = [ diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index a88a2757..12285d64 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -198,7 +198,7 @@ class FtpFs(AbstractedFS): if not avfs: raise FSE(t.format(vpath), 1) - cr, cw, cm, cd, _, _, _, _ = avfs.can_access("", self.h.uname) + cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname] if r and not cr or w and not cw or m and not cm or d and not cd: raise FSE(t.format(vpath), 1) @@ -250,6 +250,7 @@ class FtpFs(AbstractedFS): td = 0 if w and need_unlink: + assert td # type: ignore # !rm if td >= -1 and td <= self.args.ftp_wt: # within permitted timeframe; allow overwrite or resume do_it = True diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 40bff7cb..587d6fb1 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -165,6 +165,12 @@ RE_MDV = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[Mm][Dd])$") UPARAM_CC_OK = set("doc move tree".split()) +PERMS_rwh = [ + [True, False], + [False, True], + [False, False, False, False, False, False, True], +] + class HttpCli(object): """ @@ -229,6 +235,7 @@ class HttpCli(object): self.can_delete = False self.can_get = False self.can_upget = False + self.can_html = False self.can_admin = False self.can_dot = False self.out_headerlist: list[tuple[str, str]] = [] @@ -737,18 +744,21 @@ class HttpCli(object): if "bcasechk" in vn.flags and not vn.casechk(rem, True): return self.tx_404() and False - ( - self.can_read, - self.can_write, - self.can_move, - self.can_delete, - self.can_get, - self.can_upget, - self.can_admin, - self.can_dot, - ) = ( - avn.can_access("", self.uname) if avn else [False] * 8 - ) + try: + ( + self.can_read, + self.can_write, + self.can_move, + self.can_delete, + self.can_get, + self.can_upget, + self.can_html, + self.can_admin, + self.can_dot, + ) = avn.uaxs[self.uname] + except: + pass # default is all-false + self.avn = avn self.vn = vn # note: do not dbv due to walk/zipgen self.rem = rem @@ -5560,7 +5570,7 @@ class HttpCli(object): rem, self.uname, not self.args.no_scandir, - [[True, False], [False, True]], + PERMS_rwh, ) dots = self.uname in vn.axs.udot dk_sz = vn.flags.get("dk") @@ -5592,7 +5602,13 @@ class HttpCli(object): for x in vfs_virt: if x != excl: try: - dvn, drem = vfs.get(vjoin(top, x), self.uname, True, False) + dvn, drem = vfs.get(vjoin(top, x), self.uname, False, False) + if ( + self.uname not in dvn.axs.uread + and self.uname not in dvn.axs.uwrite + and self.uname not in dvn.axs.uhtml + ): + raise Exception() bos.stat(dvn.canonical(drem, False)) except: x += "\n" @@ -6518,8 +6534,7 @@ class HttpCli(object): return self.tx_svg("upload\nonly") if not self.can_read and self.can_get and self.avn: - axs = self.avn.axs - if self.uname not in axs.uhtml: + if not self.can_html: pass elif is_dir: for fn in ("index.htm", "index.html"): @@ -6734,7 +6749,7 @@ class HttpCli(object): rem, self.uname, not self.args.no_scandir, - [[True, False], [False, True]], + PERMS_rwh, lstat="lt" in self.uparam, throw=True, ) diff --git a/tests/test_vfs.py b/tests/test_vfs.py index 5f1c7e91..bd7432b7 100644 --- a/tests/test_vfs.py +++ b/tests/test_vfs.py @@ -187,9 +187,9 @@ class TestVFS(unittest.TestCase): self.assertEqual(n.realpath, os.path.join(td, "a")) self.assertAxs(n.axs.uread, ["*", "k"]) self.assertAxs(n.axs.uwrite, []) - perm_na = (False, False, False, False, False, False, False, False) - perm_rw = (True, True, False, False, False, False, False, False) - perm_ro = (True, False, False, False, False, False, False, False) + perm_na = (False, False, False, False, False, False, False, False, False) + perm_rw = (True, True, False, False, False, False, False, False, False) + perm_ro = (True, False, False, False, False, False, False, False, False) self.assertEqual(vfs.can_access("/", "*"), perm_na) self.assertEqual(vfs.can_access("/", "k"), perm_rw) self.assertEqual(vfs.can_access("/a", "*"), perm_ro)