copyparty 1.19.0__py3-none-any.whl → 1.19.2__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/httpcli.py CHANGED
@@ -557,7 +557,7 @@ class HttpCli(object):
557
557
 
558
558
  zso = self.headers.get("cookie")
559
559
  if zso:
560
- if len(zso) > 8192:
560
+ if len(zso) > self.args.cookie_cmax:
561
561
  self.loud_reply("cookie header too big", status=400)
562
562
  return False
563
563
  zsll = [x.split("=", 1) for x in zso.split(";") if "=" in x]
@@ -565,11 +565,15 @@ class HttpCli(object):
565
565
  cookie_pw = cookies.get("cppws") or cookies.get("cppwd") or ""
566
566
  if "b" in cookies and "b" not in uparam:
567
567
  uparam["b"] = cookies["b"]
568
+ if len(cookies) > self.args.cookie_nmax:
569
+ self.loud_reply("too many cookies", status=400)
568
570
  else:
569
571
  cookies = {}
570
572
  cookie_pw = ""
571
573
 
572
- if len(uparam) > 10 or len(cookies) > 50:
574
+ if len(uparam) > 12:
575
+ t = "http-request rejected; num.params: %d %r"
576
+ self.log(t % (len(uparam), self.req), 3)
573
577
  self.loud_reply("u wot m8", status=400)
574
578
  return False
575
579
 
@@ -615,8 +619,22 @@ class HttpCli(object):
615
619
  or "*"
616
620
  )
617
621
 
618
- if self.args.idp_h_usr:
619
- idp_usr = self.headers.get(self.args.idp_h_usr) or ""
622
+ if self.args.have_idp_hdrs:
623
+ idp_usr = ""
624
+ if self.args.idp_hm_usr:
625
+ for hn, hmv in self.args.idp_hm_usr_p.items():
626
+ zs = self.headers.get(hn)
627
+ if zs:
628
+ for zs1, zs2 in hmv.items():
629
+ if zs == zs1:
630
+ idp_usr = zs2
631
+ break
632
+ if idp_usr:
633
+ break
634
+ for hn in self.args.idp_h_usr:
635
+ if idp_usr:
636
+ break
637
+ idp_usr = self.headers.get(hn)
620
638
  if idp_usr:
621
639
  idp_grp = (
622
640
  self.headers.get(self.args.idp_h_grp) or ""
@@ -672,8 +690,14 @@ class HttpCli(object):
672
690
  else:
673
691
  self.log("unknown username: %r" % (idp_usr,), 1)
674
692
 
675
- if self.args.ipu and self.uname == "*":
676
- self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
693
+ if self.args.have_ipu_or_ipr:
694
+ if self.args.ipu and self.uname == "*":
695
+ self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
696
+ ipr = self.conn.hsrv.ipr
697
+ if ipr and self.uname in ipr:
698
+ if not ipr[self.uname].map(self.ip):
699
+ self.log("username [%s] rejected by --ipr" % (self.uname,), 3)
700
+ self.uname = "*"
677
701
 
678
702
  self.rvol = self.asrv.vfs.aread[self.uname]
679
703
  self.wvol = self.asrv.vfs.awrite[self.uname]
@@ -695,7 +719,7 @@ class HttpCli(object):
695
719
  cookies["b"] = ""
696
720
 
697
721
  vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
698
- if "xdev" in vn.flags or "xvol" in vn.flags:
722
+ if vn.realpath and ("xdev" in vn.flags or "xvol" in vn.flags):
699
723
  ap = vn.canonical(rem)
700
724
  avn = vn.chk_ap(ap)
701
725
  else:
@@ -1594,6 +1618,10 @@ class HttpCli(object):
1594
1618
  "quota-available-bytes": str(bfree),
1595
1619
  "quota-used-bytes": str(btot - bfree),
1596
1620
  }
1621
+ if "quotaused" in props: # macos finder crazytalk
1622
+ df["quotaused"] = df["quota-used-bytes"]
1623
+ if "quota" in props:
1624
+ df["quota"] = df["quota-available-bytes"] # idk, makes it happy
1597
1625
  else:
1598
1626
  df = {}
1599
1627
  else:
@@ -1975,6 +2003,9 @@ class HttpCli(object):
1975
2003
  if "eshare" in self.uparam:
1976
2004
  return self.handle_eshare()
1977
2005
 
2006
+ if "fs_abrt" in self.uparam:
2007
+ return self.handle_fs_abrt()
2008
+
1978
2009
  if "application/octet-stream" in ctype:
1979
2010
  return self.handle_post_binary()
1980
2011
 
@@ -4949,7 +4980,7 @@ class HttpCli(object):
4949
4980
  rip = host
4950
4981
 
4951
4982
  vp = (self.uparam["hc"] or "").lstrip("/")
4952
- pw = self.pw or "hunter2"
4983
+ pw = self.ouparam.get("pw") or "hunter2"
4953
4984
  if pw in self.asrv.sesa:
4954
4985
  pw = "hunter2"
4955
4986
 
@@ -5447,6 +5478,10 @@ class HttpCli(object):
5447
5478
  and ("*" in x.axs.uwrite or self.uname in x.axs.uwrite or x == shr_dbv)
5448
5479
  ]
5449
5480
 
5481
+ q = ""
5482
+ qp = (0,)
5483
+ q_c = -1
5484
+
5450
5485
  for vol in allvols:
5451
5486
  cur = idx.get_cur(vol)
5452
5487
  if not cur:
@@ -5454,17 +5489,31 @@ class HttpCli(object):
5454
5489
 
5455
5490
  nfk, fk_alg = fk_vols.get(vol) or (0, 0)
5456
5491
 
5492
+ zi = vol.flags["unp_who"]
5493
+ if q_c != zi:
5494
+ q_c = zi
5495
+ q = "select sz, rd, fn, at from up where "
5496
+ if zi == 1:
5497
+ q += "ip=? and un=?"
5498
+ qp = (self.ip, self.uname, lim)
5499
+ elif zi == 2:
5500
+ q += "ip=?"
5501
+ qp = (self.ip, lim)
5502
+ if zi == 3:
5503
+ q += "un=?"
5504
+ qp = (self.uname, lim)
5505
+ q += " and at>? order by at desc"
5506
+
5457
5507
  n = 2000
5458
- q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
5459
- for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
5508
+ for sz, rd, fn, at in cur.execute(q, qp):
5460
5509
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5461
- if nfi == 0 or (nfi == 1 and vfi in vp):
5510
+ if nfi == 0 or (nfi == 1 and vfi in vp.lower()):
5462
5511
  pass
5463
5512
  elif nfi == 2:
5464
- if not vp.startswith(vfi):
5513
+ if not vp.lower().startswith(vfi):
5465
5514
  continue
5466
5515
  elif nfi == 3:
5467
- if not vp.endswith(vfi):
5516
+ if not vp.lower().endswith(vfi):
5468
5517
  continue
5469
5518
  else:
5470
5519
  continue
@@ -5580,16 +5629,16 @@ class HttpCli(object):
5580
5629
  continue
5581
5630
 
5582
5631
  n = 1000
5583
- q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
5584
- for sz, rd, fn, ip, at in cur.execute(q):
5632
+ q = "select sz, rd, fn, ip, at, un from up where at>0 order by at desc"
5633
+ for sz, rd, fn, ip, at, un in cur.execute(q):
5585
5634
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5586
- if nfi == 0 or (nfi == 1 and vfi in vp):
5635
+ if nfi == 0 or (nfi == 1 and vfi in vp.lower()):
5587
5636
  pass
5588
5637
  elif nfi == 2:
5589
- if not vp.startswith(vfi):
5638
+ if not vp.lower().startswith(vfi):
5590
5639
  continue
5591
5640
  elif nfi == 3:
5592
- if not vp.endswith(vfi):
5641
+ if not vp.lower().endswith(vfi):
5593
5642
  continue
5594
5643
  else:
5595
5644
  continue
@@ -5602,6 +5651,7 @@ class HttpCli(object):
5602
5651
  "sz": sz,
5603
5652
  "ip": ip,
5604
5653
  "at": at,
5654
+ "un": un,
5605
5655
  "nfk": nfk,
5606
5656
  "adm": adm,
5607
5657
  }
@@ -5646,12 +5696,16 @@ class HttpCli(object):
5646
5696
  adm = rv.pop("adm")
5647
5697
  if not adm:
5648
5698
  rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
5699
+ if rv["un"] not in ("*", self.uname):
5700
+ rv["un"] = "(?)"
5649
5701
  else:
5650
5702
  for rv in ret:
5651
5703
  adm = rv.pop("adm")
5652
5704
  if not adm:
5653
5705
  rv["ip"] = "(You)" if rv["ip"] == self.ip else "(?)"
5654
5706
  rv["at"] = 0
5707
+ if rv["un"] not in ("*", self.uname):
5708
+ rv["un"] = "(?)"
5655
5709
 
5656
5710
  if self.is_vproxied:
5657
5711
  for v in ret:
@@ -5930,7 +5984,9 @@ class HttpCli(object):
5930
5984
  self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5931
5985
  wunlink(self.log, dabs, dvn.flags)
5932
5986
 
5933
- x = self.conn.hsrv.broker.ask("up2k.handle_mv", self.uname, self.ip, vsrc, vdst)
5987
+ x = self.conn.hsrv.broker.ask(
5988
+ "up2k.handle_mv", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst
5989
+ )
5934
5990
  self.loud_reply(x.get(), status=201)
5935
5991
  return True
5936
5992
 
@@ -5960,10 +6016,21 @@ class HttpCli(object):
5960
6016
  self.asrv.vfs.get(vdst, self.uname, False, True, False, True)
5961
6017
  wunlink(self.log, dabs, dvn.flags)
5962
6018
 
5963
- x = self.conn.hsrv.broker.ask("up2k.handle_cp", self.uname, self.ip, vsrc, vdst)
6019
+ x = self.conn.hsrv.broker.ask(
6020
+ "up2k.handle_cp", self.ouparam.get("akey"), self.uname, self.ip, vsrc, vdst
6021
+ )
5964
6022
  self.loud_reply(x.get(), status=201)
5965
6023
  return True
5966
6024
 
6025
+ def handle_fs_abrt(self):
6026
+ if self.args.no_fs_abrt:
6027
+ t = "aborting an ongoing copy/move is disabled in server config"
6028
+ raise Pebkac(403, t)
6029
+
6030
+ self.conn.hsrv.broker.say("up2k.handle_fs_abrt", self.uparam["fs_abrt"])
6031
+ self.loud_reply("aborting", status=200)
6032
+ return True
6033
+
5967
6034
  def tx_ls(self, ls ) :
5968
6035
  dirs = ls["dirs"]
5969
6036
  files = ls["files"]
@@ -6559,13 +6626,15 @@ class HttpCli(object):
6559
6626
  tags = {k: v for k, v in r}
6560
6627
 
6561
6628
  if is_admin:
6562
- q = "select ip, at from up where rd=? and fn=?"
6629
+ q = "select ip, at, un from up where rd=? and fn=?"
6563
6630
  try:
6564
- zs1, zs2 = icur.execute(q, erd_efn).fetchone()
6631
+ zs1, zs2, zs3 = icur.execute(q, erd_efn).fetchone()
6565
6632
  if zs1:
6566
6633
  tags["up_ip"] = zs1
6567
6634
  if zs2:
6568
6635
  tags[".up_at"] = zs2
6636
+ if zs3:
6637
+ tags["up_by"] = zs3
6569
6638
  except:
6570
6639
  pass
6571
6640
  elif add_up_at:
@@ -6586,7 +6655,7 @@ class HttpCli(object):
6586
6655
 
6587
6656
  lmte = list(mte)
6588
6657
  if self.can_admin:
6589
- lmte.extend(("up_ip", ".up_at"))
6658
+ lmte.extend(("up_by", "up_ip", ".up_at"))
6590
6659
 
6591
6660
  if "nodirsz" not in vf:
6592
6661
  tagset.add(".files")
copyparty/httpsrv.py CHANGED
@@ -70,6 +70,7 @@ from .util import (
70
70
  build_netmap,
71
71
  has_resource,
72
72
  ipnorm,
73
+ load_ipr,
73
74
  load_ipu,
74
75
  load_resource,
75
76
  min_ex,
@@ -189,6 +190,11 @@ class HttpSrv(object):
189
190
  else:
190
191
  self.ipu_iu = self.ipu_nm = None
191
192
 
193
+ if self.args.ipr:
194
+ self.ipr = load_ipr(self.log, self.args.ipr)
195
+ else:
196
+ self.ipr = None
197
+
192
198
  self.ipa_nm = build_netmap(self.args.ipa)
193
199
  self.xff_nm = build_netmap(self.args.xff_src)
194
200
  self.xff_lan = build_netmap("lan")
copyparty/mdns.py CHANGED
@@ -72,7 +72,8 @@ class MDNS(MCast):
72
72
  if not self.args.zm_nwa_1:
73
73
  set_avahi_379()
74
74
 
75
- zs = self.args.name + ".local."
75
+ zs = self.args.zm_fqdn or (self.args.name + ".local")
76
+ zs = zs.replace("--name", self.args.name).rstrip(".") + "."
76
77
  zs = zs.encode("ascii", "replace").decode("ascii", "replace")
77
78
  self.hn = "-".join(x for x in zs.split("?") if x) or (
78
79
  "vault-{}".format(random.randint(1, 255))
copyparty/mtag.py CHANGED
@@ -170,6 +170,9 @@ def au_unpk(
170
170
  raise Exception("no images inside cbz")
171
171
  fi = zf.open(using)
172
172
 
173
+ elif pk == "epub":
174
+ fi = get_cover_from_epub(log, abspath)
175
+
173
176
  else:
174
177
  raise Exception("unknown compression %s" % (pk,))
175
178
 
@@ -199,7 +202,7 @@ def au_unpk(
199
202
 
200
203
  def ffprobe(
201
204
  abspath , timeout = 60
202
- ) :
205
+ ) :
203
206
  cmd = [
204
207
  b"ffprobe",
205
208
  b"-hide_banner",
@@ -213,8 +216,17 @@ def ffprobe(
213
216
  return parse_ffprobe(so)
214
217
 
215
218
 
216
- def parse_ffprobe(txt ) :
217
- """ffprobe -show_format -show_streams"""
219
+ def parse_ffprobe(
220
+ txt ,
221
+ ) :
222
+ """
223
+ txt: output from ffprobe -show_format -show_streams
224
+ returns:
225
+ * normalized tags
226
+ * original/raw tags
227
+ * list of streams
228
+ * format props
229
+ """
218
230
  streams = []
219
231
  fmt = {}
220
232
  g = {}
@@ -307,7 +319,7 @@ def parse_ffprobe(txt ) :
307
319
  ret[rk] = v1
308
320
 
309
321
  if ret.get("vc") == "ansi": # shellscript
310
- return {}, {}
322
+ return {}, {}, [], {}
311
323
 
312
324
  for strm in streams:
313
325
  for sk, sv in strm.items():
@@ -356,7 +368,77 @@ def parse_ffprobe(txt ) :
356
368
  zero = int("0")
357
369
  zd = {k: (zero, v) for k, v in ret.items()}
358
370
 
359
- return zd, md
371
+ return zd, md, streams, fmt
372
+
373
+
374
+ def get_cover_from_epub(log , abspath ) :
375
+ import zipfile
376
+
377
+ from .dxml import parse_xml
378
+
379
+ try:
380
+ from urlparse import urljoin # Python2
381
+ except ImportError:
382
+ from urllib.parse import urljoin # Python3
383
+
384
+ with zipfile.ZipFile(abspath, "r") as z:
385
+ # First open the container file to find the package document (.opf file)
386
+ try:
387
+ container_root = parse_xml(z.read("META-INF/container.xml").decode())
388
+ except KeyError:
389
+ log("epub: no container file found in %s" % (abspath,))
390
+ return None
391
+
392
+ # https://www.w3.org/TR/epub-33/#sec-container.xml-rootfile-elem
393
+ container_ns = {"": "urn:oasis:names:tc:opendocument:xmlns:container"}
394
+ # One file could contain multiple package documents, default to the first one
395
+ rootfile_path = container_root.find("./rootfiles/rootfile", container_ns).get(
396
+ "full-path"
397
+ )
398
+
399
+ # Then open the first package document to find the path of the cover image
400
+ try:
401
+ package_root = parse_xml(z.read(rootfile_path).decode())
402
+ except KeyError:
403
+ log("epub: no package document found in %s" % (abspath,))
404
+ return None
405
+
406
+ # https://www.w3.org/TR/epub-33/#sec-package-doc
407
+ package_ns = {"": "http://www.idpf.org/2007/opf"}
408
+ # https://www.w3.org/TR/epub-33/#sec-cover-image
409
+ coverimage_path_node = package_root.find(
410
+ "./manifest/item[@properties='cover-image']", package_ns
411
+ )
412
+ if coverimage_path_node is not None:
413
+ coverimage_path = coverimage_path_node.get("href")
414
+ else:
415
+ # This might be an EPUB2 file, try the legacy way of specifying covers
416
+ coverimage_path = _get_cover_from_epub2(log, package_root, package_ns)
417
+
418
+ # This url is either absolute (in the .epub) or relative to the package document
419
+ adjusted_cover_path = urljoin(rootfile_path, coverimage_path)
420
+
421
+ return z.open(adjusted_cover_path)
422
+
423
+
424
+ def _get_cover_from_epub2(
425
+ log , package_root, package_ns
426
+ ) :
427
+ # <meta name="cover" content="id-to-cover-image"> in <metadata>, then
428
+ # <item> in <manifest>
429
+ cover_id = package_root.find("./metadata/meta[@name='cover']", package_ns).get(
430
+ "content"
431
+ )
432
+
433
+ if not cover_id:
434
+ return None
435
+
436
+ for node in package_root.iterfind("./manifest/item", package_ns):
437
+ if node.get("id") == cover_id:
438
+ cover_path = node.get("href")
439
+ return cover_path
440
+
441
+ return None
360
442
 
361
443
 
362
444
  class MTag(object):
@@ -627,7 +709,7 @@ class MTag(object):
627
709
  if not bos.path.isfile(abspath):
628
710
  return {}
629
711
 
630
- ret, md = ffprobe(abspath, self.args.mtag_to)
712
+ ret, md, _, _ = ffprobe(abspath, self.args.mtag_to)
631
713
 
632
714
  if self.args.mtag_vv:
633
715
  for zd in (ret, dict(md)):
copyparty/svchub.py CHANGED
@@ -2,6 +2,7 @@
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
4
  import argparse
5
+ import atexit
5
6
  import errno
6
7
  import logging
7
8
  import os
@@ -32,6 +33,7 @@ from .th_srv import (
32
33
  HAVE_FFPROBE,
33
34
  HAVE_HEIF,
34
35
  HAVE_PIL,
36
+ HAVE_RAW,
35
37
  HAVE_VIPS,
36
38
  HAVE_WEBP,
37
39
  ThumbSrv,
@@ -58,6 +60,7 @@ from .util import (
58
60
  build_netmap,
59
61
  expat_ver,
60
62
  gzip,
63
+ load_ipr,
61
64
  load_ipu,
62
65
  lock_file,
63
66
  min_ex,
@@ -66,6 +69,7 @@ from .util import (
66
69
  pybin,
67
70
  start_log_thrs,
68
71
  start_stackmon,
72
+ termsize,
69
73
  ub64enc,
70
74
  )
71
75
 
@@ -146,6 +150,7 @@ class SvcHub(object):
146
150
  args.no_del = True
147
151
  args.no_mv = True
148
152
  args.hardlink = True
153
+ args.dav_auth = True
149
154
  args.vague_403 = True
150
155
  args.nih = True
151
156
 
@@ -234,7 +239,7 @@ class SvcHub(object):
234
239
  t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
235
240
  self.log("root", t % (args.th_ram_max, zs), 3)
236
241
 
237
- if args.chpw and args.idp_h_usr:
242
+ if args.chpw and args.have_idp_hdrs:
238
243
  t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
239
244
  self.log("root", t, 1)
240
245
  raise Exception(t)
@@ -250,6 +255,10 @@ class SvcHub(object):
250
255
  setattr(args, "ipu_iu", iu)
251
256
  setattr(args, "ipu_nm", nm)
252
257
 
258
+ if args.ipr:
259
+ ipr = load_ipr(self.log, args.ipr, True)
260
+ setattr(args, "ipr_u", ipr)
261
+
253
262
  for zs in "ah_salt fk_salt dk_salt".split():
254
263
  if getattr(args, "show_%s" % (zs,)):
255
264
  self.log("root", "effective %s is %s" % (zs, getattr(args, zs)))
@@ -259,7 +268,7 @@ class SvcHub(object):
259
268
  args.no_ses = True
260
269
  args.shr = ""
261
270
 
262
- if args.idp_store and args.idp_h_usr:
271
+ if args.idp_store and args.have_idp_hdrs:
263
272
  self.setup_db("idp")
264
273
 
265
274
  if not self.args.no_ses:
@@ -315,6 +324,8 @@ class SvcHub(object):
315
324
  decs.pop("vips", None)
316
325
  if not HAVE_PIL:
317
326
  decs.pop("pil", None)
327
+ if not HAVE_RAW:
328
+ decs.pop("raw", None)
318
329
  if not HAVE_FFMPEG or not HAVE_FFPROBE:
319
330
  decs.pop("ff", None)
320
331
 
@@ -421,6 +432,9 @@ class SvcHub(object):
421
432
  getattr(args, zs).mutex = threading.Lock()
422
433
  except:
423
434
  pass
435
+ if args.ipr:
436
+ for nm in args.ipr_u.values():
437
+ nm.mutex = threading.Lock()
424
438
 
425
439
  def _db_onfail_ses(self) :
426
440
  self.args.no_ses = True
@@ -762,6 +776,39 @@ class SvcHub(object):
762
776
  def sigterm(self) :
763
777
  self.signal_handler(signal.SIGTERM, None)
764
778
 
779
+ def sticky_qr(self) :
780
+ tw, th = termsize()
781
+ zs1, qr = self.tcpsrv.qr.split("\n", 1)
782
+ url, colr = zs1.split(" ", 1)
783
+ nl = len(qr.split("\n")) # numlines
784
+ lp = 3 if nl * 2 + 4 < tw else 0 # leftpad
785
+ lp0 = lp
786
+ if self.args.qr_pin == 2:
787
+ url = ""
788
+ else:
789
+ while lp and (nl + lp) * 2 + len(url) + 1 > tw:
790
+ lp -= 1
791
+ if (nl + lp) * 2 + len(url) + 1 > tw:
792
+ qr = url + "\n" + qr
793
+ url = ""
794
+ nl += 1
795
+ lp = lp0
796
+ sh = 1 + th - nl
797
+ if lp:
798
+ zs = " " * lp
799
+ qr = zs + qr.replace("\n", "\n" + zs)
800
+ if url:
801
+ url = "%s\033[%d;%dH%s\033[0m" % (colr, sh + 1, (nl + lp) * 2, url)
802
+ qr = colr + qr
803
+
804
+ def unlock():
805
+ print("\033[s\033[r\033[u", file=sys.stderr)
806
+
807
+ atexit.register(unlock)
808
+ t = "%s\033[%dA" % ("\n" * nl, nl)
809
+ t = "%s\033[s\033[1;%dr\033[%dH%s%s\033[u" % (t, sh - 1, sh, qr, url)
810
+ self.pr(t, file=sys.stderr)
811
+
765
812
  def cb_httpsrv_up(self) :
766
813
  self.httpsrv_up += 1
767
814
  if self.httpsrv_up != self.broker.num_workers:
@@ -774,7 +821,10 @@ class SvcHub(object):
774
821
  break
775
822
 
776
823
  if self.tcpsrv.qr:
777
- self.log("qr-code", self.tcpsrv.qr)
824
+ if self.args.qr_pin:
825
+ self.sticky_qr()
826
+ else:
827
+ self.log("qr-code", self.tcpsrv.qr)
778
828
  else:
779
829
  self.log("root", "workers OK\n")
780
830
 
@@ -801,6 +851,7 @@ class SvcHub(object):
801
851
  (HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
802
852
  (HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
803
853
  (HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
854
+ (HAVE_RAW, "rawpy", "read RAW images"),
804
855
  ]
805
856
  if ANYWIN:
806
857
  to_check += [
@@ -959,10 +1010,23 @@ class SvcHub(object):
959
1010
  al.sus_urls = None
960
1011
 
961
1012
  al.xff_hdr = al.xff_hdr.lower()
962
- al.idp_h_usr = al.idp_h_usr.lower()
1013
+ al.idp_h_usr = [x.lower() for x in al.idp_h_usr or []]
963
1014
  al.idp_h_grp = al.idp_h_grp.lower()
964
1015
  al.idp_h_key = al.idp_h_key.lower()
965
1016
 
1017
+ al.idp_hm_usr_p = {}
1018
+ for zs0 in al.idp_hm_usr or []:
1019
+ try:
1020
+ sep = zs0[:1]
1021
+ hn, zs1, zs2 = zs0[1:].split(sep)
1022
+ hn = hn.lower()
1023
+ if hn in al.idp_hm_usr_p:
1024
+ al.idp_hm_usr_p[hn][zs1] = zs2
1025
+ else:
1026
+ al.idp_hm_usr_p[hn] = {zs1: zs2}
1027
+ except:
1028
+ raise Exception("invalid --idp-hm-usr [%s]" % (zs0,))
1029
+
966
1030
  al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa, True)
967
1031
  al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa, True)
968
1032
 
@@ -1390,7 +1454,14 @@ class SvcHub(object):
1390
1454
 
1391
1455
  fmt = "\033[36m%s \033[33m%-21s \033[0m%s\n"
1392
1456
  if self.no_ansi:
1393
- fmt = "%s %-21s %s\n"
1457
+ if c == 1:
1458
+ fmt = "%s %-21s CRIT: %s\n"
1459
+ elif c == 3:
1460
+ fmt = "%s %-21s WARN: %s\n"
1461
+ elif c == 6:
1462
+ fmt = "%s %-21s BTW: %s\n"
1463
+ else:
1464
+ fmt = "%s %-21s LOG: %s\n"
1394
1465
  if "\033" in msg:
1395
1466
  msg = RE_ANSI.sub("", msg)
1396
1467
  if "\033" in src:
copyparty/tcpsrv.py CHANGED
@@ -611,6 +611,10 @@ class TcpSrv(object):
611
611
 
612
612
  fg = self.args.qr_fg
613
613
  bg = self.args.qr_bg
614
+ nocolor = fg == -1
615
+ if nocolor:
616
+ fg = 0
617
+
614
618
  pad = self.args.qrp
615
619
  zoom = self.args.qrz
616
620
  qrc = QrCode.encode_binary(btxt)
@@ -638,6 +642,8 @@ class TcpSrv(object):
638
642
 
639
643
  qr = qr.replace("\n", "\033[K\n") + "\033[K" # win10do
640
644
  cc = " \033[0;38;5;{0};47;48;5;{1}m" if fg else " \033[0;30;47m"
645
+ if nocolor:
646
+ cc = " \033[0m"
641
647
  t = cc + "\n{2}\033[999G\033[0m\033[J"
642
648
  t = t.format(fg, bg, qr)
643
649
  if ANYWIN:
copyparty/th_cli.py CHANGED
@@ -33,11 +33,15 @@ class ThumbCli(object):
33
33
  if not c:
34
34
  raise Exception()
35
35
  except:
36
- c = {k: set() for k in ["thumbable", "pil", "vips", "ffi", "ffv", "ffa"]}
36
+ c = {
37
+ k: set()
38
+ for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"]
39
+ }
37
40
 
38
41
  self.thumbable = c["thumbable"]
39
42
  self.fmt_pil = c["pil"]
40
43
  self.fmt_vips = c["vips"]
44
+ self.fmt_raw = c["raw"]
41
45
  self.fmt_ffi = c["ffi"]
42
46
  self.fmt_ffv = c["ffv"]
43
47
  self.fmt_ffa = c["ffa"]