diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 272fa244..e84cab05 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -510,6 +510,8 @@ def get_sects(): \033[0mupload rules: \033[36mmaxn=250,600\033[35m max 250 uploads over 15min \033[36mmaxb=1g,300\033[35m max 1 GiB over 5min (suffixes: b, k, m, g) + \033[36mrand\033[35m force randomized filenames, 9 chars long by default + \033[36mnrand=N\033[35m randomized filenames are N chars long \033[36msz=1k-3m\033[35m allow filesizes between 1 KiB and 3MiB \033[36mdf=1g\033[35m ensure 1 GiB free disk space @@ -712,6 +714,8 @@ def add_upload(ap): ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes") ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)") ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually") + ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)") + ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)") ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)") ap2.add_argument("--df", metavar="GiB", type=float, default=0, help="ensure GiB free disk space by rejecting upload requests") ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 789c7af5..f03816ac 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -1114,40 +1114,58 @@ class AuthSrv(object): if ptn: vol.flags[vf] = re.compile(ptn) - for k in ["e2t", "e2ts", "e2tsr", "e2v", "e2vu", "e2vp", "xdev", "xvol"]: + for k in ( + "dotsrch", + "e2t", + "e2ts", + "e2tsr", + "e2v", + "e2vu", + "e2vp", + "hardlink", + "magic", + "no_sb_md", + "no_sb_lg", + "rand", + "xdev", + "xlink", + "xvol", + ): if getattr(self.args, k): vol.flags[k] = True for ga, vf in ( - ("no_sb_md", "no_sb_md"), - ("no_sb_lg", "no_sb_lg"), - ("no_forget", "noforget"), - ("no_dupe", "nodupe"), - ("hardlink", "hardlink"), ("never_symlink", "neversymlink"), ("no_dedup", "copydupes"), - ("magic", "magic"), - ("xlink", "xlink"), - ("dotsrch", "dotsrch"), + ("no_dupe", "nodupe"), + ("no_forget", "noforget"), ): if getattr(self.args, ga): vol.flags[vf] = True for ve, vd in ( - ("sb_md", "no_sb_md"), - ("sb_lg", "no_sb_lg"), ("nodotsrch", "dotsrch"), + ("sb_lg", "no_sb_lg"), + ("sb_md", "no_sb_md"), ): if ve in vol.flags: vol.flags.pop(vd, None) for ga, vf in ( - ("md_sbf", "md_sbf"), ("lg_sbf", "lg_sbf"), + ("md_sbf", "md_sbf"), ): if vf not in vol.flags: vol.flags[vf] = getattr(self.args, ga) + for k in ("nrand",): + if k not in vol.flags: + vol.flags[k] = getattr(self.args, k) + + for k in ("nrand",): + if k in vol.flags: + vol.flags[k] = int(vol.flags[k]) + for k1, k2 in IMPLICATIONS: if k1 in vol.flags: vol.flags[k2] = True diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index a90b80ab..cb10df67 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -58,6 +58,7 @@ from .util import ( ipnorm, min_ex, quotep, + rand_name, read_header, read_socket, read_socket_chunked, @@ -1395,7 +1396,7 @@ class HttpCli(object): if not self.args.nw: if rnd: - fn = self.rand_name(fdir, fn, rnd) + fn = rand_name(fdir, fn, rnd) fn = sanitize_fn(fn or "", "", [".prologue.html", ".epilogue.html"]) @@ -1465,7 +1466,7 @@ class HttpCli(object): if ext: if rnd: - fn2 = self.rand_name(fdir, "a." + ext, rnd) + fn2 = rand_name(fdir, "a." + ext, rnd) else: fn2 = fn.rsplit(".", 1)[0] + "." + ext @@ -1577,27 +1578,6 @@ class HttpCli(object): else: self.log("bakflip ok", 2) - def rand_name(self, fdir: str, fn: str, rnd: int) -> str: - ok = False - try: - ext = "." + fn.rsplit(".", 1)[1] - except: - ext = "" - - for extra in range(16): - for _ in range(16): - if ok: - break - - nc = rnd + extra - nb = int((6 + 6 * nc) / 8) - zb = os.urandom(nb) - zb = base64.urlsafe_b64encode(zb) - fn = zb[:nc].decode("utf-8") + ext - ok = not bos.path.exists(os.path.join(fdir, fn)) - - return fn - def _spd(self, nbytes: int, add: bool = True) -> str: if add: self.conn.nbyte += nbytes @@ -2026,8 +2006,13 @@ class HttpCli(object): return True def upload_flags(self, vfs: VFS) -> tuple[int, bool, int, list[str], list[str]]: - srnd = self.uparam.get("rand", self.headers.get("rand", "")) - rnd = int(srnd) if srnd and not self.args.nw else 0 + if self.args.nw: + rnd = 0 + else: + rnd = int(self.uparam.get("rand") or self.headers.get("rand") or 0) + if vfs.flags.get("rand"): # force-enable + rnd = max(rnd, vfs.flags["nrand"]) + ac = self.uparam.get( "want", self.headers.get("accept", "").lower().split(";")[-1] ) @@ -2082,7 +2067,7 @@ class HttpCli(object): ) if p_file and not nullwrite: if rnd: - fname = self.rand_name(fdir, fname, rnd) + fname = rand_name(fdir, fname, rnd) if not bos.path.isdir(fdir): raise Pebkac(404, "that folder does not exist") diff --git a/copyparty/up2k.py b/copyparty/up2k.py index ecb3039b..adfeede9 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -41,6 +41,7 @@ from .util import ( hidedir, min_ex, quotep, + rand_name, ren_open, rmdirs, rmdirs_up, @@ -2184,8 +2185,11 @@ class Up2k(object): cur.connection.commit() if not job: + ap1 = djoin(cj["ptop"], cj["prel"]) + if vfs.flags.get("rand") or cj.get("rand"): + cj["name"] = rand_name(ap1, cj["name"], vfs.flags["nrand"]) + if vfs.lim: - ap1 = djoin(cj["ptop"], cj["prel"]) ap2, cj["prel"] = vfs.lim.all( cj["addr"], cj["prel"], cj["size"], ap1, reg ) diff --git a/copyparty/util.py b/copyparty/util.py index 2c7f0ace..e61846f4 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -1505,6 +1505,28 @@ def read_header(sr: Unrecv) -> list[str]: return ret[:ofs].decode("utf-8", "surrogateescape").lstrip("\r\n").split("\r\n") +def rand_name(fdir: str, fn: str, rnd: int) -> str: + ok = False + try: + ext = "." + fn.rsplit(".", 1)[1] + except: + ext = "" + + for extra in range(16): + for _ in range(16): + if ok: + break + + nc = rnd + extra + nb = int((6 + 6 * nc) / 8) + zb = os.urandom(nb) + zb = base64.urlsafe_b64encode(zb) + fn = zb[:nc].decode("utf-8") + ext + ok = not os.path.exists(fsenc(os.path.join(fdir, fn))) + + return fn + + def gen_filekey(salt: str, fspath: str, fsize: int, inode: int) -> str: return base64.urlsafe_b64encode( hashlib.sha512(