From 1c3894743af30b86acb57080583c184fa4ac987f Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 2 Aug 2022 20:26:51 +0200 Subject: [PATCH] fix filekeys inside symlinked volumes --- copyparty/__main__.py | 1 + copyparty/authsrv.py | 11 ++++++++--- copyparty/httpcli.py | 38 +++++++++++++++++++++++--------------- copyparty/svchub.py | 4 ++++ copyparty/util.py | 20 +++++++++++++++++++- tests/util.py | 2 +- 6 files changed, 56 insertions(+), 20 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 79d7f3bc..f92542c5 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -634,6 +634,7 @@ def run_argparse(argv: list[str], formatter: Any, retry: bool) -> argparse.Names ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead") ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second") ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC") + ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches REGEX; '.' (a single dot) = all files") # fmt: on ap2 = ap.add_argument_group("help sections") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 1f0cf16d..3a1d5eb0 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -707,7 +707,7 @@ class AuthSrv(object): raise Exception('invalid mountpoint "{}"'.format(vol_dst)) # cfg files override arguments and previous files - vol_src = bos.path.abspath(vol_src) + vol_src = absreal(vol_src) vol_dst = vol_dst.strip("/") self._map_volume(vol_src, vol_dst, mount, daxs, mflags) continue @@ -818,7 +818,7 @@ class AuthSrv(object): src = uncyg(src) # print("\n".join([src, dst, perms])) - src = bos.path.abspath(src) + src = absreal(src) dst = dst.strip("/") self._map_volume(src, dst, mount, daxs, mflags) @@ -847,7 +847,7 @@ class AuthSrv(object): if not mount: # -h says our defaults are CWD at root and read/write for everyone axs = AXS(["*"], ["*"], None, None) - vfs = VFS(self.log_func, bos.path.abspath("."), "", axs, {}) + vfs = VFS(self.log_func, absreal("."), "", axs, {}) elif "" not in mount: # there's volumes but no root; make root inaccessible vfs = VFS(self.log_func, "", "", AXS(), {}) @@ -1029,10 +1029,15 @@ class AuthSrv(object): vol.flags["dathumb"] = True vol.flags["dithumb"] = True + have_fk = False for vol in vfs.all_vols.values(): fk = vol.flags.get("fk") if fk: vol.flags["fk"] = int(fk) if fk is not True else 8 + have_fk = True + + if have_fk and re.match("^[0-9\.]+$", self.args.fk_salt): + self.log("filekey salt: {}".format(self.args.fk_salt)) for vol in vfs.all_vols.values(): if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags: diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index b8d001d3..23497338 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -42,6 +42,7 @@ from .util import ( exclude_dotfiles, fsenc, gen_filekey, + gen_filekey_dbg, gencookie, get_df, get_spd, @@ -108,6 +109,7 @@ class HttpCli(object): self.u2fh = conn.u2fh # mypy404 self.log_func = conn.log_func # mypy404 self.log_src = conn.log_src # mypy404 + self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey self.tls: bool = hasattr(self.s, "cipher") # placeholders; assigned by run() @@ -177,6 +179,9 @@ class HttpCli(object): if rem.startswith("/") or rem.startswith("../") or "/../" in rem: raise Exception("that was close") + def _gen_fk(self, salt: str, fspath: str, fsize: int, inode: int) -> str: + return gen_filekey_dbg(salt, fspath, fsize, inode, self.log, self.args.log_fk) + def j2s(self, name: str, **ka: Any) -> str: tpl = self.conn.hsrv.j2[name] ka["ts"] = self.conn.hsrv.cachebuster() @@ -711,7 +716,7 @@ class HttpCli(object): reader, remains = self.get_body_reader() vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True) lim = vfs.get_dbv(rem)[0].lim - fdir = os.path.join(vfs.realpath, rem) + fdir = vfs.canonical(rem) if lim: fdir, rem = lim.all(self.ip, rem, remains, fdir) @@ -813,7 +818,7 @@ class HttpCli(object): vsuf = "" if self.can_read and "fk" in vfs.flags: - vsuf = "?k=" + gen_filekey( + vsuf = "?k=" + self.gen_fk( self.args.fk_salt, path, post_sz, @@ -950,7 +955,7 @@ class HttpCli(object): if rem: try: - dst = os.path.join(vfs.realpath, rem) + dst = vfs.canonical(rem) if not bos.path.isdir(dst): bos.makedirs(dst) except OSError as ex: @@ -1185,7 +1190,7 @@ class HttpCli(object): sanitized = sanitize_fn(new_dir, "", []) if not nullwrite: - fdir = os.path.join(vfs.realpath, rem) + fdir = vfs.canonical(rem) fn = os.path.join(fdir, sanitized) if not bos.path.isdir(fdir): @@ -1224,7 +1229,7 @@ class HttpCli(object): sanitized = sanitize_fn(new_file, "", []) if not nullwrite: - fdir = os.path.join(vfs.realpath, rem) + fdir = vfs.canonical(rem) fn = os.path.join(fdir, sanitized) if bos.path.exists(fn): @@ -1245,7 +1250,7 @@ class HttpCli(object): upload_vpath = self.vpath lim = vfs.get_dbv(rem)[0].lim - fdir_base = os.path.join(vfs.realpath, rem) + fdir_base = vfs.canonical(rem) if lim: fdir_base, rem = lim.all(self.ip, rem, -1, fdir_base) upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/") @@ -1286,7 +1291,7 @@ class HttpCli(object): else: open_args = {} tnam = fname = os.devnull - fdir = "" + fdir = abspath = "" if lim: lim.chk_bup(self.ip) @@ -1318,12 +1323,15 @@ class HttpCli(object): lim.chk_bup(self.ip) lim.chk_nup(self.ip) except: - bos.unlink(tabspath) - bos.unlink(abspath) + if not nullwrite: + bos.unlink(tabspath) + bos.unlink(abspath) fname = os.devnull raise - atomic_move(tabspath, abspath) + if not nullwrite: + atomic_move(tabspath, abspath) + files.append( (sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath) ) @@ -1373,9 +1381,9 @@ class HttpCli(object): for sz, sha_hex, sha_b64, ofn, lfn, ap in files: vsuf = "" if self.can_read and "fk" in vfs.flags: - vsuf = "?k=" + gen_filekey( + vsuf = "?k=" + self.gen_fk( self.args.fk_salt, - abspath, + ap, sz, 0 if ANYWIN or not ap else bos.stat(ap).st_ino, )[: vfs.flags["fk"]] @@ -1453,7 +1461,7 @@ class HttpCli(object): raise Pebkac(411) rp, fn = vsplit(rem) - fp = os.path.join(vfs.realpath, rp) + fp = vfs.canonical(rp) lim = vfs.get_dbv(rem)[0].lim if lim: fp, rp = lim.all(self.ip, rp, clen, fp) @@ -2310,7 +2318,7 @@ class HttpCli(object): if not is_dir and (self.can_read or self.can_get): if not self.can_read and "fk" in vn.flags: - correct = gen_filekey( + correct = self.gen_fk( self.args.fk_salt, abspath, st.st_size, 0 if ANYWIN else st.st_ino )[: vn.flags["fk"]] got = self.uparam.get("k") @@ -2534,7 +2542,7 @@ class HttpCli(object): if add_fk: href = "{}?k={}".format( quotep(href), - gen_filekey( + self.gen_fk( self.args.fk_salt, fspath, sz, 0 if ANYWIN else inf.st_ino )[:add_fk], ) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 287dc672..bf1d9fbf 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -6,6 +6,7 @@ import base64 import calendar import gzip import os +import re import shlex import signal import socket @@ -113,6 +114,9 @@ class SvcHub(object): if not args.hardlink and args.never_symlink: args.no_dedup = True + if args.log_fk: + args.log_fk = re.compile(args.log_fk) + # initiate all services to manage self.asrv = AuthSrv(self.args, self.log) if args.ls: diff --git a/copyparty/util.py b/copyparty/util.py index fc4d4e41..64045e2b 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -46,7 +46,7 @@ try: from collections.abc import Callable, Iterable import typing - from typing import Any, Generator, Optional, Protocol, Union + from typing import Any, Generator, Optional, Pattern, Protocol, Union class RootLogger(Protocol): def __call__(self, src: str, msg: str, c: Union[int, str] = 0) -> None: @@ -932,6 +932,24 @@ def gen_filekey(salt: str, fspath: str, fsize: int, inode: int) -> str: ).decode("ascii") +def gen_filekey_dbg( + salt: str, + fspath: str, + fsize: int, + inode: int, + log: "NamedLogger", + log_ptn: Optional[Pattern[str]], +) -> str: + ret = gen_filekey(salt, fspath, fsize, inode) + + assert log_ptn + if log_ptn.search(fspath): + t = "fk({}) salt({}) size({}) inode({}) fspath({})" + log(t.format(ret[:8], salt, fsize, inode, fspath)) + + return ret + + def gencookie(k: str, v: str, dur: Optional[int]) -> str: v = v.replace(";", "") if dur: diff --git a/tests/util.py b/tests/util.py index 0135250f..33654a2e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -106,7 +106,7 @@ class Cfg(Namespace): ex = "re_maxage rproxy rsp_slp s_wr_slp theme themes turbo df" ka.update(**{k: 0 for k in ex.split()}) - ex = "doctitle favico html_head mth textfiles" + ex = "doctitle favico html_head mth textfiles log_fk" ka.update(**{k: "" for k in ex.split()}) super(Cfg, self).__init__(