copyparty 1.16.8__py3-none-any.whl → 1.16.9__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/__main__.py CHANGED
@@ -54,6 +54,8 @@ from .util import (
54
54
  RAM_TOTAL,
55
55
  SQLITE_VER,
56
56
  UNPLICATIONS,
57
+ URL_BUG,
58
+ URL_PRJ,
57
59
  Daemon,
58
60
  align_tab,
59
61
  ansi_re,
@@ -326,17 +328,16 @@ def ensure_webdeps() :
326
328
  if has_resource(E, "web/deps/mini-fa.woff"):
327
329
  return
328
330
 
329
- warn(
330
- """could not find webdeps;
331
+ t = """could not find webdeps;
331
332
  if you are running the sfx, or exe, or pypi package, or docker image,
332
333
  then this is a bug! Please let me know so I can fix it, thanks :-)
333
- https://github.com/9001/copyparty/issues/new?labels=bug&template=bug_report.md
334
+ %s
334
335
 
335
336
  however, if you are a dev, or running copyparty from source, and you want
336
337
  full client functionality, you will need to build or obtain the webdeps:
337
- https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#building
338
+ %s/blob/hovudstraum/docs/devnotes.md#building
338
339
  """
339
- )
340
+ warn(t % (URL_BUG, URL_PRJ))
340
341
 
341
342
 
342
343
  def configure_ssl_ver(al ) :
@@ -731,6 +732,10 @@ def get_sects():
731
732
  the \033[33m,,\033[35m stops copyparty from reading the rest as flags and
732
733
  the \033[33m--\033[35m stops notify-send from reading the message as args
733
734
  and the alert will be "hey" followed by the messagetext
735
+
736
+ \033[36m--xau zmq:pub:tcp://*:5556\033[35m announces uploads on zeromq;
737
+ \033[36m--xau t3,zmq:push:tcp://*:5557\033[35m also works, and you can
738
+ \033[36m--xau t3,j,zmq:req:tcp://localhost:5555\033[35m too for example
734
739
  \033[0m
735
740
  each hook is executed once for each event, except for \033[36mxiu\033[0m
736
741
  which builds up a backlog of uploads, running the hook just once
@@ -1468,12 +1473,14 @@ def add_ui(ap, retry):
1468
1473
  ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
1469
1474
  ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
1470
1475
  ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
1471
- ap2.add_argument("--pb-url", metavar="URL", type=u, default="https://github.com/9001/copyparty", help="powered-by link; disable with \033[33m-np\033[0m")
1476
+ ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-np\033[0m")
1472
1477
  ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m)")
1473
1478
  ap2.add_argument("--k304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
1474
1479
  ap2.add_argument("--no304", metavar="NUM", type=int, default=0, help="configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [\033[32m0\033[0m] = hidden and default-off, [\033[32m1\033[0m] = visible and default-off, [\033[32m2\033[0m] = visible and default-on")
1475
- ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox")
1476
- ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to ALLOW for prologue/epilogue docs (volflag=lg_sbf)")
1480
+ ap2.add_argument("--md-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox")
1481
+ ap2.add_argument("--lg-sbf", metavar="FLAGS", type=u, default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf)")
1482
+ ap2.add_argument("--md-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for README.md docs, for example [\033[32mfullscreen\033[0m] (volflag=md_sba)")
1483
+ ap2.add_argument("--lg-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes")
1477
1484
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1478
1485
  ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
1479
1486
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 8)
3
+ VERSION = (1, 16, 9)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2025, 1, 11)
5
+ BUILD_DT = (2025, 1, 22)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -1825,7 +1825,11 @@ class AuthSrv(object):
1825
1825
  if fka and not fk:
1826
1826
  fk = fka
1827
1827
  if fk:
1828
- vol.flags["fk"] = int(fk) if fk is not True else 8
1828
+ fk = 8 if fk is True else int(fk)
1829
+ if fk > 72:
1830
+ t = "max filekey-length is 72; volume /%s specified %d (anything higher than 16 is pointless btw)"
1831
+ raise Exception(t % (vol.vpath, fk))
1832
+ vol.flags["fk"] = fk
1829
1833
  have_fk = True
1830
1834
 
1831
1835
  dk = vol.flags.get("dk")
@@ -2332,6 +2336,7 @@ class AuthSrv(object):
2332
2336
  "frand": bool(vf.get("rand")),
2333
2337
  "lifetime": vf.get("lifetime") or 0,
2334
2338
  "unlist": vf.get("unlist") or "",
2339
+ "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
2335
2340
  }
2336
2341
  js_htm = {
2337
2342
  "s_name": self.args.bname,
@@ -2344,6 +2349,8 @@ class AuthSrv(object):
2344
2349
  "have_unpost": int(self.args.unpost),
2345
2350
  "have_emp": self.args.emp,
2346
2351
  "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
2352
+ "sba_md": vf.get("md_sba") or "",
2353
+ "sba_lg": vf.get("lg_sba") or "",
2347
2354
  "txt_ext": self.args.textfiles.replace(",", " "),
2348
2355
  "def_hcols": list(vf.get("mth") or []),
2349
2356
  "unlist0": vf.get("unlist") or "",
copyparty/cfg.py CHANGED
@@ -74,6 +74,8 @@ def vf_vmap() :
74
74
  "html_head",
75
75
  "lg_sbf",
76
76
  "md_sbf",
77
+ "lg_sba",
78
+ "md_sba",
77
79
  "nrand",
78
80
  "og_desc",
79
81
  "og_site",
@@ -144,6 +146,7 @@ flagcats = {
144
146
  "noclone": "take dupe data from clients, even if available on HDD",
145
147
  "nodupe": "rejects existing files (instead of linking/cloning them)",
146
148
  "sparse": "force use of sparse files, mainly for s3-backed storage",
149
+ "nosparse": "deny use of sparse files, mainly for slow storage",
147
150
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
148
151
  "nosub": "forces all uploads into the top folder of the vfs",
149
152
  "magic": "enables filetype detection for nameless uploads",
@@ -240,6 +243,8 @@ flagcats = {
240
243
  "sb_lg": "enable js sandbox for prologue/epilogue (default)",
241
244
  "md_sbf": "list of markdown-sandbox safeguards to disable",
242
245
  "lg_sbf": "list of *logue-sandbox safeguards to disable",
246
+ "md_sba": "value of iframe allow-prop for markdown-sandbox",
247
+ "lg_sba": "value of iframe allow-prop for *logue-sandbox",
243
248
  "nohtml": "return html and markdown as text/html",
244
249
  },
245
250
  "others": {
copyparty/dxml.py CHANGED
@@ -1,9 +1,16 @@
1
+ # coding: utf-8
2
+ from __future__ import print_function, unicode_literals
3
+
1
4
  import importlib
2
5
  import sys
3
6
  import xml.etree.ElementTree as ET
4
7
 
5
8
  from .__init__ import PY2
6
9
 
10
+ class BadXML(Exception):
11
+ pass
12
+
13
+
7
14
  def get_ET() :
8
15
  pn = "xml.etree.ElementTree"
9
16
  cn = "_elementtree"
@@ -30,7 +37,7 @@ def get_ET() :
30
37
  XMLParser = get_ET()
31
38
 
32
39
 
33
- class DXMLParser(XMLParser): # type: ignore
40
+ class _DXMLParser(XMLParser): # type: ignore
34
41
  def __init__(self) :
35
42
  tb = ET.TreeBuilder()
36
43
  super(DXMLParser, self).__init__(target=tb)
@@ -45,8 +52,12 @@ class DXMLParser(XMLParser): # type: ignore
45
52
  raise BadXML("{}, {}".format(a, ka))
46
53
 
47
54
 
48
- class BadXML(Exception):
49
- pass
55
+ class _NG(XMLParser): # type: ignore
56
+ def __int__(self) :
57
+ raise BadXML("dxml selftest failed")
58
+
59
+
60
+ DXMLParser = _DXMLParser
50
61
 
51
62
 
52
63
  def parse_xml(txt ) :
@@ -55,6 +66,40 @@ def parse_xml(txt ) :
55
66
  return parser.close() # type: ignore
56
67
 
57
68
 
69
+ def selftest() :
70
+ qbe = r"""<!DOCTYPE d [
71
+ <!ENTITY a "nice_bakuretsu">
72
+ ]>
73
+ <root>&a;&a;&a;</root>"""
74
+
75
+ emb = r"""<!DOCTYPE d [
76
+ <!ENTITY a SYSTEM "file:///etc/hostname">
77
+ ]>
78
+ <root>&a;</root>"""
79
+
80
+ # future-proofing; there's never been any known vulns
81
+ # regarding DTDs and ET.XMLParser, but might as well
82
+ # block them since webdav-clients don't use them
83
+ dtd = r"""<!DOCTYPE d SYSTEM "a.dtd">
84
+ <root>a</root>"""
85
+
86
+ for txt in (qbe, emb, dtd):
87
+ try:
88
+ parse_xml(txt)
89
+ t = "WARNING: dxml selftest failed:\n%s\n"
90
+ print(t % (txt,), file=sys.stderr)
91
+ return False
92
+ except BadXML:
93
+ pass
94
+
95
+ return True
96
+
97
+
98
+ DXML_OK = selftest()
99
+ if not DXML_OK:
100
+ DXMLParser = _NG
101
+
102
+
58
103
  def mktnod(name , text ) :
59
104
  el = ET.Element(name)
60
105
  el.text = text
copyparty/httpcli.py CHANGED
@@ -128,6 +128,8 @@ NO_CACHE = {"Cache-Control": "no-cache"}
128
128
 
129
129
  ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
130
130
 
131
+ BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff-hdr was received from an untrusted reverse-proxy)"
132
+
131
133
  H_CONN_KEEPALIVE = "Connection: Keep-Alive"
132
134
  H_CONN_CLOSE = "Connection: Close"
133
135
 
@@ -157,6 +159,8 @@ class HttpCli(object):
157
159
 
158
160
  def __init__(self, conn ) :
159
161
 
162
+ empty_stringlist = []
163
+
160
164
  self.t0 = time.time()
161
165
  self.conn = conn
162
166
  self.u2mutex = conn.u2mutex # mypy404
@@ -202,9 +206,7 @@ class HttpCli(object):
202
206
  self.trailing_slash = True
203
207
  self.uname = " "
204
208
  self.pw = " "
205
- self.rvol = [" "]
206
- self.wvol = [" "]
207
- self.avol = [" "]
209
+ self.rvol = self.wvol = self.avol = empty_stringlist
208
210
  self.do_log = True
209
211
  self.can_read = False
210
212
  self.can_write = False
@@ -385,6 +387,7 @@ class HttpCli(object):
385
387
  ) + "0.0/16"
386
388
  zs2 = ' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
387
389
  self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs, zs2), 3)
390
+ self.bad_xff = True
388
391
  else:
389
392
  self.ip = cli_ip
390
393
  self.is_vproxied = bool(self.args.R)
@@ -505,7 +508,7 @@ class HttpCli(object):
505
508
  return False
506
509
 
507
510
  if "k" in uparam:
508
- m = RE_K.search(uparam["k"])
511
+ m = re_k.search(uparam["k"])
509
512
  if m:
510
513
  zs = uparam["k"]
511
514
  t = "malicious user; illegal filekey; req(%r) k(%r) => %r"
@@ -4334,7 +4337,7 @@ class HttpCli(object):
4334
4337
  self.log,
4335
4338
  self.asrv,
4336
4339
  fgen,
4337
- utf8="utf" in uarg,
4340
+ utf8="utf" in uarg or not uarg,
4338
4341
  pre_crc="crc" in uarg,
4339
4342
  cmp=uarg if cancmp or uarg == "pax" else "",
4340
4343
  )
@@ -4982,8 +4985,16 @@ class HttpCli(object):
4982
4985
  and (self.uname in vol.axs.uread or self.uname in vol.axs.upget)
4983
4986
  }
4984
4987
 
4988
+ bad_xff = hasattr(self, "bad_xff")
4989
+ if bad_xff:
4990
+ allvols = []
4991
+ t = "will not return list of recent uploads" + BADXFF
4992
+ self.log(t, 1)
4993
+ if self.avol:
4994
+ raise Pebkac(500, t)
4995
+
4985
4996
  x = self.conn.hsrv.broker.ask(
4986
- "up2k.get_unfinished_by_user", self.uname, self.ip
4997
+ "up2k.get_unfinished_by_user", self.uname, "" if bad_xff else self.ip
4987
4998
  )
4988
4999
  uret = x.get()
4989
5000
 
@@ -5377,12 +5388,16 @@ class HttpCli(object):
5377
5388
  if self.args.no_del:
5378
5389
  raise Pebkac(403, "the delete feature is disabled in server config")
5379
5390
 
5391
+ unpost = "unpost" in self.uparam
5392
+ if unpost and hasattr(self, "bad_xff"):
5393
+ self.log("unpost was denied" + BADXFF, 1)
5394
+ raise Pebkac(403, "the delete feature is disabled in server config")
5395
+
5380
5396
  if not req:
5381
5397
  req = [self.vpath]
5382
5398
  elif self.is_vproxied:
5383
5399
  req = [x[len(self.args.SR) :] for x in req]
5384
5400
 
5385
- unpost = "unpost" in self.uparam
5386
5401
  nlim = int(self.uparam.get("lim") or 0)
5387
5402
  lim = [nlim, nlim] if nlim else []
5388
5403
 
@@ -5782,7 +5797,7 @@ class HttpCli(object):
5782
5797
  "taglist": [],
5783
5798
  "have_tags_idx": int(e2t),
5784
5799
  "have_b_u": (self.can_write and self.uparam.get("b") == "u"),
5785
- "sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
5800
+ "sb_lg": vn.js_ls["sb_lg"],
5786
5801
  "url_suf": url_suf,
5787
5802
  "title": html_escape("%s %s" % (self.args.bname, self.vpath), crlf=True),
5788
5803
  "srv_info": srv_infot,
copyparty/svchub.py CHANGED
@@ -44,6 +44,8 @@ from .util import (
44
44
  FFMPEG_URL,
45
45
  HAVE_PSUTIL,
46
46
  HAVE_SQLITE3,
47
+ HAVE_ZMQ,
48
+ URL_BUG,
47
49
  UTC,
48
50
  VERSIONS,
49
51
  Daemon,
@@ -54,6 +56,7 @@ from .util import (
54
56
  alltrace,
55
57
  ansi_re,
56
58
  build_netmap,
59
+ expat_ver,
57
60
  load_ipu,
58
61
  min_ex,
59
62
  mp,
@@ -629,6 +632,7 @@ class SvcHub(object):
629
632
  (HAVE_FFPROBE, "ffprobe", t_ff + ", read audio/media tags"),
630
633
  (HAVE_MUTAGEN, "mutagen", "read audio tags (ffprobe is better but slower)"),
631
634
  (HAVE_ARGON2, "argon2", "secure password hashing (advanced users only)"),
635
+ (HAVE_ZMQ, "pyzmq", "send zeromq messages from event-hooks"),
632
636
  (HAVE_HEIF, "pillow-heif", "read .heif images with pillow (rarely useful)"),
633
637
  (HAVE_AVIF, "pillow-avif", "read .avif images with pillow (rarely useful)"),
634
638
  ]
@@ -685,6 +689,15 @@ class SvcHub(object):
685
689
  if self.args.bauth_last:
686
690
  self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
687
691
 
692
+ if not self.args.no_dav:
693
+ from .dxml import DXML_OK
694
+
695
+ if not DXML_OK:
696
+ if not self.args.no_dav:
697
+ self.args.no_dav = True
698
+ t = "WARNING:\nDisabling WebDAV support because dxml selftest failed. Please report this bug;\n%s\n...and include the following information in the bug-report:\n%s | expat %s\n"
699
+ self.log("root", t % (URL_BUG, VERSIONS, expat_ver()), 1)
700
+
688
701
  def _process_config(self) :
689
702
  al = self.args
690
703
 
copyparty/up2k.py CHANGED
@@ -790,7 +790,7 @@ class Up2k(object):
790
790
  continue
791
791
 
792
792
  self.log("xiu: %d# %r" % (len(wrfs), cmd))
793
- runihook(self.log, cmd, vol, ups)
793
+ runihook(self.log, self.args.hook_v, cmd, vol, ups)
794
794
 
795
795
  def _vis_job_progress(self, job ) :
796
796
  perc = 100 - (len(job["need"]) * 100.0 / (len(job["hash"]) or 1))
@@ -4881,7 +4881,8 @@ class Up2k(object):
4881
4881
  except:
4882
4882
  pass
4883
4883
 
4884
- xbu = self.flags[job["ptop"]].get("xbu")
4884
+ vf = self.flags[job["ptop"]]
4885
+ xbu = vf.get("xbu")
4885
4886
  ap_chk = djoin(pdir, job["name"])
4886
4887
  vp_chk = djoin(job["vtop"], job["prel"], job["name"])
4887
4888
  if xbu:
@@ -4911,7 +4912,7 @@ class Up2k(object):
4911
4912
  if x:
4912
4913
  zvfs = vfs
4913
4914
  pdir, _, job["name"], (vfs, rem) = x
4914
- job["vcfg"] = vfs.flags
4915
+ job["vcfg"] = vf = vfs.flags
4915
4916
  job["ptop"] = vfs.realpath
4916
4917
  job["vtop"] = vfs.vpath
4917
4918
  job["prel"] = rem
@@ -4961,8 +4962,13 @@ class Up2k(object):
4961
4962
  fs = self.fstab.get(pdir)
4962
4963
  if fs == "ok":
4963
4964
  pass
4964
- elif "sparse" in self.flags[job["ptop"]]:
4965
- t = "volflag 'sparse' is forcing use of sparse files for uploads to [%s]"
4965
+ elif "nosparse" in vf:
4966
+ t = "volflag 'nosparse' is preventing creation of sparse files for uploads to [%s]"
4967
+ self.log(t % (job["ptop"],))
4968
+ relabel = True
4969
+ sprs = False
4970
+ elif "sparse" in vf:
4971
+ t = "volflag 'sparse' is forcing creation of sparse files for uploads to [%s]"
4966
4972
  self.log(t % (job["ptop"],))
4967
4973
  relabel = True
4968
4974
  else:
copyparty/util.py CHANGED
@@ -120,6 +120,13 @@ try:
120
120
  except:
121
121
  HAVE_SQLITE3 = False
122
122
 
123
+ try:
124
+ import importlib.util
125
+
126
+ HAVE_ZMQ = bool(importlib.util.find_spec("zmq"))
127
+ except:
128
+ HAVE_ZMQ = False
129
+
123
130
  try:
124
131
  if os.environ.get("PRTY_NO_PSUTIL"):
125
132
  raise Exception()
@@ -208,6 +215,10 @@ META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
208
215
 
209
216
  FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
210
217
 
218
+ URL_PRJ = "https://github.com/9001/copyparty"
219
+
220
+ URL_BUG = URL_PRJ + "/issues/new?labels=bug&template=bug_report.md"
221
+
211
222
  HTTPCODE = {
212
223
  200: "OK",
213
224
  201: "Created",
@@ -470,6 +481,15 @@ def py_desc() :
470
481
  )
471
482
 
472
483
 
484
+ def expat_ver() :
485
+ try:
486
+ import pyexpat
487
+
488
+ return ".".join([str(x) for x in pyexpat.version_info])
489
+ except:
490
+ return "?"
491
+
492
+
473
493
  def _sqlite_ver() :
474
494
  try:
475
495
  co = sqlite3.connect(":memory:")
@@ -3297,6 +3317,7 @@ def _parsehook(
3297
3317
 
3298
3318
  def runihook(
3299
3319
  log ,
3320
+ verbose ,
3300
3321
  cmd ,
3301
3322
  vol ,
3302
3323
  ups ,
@@ -3326,6 +3347,17 @@ def runihook(
3326
3347
  else:
3327
3348
  sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps)
3328
3349
 
3350
+ if acmd[0].startswith("zmq:"):
3351
+ try:
3352
+ msg = sp_ka["sin"].decode("utf-8", "replace")
3353
+ _zmq_hook(log, verbose, "xiu", acmd[0][4:].lower(), msg, wait, sp_ka)
3354
+ if verbose and log:
3355
+ log("hook(xiu) %r OK" % (cmd,), 6)
3356
+ except Exception as ex:
3357
+ if log:
3358
+ log("zeromq failed: %r" % (ex,))
3359
+ return True
3360
+
3329
3361
  t0 = time.time()
3330
3362
  if fork:
3331
3363
  Daemon(runcmd, cmd, bcmd, ka=sp_ka)
@@ -3335,15 +3367,118 @@ def runihook(
3335
3367
  retchk(rc, bcmd, err, log, 5)
3336
3368
  return False
3337
3369
 
3338
- wait -= time.time() - t0
3339
- if wait > 0:
3340
- time.sleep(wait)
3370
+ if wait:
3371
+ wait -= time.time() - t0
3372
+ if wait > 0:
3373
+ time.sleep(wait)
3341
3374
 
3342
3375
  return True
3343
3376
 
3344
3377
 
3378
+ ZMQ = {}
3379
+ ZMQ_DESC = {
3380
+ "pub": "fire-and-forget to all/any connected SUB-clients",
3381
+ "push": "fire-and-forget to one of the connected PULL-clients",
3382
+ "req": "send messages to a REP-server and blocking-wait for ack",
3383
+ }
3384
+
3385
+
3386
+ def _zmq_hook(
3387
+ log ,
3388
+ verbose ,
3389
+ src ,
3390
+ cmd ,
3391
+ msg ,
3392
+ wait ,
3393
+ sp_ka ,
3394
+ ) :
3395
+ import zmq
3396
+
3397
+ try:
3398
+ mtx = ZMQ["mtx"]
3399
+ except:
3400
+ ZMQ["mtx"] = threading.Lock()
3401
+ time.sleep(0.1)
3402
+ mtx = ZMQ["mtx"]
3403
+
3404
+ ret = ""
3405
+ t0 = time.time()
3406
+ if verbose and log:
3407
+ log("hook(%s) %r entering zmq-main-lock" % (src, cmd), 6)
3408
+
3409
+ with mtx:
3410
+ try:
3411
+ mode, sck, mtx = ZMQ[cmd]
3412
+ except:
3413
+ mode, uri = cmd.split(":", 1)
3414
+ try:
3415
+ desc = ZMQ_DESC[mode]
3416
+ if log:
3417
+ t = "libzmq(%s) pyzmq(%s) init(%s); %s"
3418
+ log(t % (zmq.zmq_version(), zmq.__version__, cmd, desc))
3419
+ except:
3420
+ raise Exception("the only supported ZMQ modes are REQ PUB PUSH")
3421
+
3422
+ try:
3423
+ ctx = ZMQ["ctx"]
3424
+ except:
3425
+ ctx = ZMQ["ctx"] = zmq.Context()
3426
+
3427
+ timeout = sp_ka["timeout"]
3428
+
3429
+ if mode == "pub":
3430
+ sck = ctx.socket(zmq.PUB)
3431
+ sck.bind(uri)
3432
+ time.sleep(1) # give clients time to connect; avoids losing first msg
3433
+ elif mode == "push":
3434
+ sck = ctx.socket(zmq.PUSH)
3435
+ sck.bind(uri)
3436
+ if timeout:
3437
+ sck.SNDTIMEO = int(timeout * 1000)
3438
+ elif mode == "req":
3439
+ sck = ctx.socket(zmq.REQ)
3440
+ sck.connect(uri)
3441
+ if timeout:
3442
+ sck.RCVTIMEO = int(timeout * 1000)
3443
+ else:
3444
+ raise Exception()
3445
+
3446
+ mtx = threading.Lock()
3447
+ ZMQ[cmd] = (mode, sck, mtx)
3448
+
3449
+ if verbose and log:
3450
+ log("hook(%s) %r entering socket-lock" % (src, cmd), 6)
3451
+
3452
+ with mtx:
3453
+ if verbose and log:
3454
+ log("hook(%s) %r sending |%d|" % (src, cmd, len(msg)), 6)
3455
+
3456
+ sck.send_string(msg) # PUSH can safely timeout here
3457
+
3458
+ if mode == "req":
3459
+ if verbose and log:
3460
+ log("hook(%s) %r awaiting ack from req" % (src, cmd), 6)
3461
+ try:
3462
+ ret = sck.recv().decode("utf-8", "replace")
3463
+ except:
3464
+ sck.close()
3465
+ del ZMQ[cmd] # bad state; must reset
3466
+ raise Exception("ack timeout; zmq socket killed")
3467
+
3468
+ if ret and log:
3469
+ log("hook(%s) %r ACK: %r" % (src, cmd, ret), 6)
3470
+
3471
+ if wait:
3472
+ wait -= time.time() - t0
3473
+ if wait > 0:
3474
+ time.sleep(wait)
3475
+
3476
+ return ret
3477
+
3478
+
3345
3479
  def _runhook(
3346
3480
  log ,
3481
+ verbose ,
3347
3482
  src ,
3348
3483
  cmd ,
3349
3484
  ap ,
@@ -3384,6 +3519,15 @@ def _runhook(
3384
3519
  else:
3385
3520
  arg = txt or ap
3386
3521
 
3522
+ if acmd[0].startswith("zmq:"):
3523
+ zs = "zmq-error"
3524
+ try:
3525
+ zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
3526
+ except Exception as ex:
3527
+ if log:
3528
+ log("zeromq failed: %r" % (ex,))
3529
+ return {"rc": 0, "stdout": zs}
3530
+
3387
3531
  acmd += [arg]
3388
3532
  if acmd[0].endswith(".py"):
3389
3533
  acmd = [pybin] + acmd
@@ -3412,9 +3556,10 @@ def _runhook(
3412
3556
  except:
3413
3557
  ret = {"rc": rc, "stdout": v}
3414
3558
 
3415
- wait -= time.time() - t0
3416
- if wait > 0:
3417
- time.sleep(wait)
3559
+ if wait:
3560
+ wait -= time.time() - t0
3561
+ if wait > 0:
3562
+ time.sleep(wait)
3418
3563
 
3419
3564
  return ret
3420
3565
 
@@ -3437,14 +3582,15 @@ def runhook(
3437
3582
  txt ,
3438
3583
  ) :
3439
3584
  args = (broker or up2k).args
3585
+ verbose = args.hook_v
3440
3586
  vp = vp.replace("\\", "/")
3441
3587
  ret = {"rc": 0}
3442
3588
  for cmd in cmds:
3443
3589
  try:
3444
3590
  hr = _runhook(
3445
- log, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
3591
+ log, verbose, src, cmd, ap, vp, host, uname, perms, mt, sz, ip, at, txt
3446
3592
  )
3447
- if log and args.hook_v:
3593
+ if verbose and log:
3448
3594
  log("hook(%s) %r => \033[32m%s" % (src, cmd, hr), 6)
3449
3595
  if not hr:
3450
3596
  return {}
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "2.7"
5
- S_BUILD_DT = "2024-12-06"
4
+ S_VERSION = "2.8"
5
+ S_BUILD_DT = "2025-01-21"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1247,7 +1247,7 @@ class Ctl(object):
1247
1247
  for n, zsii in enumerate(file.cids)
1248
1248
  ]
1249
1249
  print("chs: %s\n%s" % (vp, "\n".join(zsl)))
1250
- zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.kchunks]
1250
+ zsl = [self.ar.wsalt, str(file.size)] + [x[0] for x in file.cids]
1251
1251
  zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
1252
1252
  wark = ub64enc(zb).decode("utf-8")
1253
1253
  if self.ar.jw:
Binary file
copyparty/web/svcs.html CHANGED
@@ -239,7 +239,7 @@
239
239
  <div class="os win">
240
240
  <h1>ShareX</h1>
241
241
 
242
- <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.4.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
242
+ <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.1.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
243
243
 
244
244
  <pre class="dl" name="copyparty.sxcu">
245
245
  { "Name": "copyparty",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.8
3
+ Version: 1.16.9
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -52,6 +52,8 @@ Provides-Extra: tftpd
52
52
  Requires-Dist: partftpy>=0.4.0; extra == "tftpd"
53
53
  Provides-Extra: pwhash
54
54
  Requires-Dist: argon2-cffi; extra == "pwhash"
55
+ Provides-Extra: zeromq
56
+ Requires-Dist: pyzmq; extra == "zeromq"
55
57
 
56
58
  <img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
57
59
 
@@ -135,6 +137,7 @@ turn almost any device into a file server with resumable uploads/downloads using
135
137
  * [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
136
138
  * [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
137
139
  * [event hooks](#event-hooks) - trigger a program on uploads, renames etc ([examples](./bin/hooks/))
140
+ * [zeromq](#zeromq) - event-hooks can send zeromq messages
138
141
  * [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
139
142
  * [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
140
143
  * [ip auth](#ip-auth) - autologin based on IP range (CIDR)
@@ -670,8 +673,8 @@ select which type of archive you want in the `[⚙️] config` tab:
670
673
  | `pax` | `?tar=pax` | pax-format tar, futureproof, not as fast |
671
674
  | `tgz` | `?tar=gz` | gzip compressed gnu-tar (slow), for `curl \| tar -xvz` |
672
675
  | `txz` | `?tar=xz` | gnu-tar with xz / lzma compression (v.slow) |
673
- | `zip` | `?zip=utf8` | works everywhere, glitchy filenames on win7 and older |
674
- | `zip_dos` | `?zip` | traditional cp437 (no unicode) to fix glitchy filenames |
676
+ | `zip` | `?zip` | works everywhere, glitchy filenames on win7 and older |
677
+ | `zip_dos` | `?zip=dos` | traditional cp437 (no unicode) to fix glitchy filenames |
675
678
  | `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software |
676
679
 
677
680
  * gzip default level is `3` (0=fast, 9=best), change with `?tar=gz:9`
@@ -679,7 +682,7 @@ select which type of archive you want in the `[⚙️] config` tab:
679
682
  * bz2 default level is `2` (1=fast, 9=best), change with `?tar=bz2:9`
680
683
  * hidden files ([dotfiles](#dotfiles)) are excluded unless account is allowed to list them
681
684
  * `up2k.db` and `dir.txt` is always excluded
682
- * bsdtar supports streaming unzipping: `curl foo?zip=utf8 | bsdtar -xv`
685
+ * bsdtar supports streaming unzipping: `curl foo?zip | bsdtar -xv`
683
686
  * good, because copyparty's zip is faster than tar on small files
684
687
  * `zip_crc` will take longer to download since the server has to read each file twice
685
688
  * this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
@@ -1513,6 +1516,23 @@ there's a bunch of flags and stuff, see `--help-hooks`
1513
1516
  if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
1514
1517
 
1515
1518
 
1519
+ ### zeromq
1520
+
1521
+ event-hooks can send zeromq messages instead of running programs
1522
+
1523
+ to send a 0mq message every time a file is uploaded,
1524
+
1525
+ * `--xau zmq:pub:tcp://*:5556` sends a PUB to any/all connected SUB clients
1526
+ * `--xau t3,zmq:push:tcp://*:5557` sends a PUSH to exactly one connected PULL client
1527
+ * `--xau t3,j,zmq:req:tcp://localhost:5555` sends a REQ to the connected REP client
1528
+
1529
+ the PUSH and REQ examples have `t3` (timeout after 3 seconds) because they block if there's no clients to talk to
1530
+
1531
+ * the REQ example does `t3,j` to send extended upload-info as json instead of just the filesystem-path
1532
+
1533
+ see [zmq-recv.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/zmq-recv.py) if you need something to receive the messages with
1534
+
1535
+
1516
1536
  ### upload events
1517
1537
 
1518
1538
  the older, more powerful approach ([examples](./bin/mtag/)):
@@ -1600,12 +1620,16 @@ connecting to an aws s3 bucket and similar
1600
1620
 
1601
1621
  there is no built-in support for this, but you can use FUSE-software such as [rclone](https://rclone.org/) / [geesefs](https://github.com/yandex-cloud/geesefs) / [JuiceFS](https://juicefs.com/en/) to first mount your cloud storage as a local disk, and then let copyparty use (a folder in) that disk as a volume
1602
1622
 
1603
- you may experience poor upload performance this way, but that can sometimes be fixed by specifying the volflag `sparse` to force the use of sparse files; this has improved the upload speeds from `1.5 MiB/s` to over `80 MiB/s` in one case, but note that you are also more likely to discover funny bugs in your FUSE software this way, so buckle up
1623
+ you will probably get decent speeds with the default config, however most likely restricted to using one TCP connection per file, so the upload-client won't be able to send multiple chunks in parallel
1624
+
1625
+ > before [v1.13.5](https://github.com/9001/copyparty/releases/tag/v1.13.5) it was recommended to use the volflag `sparse` to force-allow multiple chunks in parallel; this would improve the upload-speed from `1.5 MiB/s` to over `80 MiB/s` at the risk of provoking latent bugs in S3 or JuiceFS. But v1.13.5 added chunk-stitching, so this is now probably much less important. On the contrary, `nosparse` *may* now increase performance in some cases. Please try all three options (default, `sparse`, `nosparse`) as the optimal choice depends on your network conditions and software stack (both the FUSE-driver and cloud-server)
1604
1626
 
1605
1627
  someone has also tested geesefs in combination with [gocryptfs](https://nuetzlich.net/gocryptfs/) with surprisingly good results, getting 60 MiB/s upload speeds on a gbit line, but JuiceFS won with 80 MiB/s using its built-in encryption
1606
1628
 
1607
1629
  you may improve performance by specifying larger values for `--iobuf` / `--s-rd-sz` / `--s-wr-sz`
1608
1630
 
1631
+ > if you've experimented with this and made interesting observations, please share your findings so we can add a section with specific recommendations :-)
1632
+
1609
1633
 
1610
1634
  ## hiding from google
1611
1635
 
@@ -2359,13 +2383,13 @@ mandatory deps:
2359
2383
 
2360
2384
  install these to enable bonus features
2361
2385
 
2362
- enable hashed passwords in config: `argon2-cffi`
2386
+ enable [hashed passwords](#password-hashing) in config: `argon2-cffi`
2363
2387
 
2364
- enable ftp-server:
2388
+ enable [ftp-server](#ftp-server):
2365
2389
  * for just plaintext FTP, `pyftpdlib` (is built into the SFX)
2366
2390
  * with TLS encryption, `pyftpdlib pyopenssl`
2367
2391
 
2368
- enable music tags:
2392
+ enable [music tags](#metadata-from-audio-files):
2369
2393
  * either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
2370
2394
  * or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
2371
2395
 
@@ -2376,8 +2400,9 @@ enable [thumbnails](#thumbnails) of...
2376
2400
  * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
2377
2401
  * **JPEG XL pictures:** `pyvips` or `ffmpeg`
2378
2402
 
2379
- enable [smb](#smb-server) support (**not** recommended):
2380
- * `impacket==0.12.0`
2403
+ enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
2404
+
2405
+ enable [smb](#smb-server) support (**not** recommended): `impacket==0.12.0`
2381
2406
 
2382
2407
  `pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
2383
2408
 
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
- copyparty/__main__.py,sha256=acjihZUEvEwFp6d6BnT35oFqminMZBruDUfVU76YijA,113564
3
- copyparty/__version__.py,sha256=O9UOPs_HcmEufkcuaes_ZA7U8kK_sVtXCGmVqE69BnA,251
4
- copyparty/authsrv.py,sha256=h1OhdMR_hrqZA9NH2pMRZ-Axc_GSoUajioCZhimVVsQ,104109
2
+ copyparty/__main__.py,sha256=OvcLYTmA_IbuoA7PzqfozlfMUoWVhgK8OZ89htVQeHQ,114236
3
+ copyparty/__version__.py,sha256=rMDKjaOJgDxkWi5DqhkiIl0LpXkT0uLtMhbMTaIUfuM,251
4
+ copyparty/authsrv.py,sha256=uADnPkHiTH0UcBUr-w5hdUare6p-74O7cJQaL9lkpws,104513
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
8
8
  copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
9
9
  copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=UUmFpFbTm750Nv9RnofS80-FTpWT37EggEtmkE1wbxE,10374
11
- copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
10
+ copyparty/cfg.py,sha256=WY4gMZ42T-mBitZivxh5wcN1JIN4i2m8Gg8kGumnDPw,10619
11
+ copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=IVOFG8zBQPMQDDv7RIStSJHwHiAnVNROZS37O5k465A,4524
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=jiR7zHGGyVp-ZwK-j0I2eNkwbEOYDTc7xGrGzrSb0iQ,215225
14
+ copyparty/httpcli.py,sha256=lTMSK9xLXhRsPo1XPeV_RRTJZ6jQgx17wt_BFdQ26M4,215837
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
@@ -24,15 +24,15 @@ copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
27
- copyparty/svchub.py,sha256=sAHkiPGzzKACLqKlem2V-bps9Xh-wHlcfwaNywxcd5A,40877
27
+ copyparty/svchub.py,sha256=mgZ2UndrMqWuUywecsCgVz488WoS0EFHR94VvWZjeT0,41454
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
29
  copyparty/tcpsrv.py,sha256=2q18dGR8jnezA4SMfUXa-wrGRGX3nHIwkxkWvkTzF2A,19889
30
30
  copyparty/tftpd.py,sha256=PXgG4rTmiaU_TavSyZWD5cFphdfChs9YvNY21qfExt8,13611
31
31
  copyparty/th_cli.py,sha256=1B8el4NNs5cNyJyjOPiAdvLOX2HQXaebsHn6VLdZ_gU,4630
32
32
  copyparty/th_srv.py,sha256=uAcz-wZJQEG5KavcZDkwToONZujyoOeC4oCxxKTD5us,29575
33
33
  copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
- copyparty/up2k.py,sha256=0YPdP2UHoJr0c3QhS6EsOIUPSq2JztCD43Y3D7lN6Po,175144
35
- copyparty/util.py,sha256=qhNakLtygJveVbZUuPvDY7Tm1E48E67CS9XBmosu5i4,95101
34
+ copyparty/up2k.py,sha256=alObSL1EVyPedi_24s7DBFlBzA0ZMkr9_jOtzdRV3vk,175427
35
+ copyparty/util.py,sha256=Msbj0esxQNmEEOheL6ffubMeyPLEqyRnMJ1g5t2pI68,99012
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -57,7 +57,7 @@ copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8
57
57
  copyparty/web/baguettebox.js.gz,sha256=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
58
58
  copyparty/web/browser.css.gz,sha256=kurx_iA-KxLYx8PqJsn0bJVjkAxP-0YTOHSV9l_oouo,11645
59
59
  copyparty/web/browser.html,sha256=dekrZQ6w8ciB-QPlp-mjcuzUVKlsCYcvvi6efmXRfQE,4822
60
- copyparty/web/browser.js.gz,sha256=HejO4Brwmw-NmLVmqbK1I31LPP9arf7G9JaHOUpNcfY,89876
60
+ copyparty/web/browser.js.gz,sha256=V9_Xz8uZsJ01MNoc480L0SMduAOINGrcQ2296FNRoeA,89860
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -80,7 +80,7 @@ copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,94
80
80
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
81
  copyparty/web/splash.html,sha256=QEnTH9QZXFmAuyVtgqOuuHKBtIdi7uclpRqe0ZMewj4,6249
82
82
  copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
- copyparty/web/svcs.html,sha256=s2uMblxDpYo8l-M--KB1BAF1ZiQrWf5p4v1sDjSs1MQ,14140
83
+ copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
84
84
  copyparty/web/svcs.js.gz,sha256=rcc75HMmoc3KA7Ld2j8X9AKX_elZgwUD6Vnm2F-yj_U,805
85
85
  copyparty/web/ui.css.gz,sha256=0sHIwGsL3_xH8Uu6N0Ag3bKBTjf-e_yfFbKynEZXAnk,2800
86
86
  copyparty/web/up2k.js.gz,sha256=f00wmaThk1CoFYzFBG_6KLG0acq60cAn3p12tngqprk,23843
@@ -88,7 +88,7 @@ copyparty/web/util.js.gz,sha256=uCCnKWT_FeG-ShKPoj0MtXnBwtdAbyDSM9AP9KiBDjw,1509
88
88
  copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
91
- copyparty/web/a/u2c.py,sha256=OuA0UMtPDSrMHgd4ebANJXoLeIa1RKxzbBXUV3H8jIw,51606
91
+ copyparty/web/a/u2c.py,sha256=N9NZHF6CbooNxIYkZMVz7wWTMh88nXCXcd4bDKzaanw,51603
92
92
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
93
93
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
94
94
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.16.8.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.8.dist-info/METADATA,sha256=-4246pvuZxdG_MtxGM7euDMxpPVVGiXgIzBEdvlRGLk,144011
114
- copyparty-1.16.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
- copyparty-1.16.8.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.8.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.8.dist-info/RECORD,,
112
+ copyparty-1.16.9.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.9.dist-info/METADATA,sha256=-gjl5i-kYVmjMeHdJ3ZXxeAu0FFWH2zcD0LTu2rb-Cw,145641
114
+ copyparty-1.16.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.9.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.9.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.9.dist-info/RECORD,,