From 983865d96c9aaf432a0d8798115478f985c97ca1 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 18:39:18 +0000 Subject: [PATCH 1/6] cbz thumbs without ffmpeg; closes #859 --- copyparty/svchub.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 7e8d147d..bec9ee3d 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -391,7 +391,10 @@ class SvcHub(object): t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]" raise Exception(t % (args.q_mp3,)) else: - args.au_unpk = {} + zss = set(args.th_r_ffa.split(",") + args.th_r_ffv.split(",")) + args.au_unpk = { + k: v for k, v in args.au_unpk.items() if v.split(".")[0] not in zss + } args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage) From 57650a218fa71f94e14de88ca682da64892a404a Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 18:44:14 +0000 Subject: [PATCH 2/6] use reflinks (not hardlinks) in -ss; closes #858 --- copyparty/__main__.py | 2 +- copyparty/svchub.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 9964ca6a..6dc76f04 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1515,7 +1515,7 @@ def add_optouts(ap): def add_safety(ap): ap2 = ap.add_argument_group("safety options") ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js") - ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --dav-auth --vague-403 -nih") + ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih") ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r") ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]") ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)") diff --git a/copyparty/svchub.py b/copyparty/svchub.py index bec9ee3d..100c9bd8 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -157,7 +157,7 @@ class SvcHub(object): args.unpost = 0 args.no_del = True args.no_mv = True - args.hardlink = True + args.reflink = True args.dav_auth = True args.vague_403 = True args.nih = True From a3d950678302e61e62e0d5383528342d2109968b Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 19:11:15 +0000 Subject: [PATCH 3/6] mdns: customize http/https ports (#855) --- copyparty/__main__.py | 2 ++ copyparty/mdns.py | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 6dc76f04..6223a0ba 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1364,6 +1364,8 @@ def add_zc_mdns(ap): ap2.add_argument("--zm6", action="store_true", help="IPv6 only") ap2.add_argument("--zmv", action="store_true", help="verbose mdns") ap2.add_argument("--zmvv", action="store_true", help="verboser mdns") + ap2.add_argument("--zm-http", metavar="PORT", type=int, default=-1, help="port to announce for http/webdav; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649") + ap2.add_argument("--zm-https", metavar="PORT", type=int, default=-1, help="port to announce for https/webdavs; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649") ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)") ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)") ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)") diff --git a/copyparty/mdns.py b/copyparty/mdns.py index 6cb02ad0..fc429aad 100644 --- a/copyparty/mdns.py +++ b/copyparty/mdns.py @@ -102,9 +102,14 @@ class MDNS(MCast): self.log_func(self.logsrc, msg, c) def build_svcs(self) -> tuple[dict[str, dict[str, Any]], set[str]]: + ar = self.args zms = self.args.zms - http = {"port": 80 if 80 in self.args.p else self.args.p[0]} - https = {"port": 443 if 443 in self.args.p else self.args.p[0]} + + zi = ar.zm_http + http = {"port": zi if zi != -1 else 80 if 80 in ar.p else ar.p[0]} + zi = ar.zm_https + https = {"port": zi if zi != -1 else 443 if 443 in ar.p else ar.p[0]} + webdav = http.copy() webdavs = https.copy() webdav["u"] = webdavs["u"] = "u" # KDE requires username @@ -129,16 +134,16 @@ class MDNS(MCast): svcs: dict[str, dict[str, Any]] = {} - if "d" in zms: + if "d" in zms and http["port"]: svcs["_webdav._tcp.local."] = webdav - if "D" in zms: + if "D" in zms and https["port"]: svcs["_webdavs._tcp.local."] = webdavs - if "h" in zms: + if "h" in zms and http["port"]: svcs["_http._tcp.local."] = http - if "H" in zms: + if "H" in zms and https["port"]: svcs["_https._tcp.local."] = https if "f" in zms.lower(): From ec7418734dc3879418757c14a8d065f41cb79655 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 19:12:06 +0000 Subject: [PATCH 4/6] uds-only http/https; closes #855 --- copyparty/__main__.py | 1 + copyparty/tcpsrv.py | 2 ++ tests/util.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 6223a0ba..7b52db4f 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1257,6 +1257,7 @@ def add_network(ap): ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)") ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [\033[32m/foo/bar\033[0m]") + ap2.add_argument("--http-no-tcp", action="store_true", help="do not listen on TCP/IP for http/https; only listen on unix-domain-sockets") if ANYWIN: ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") elif not MACOS: diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 6d294336..f92d5293 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -299,6 +299,8 @@ class TcpSrv(object): try: if tcp: + if self.args.http_no_tcp: + return srv.bind((ip, port)) else: if ANYWIN or self.args.rm_sck: diff --git a/tests/util.py b/tests/util.py index 5ec5746e..7438ab23 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump wram re_dhash see_dots plain_ip" From eb5d767b0139dbd088eae60f741ccdd29611ab1b Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 19:28:41 +0000 Subject: [PATCH 5/6] MTHash: fully preserve exception info --- copyparty/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/copyparty/util.py b/copyparty/util.py index 33f64855..03bf7ade 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -1197,21 +1197,21 @@ class MTHash(object): for nch in range(nchunks): self.work_q.put(nch) - ex = "" + ex: Optional[Exception] = None for nch in range(nchunks): qe = self.done_q.get() try: nch, dig, ofs, csz = qe chunks[nch] = (dig, ofs, csz) except: - ex = ex or str(qe) + ex = ex or qe # type: ignore if pp: mb = (fsz - nch * chunksz) // (1024 * 1024) pp.msg = prefix + str(mb) + suffix if ex: - raise Exception(ex) + raise ex ret = [] for n in range(nchunks): @@ -1228,7 +1228,7 @@ class MTHash(object): try: v = self.hash_at(ofs) except Exception as ex: - v = str(ex) # type: ignore + v = ex # type: ignore self.done_q.put(v) From e3baf932f3fd1256d7bc62b8c2b059f1f39da0bc Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 27 Sep 2025 19:29:36 +0000 Subject: [PATCH 6/6] reflinks are non-e2d safe --- copyparty/authsrv.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index 362d1837..7f3097b4 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -2726,7 +2726,11 @@ class AuthSrv(object): if "dedup" in zv.flags: have_dedup = True - if "e2d" not in zv.flags and "hardlink" not in zv.flags: + if ( + "e2d" not in zv.flags + and "hardlink" not in zv.flags + and "reflink" not in zv.flags + ): unsafe_dedup.append("/" + zv.vpath) t += "\n"