From df0fa9d1b731a93862d558b7ff1ad5c3f19db848 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 26 Sep 2025 23:49:32 +0000 Subject: [PATCH] xbu/xau with custom message --- bin/hooks/reject-and-explain.py | 61 +++++++++++++++++ copyparty/ftpd.py | 42 +++++++----- copyparty/httpcli.py | 114 ++++++++++++++++++++++---------- copyparty/smbd.py | 41 +++++++----- copyparty/tftpd.py | 41 +++++++----- copyparty/up2k.py | 20 ++++-- 6 files changed, 224 insertions(+), 95 deletions(-) create mode 100644 bin/hooks/reject-and-explain.py diff --git a/bin/hooks/reject-and-explain.py b/bin/hooks/reject-and-explain.py new file mode 100644 index 00000000..83152d6f --- /dev/null +++ b/bin/hooks/reject-and-explain.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import sys + + +_ = r""" +reject file upload (with a nice explanation why) + +example usage as global config: + --xbu j,c1,bin/hooks/reject-and-explain.py + +example usage as a volflag (per-volume config): + -v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reject-and-explain.py + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + (share filesystem-path srv/inc as volume /inc, + readable by everyone, read-write for user 'ed', + running this plugin on all uploads with the params listed below) + +example usage as a volflag in a copyparty config file: + [/inc] + srv/inc + accs: + r: * + rw: ed + flags: + xbu: j,c1,bin/hooks/reject-and-explain.py + +parameters explained, + xbu = execute-before-upload (can also be xau, execute-after-upload) + j = this hook needs upload information as json (not just the filename) + c1 = this hook returns json on stdout, so tell copyparty to read that +""" + + +def main(): + inf = json.loads(sys.argv[1]) + vdir, fn = os.path.split(inf["vp"]) + print("inf[vp] = %r" % (inf["vp"],), file=sys.stderr) + + # reject upload if the following regex-pattern does not match: + ok = re.search(r"(^|/)day[0-9]+$", vdir, re.IGNORECASE) + + if ok: + # allow the upload + print("{}") + return + + # the upload was rejected; display the following errortext + # (NOTE: you can optionally mention {0!r} anywhere in the message (zero or more times), and it will be replaced with the file's URL) + + errmsg = "Files can only be uploaded into a folder named 'DayN' where N is a number, for example 'Day573'. This file was REJECTED: " + errmsg += inf["vp"] # mention the file's url at the end of the message + print(json.dumps({"rejectmsg": errmsg})) + + +if __name__ == "__main__": + main() diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 9c9e7ff4..5482436b 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -493,24 +493,30 @@ class FtpHandler(FTPHandler): return self.vfs_map[ap] = vp xbu = vfs.flags.get("xbu") - if xbu and not runhook( - None, - None, - self.hub.up2k, - "xbu.ftpd", - xbu, - ap, - vp, - "", - self.uname, - self.hub.asrv.vfs.get_perms(vp, self.uname), - 0, - 0, - self.cli_ip, - time.time(), - "", - ): - raise FSE("Upload blocked by xbu server config") + if xbu: + hr = runhook( + None, + None, + self.hub.up2k, + "xbu.ftpd", + xbu, + ap, + vp, + "", + self.uname, + self.hub.asrv.vfs.get_perms(vp, self.uname), + 0, + 0, + self.cli_ip, + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "Upload blocked by xbu server config: %r" % (vp,) + self.respond("550 %s" % (t,), logging.info) + return # print("ftp_STOR: {} {} => {}".format(vp, mode, ap)) ret = FTPHandler.ftp_STOR(self, file, mode) diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index d18be57f..6232f93e 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -2268,8 +2268,10 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xbu server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vp,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -2401,8 +2403,10 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" % (vp,) self.log(t, 1) wunlink(self.log, path, vfs.flags) raise Pebkac(403, t) @@ -3214,11 +3218,38 @@ class HttpCli(object): new_file += ".md" sanitized = sanitize_fn(new_file, "") + fdir = vfs.canonical(rem) + fn = os.path.join(fdir, sanitized) + + for hn in ("xbu", "xau"): + xxu = vfs.flags.get(hn) + if xxu: + hr = runhook( + self.log, + self.conn.hsrv.broker, + None, + "%s.http.new-md" % (hn,), + xxu, + fn, + vjoin(self.vpath, sanitized), + self.host, + self.uname, + self.asrv.vfs.get_perms(self.vpath, self.uname), + time.time(), + 0, + self.ip, + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "new-md blocked by " + hn + " server config: %r" + t = t % (vjoin(vfs.vpath, rem),) + self.log(t, 1) + raise Pebkac(403, t) if not nullwrite: - fdir = vfs.canonical(rem) - fn = os.path.join(fdir, sanitized) - if bos.path.exists(fn): raise Pebkac(500, "that file exists already") @@ -3382,8 +3413,11 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xbu server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" + t = t % (vjoin(upload_vpath, fname),) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -3486,8 +3520,11 @@ class HttpCli(object): at, "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" + t = t % (vjoin(upload_vpath, fname),) self.log(t, 1) wunlink(self.log, abspath, vfs.flags) raise Pebkac(403, t) @@ -3779,7 +3816,7 @@ class HttpCli(object): xbu = vfs.flags.get("xbu") if xbu: - if not runhook( + hr = runhook( self.log, self.conn.hsrv.broker, None, @@ -3795,8 +3832,11 @@ class HttpCli(object): self.ip, time.time(), "", - ): - t = "save blocked by xbu server config" + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "save blocked by xbu server config" self.log(t, 1) raise Pebkac(403, t) @@ -3823,27 +3863,31 @@ class HttpCli(object): sha512 = sha512[:56] xau = vfs.flags.get("xau") - if xau and not runhook( - self.log, - self.conn.hsrv.broker, - None, - "xau.http.txt", - xau, - fp, - self.vpath, - self.host, - self.uname, - self.asrv.vfs.get_perms(self.vpath, self.uname), - new_lastmod, - sz, - self.ip, - new_lastmod, - "", - ): - t = "save blocked by xau server config" - self.log(t, 1) - wunlink(self.log, fp, vfs.flags) - raise Pebkac(403, t) + if xau: + hr = runhook( + self.log, + self.conn.hsrv.broker, + None, + "xau.http.txt", + xau, + fp, + self.vpath, + self.host, + self.uname, + self.asrv.vfs.get_perms(self.vpath, self.uname), + new_lastmod, + sz, + self.ip, + new_lastmod, + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "save blocked by xau server config" + self.log(t, 1) + wunlink(self.log, fp, vfs.flags) + raise Pebkac(403, t) self.conn.hsrv.broker.say( "up2k.hash_file", diff --git a/copyparty/smbd.py b/copyparty/smbd.py index 3fbc1130..0d03ac4e 100644 --- a/copyparty/smbd.py +++ b/copyparty/smbd.py @@ -246,24 +246,29 @@ class SMB(object): ap = absreal(ap) xbu = vfs.flags.get("xbu") - if xbu and not runhook( - self.nlog, - None, - self.hub.up2k, - "xbu.smb", - xbu, - ap, - vpath, - "", - "", - "", - 0, - 0, - "1.7.6.2", - time.time(), - "", - ): - yeet("blocked by xbu server config: %r" % (vpath,)) + if xbu: + hr = runhook( + self.nlog, + None, + self.hub.up2k, + "xbu.smb", + xbu, + ap, + vpath, + "", + "", + "", + 0, + 0, + "1.7.6.2", + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "blocked by xbu server config: %r" % (vpath,) + yeet(t) ret = bos.open(ap, flags, *a, mode=chmod, **ka) if wr: diff --git a/copyparty/tftpd.py b/copyparty/tftpd.py index 4ef01103..4b95a2b6 100644 --- a/copyparty/tftpd.py +++ b/copyparty/tftpd.py @@ -363,24 +363,29 @@ class Tftpd(object): yeet("blocked write; folder not world-deletable: /%s" % (vpath,)) xbu = vfs.flags.get("xbu") - if xbu and not runhook( - self.nlog, - None, - self.hub.up2k, - "xbu.tftpd", - xbu, - ap, - vpath, - "", - "", - "", - 0, - 0, - "8.3.8.7", - time.time(), - "", - ): - yeet("blocked by xbu server config: %r" % (vpath,)) + if xbu: + hr = runhook( + self.nlog, + None, + self.hub.up2k, + "xbu.tftpd", + xbu, + ap, + vpath, + "", + "", + "", + 0, + 0, + "8.3.8.7", + time.time(), + "", + ) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vpath,) + yeet(t) if not self.args.tftp_nols and bos.path.isdir(ap): return self._ls(vpath, "", 0, True) diff --git a/copyparty/up2k.py b/copyparty/up2k.py index f598b69d..85ad6517 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -3300,8 +3300,11 @@ class Up2k(object): job["at"], "", ) - if not hr: - t = "upload blocked by xbu server config: %r" % (dst,) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" + t = t % (vp,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"): @@ -3981,8 +3984,11 @@ class Up2k(object): at or time.time(), "", ) - if not hr: - t = "upload blocked by xau server config" + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xau server config: %r" + t = t % (djoin(vtop, rd, fn),) self.log(t, 1) wunlink(self.log, dst, vflags) self.registry[ptop].pop(wark, None) @@ -5132,8 +5138,10 @@ class Up2k(object): job["t0"], "", ) - if not hr: - t = "upload blocked by xbu server config: %r" % (vp_chk,) + t = hr.get("rejectmsg") or "" + if t or not hr: + if not t: + t = "upload blocked by xbu server config: %r" % (vp_chk,) self.log(t, 1) raise Pebkac(403, t) if hr.get("reloc"):