copyparty 1.13.3__py3-none-any.whl → 1.13.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/ssdp.py CHANGED
@@ -184,7 +184,7 @@ class SSDPd(MCast):
184
184
  except:
185
185
  pass
186
186
 
187
- self.srv = {}
187
+ self.srv.clear()
188
188
 
189
189
  def eat(self, buf , addr ) :
190
190
  cip = addr[0]
copyparty/svchub.py CHANGED
@@ -473,8 +473,10 @@ class SvcHub(object):
473
473
  zsl = al.th_covers.split(",")
474
474
  zsl = [x.strip() for x in zsl]
475
475
  zsl = [x for x in zsl if x]
476
- al.th_covers = set(zsl)
477
- al.th_coversd = set(zsl + ["." + x for x in zsl])
476
+ al.th_covers = zsl
477
+ al.th_coversd = zsl + ["." + x for x in zsl]
478
+ al.th_covers_set = set(al.th_covers)
479
+ al.th_coversd_set = set(al.th_coversd)
478
480
 
479
481
  for k in "c".split(" "):
480
482
  vl = getattr(al, k)
copyparty/tcpsrv.py CHANGED
@@ -15,6 +15,7 @@ from .util import (
15
15
  E_ADDR_IN_USE,
16
16
  E_ADDR_NOT_AVAIL,
17
17
  E_UNREACH,
18
+ HAVE_IPV6,
18
19
  IP6ALL,
19
20
  Netdev,
20
21
  min_ex,
@@ -108,8 +109,10 @@ class TcpSrv(object):
108
109
 
109
110
  eps = {
110
111
  "127.0.0.1": Netdev("127.0.0.1", 0, "", "local only"),
111
- "::1": Netdev("::1", 0, "", "local only"),
112
112
  }
113
+ if HAVE_IPV6:
114
+ eps["::1"] = Netdev("::1", 0, "", "local only")
115
+
113
116
  nonlocals = [x for x in self.args.i if x not in [k.split("/")[0] for k in eps]]
114
117
  if nonlocals:
115
118
  try:
copyparty/tftpd.py CHANGED
@@ -33,7 +33,7 @@ from partftpy import (
33
33
  )
34
34
  from partftpy.TftpShared import TftpException
35
35
 
36
- from .__init__ import EXE, TYPE_CHECKING
36
+ from .__init__ import EXE, PY2, TYPE_CHECKING
37
37
  from .authsrv import VFS
38
38
  from .bos import bos
39
39
  from .util import BytesIO, Daemon, ODict, exclude_dotfiles, min_ex, runhook, undot
@@ -92,7 +92,7 @@ class Tftpd(object):
92
92
  TftpServer,
93
93
  ]
94
94
  cbak = []
95
- if not self.args.tftp_no_fast and not EXE:
95
+ if not self.args.tftp_no_fast and not EXE and not PY2:
96
96
  try:
97
97
  ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
98
98
  for C in Cs:
@@ -102,7 +102,7 @@ class Tftpd(object):
102
102
  cfn = C.__spec__.origin
103
103
  exec (compile(src2, filename=cfn, mode="exec"), C.__dict__)
104
104
  except Exception:
105
- t = "failed to optimize tftp code; run with --tftp-noopt if there are issues:\n"
105
+ t = "failed to optimize tftp code; run with --tftp-no-fast if there are issues:\n"
106
106
  self.log("tftp", t + min_ex(), 3)
107
107
  for n, zd in enumerate(cbak):
108
108
  Cs[n].__dict__ = zd
@@ -147,11 +147,6 @@ class Tftpd(object):
147
147
 
148
148
  self._disarm(fos)
149
149
 
150
- ip = next((x for x in self.args.i if ":" not in x), None)
151
- if not ip:
152
- self.log("tftp", "IPv6 not supported for tftp; listening on 0.0.0.0", 3)
153
- ip = "0.0.0.0"
154
-
155
150
  self.port = int(self.args.tftp)
156
151
  self.srv = []
157
152
  self.ips = []
@@ -165,7 +160,7 @@ class Tftpd(object):
165
160
  if "::" in ips:
166
161
  ips.append("0.0.0.0")
167
162
 
168
- if self.args.ftp4:
163
+ if self.args.tftp4:
169
164
  ips = [x for x in ips if ":" not in x]
170
165
 
171
166
  ips = list(ODict.fromkeys(ips)) # dedup
@@ -330,7 +325,7 @@ class Tftpd(object):
330
325
 
331
326
  xbu = vfs.flags.get("xbu")
332
327
  if xbu and not runhook(
333
- self.nlog, xbu, ap, vpath, "", "", 0, 0, "8.3.8.7", 0, ""
328
+ self.nlog, xbu, ap, vpath, "", "", "", 0, 0, "8.3.8.7", 0, ""
334
329
  ):
335
330
  yeet("blocked by xbu server config: " + vpath)
336
331
 
copyparty/th_cli.py CHANGED
@@ -56,7 +56,8 @@ class ThumbCli(object):
56
56
 
57
57
  want_opus = fmt in ("opus", "caf", "mp3")
58
58
  is_au = ext in self.fmt_ffa
59
- if is_au:
59
+ is_vau = want_opus and ext in self.fmt_ffv
60
+ if is_au or is_vau:
60
61
  if want_opus:
61
62
  if self.args.no_acode:
62
63
  return None
@@ -104,7 +105,7 @@ class ThumbCli(object):
104
105
 
105
106
  fmt = sfmt
106
107
 
107
- elif fmt[:1] == "p" and not is_au:
108
+ elif fmt[:1] == "p" and not is_au and not is_vid:
108
109
  t = "cannot thumbnail [%s]: png only allowed for waveforms"
109
110
  self.log(t % (rem), 6)
110
111
  return None
copyparty/th_srv.py CHANGED
@@ -301,23 +301,31 @@ class ThumbSrv(object):
301
301
  ap_unpk = abspath
302
302
 
303
303
  if not bos.path.exists(tpath):
304
+ want_mp3 = tpath.endswith(".mp3")
305
+ want_opus = tpath.endswith(".opus") or tpath.endswith(".caf")
306
+ want_png = tpath.endswith(".png")
307
+ want_au = want_mp3 or want_opus
304
308
  for lib in self.args.th_dec:
309
+ can_au = lib == "ff" and (
310
+ ext in self.fmt_ffa or ext in self.fmt_ffv
311
+ )
312
+
305
313
  if lib == "pil" and ext in self.fmt_pil:
306
314
  funs.append(self.conv_pil)
307
315
  elif lib == "vips" and ext in self.fmt_vips:
308
316
  funs.append(self.conv_vips)
309
- elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv:
310
- funs.append(self.conv_ffmpeg)
311
- elif lib == "ff" and ext in self.fmt_ffa:
312
- if tpath.endswith(".opus") or tpath.endswith(".caf"):
317
+ elif can_au and (want_png or want_au):
318
+ if want_opus:
313
319
  funs.append(self.conv_opus)
314
- elif tpath.endswith(".mp3"):
320
+ elif want_mp3:
315
321
  funs.append(self.conv_mp3)
316
- elif tpath.endswith(".png"):
322
+ elif want_png:
317
323
  funs.append(self.conv_waves)
318
324
  png_ok = True
319
- else:
320
- funs.append(self.conv_spec)
325
+ elif lib == "ff" and (ext in self.fmt_ffi or ext in self.fmt_ffv):
326
+ funs.append(self.conv_ffmpeg)
327
+ elif lib == "ff" and ext in self.fmt_ffa and not want_au:
328
+ funs.append(self.conv_spec)
321
329
 
322
330
  tdir, tfn = os.path.split(tpath)
323
331
  ttpath = os.path.join(tdir, "w", tfn)
copyparty/up2k.py CHANGED
@@ -651,7 +651,7 @@ class Up2k(object):
651
651
  return False, flags
652
652
 
653
653
  ret = {k: v for k, v in flags.items() if not k.startswith("e2t")}
654
- if ret.keys() == flags.keys():
654
+ if len(ret) == len(flags):
655
655
  return False, flags
656
656
 
657
657
  return True, ret
@@ -1192,6 +1192,9 @@ class Up2k(object):
1192
1192
  fat32 = True
1193
1193
  cv = ""
1194
1194
 
1195
+ th_cvd = self.args.th_coversd
1196
+ th_cvds = self.args.th_coversd_set
1197
+
1195
1198
  assert self.pp and self.mem_cur
1196
1199
  self.pp.msg = "a%d %s" % (self.pp.n, cdir)
1197
1200
 
@@ -1276,12 +1279,21 @@ class Up2k(object):
1276
1279
 
1277
1280
  files.append((sz, lmod, iname))
1278
1281
  liname = iname.lower()
1279
- if sz and (
1280
- iname in self.args.th_coversd
1281
- or (
1282
+ if (
1283
+ sz
1284
+ and (
1285
+ liname in th_cvds
1286
+ or (
1287
+ not cv
1288
+ and liname.rsplit(".", 1)[-1] in CV_EXTS
1289
+ and not iname.startswith(".")
1290
+ )
1291
+ )
1292
+ and (
1282
1293
  not cv
1283
- and liname.rsplit(".", 1)[-1] in CV_EXTS
1284
- and not iname.startswith(".")
1294
+ or liname not in th_cvds
1295
+ or cv.lower() not in th_cvds
1296
+ or th_cvd.index(liname) < th_cvd.index(cv.lower())
1285
1297
  )
1286
1298
  ):
1287
1299
  cv = iname
@@ -2755,6 +2767,7 @@ class Up2k(object):
2755
2767
  job["vtop"],
2756
2768
  job["host"],
2757
2769
  job["user"],
2770
+ self.asrv.vfs.get_perms(job["vtop"], job["user"]),
2758
2771
  job["lmod"],
2759
2772
  job["size"],
2760
2773
  job["addr"],
@@ -2997,8 +3010,8 @@ class Up2k(object):
2997
3010
  times = (int(time.time()), int(lmod))
2998
3011
  bos.utime(dst, times, False)
2999
3012
 
3000
- def handle_chunk(
3001
- self, ptop , wark , chash
3013
+ def handle_chunks(
3014
+ self, ptop , wark , chashes
3002
3015
  ) :
3003
3016
  with self.mutex, self.reg_mutex:
3004
3017
  self.db_act = self.vol_act[ptop] = time.time()
@@ -3008,26 +3021,37 @@ class Up2k(object):
3008
3021
  self.log("unknown wark [{}], known: {}".format(wark, known))
3009
3022
  raise Pebkac(400, "unknown wark" + SSEELOG)
3010
3023
 
3011
- if chash not in job["need"]:
3012
- msg = "chash = {} , need:\n".format(chash)
3013
- msg += "\n".join(job["need"])
3014
- self.log(msg)
3015
- raise Pebkac(400, "already got that but thanks??")
3024
+ for chash in chashes:
3025
+ if chash not in job["need"]:
3026
+ msg = "chash = {} , need:\n".format(chash)
3027
+ msg += "\n".join(job["need"])
3028
+ self.log(msg)
3029
+ raise Pebkac(400, "already got that (%s) but thanks??" % (chash,))
3030
+
3031
+ if chash in job["busy"]:
3032
+ nh = len(job["hash"])
3033
+ idx = job["hash"].index(chash)
3034
+ t = "that chunk is already being written to:\n {}\n {} {}/{}\n {}"
3035
+ raise Pebkac(400, t.format(wark, chash, idx, nh, job["name"]))
3016
3036
 
3017
- nchunk = [n for n, v in enumerate(job["hash"]) if v == chash]
3018
- if not nchunk:
3019
- raise Pebkac(400, "unknown chunk")
3037
+ chunksize = up2k_chunksize(job["size"])
3020
3038
 
3021
- if chash in job["busy"]:
3022
- nh = len(job["hash"])
3023
- idx = job["hash"].index(chash)
3024
- t = "that chunk is already being written to:\n {}\n {} {}/{}\n {}"
3025
- raise Pebkac(400, t.format(wark, chash, idx, nh, job["name"]))
3039
+ coffsets = []
3040
+ for chash in chashes:
3041
+ nchunk = [n for n, v in enumerate(job["hash"]) if v == chash]
3042
+ if not nchunk:
3043
+ raise Pebkac(400, "unknown chunk %s" % (chash))
3026
3044
 
3027
- path = djoin(job["ptop"], job["prel"], job["tnam"])
3045
+ ofs = [chunksize * x for x in nchunk]
3046
+ coffsets.append(ofs)
3028
3047
 
3029
- chunksize = up2k_chunksize(job["size"])
3030
- ofs = [chunksize * x for x in nchunk]
3048
+ for ofs1, ofs2 in zip(coffsets, coffsets[1:]):
3049
+ gap = (ofs2[0] - ofs1[0]) - chunksize
3050
+ if gap:
3051
+ t = "only sibling chunks can be stitched; gap of %d bytes between offsets %d and %d in %s"
3052
+ raise Pebkac(400, t % (gap, ofs1[0], ofs2[0], job["name"]))
3053
+
3054
+ path = djoin(job["ptop"], job["prel"], job["tnam"])
3031
3055
 
3032
3056
  if not job["sprs"]:
3033
3057
  cur_sz = bos.path.getsize(path)
@@ -3040,17 +3064,20 @@ class Up2k(object):
3040
3064
 
3041
3065
  job["poke"] = time.time()
3042
3066
 
3043
- return chunksize, ofs, path, job["lmod"], job["sprs"]
3067
+ return chunksize, coffsets, path, job["lmod"], job["sprs"]
3044
3068
 
3045
- def release_chunk(self, ptop , wark , chash ) :
3069
+ def release_chunks(self, ptop , wark , chashes ) :
3046
3070
  with self.reg_mutex:
3047
3071
  job = self.registry[ptop].get(wark)
3048
3072
  if job:
3049
- job["busy"].pop(chash, None)
3073
+ for chash in chashes:
3074
+ job["busy"].pop(chash, None)
3050
3075
 
3051
3076
  return True
3052
3077
 
3053
- def confirm_chunk(self, ptop , wark , chash ) :
3078
+ def confirm_chunks(
3079
+ self, ptop , wark , chashes
3080
+ ) :
3054
3081
  with self.mutex, self.reg_mutex:
3055
3082
  self.db_act = self.vol_act[ptop] = time.time()
3056
3083
  try:
@@ -3059,14 +3086,16 @@ class Up2k(object):
3059
3086
  src = djoin(pdir, job["tnam"])
3060
3087
  dst = djoin(pdir, job["name"])
3061
3088
  except Exception as ex:
3062
- return "confirm_chunk, wark, " + repr(ex) # type: ignore
3089
+ return "confirm_chunk, wark(%r)" % (ex,) # type: ignore
3063
3090
 
3064
- job["busy"].pop(chash, None)
3091
+ for chash in chashes:
3092
+ job["busy"].pop(chash, None)
3065
3093
 
3066
3094
  try:
3067
- job["need"].remove(chash)
3095
+ for chash in chashes:
3096
+ job["need"].remove(chash)
3068
3097
  except Exception as ex:
3069
- return "confirm_chunk, chash, " + repr(ex) # type: ignore
3098
+ return "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore
3070
3099
 
3071
3100
  ret = len(job["need"])
3072
3101
  if ret > 0:
@@ -3077,7 +3106,7 @@ class Up2k(object):
3077
3106
 
3078
3107
  return ret, dst
3079
3108
 
3080
- def finish_upload(self, ptop , wark , busy_aps ) :
3109
+ def finish_upload(self, ptop , wark , busy_aps ) :
3081
3110
  self.busy_aps = busy_aps
3082
3111
  with self.mutex, self.reg_mutex:
3083
3112
  self._finish_upload(ptop, wark)
@@ -3282,6 +3311,7 @@ class Up2k(object):
3282
3311
  djoin(vtop, rd, fn),
3283
3312
  host,
3284
3313
  usr,
3314
+ self.asrv.vfs.get_perms(djoin(vtop, rd, fn), usr),
3285
3315
  int(ts),
3286
3316
  sz,
3287
3317
  ip,
@@ -3316,15 +3346,29 @@ class Up2k(object):
3316
3346
  with self.rescan_cond:
3317
3347
  self.rescan_cond.notify_all()
3318
3348
 
3319
- if rd and sz and fn.lower() in self.args.th_coversd:
3349
+ if rd and sz and fn.lower() in self.args.th_coversd_set:
3320
3350
  # wasteful; db_add will re-index actual covers
3321
3351
  # but that won't catch existing files
3322
3352
  crd, cdn = rd.rsplit("/", 1) if "/" in rd else ("", rd)
3323
3353
  try:
3324
- db.execute("delete from cv where rd=? and dn=?", (crd, cdn))
3325
- db.execute("insert into cv values (?,?,?)", (crd, cdn, fn))
3354
+ q = "select fn from cv where rd=? and dn=?"
3355
+ db_cv = db.execute(q, (crd, cdn)).fetchone()[0]
3356
+ db_lcv = db_cv.lower()
3357
+ if db_lcv in self.args.th_coversd_set:
3358
+ idx_db = self.args.th_coversd.index(db_lcv)
3359
+ idx_fn = self.args.th_coversd.index(fn.lower())
3360
+ add_cv = idx_fn < idx_db
3361
+ else:
3362
+ add_cv = True
3326
3363
  except:
3327
- pass
3364
+ add_cv = True
3365
+
3366
+ if add_cv:
3367
+ try:
3368
+ db.execute("delete from cv where rd=? and dn=?", (crd, cdn))
3369
+ db.execute("insert into cv values (?,?,?)", (crd, cdn, fn))
3370
+ except:
3371
+ pass
3328
3372
 
3329
3373
  def handle_rm(
3330
3374
  self,
@@ -3467,6 +3511,7 @@ class Up2k(object):
3467
3511
  vpath,
3468
3512
  "",
3469
3513
  uname,
3514
+ self.asrv.vfs.get_perms(vpath, uname),
3470
3515
  stl.st_mtime,
3471
3516
  st.st_size,
3472
3517
  ip,
@@ -3500,6 +3545,7 @@ class Up2k(object):
3500
3545
  vpath,
3501
3546
  "",
3502
3547
  uname,
3548
+ self.asrv.vfs.get_perms(vpath, uname),
3503
3549
  stl.st_mtime,
3504
3550
  st.st_size,
3505
3551
  ip,
@@ -3632,7 +3678,18 @@ class Up2k(object):
3632
3678
  xar = dvn.flags.get("xar")
3633
3679
  if xbr:
3634
3680
  if not runhook(
3635
- self.log, xbr, sabs, svp, "", uname, stl.st_mtime, st.st_size, "", 0, ""
3681
+ self.log,
3682
+ xbr,
3683
+ sabs,
3684
+ svp,
3685
+ "",
3686
+ uname,
3687
+ self.asrv.vfs.get_perms(svp, uname),
3688
+ stl.st_mtime,
3689
+ st.st_size,
3690
+ "",
3691
+ 0,
3692
+ "",
3636
3693
  ):
3637
3694
  t = "move blocked by xbr server config: {}".format(svp)
3638
3695
  self.log(t, 1)
@@ -3657,7 +3714,20 @@ class Up2k(object):
3657
3714
  self.rescan_cond.notify_all()
3658
3715
 
3659
3716
  if xar:
3660
- runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "")
3717
+ runhook(
3718
+ self.log,
3719
+ xar,
3720
+ dabs,
3721
+ dvp,
3722
+ "",
3723
+ uname,
3724
+ self.asrv.vfs.get_perms(dvp, uname),
3725
+ 0,
3726
+ 0,
3727
+ "",
3728
+ 0,
3729
+ "",
3730
+ )
3661
3731
 
3662
3732
  return "k"
3663
3733
 
@@ -3756,7 +3826,20 @@ class Up2k(object):
3756
3826
  wunlink(self.log, sabs, svn.flags)
3757
3827
 
3758
3828
  if xar:
3759
- runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "")
3829
+ runhook(
3830
+ self.log,
3831
+ xar,
3832
+ dabs,
3833
+ dvp,
3834
+ "",
3835
+ uname,
3836
+ self.asrv.vfs.get_perms(dvp, uname),
3837
+ 0,
3838
+ 0,
3839
+ "",
3840
+ 0,
3841
+ "",
3842
+ )
3760
3843
 
3761
3844
  return "k"
3762
3845
 
@@ -4045,6 +4128,7 @@ class Up2k(object):
4045
4128
  vp_chk,
4046
4129
  job["host"],
4047
4130
  job["user"],
4131
+ self.asrv.vfs.get_perms(vp_chk, job["user"]),
4048
4132
  int(job["lmod"]),
4049
4133
  job["size"],
4050
4134
  job["addr"],
copyparty/util.py CHANGED
@@ -137,6 +137,18 @@ else:
137
137
  from urllib import unquote # type: ignore # pylint: disable=no-name-in-module
138
138
 
139
139
 
140
+ try:
141
+ socket.inet_pton(socket.AF_INET6, "::1")
142
+ HAVE_IPV6 = True
143
+ except:
144
+
145
+ def inet_pton(fam, ip):
146
+ return socket.inet_aton(ip)
147
+
148
+ socket.inet_pton = inet_pton
149
+ HAVE_IPV6 = False
150
+
151
+
140
152
  try:
141
153
  struct.unpack(b">i", b"idgi")
142
154
  spack = struct.pack # type: ignore
@@ -210,6 +222,7 @@ IMPLICATIONS = [
210
222
  ["e2vu", "e2v"],
211
223
  ["e2vp", "e2v"],
212
224
  ["e2v", "e2d"],
225
+ ["tftpvv", "tftpv"],
213
226
  ["smbw", "smb"],
214
227
  ["smb1", "smb"],
215
228
  ["smbvvv", "smbvv"],
@@ -1343,7 +1356,7 @@ def vol_san(vols , txt ) :
1343
1356
  def min_ex(max_lines = 8, reverse = False) :
1344
1357
  et, ev, tb = sys.exc_info()
1345
1358
  stb = traceback.extract_tb(tb) if tb else traceback.extract_stack()[:-1]
1346
- fmt = "%s @ %d <%s>: %s"
1359
+ fmt = "%s:%d <%s>: %s"
1347
1360
  ex = [fmt % (fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in stb]
1348
1361
  if et or ev or tb:
1349
1362
  ex.append("[%s] %s" % (et.__name__ if et else "(anonymous)", ev))
@@ -2437,6 +2450,9 @@ def build_netmap(csv ):
2437
2450
  csv += ", 127.0.0.0/8, ::1/128" # loopback
2438
2451
 
2439
2452
  srcs = [x.strip() for x in csv.split(",") if x.strip()]
2453
+ if not HAVE_IPV6:
2454
+ srcs = [x for x in srcs if ":" not in x]
2455
+
2440
2456
  cidrs = []
2441
2457
  for zs in srcs:
2442
2458
  if not zs.endswith("."):
@@ -2955,7 +2971,8 @@ def retchk(
2955
2971
 
2956
2972
  def _parsehook(
2957
2973
  log , cmd
2958
- ) :
2974
+ ) :
2975
+ areq = ""
2959
2976
  chk = False
2960
2977
  fork = False
2961
2978
  jtxt = False
@@ -2980,8 +2997,12 @@ def _parsehook(
2980
2997
  cap = int(arg[1:]) # 0=none 1=stdout 2=stderr 3=both
2981
2998
  elif arg.startswith("k"):
2982
2999
  kill = arg[1:] # [t]ree [m]ain [n]one
3000
+ elif arg.startswith("a"):
3001
+ areq = arg[1:] # required perms
2983
3002
  elif arg.startswith("i"):
2984
3003
  pass
3004
+ elif not arg:
3005
+ break
2985
3006
  else:
2986
3007
  t = "hook: invalid flag {} in {}"
2987
3008
  (log or print)(t.format(arg, ocmd))
@@ -3008,9 +3029,11 @@ def _parsehook(
3008
3029
  "capture": cap,
3009
3030
  }
3010
3031
 
3011
- cmd = os.path.expandvars(os.path.expanduser(cmd))
3032
+ argv = cmd.split(",") if "," in cmd else [cmd]
3033
+
3034
+ argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
3012
3035
 
3013
- return chk, fork, jtxt, wait, sp_ka, cmd
3036
+ return areq, chk, fork, jtxt, wait, sp_ka, argv
3014
3037
 
3015
3038
 
3016
3039
  def runihook(
@@ -3019,10 +3042,9 @@ def runihook(
3019
3042
  vol ,
3020
3043
  ups ,
3021
3044
  ) :
3022
- ocmd = cmd
3023
- chk, fork, jtxt, wait, sp_ka, cmd = _parsehook(log, cmd)
3024
- bcmd = [sfsenc(cmd)]
3025
- if cmd.endswith(".py"):
3045
+ _, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
3046
+ bcmd = [sfsenc(x) for x in acmd]
3047
+ if acmd[0].endswith(".py"):
3026
3048
  bcmd = [sfsenc(pybin)] + bcmd
3027
3049
 
3028
3050
  vps = [vjoin(*list(s3dec(x[3], x[4]))) for x in ups]
@@ -3047,7 +3069,7 @@ def runihook(
3047
3069
 
3048
3070
  t0 = time.time()
3049
3071
  if fork:
3050
- Daemon(runcmd, ocmd, [bcmd], ka=sp_ka)
3072
+ Daemon(runcmd, cmd, bcmd, ka=sp_ka)
3051
3073
  else:
3052
3074
  rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
3053
3075
  if chk and rc:
@@ -3068,14 +3090,20 @@ def _runhook(
3068
3090
  vp ,
3069
3091
  host ,
3070
3092
  uname ,
3093
+ perms ,
3071
3094
  mt ,
3072
3095
  sz ,
3073
3096
  ip ,
3074
3097
  at ,
3075
3098
  txt ,
3076
3099
  ) :
3077
- ocmd = cmd
3078
- chk, fork, jtxt, wait, sp_ka, cmd = _parsehook(log, cmd)
3100
+ areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
3101
+ if areq:
3102
+ for ch in areq:
3103
+ if ch not in perms:
3104
+ t = "user %s not allowed to run hook %s; need perms %s, have %s"
3105
+ log(t % (uname, cmd, areq, perms))
3106
+ return True # fallthrough to next hook
3079
3107
  if jtxt:
3080
3108
  ja = {
3081
3109
  "ap": ap,
@@ -3086,21 +3114,22 @@ def _runhook(
3086
3114
  "at": at or time.time(),
3087
3115
  "host": host,
3088
3116
  "user": uname,
3117
+ "perms": perms,
3089
3118
  "txt": txt,
3090
3119
  }
3091
3120
  arg = json.dumps(ja)
3092
3121
  else:
3093
3122
  arg = txt or ap
3094
3123
 
3095
- acmd = [cmd, arg]
3096
- if cmd.endswith(".py"):
3124
+ acmd += [arg]
3125
+ if acmd[0].endswith(".py"):
3097
3126
  acmd = [pybin] + acmd
3098
3127
 
3099
3128
  bcmd = [fsenc(x) if x == ap else sfsenc(x) for x in acmd]
3100
3129
 
3101
3130
  t0 = time.time()
3102
3131
  if fork:
3103
- Daemon(runcmd, ocmd, [bcmd], ka=sp_ka)
3132
+ Daemon(runcmd, cmd, [bcmd], ka=sp_ka)
3104
3133
  else:
3105
3134
  rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
3106
3135
  if chk and rc:
@@ -3121,6 +3150,7 @@ def runhook(
3121
3150
  vp ,
3122
3151
  host ,
3123
3152
  uname ,
3153
+ perms ,
3124
3154
  mt ,
3125
3155
  sz ,
3126
3156
  ip ,
@@ -3130,7 +3160,7 @@ def runhook(
3130
3160
  vp = vp.replace("\\", "/")
3131
3161
  for cmd in cmds:
3132
3162
  try:
3133
- if not _runhook(log, cmd, ap, vp, host, uname, mt, sz, ip, at, txt):
3163
+ if not _runhook(log, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt):
3134
3164
  return False
3135
3165
  except Exception as ex:
3136
3166
  (log or print)("hook: {}".format(ex))