mirror of
https://github.com/9001/copyparty.git
synced 2025-12-08 06:03:29 -07:00
hooks: retcode 100, zmq json;
hooks returning exitcode 0 will: * run the next hook, if any * allow the original action, unless successive hook opposes hooks returning exitcode 100 will: * abort running successive hooks * allow the original action hooks returning anything other than 0 or 100 will: * abort running successive hooks * REJECT the original action zmq can now respond with json; a dict with "rc", "rejectmsg", "reloc" and so on, just like other hooks replying with json
This commit is contained in:
parent
ca6d3a5c16
commit
889bd3242a
|
|
@ -4,6 +4,11 @@ these programs either take zero arguments, or a filepath (the affected file), or
|
|||
|
||||
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbc/xac/xbr/xar/xbd/xad/xban)
|
||||
|
||||
in particular, if a hook is loaded into copyparty with the hook-flag `c` ("check") then its exit-code controls the action that launched the hook:
|
||||
* exit-code `0` = allow the action, and/or continue running the next hook
|
||||
* exit-code `100` = allow the action, and stop running any remaining consecutive hooks
|
||||
* anything else = reject/prevent the original action, and don't run the remaining hooks
|
||||
|
||||
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ def rep_server():
|
|||
print("copyparty says %r" % (sck.recv_string(),))
|
||||
reply = b"thx"
|
||||
# reply = b"return 1" # non-zero to block an upload
|
||||
# reply = b'{"rc":1}' # or as json, that's fine too
|
||||
# reply = b'{"rejectmsg":"naw dude"}' # or custom message
|
||||
sck.send(reply)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -809,7 +809,8 @@ def get_sects():
|
|||
\033[0m
|
||||
hooks specified as commandline --args are appended to volflags;
|
||||
each commandline --arg and volflag can be specified multiple times,
|
||||
each hook will execute in order unless one returns non-zero
|
||||
each hook will execute in order unless one returns non-zero, or
|
||||
"100" which means "stop daisychaining and return 0 (success/OK)"
|
||||
|
||||
optionally prefix the command with comma-sep. flags similar to -mtp:
|
||||
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ class FtpHandler(FTPHandler):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "Upload blocked by xbu server config: %r" % (vp,)
|
||||
self.respond("550 %s" % (t,), logging.info)
|
||||
|
|
|
|||
|
|
@ -913,29 +913,31 @@ class HttpCli(object):
|
|||
return False
|
||||
|
||||
xban = self.vn.flags.get("xban")
|
||||
if not xban or not runhook(
|
||||
self.log,
|
||||
self.conn.hsrv.broker,
|
||||
None,
|
||||
"xban",
|
||||
xban,
|
||||
self.vn.canonical(self.rem),
|
||||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
"",
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
time.time(),
|
||||
[reason, reason],
|
||||
):
|
||||
self.log("client banned: %s" % (descr,), 1)
|
||||
self.conn.hsrv.bans[ip] = bonk
|
||||
self.conn.hsrv.nban += 1
|
||||
return True
|
||||
if xban:
|
||||
hr = runhook(
|
||||
self.log,
|
||||
self.conn.hsrv.broker,
|
||||
None,
|
||||
"xban",
|
||||
xban,
|
||||
self.vn.canonical(self.rem),
|
||||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
"",
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
time.time(),
|
||||
[reason, reason],
|
||||
)
|
||||
if hr.get("rv") == 0:
|
||||
return False
|
||||
|
||||
return False
|
||||
self.log("client banned: %s" % (descr,), 1)
|
||||
self.conn.hsrv.bans[ip] = bonk
|
||||
self.conn.hsrv.nban += 1
|
||||
return True
|
||||
|
||||
def is_banned(self) -> bool:
|
||||
if not self.conn.bans:
|
||||
|
|
@ -2386,7 +2388,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vp,)
|
||||
self.log(t, 1)
|
||||
|
|
@ -2521,7 +2523,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r" % (vp,)
|
||||
self.log(t, 1)
|
||||
|
|
@ -3359,7 +3361,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "new-md blocked by " + hn + " server config: %r"
|
||||
t = t % (vjoin(vfs.vpath, rem),)
|
||||
|
|
@ -3530,7 +3532,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r"
|
||||
t = t % (vjoin(upload_vpath, fname),)
|
||||
|
|
@ -3637,7 +3639,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r"
|
||||
t = t % (vjoin(upload_vpath, fname),)
|
||||
|
|
@ -3950,7 +3952,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "save blocked by xbu server config"
|
||||
self.log(t, 1)
|
||||
|
|
@ -3998,7 +4000,7 @@ class HttpCli(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "save blocked by xau server config"
|
||||
self.log(t, 1)
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ class SMB(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "blocked by xbu server config: %r" % (vpath,)
|
||||
yeet(t)
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ class Tftpd(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vpath,)
|
||||
yeet(t)
|
||||
|
|
|
|||
|
|
@ -3307,7 +3307,7 @@ class Up2k(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r"
|
||||
t = t % (vp,)
|
||||
|
|
@ -4003,7 +4003,7 @@ class Up2k(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r"
|
||||
t = t % (djoin(vtop, rd, fn),)
|
||||
|
|
@ -4221,7 +4221,7 @@ class Up2k(object):
|
|||
_ = dbv.get(volpath, uname, *permsets[0])
|
||||
|
||||
if xbd:
|
||||
if not runhook(
|
||||
hr = runhook(
|
||||
self.log,
|
||||
None,
|
||||
self,
|
||||
|
|
@ -4237,9 +4237,12 @@ class Up2k(object):
|
|||
ip,
|
||||
time.time(),
|
||||
None,
|
||||
):
|
||||
t = "delete blocked by xbd server config: %r"
|
||||
self.log(t % (abspath,), 1)
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "delete blocked by xbd server config: %r" % (abspath,)
|
||||
self.log(t, 1)
|
||||
continue
|
||||
|
||||
n_files += 1
|
||||
|
|
@ -4389,7 +4392,7 @@ class Up2k(object):
|
|||
xbc = svn.flags.get("xbc")
|
||||
xac = dvn.flags.get("xac")
|
||||
if xbc:
|
||||
if not runhook(
|
||||
hr = runhook(
|
||||
self.log,
|
||||
None,
|
||||
self,
|
||||
|
|
@ -4405,8 +4408,11 @@ class Up2k(object):
|
|||
ip,
|
||||
time.time(),
|
||||
None,
|
||||
):
|
||||
t = "copy blocked by xbr server config: %r" % (svp,)
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "copy blocked by xbr server config: %r" % (svp,)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(405, t)
|
||||
|
||||
|
|
@ -4641,7 +4647,7 @@ class Up2k(object):
|
|||
xbr = svn.flags.get("xbr")
|
||||
xar = dvn.flags.get("xar")
|
||||
if xbr:
|
||||
if not runhook(
|
||||
hr = runhook(
|
||||
self.log,
|
||||
None,
|
||||
self,
|
||||
|
|
@ -4657,8 +4663,11 @@ class Up2k(object):
|
|||
ip,
|
||||
time.time(),
|
||||
None,
|
||||
):
|
||||
t = "move blocked by xbr server config: %r" % (svp,)
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "move blocked by xbr server config: %r" % (svp,)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(405, t)
|
||||
|
||||
|
|
@ -5163,7 +5172,7 @@ class Up2k(object):
|
|||
None,
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if t or hr.get("rc") != 0:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vp_chk,)
|
||||
self.log(t, 1)
|
||||
|
|
|
|||
|
|
@ -3930,7 +3930,13 @@ def _runhook(
|
|||
zi, zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
|
||||
if zi:
|
||||
raise Exception("zmq says %d" % (zi,))
|
||||
return {"rc": 0, "stdout": zs}
|
||||
try:
|
||||
ret = json.loads(zs)
|
||||
if "rc" not in ret:
|
||||
ret["rc"] = 0
|
||||
return ret
|
||||
except:
|
||||
return {"rc": 0, "stdout": zs}
|
||||
|
||||
if sin:
|
||||
sp_ka["sin"] = (arg + "\n").encode("utf-8", "replace")
|
||||
|
|
@ -3949,20 +3955,23 @@ def _runhook(
|
|||
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
|
||||
if chk and rc:
|
||||
ret["rc"] = rc
|
||||
retchk(rc, bcmd, err, log, 5)
|
||||
zi = 0 if rc == 100 else rc
|
||||
retchk(zi, bcmd, err, log, 5)
|
||||
else:
|
||||
try:
|
||||
ret = json.loads(v)
|
||||
except:
|
||||
ret = {}
|
||||
pass
|
||||
|
||||
try:
|
||||
if "stdout" not in ret:
|
||||
ret["stdout"] = v
|
||||
if "stderr" not in ret:
|
||||
ret["stderr"] = err
|
||||
if "rc" not in ret:
|
||||
ret["rc"] = rc
|
||||
except:
|
||||
ret = {"rc": rc, "stdout": v}
|
||||
ret = {"rc": rc, "stdout": v, "stderr": err}
|
||||
|
||||
if wait:
|
||||
wait -= time.time() - t0
|
||||
|
|
@ -3994,6 +4003,7 @@ def runhook(
|
|||
verbose = args.hook_v
|
||||
vp = vp.replace("\\", "/")
|
||||
ret = {"rc": 0}
|
||||
stop = False
|
||||
for cmd in cmds:
|
||||
try:
|
||||
hr = _runhook(
|
||||
|
|
@ -4001,8 +4011,6 @@ def runhook(
|
|||
)
|
||||
if verbose and log:
|
||||
log("hook(%s) %r => \033[32m%s" % (src, cmd, hr), 6)
|
||||
if not hr:
|
||||
return {}
|
||||
for k, v in hr.items():
|
||||
if k in ("idx", "del") and v:
|
||||
if broker:
|
||||
|
|
@ -4013,17 +4021,20 @@ def runhook(
|
|||
elif k == "reloc" and v:
|
||||
# idk, just take the last one ig
|
||||
ret["reloc"] = v
|
||||
elif k == "rc" and v:
|
||||
stop = True
|
||||
ret[k] = 0 if v == 100 else v
|
||||
elif k in ret:
|
||||
if k == "rc" and v:
|
||||
ret[k] = v
|
||||
elif k == "stdout" and v and not ret[k]:
|
||||
if k == "stdout" and v and not ret[k]:
|
||||
ret[k] = v
|
||||
else:
|
||||
ret[k] = v
|
||||
except Exception as ex:
|
||||
(log or print)("hook: %r, %s" % (ex, ex))
|
||||
if ",c," in "," + cmd:
|
||||
return {}
|
||||
return {"rc": 1}
|
||||
break
|
||||
if stop:
|
||||
break
|
||||
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -78,6 +78,41 @@ class TestHooks(tu.TC):
|
|||
h, b = self.curl(url_dl)
|
||||
self.assertEqual(b, "ok %s\n" % (url_up))
|
||||
|
||||
def test2(self):
|
||||
hooktxt = "import sys\nopen('h%d','wb').close()\nsys.exit(%d)\n"
|
||||
for hooktype in ("xbu", "xau"):
|
||||
for upfun in (self.put, self.bup):
|
||||
self.reset()
|
||||
for n in [0, 1, 100]:
|
||||
with open("h%d.py" % (n,), "wb") as f:
|
||||
f.write((hooktxt % (n, n)).encode("utf-8"))
|
||||
vcfg = [
|
||||
"012:012:A:c,H=c,h0.py:c,H=c,h1.py:c,H=c,h100.py",
|
||||
"021:021:A:c,H=c,h0.py:c,H=c,h100.py:c,H=c,h1.py",
|
||||
"120:120:A:c,H=c,h1.py:c,H=c,h100.py:c,H=c,h0.py",
|
||||
"30:30:A:c,H=c,enoent.py:c,H=c,h100.py", # not-exist
|
||||
]
|
||||
vcfg = [x.replace("H", hooktype) for x in vcfg]
|
||||
self.args = Cfg(v=vcfg, a=["o:o"], e2d=True)
|
||||
self.asrv = AuthSrv(self.args, self.log)
|
||||
self.cinit()
|
||||
scenarios = (
|
||||
("012", False, True, True, False),
|
||||
("021", True, True, False, True),
|
||||
("120", False, False, True, False),
|
||||
("30", False, False, False, False),
|
||||
)
|
||||
for (vp, ok, h0, h1, h2) in scenarios:
|
||||
for zs in ("h0", "h1", "h100"):
|
||||
if os.path.exists(zs):
|
||||
os.unlink(zs)
|
||||
vp = "%s/f" % (vp,)
|
||||
h, b = upfun(vp)
|
||||
self.assertEqual(ok, os.path.exists(vp))
|
||||
self.assertEqual(h0, os.path.exists("h0"))
|
||||
self.assertEqual(h1, os.path.exists("h1"))
|
||||
self.assertEqual(h2, os.path.exists("h100"))
|
||||
|
||||
def makehook(self, hs):
|
||||
with open("h.py", "wb") as f:
|
||||
f.write(hs.encode("utf-8"))
|
||||
|
|
|
|||
Loading…
Reference in a new issue