copyparty 1.19.8__py3-none-any.whl → 1.19.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.
Files changed (50) hide show
  1. copyparty/__main__.py +22 -14
  2. copyparty/__version__.py +2 -2
  3. copyparty/authsrv.py +102 -9
  4. copyparty/cfg.py +2 -0
  5. copyparty/ftpd.py +3 -0
  6. copyparty/httpcli.py +23 -11
  7. copyparty/mdns.py +3 -1
  8. copyparty/mtag.py +0 -1
  9. copyparty/svchub.py +11 -2
  10. copyparty/u2idx.py +29 -4
  11. copyparty/up2k.py +15 -5
  12. copyparty/util.py +41 -19
  13. copyparty/web/baguettebox.js.gz +0 -0
  14. copyparty/web/browser.css.gz +0 -0
  15. copyparty/web/browser.js.gz +0 -0
  16. copyparty/web/dbg-audio.js.gz +0 -0
  17. copyparty/web/deps/busy.mp3.gz +0 -0
  18. copyparty/web/deps/easymde.css.gz +0 -0
  19. copyparty/web/deps/easymde.js.gz +0 -0
  20. copyparty/web/deps/marked.js.gz +0 -0
  21. copyparty/web/deps/mini-fa.css.gz +0 -0
  22. copyparty/web/deps/prism.css.gz +0 -0
  23. copyparty/web/deps/prism.js.gz +0 -0
  24. copyparty/web/deps/prismd.css.gz +0 -0
  25. copyparty/web/deps/scp.woff2 +0 -0
  26. copyparty/web/deps/sha512.ac.js.gz +0 -0
  27. copyparty/web/md.css.gz +0 -0
  28. copyparty/web/md.js.gz +0 -0
  29. copyparty/web/md2.css.gz +0 -0
  30. copyparty/web/md2.js.gz +0 -0
  31. copyparty/web/mde.css.gz +0 -0
  32. copyparty/web/mde.js.gz +0 -0
  33. copyparty/web/msg.css.gz +0 -0
  34. copyparty/web/rups.css.gz +0 -0
  35. copyparty/web/rups.js.gz +0 -0
  36. copyparty/web/shares.css.gz +0 -0
  37. copyparty/web/shares.js.gz +0 -0
  38. copyparty/web/splash.css.gz +0 -0
  39. copyparty/web/splash.js.gz +0 -0
  40. copyparty/web/svcs.js.gz +0 -0
  41. copyparty/web/ui.css.gz +0 -0
  42. copyparty/web/up2k.js.gz +0 -0
  43. copyparty/web/util.js.gz +0 -0
  44. copyparty/web/w.hash.js.gz +0 -0
  45. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/METADATA +5 -1
  46. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/RECORD +50 -50
  47. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/WHEEL +0 -0
  48. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/entry_points.txt +0 -0
  49. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/licenses/LICENSE +0 -0
  50. {copyparty-1.19.8.dist-info → copyparty-1.19.9.dist-info}/top_level.txt +0 -0
copyparty/__main__.py CHANGED
@@ -1169,11 +1169,14 @@ def add_qr(ap, tty):
1169
1169
  ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
1170
1170
  ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
1171
1171
  ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
1172
+ ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
1173
+ ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
1172
1174
 
1173
1175
 
1174
1176
  def add_fs(ap):
1175
1177
  ap2 = ap.add_argument_group("filesystem options")
1176
1178
  rm_re_def = "15/0.1" if ANYWIN else "0/0"
1179
+ ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)")
1177
1180
  ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
1178
1181
  ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
1179
1182
  ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
@@ -1673,6 +1676,7 @@ def add_db_general(ap, hcores):
1673
1676
  ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
1674
1677
  ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
1675
1678
  ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
1679
+ ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags")
1676
1680
  ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
1677
1681
  ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
1678
1682
  ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
@@ -1727,7 +1731,7 @@ def add_og(ap):
1727
1731
  ap2.add_argument("--uqe", action="store_true", help="query-string parceling; translate a request for \033[33m/foo/.uqe/BASE64\033[0m into \033[33m/foo?TEXT\033[0m, or \033[33m/foo/?TEXT\033[0m if the first character in \033[33mTEXT\033[0m is a slash. Automatically enabled for \033[33m--og\033[0m")
1728
1732
 
1729
1733
 
1730
- def add_ui(ap, retry):
1734
+ def add_ui(ap, retry ):
1731
1735
  THEMES = 10
1732
1736
  ap2 = ap.add_argument_group("ui options")
1733
1737
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
@@ -1869,18 +1873,21 @@ def run_argparse(
1869
1873
  for k, h, _ in sects:
1870
1874
  ap2.add_argument("--help-" + k, action="store_true", help=h)
1871
1875
 
1872
- try:
1873
- if not retry:
1874
- raise Exception()
1875
-
1876
+ if retry:
1877
+ a = ["ascii", "replace"]
1876
1878
  for x in ap._actions:
1877
- if not x.help:
1878
- continue
1879
+ try:
1880
+ x.default = x.default.encode(*a).decode(*a)
1881
+ except:
1882
+ pass
1879
1883
 
1880
- a = ["ascii", "replace"]
1881
- x.help = x.help.encode(*a).decode(*a) + "\033[0m"
1882
- except:
1883
- pass
1884
+ try:
1885
+ if x.help and x.help is not argparse.SUPPRESS:
1886
+ x.help = x.help.replace("└─", "`-").encode(*a).decode(*a)
1887
+ if retry > 2:
1888
+ x.help = RE_ANSI.sub("", x.help)
1889
+ except:
1890
+ pass
1884
1891
 
1885
1892
  ret = ap.parse_args(args=argv[1:])
1886
1893
  for k, h, t in sects:
@@ -1990,7 +1997,7 @@ def main(argv = None) :
1990
1997
  except:
1991
1998
  nc = 486 # mdns/ssdp restart headroom; select() maxfd is 512 on windows
1992
1999
 
1993
- retry = False
2000
+ retry = 0
1994
2001
  for fmtr in [RiceFormatter, RiceFormatter, Dodge11874, BasicDodge11874]:
1995
2002
  try:
1996
2003
  al = run_argparse(argv, fmtr, retry, nc)
@@ -1999,8 +2006,9 @@ def main(argv = None) :
1999
2006
  except SystemExit:
2000
2007
  raise
2001
2008
  except:
2002
- retry = True
2003
- lprint("\n[ {} ]:\n{}\n".format(fmtr, min_ex()))
2009
+ retry += 1
2010
+ t = "WARNING: due to limitations in your terminal and/or OS, the helptext cannot be displayed correctly. Will show a simplified version due to the following error:\n[ %s ]:\n%s\n"
2011
+ lprint(t % (fmtr, min_ex()))
2004
2012
 
2005
2013
  try:
2006
2014
  assert al # type: ignore
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 8)
3
+ VERSION = (1, 19, 9)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 9, 7)
5
+ BUILD_DT = (2025, 9, 15)
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
@@ -13,7 +13,7 @@ import threading
13
13
  import time
14
14
  from datetime import datetime
15
15
 
16
- from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
16
+ from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
17
17
  from .bos import bos
18
18
  from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
19
19
  from .pwhash import PWHash
@@ -92,6 +92,8 @@ SBADCFG = " ({})".format(BAD_CFG)
92
92
 
93
93
  PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
94
94
  PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
95
+ PTN_U_ANY = re.compile(r"(\${[u][}%])")
96
+ PTN_G_ANY = re.compile(r"(\${[g][}%])")
95
97
  PTN_SIGIL = re.compile(r"(\${[ug][}%])")
96
98
 
97
99
 
@@ -417,15 +419,17 @@ class VFS(object):
417
419
  self.all_nodes[vpath] = self
418
420
  self.all_aps = [(rp, [self])]
419
421
  self.all_vps = [(vp, self)]
422
+ self.canonical = self._canonical
423
+ self.dcanonical = self._dcanonical
420
424
  else:
421
425
  self.histpath = self.dbpath = ""
422
426
  self.all_aps = []
423
427
  self.all_vps = []
428
+ self.canonical = self._canonical_null
429
+ self.dcanonical = self._dcanonical_null
424
430
 
425
431
  self.get_dbv = self._get_dbv
426
432
  self.ls = self._ls
427
- self.canonical = self._canonical
428
- self.dcanonical = self._dcanonical
429
433
 
430
434
  def __repr__(self) :
431
435
  return "VFS(%s)" % (
@@ -619,6 +623,34 @@ class VFS(object):
619
623
  vrem = vjoin(self.vpath[len(dbv.vpath) :].lstrip("/"), vrem)
620
624
  return dbv, vrem
621
625
 
626
+ def casechk(self, rem , do_stat ) :
627
+ ap = self.canonical(rem, False)
628
+ if do_stat and not bos.path.exists(ap):
629
+ return True # doesn't exist at all; good to go
630
+ dp, fn = os.path.split(ap)
631
+ try:
632
+ fns = os.listdir(dp)
633
+ except:
634
+ return True # maybe chmod 111; assume ok
635
+ if fn in fns:
636
+ return True
637
+ hit = "<?>"
638
+ lfn = fn.lower()
639
+ for zs in fns:
640
+ if lfn == zs.lower():
641
+ hit = zs
642
+ break
643
+ if self.log:
644
+ t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r"
645
+ self.log("vfs", t % (fn, hit))
646
+ return False
647
+
648
+ def _canonical_null(self, rem , resolve = True) :
649
+ return ""
650
+
651
+ def _dcanonical_null(self, rem ) :
652
+ return ""
653
+
622
654
  def _canonical(self, rem , resolve = True) :
623
655
  """returns the canonical path (fully-resolved absolute fs path)"""
624
656
  ap = self.realpath
@@ -704,8 +736,12 @@ class VFS(object):
704
736
  """return user-readable [fsdir,real,virt] items at vpath"""
705
737
  virt_vis = {} # nodes readable by user
706
738
  abspath = self.canonical(rem)
707
- real = list(statdir(self.log, scandir, lstat, abspath, throw))
708
- real.sort()
739
+ if abspath:
740
+ real = list(statdir(self.log, scandir, lstat, abspath, throw))
741
+ real.sort()
742
+ else:
743
+ real = []
744
+
709
745
  if not rem:
710
746
  # no vfs nodes in the list of real inodes
711
747
  real = [x for x in real if x[0] not in self.nodes]
@@ -1121,6 +1157,16 @@ class AuthSrv(object):
1121
1157
  src0 = src # abspath
1122
1158
  dst0 = dst # vpath
1123
1159
 
1160
+ zsl = []
1161
+ for ptn, sigil in ((PTN_U_ANY, "${u}"), (PTN_G_ANY, "${g}")):
1162
+ if bool(ptn.search(src)) != bool(ptn.search(dst)):
1163
+ zsl.append(sigil)
1164
+ if zsl:
1165
+ t = "ERROR: if %s is mentioned in a volume definition, it must be included in both the filesystem-path [%s] and the volume-url [/%s]"
1166
+ t = "\n".join([t % (x, src, dst) for x in zsl])
1167
+ self.log(t, 1)
1168
+ raise Exception(t)
1169
+
1124
1170
  un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
1125
1171
  if not un_gn:
1126
1172
  # ensure volume creation if there's no users
@@ -1213,8 +1259,8 @@ class AuthSrv(object):
1213
1259
  self.log(t, c=3)
1214
1260
  raise Exception(BAD_CFG)
1215
1261
 
1216
- if not bos.path.isdir(src):
1217
- self.log("warning: filesystem-path does not exist: {}".format(src), 3)
1262
+ if not bos.path.exists(src):
1263
+ self.log("warning: filesystem-path did not exist: %r" % (src,), 3)
1218
1264
 
1219
1265
  mount[dst] = (src, dst0)
1220
1266
  daxs[dst] = AXS()
@@ -1836,7 +1882,7 @@ class AuthSrv(object):
1836
1882
  vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
1837
1883
  vol.root = vfs
1838
1884
 
1839
- zs = "neversymlink"
1885
+ zs = "neversymlink du_iwho"
1840
1886
  k_ign = set(zs.split())
1841
1887
  for vol in vfs.all_vols.values():
1842
1888
  unknown_flags = set()
@@ -1987,6 +2033,8 @@ class AuthSrv(object):
1987
2033
  promote = []
1988
2034
  demote = []
1989
2035
  for vol in vfs.all_vols.values():
2036
+ if not vol.realpath:
2037
+ continue
1990
2038
  hid = self.hid_cache.get(vol.realpath)
1991
2039
  if not hid:
1992
2040
  zb = hashlib.sha512(afsenc(vol.realpath)).digest()
@@ -2025,6 +2073,8 @@ class AuthSrv(object):
2025
2073
  vol.histpath = absreal(vol.histpath)
2026
2074
 
2027
2075
  for vol in vfs.all_vols.values():
2076
+ if not vol.realpath:
2077
+ continue
2028
2078
  hid = self.hid_cache[vol.realpath]
2029
2079
  vflag = vol.flags.get("dbpath")
2030
2080
  if vflag == "-":
@@ -2336,7 +2386,7 @@ class AuthSrv(object):
2336
2386
  vol.flags["du_iwho"] = n_du_who(vol.flags["du_who"])
2337
2387
 
2338
2388
  if not enshare:
2339
- vol.flags["shr_who"] = "no"
2389
+ vol.flags["shr_who"] = self.args.shr_who = "no"
2340
2390
 
2341
2391
  if vol.flags.get("og"):
2342
2392
  self.args.uqe = True
@@ -2513,6 +2563,47 @@ class AuthSrv(object):
2513
2563
  self.log(t.format(vol.vpath, mtp), 1)
2514
2564
  errors = True
2515
2565
 
2566
+ for vol in vfs.all_nodes.values():
2567
+ if not vol.realpath or os.path.isfile(vol.realpath):
2568
+ continue
2569
+ ccs = vol.flags["casechk"][:1].lower()
2570
+ if ccs in ("y", "n"):
2571
+ if ccs == "y":
2572
+ vol.flags["bcasechk"] = True
2573
+ continue
2574
+ try:
2575
+ bos.makedirs(vol.realpath, vf=vol.flags)
2576
+ files = os.listdir(vol.realpath)
2577
+ for fn in files:
2578
+ fn2 = fn.lower()
2579
+ if fn == fn2:
2580
+ fn2 = fn.upper()
2581
+ if fn == fn2 or fn2 in files:
2582
+ continue
2583
+ is_ci = os.path.exists(os.path.join(vol.realpath, fn2))
2584
+ ccs = "y" if is_ci else "n"
2585
+ break
2586
+ if ccs not in ("y", "n"):
2587
+ ap = os.path.join(vol.realpath, "casechk")
2588
+ open(ap, "wb").close()
2589
+ ccs = "y" if os.path.exists(ap[:-1] + "K") else "n"
2590
+ os.unlink(ap)
2591
+ except Exception as ex:
2592
+ if ANYWIN:
2593
+ zs = "Windows"
2594
+ ccs = "y"
2595
+ elif MACOS:
2596
+ zs = "Macos"
2597
+ ccs = "y"
2598
+ else:
2599
+ zs = "Linux"
2600
+ ccs = "n"
2601
+ t = "unable to determine if filesystem at %r is case-insensitive due to %r; assuming casechk=%s due to %s"
2602
+ self.log(t % (vol.realpath, ex, ccs, zs), 3)
2603
+ vol.flags["casechk"] = ccs
2604
+ if ccs == "y":
2605
+ vol.flags["bcasechk"] = True
2606
+
2516
2607
  tags = self.args.mtp or []
2517
2608
  tags = [x.split("=")[0] for x in tags]
2518
2609
  tags = [y for x in tags for y in x.split(",")]
@@ -2784,6 +2875,8 @@ class AuthSrv(object):
2784
2875
  shn.dcanonical = shn._dcanonical_shr
2785
2876
  else:
2786
2877
  shn.ls = shn._ls
2878
+ shn.canonical = shn._canonical
2879
+ shn.dcanonical = shn._dcanonical
2787
2880
 
2788
2881
  shn.shr_owner = s_un
2789
2882
  shn.shr_src = (s_vfs, s_rem)
copyparty/cfg.py CHANGED
@@ -81,6 +81,7 @@ def vf_vmap() :
81
81
  }
82
82
  for k in (
83
83
  "bup_ck",
84
+ "casechk",
84
85
  "chmod_d",
85
86
  "chmod_f",
86
87
  "dbd",
@@ -244,6 +245,7 @@ flagcats = {
244
245
  "no_db_ip": "never store uploader-IP in the db; disables unpost",
245
246
  "fat32": "avoid excessive reindexing on android sdcardfs",
246
247
  "dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
248
+ "casechk=auto": "actively prevent case-insensitive filesystem? y/n",
247
249
  "xlink": "cross-volume dupe detection / linking (dangerous)",
248
250
  "xdev": "do not descend into other filesystems",
249
251
  "xvol": "do not follow symlinks leaving the volume root",
copyparty/ftpd.py CHANGED
@@ -198,6 +198,9 @@ class FtpFs(AbstractedFS):
198
198
  if r and not cr or w and not cw or m and not cm or d and not cd:
199
199
  raise FSE(t.format(vpath), 1)
200
200
 
201
+ if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
202
+ raise FSE("No such file or directory", 1)
203
+
201
204
  return os.path.join(vfs.realpath, rem), vfs, rem
202
205
  except Pebkac as ex:
203
206
  raise FSE(str(ex))
copyparty/httpcli.py CHANGED
@@ -730,6 +730,9 @@ class HttpCli(object):
730
730
  else:
731
731
  avn = vn
732
732
 
733
+ if "bcasechk" in vn.flags and not vn.casechk(rem, True):
734
+ return self.tx_404() and False
735
+
733
736
  (
734
737
  self.can_read,
735
738
  self.can_write,
@@ -1546,6 +1549,7 @@ class HttpCli(object):
1546
1549
  if xtag is not None:
1547
1550
  props = set([y.tag.split("}")[-1] for y in xtag])
1548
1551
  # assume <allprop/> otherwise; nobody ever gonna <propname/>
1552
+ self.hint = ""
1549
1553
 
1550
1554
  zi = int(time.time())
1551
1555
  vst = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi))
@@ -1555,7 +1559,9 @@ class HttpCli(object):
1555
1559
  except OSError as ex:
1556
1560
  if ex.errno not in (errno.ENOENT, errno.ENOTDIR):
1557
1561
  raise
1558
- raise Pebkac(404)
1562
+ if tap:
1563
+ raise Pebkac(404)
1564
+ st = vst
1559
1565
 
1560
1566
  topdir = {"vp": "", "st": st}
1561
1567
  fgen = []
@@ -1595,6 +1601,9 @@ class HttpCli(object):
1595
1601
  )
1596
1602
 
1597
1603
  elif depth == "0" or not stat.S_ISDIR(st.st_mode):
1604
+ if depth == "0" and not self.vpath and not vn.realpath:
1605
+ # rootless server; give dummy listing
1606
+ self.can_read = True
1598
1607
  # propfind on a file; return as topdir
1599
1608
  if not self.can_read and not self.can_get:
1600
1609
  self.log("inaccessible: %r" % ("/" + self.vpath,))
@@ -1627,7 +1636,11 @@ class HttpCli(object):
1627
1636
  self.log("inaccessible: %r" % ("/" + self.vpath,))
1628
1637
  raise Pebkac(401, "authenticate")
1629
1638
 
1630
- zi = vn.flags["du_iwho"] if "quota-available-bytes" in props else 0
1639
+ zi = (
1640
+ vn.flags["du_iwho"]
1641
+ if vn.realpath and "quota-available-bytes" in props
1642
+ else 0
1643
+ )
1631
1644
  if zi and (
1632
1645
  zi == 9
1633
1646
  or (zi == 7 and self.uname != "*")
@@ -1761,6 +1774,7 @@ class HttpCli(object):
1761
1774
  xprop = xroot.find(r"./{DAV:}propertyupdate/{DAV:}set/{DAV:}prop")
1762
1775
  for ze in xprop:
1763
1776
  ze.clear()
1777
+ self.hint = ""
1764
1778
 
1765
1779
  txt = """<multistatus xmlns="DAV:"><response><propstat><status>HTTP/1.1 403 Forbidden</status></propstat></response></multistatus>"""
1766
1780
  xroot = parse_xml(txt)
@@ -1816,6 +1830,7 @@ class HttpCli(object):
1816
1830
  ET.register_namespace("D", "DAV:")
1817
1831
  lk = parse_xml(txt)
1818
1832
  assert lk.tag == "{DAV:}lockinfo"
1833
+ self.hint = ""
1819
1834
 
1820
1835
  token = str(uuid.uuid4())
1821
1836
 
@@ -3403,8 +3418,6 @@ class HttpCli(object):
3403
3418
  sz, sha_hex, sha_b64 = copier(
3404
3419
  p_data, f, hasher, max_sz, self.args.s_wr_slp
3405
3420
  )
3406
- if sz == 0:
3407
- raise Pebkac(400, "empty files in post")
3408
3421
  finally:
3409
3422
  f.close()
3410
3423
 
@@ -4711,11 +4724,9 @@ class HttpCli(object):
4711
4724
  packer = StreamZip
4712
4725
  ext = "zip"
4713
4726
 
4714
- fn = items[0] if items and items[0] else self.vpath
4715
- if fn:
4716
- fn = fn.rstrip("/").split("/")[-1]
4717
- else:
4718
- fn = self.host.split(":")[0]
4727
+ fn = self.vpath.split("/")[-1] or self.host.split(":")[0]
4728
+ if items:
4729
+ fn = "sel-" + fn
4719
4730
 
4720
4731
  if vn.flags.get("zipmax") and not (
4721
4732
  vn.flags.get("zipmaxu") and self.uname != "*"
@@ -4891,8 +4902,8 @@ class HttpCli(object):
4891
4902
  else:
4892
4903
  fullfile = b""
4893
4904
 
4894
- if not sz_md and b"\n" in buf[:2]:
4895
- lead = buf[: buf.find(b"\n") + 1]
4905
+ if not sz_md and buf.startswith((b"\n", b"\r\n")):
4906
+ lead = b"\n" if buf.startswith(b"\n") else b"\r\n"
4896
4907
  sz_md += len(lead)
4897
4908
 
4898
4909
  sz_md += len(buf)
@@ -6207,6 +6218,7 @@ class HttpCli(object):
6207
6218
 
6208
6219
  if "v" in self.uparam:
6209
6220
  add_og = True
6221
+ og_fn = ""
6210
6222
 
6211
6223
  if "b" in self.uparam:
6212
6224
  self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
copyparty/mdns.py CHANGED
@@ -12,7 +12,9 @@ from ipaddress import IPv4Network, IPv6Network
12
12
  from .__init__ import TYPE_CHECKING
13
13
  from .__init__ import unicode as U
14
14
  from .multicast import MC_Sck, MCast
15
- from .stolen.dnslib import AAAA
15
+ from .stolen.dnslib import (
16
+ AAAA,
17
+ )
16
18
  from .stolen.dnslib import CLASS as DC
17
19
  from .stolen.dnslib import (
18
20
  NSEC,
copyparty/mtag.py CHANGED
@@ -504,7 +504,6 @@ class MTag(object):
504
504
  "album-artist",
505
505
  "tpe2",
506
506
  "aart",
507
- "conductor",
508
507
  "organization",
509
508
  "band",
510
509
  ],
copyparty/svchub.py CHANGED
@@ -842,6 +842,10 @@ class SvcHub(object):
842
842
  if w8:
843
843
  time.sleep(w8)
844
844
  self.log("qr-code", qr)
845
+ if self.args.qr_stdout:
846
+ self.pr(self.tcpsrv.qr)
847
+ if self.args.qr_stderr:
848
+ self.pr(self.tcpsrv.qr, file=sys.stderr)
845
849
  w8 = self.args.qr_every
846
850
  msg = "%s\033[%dA" % (qr, len(qr.split("\n")))
847
851
  while w8:
@@ -875,8 +879,13 @@ class SvcHub(object):
875
879
  self.sticky_qr()
876
880
  if self.args.qr_wait or self.args.qr_every or self.args.qr_winch:
877
881
  Daemon(self._qr_thr, "qr")
878
- elif not self.args.qr_pin:
879
- self.log("qr-code", self.tcpsrv.qr)
882
+ else:
883
+ if not self.args.qr_pin:
884
+ self.log("qr-code", self.tcpsrv.qr)
885
+ if self.args.qr_stdout:
886
+ self.pr(self.tcpsrv.qr)
887
+ if self.args.qr_stderr:
888
+ self.pr(self.tcpsrv.qr, file=sys.stderr)
880
889
  else:
881
890
  self.log("root", "workers OK\n")
882
891
 
copyparty/u2idx.py CHANGED
@@ -50,6 +50,11 @@ class U2idx(object):
50
50
  self.log("your python does not have sqlite3; searching will be disabled")
51
51
  return
52
52
 
53
+ if self.args.srch_icase:
54
+ self._open_db = self._open_db_icase
55
+ else:
56
+ self._open_db = self._open_db_std
57
+
53
58
 
54
59
  self.active_id = ""
55
60
  self.active_cur = None
@@ -65,6 +70,15 @@ class U2idx(object):
65
70
  def log(self, msg , c = 0) :
66
71
  self.log_func("u2idx", msg, c)
67
72
 
73
+ def _open_db_std(self, *args, **kwargs):
74
+ kwargs["check_same_thread"] = False
75
+ return sqlite3.connect(*args, **kwargs)
76
+
77
+ def _open_db_icase(self, *args, **kwargs):
78
+ db = self._open_db_std(*args, **kwargs)
79
+ db.create_function("casefold", 1, lambda x: x.casefold() if x else x)
80
+ return db
81
+
68
82
  def shutdown(self) :
69
83
  if not HAVE_SQLITE3:
70
84
  return
@@ -142,8 +156,7 @@ class U2idx(object):
142
156
  uri = ""
143
157
  try:
144
158
  uri = "{}?mode=ro&nolock=1".format(Path(db_path).as_uri())
145
- db = sqlite3.connect(uri, timeout=2, uri=True, check_same_thread=False)
146
- cur = db.cursor()
159
+ cur = self._open_db(uri, timeout=2, uri=True).cursor()
147
160
  cur.execute('pragma table_info("up")').fetchone()
148
161
  self.log("ro: %r" % (db_path,))
149
162
  except:
@@ -154,7 +167,7 @@ class U2idx(object):
154
167
  if not cur:
155
168
  # on windows, this steals the write-lock from up2k.deferred_init --
156
169
  # seen on win 10.0.17763.2686, py 3.10.4, sqlite 3.37.2
157
- cur = sqlite3.connect(db_path, timeout=2, check_same_thread=False).cursor()
170
+ cur = self._open_db(db_path, timeout=2).cursor()
158
171
  self.log("opened %r" % (db_path,))
159
172
 
160
173
  self.cur[ptop] = cur
@@ -167,6 +180,8 @@ class U2idx(object):
167
180
  if not HAVE_SQLITE3:
168
181
  return [], [], False
169
182
 
183
+ icase = self.args.srch_icase
184
+
170
185
  q = ""
171
186
  v = ""
172
187
  va = []
@@ -226,9 +241,13 @@ class U2idx(object):
226
241
  elif v == "path":
227
242
  v = "trim(?||up.rd,'/')"
228
243
  va.append("\nrd")
244
+ if icase:
245
+ v = "casefold(%s)" % (v,)
229
246
 
230
247
  elif v == "name":
231
248
  v = "up.fn"
249
+ if icase:
250
+ v = "casefold(%s)" % (v,)
232
251
 
233
252
  elif v == "tags" or ptn_mt.match(v):
234
253
  have_mt = True
@@ -279,6 +298,12 @@ class U2idx(object):
279
298
  tail = "||'%'"
280
299
  v = v[:-1]
281
300
 
301
+ if icase and "casefold(" in q:
302
+ try:
303
+ v = unicode(v).casefold()
304
+ except:
305
+ v = unicode(v).lower()
306
+
282
307
  q += " {}?{} ".format(head, tail)
283
308
  va.append(v)
284
309
  is_key = True
@@ -313,7 +338,7 @@ class U2idx(object):
313
338
  uname ,
314
339
  vols ,
315
340
  uq ,
316
- uv ,
341
+ uv ,
317
342
  have_mt ,
318
343
  sort ,
319
344
  lim ,
copyparty/up2k.py CHANGED
@@ -60,6 +60,7 @@ from .util import (
60
60
  sfsenc,
61
61
  spack,
62
62
  statdir,
63
+ trystat_shutil_copy2,
63
64
  ub64enc,
64
65
  unhumanize,
65
66
  vjoin,
@@ -1132,7 +1133,7 @@ class Up2k(object):
1132
1133
  ft = "\033[0;32m{}{:.0}"
1133
1134
  ff = "\033[0;35m{}{:.0}"
1134
1135
  fv = "\033[0;36m{}:\033[90m{}"
1135
- zs = "du_iwho ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1136
+ zs = "bcasechk du_iwho ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1136
1137
  fx = set(zs.split())
1137
1138
  fd = vf_bmap()
1138
1139
  fd.update(vf_cmap())
@@ -2753,7 +2754,7 @@ class Up2k(object):
2753
2754
  cur.close()
2754
2755
  db.close()
2755
2756
 
2756
- shutil.copy2(fsenc(db_path), fsenc(bak))
2757
+ trystat_shutil_copy2(self.log, fsenc(db_path), fsenc(bak))
2757
2758
  return self._orz(db_path)
2758
2759
 
2759
2760
  def _read_ver(self, cur ) :
@@ -3575,7 +3576,7 @@ class Up2k(object):
3575
3576
  t = "BUG: no valid sources to link from! orig(%r) fsrc(%r) link(%r)"
3576
3577
  self.log(t, 1)
3577
3578
  raise Exception(t % (src, fsrc, dst))
3578
- shutil.copy2(fsenc(csrc), fsenc(dst))
3579
+ trystat_shutil_copy2(self.log, fsenc(csrc), fsenc(dst))
3579
3580
 
3580
3581
  if lmod and (not linked or SYMTIME):
3581
3582
  bos.utime_c(self.log, dst, int(lmod), False)
@@ -4124,6 +4125,9 @@ class Up2k(object):
4124
4125
  except:
4125
4126
  raise Pebkac(400, "file not found on disk (already deleted?)")
4126
4127
 
4128
+ if "bcasechk" in vn.flags and not vn.casechk(rem, False):
4129
+ raise Pebkac(400, "file does not exist case-sensitively")
4130
+
4127
4131
  scandir = not self.args.no_scandir
4128
4132
  if is_dir:
4129
4133
  # note: deletion inside shares would require a rewrite here;
@@ -4248,6 +4252,9 @@ class Up2k(object):
4248
4252
  self.db_act = self.vol_act[svn_dbv.realpath] = time.time()
4249
4253
 
4250
4254
  st = bos.stat(sabs)
4255
+ if "bcasechk" in svn.flags and not svn.casechk(srem, False):
4256
+ raise Pebkac(400, "file does not exist case-sensitively")
4257
+
4251
4258
  if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
4252
4259
  with self.mutex:
4253
4260
  try:
@@ -4405,7 +4412,7 @@ class Up2k(object):
4405
4412
  b1, b2 = fsenc(sabs), fsenc(dabs)
4406
4413
  is_link = os.path.islink(b1) # due to _relink
4407
4414
  try:
4408
- shutil.copy2(b1, b2)
4415
+ trystat_shutil_copy2(self.log, b1, b2)
4409
4416
  except:
4410
4417
  try:
4411
4418
  wunlink(self.log, dabs, dvn.flags)
@@ -4465,6 +4472,9 @@ class Up2k(object):
4465
4472
  raise Pebkac(400, "mv: cannot move a mountpoint")
4466
4473
 
4467
4474
  st = bos.lstat(sabs)
4475
+ if "bcasechk" in svn.flags and not svn.casechk(srem, False):
4476
+ raise Pebkac(400, "file does not exist case-sensitively")
4477
+
4468
4478
  if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
4469
4479
  with self.mutex:
4470
4480
  try:
@@ -4710,7 +4720,7 @@ class Up2k(object):
4710
4720
  b1, b2 = fsenc(sabs), fsenc(dabs)
4711
4721
  is_link = os.path.islink(b1) # due to _relink
4712
4722
  try:
4713
- shutil.copy2(b1, b2)
4723
+ trystat_shutil_copy2(self.log, b1, b2)
4714
4724
  except:
4715
4725
  try:
4716
4726
  wunlink(self.log, dabs, dvn.flags)
copyparty/util.py CHANGED
@@ -1495,10 +1495,12 @@ def vol_san(vols , txt ) :
1495
1495
  bvp = vol.vpath.encode("utf-8")
1496
1496
  bvph = b"$hist(/" + bvp + b")"
1497
1497
 
1498
- txt = txt.replace(bap, bvp)
1499
- txt = txt.replace(bhp, bvph)
1500
- txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
1501
- txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
1498
+ if bap:
1499
+ txt = txt.replace(bap, bvp)
1500
+ txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
1501
+ if bhp:
1502
+ txt = txt.replace(bhp, bvph)
1503
+ txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
1502
1504
 
1503
1505
  if vol.histpath != vol.dbpath:
1504
1506
  bdp = vol.dbpath.encode("utf-8")
@@ -2174,14 +2176,14 @@ def odfusion(
2174
2176
  ret = base.copy()
2175
2177
  if oth.startswith("+"):
2176
2178
  for k in words1:
2177
- ret[k] = True
2179
+ ret[k] = True # type: ignore
2178
2180
  elif oth[:1] in ("-", "/"):
2179
2181
  for k in words1:
2180
- ret.pop(k, None)
2182
+ ret.pop(k, None) # type: ignore
2181
2183
  else:
2182
2184
  ret = ODict.fromkeys(words0, True)
2183
2185
 
2184
- return ret
2186
+ return ret # type: ignore
2185
2187
 
2186
2188
 
2187
2189
  def ipnorm(ip ) :
@@ -2553,6 +2555,24 @@ def set_fperms(f , vf ) :
2553
2555
  os.fchown(fno, vf["uid"], vf["gid"])
2554
2556
 
2555
2557
 
2558
+ def trystat_shutil_copy2(log , src , dst ) :
2559
+ try:
2560
+ return shutil.copy2(src, dst)
2561
+ except:
2562
+ # ignore failed mtime on linux+ntfs; for example:
2563
+ # shutil.py:437 <copy2>: copystat(src, dst, follow_symlinks=follow_symlinks)
2564
+ # shutil.py:376 <copystat>: lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
2565
+ # [PermissionError] [Errno 1] Operation not permitted, '/windows/_videos'
2566
+ _, _, tb = sys.exc_info()
2567
+ for _, _, fun, _ in traceback.extract_tb(tb):
2568
+ if fun == "copystat":
2569
+ if log:
2570
+ t = "warning: failed to retain some file attributes (timestamp and/or permissions) during copy from %r to %r:\n%s"
2571
+ log(t % (src, dst, min_ex()), 3)
2572
+ return dst # close enough
2573
+ raise
2574
+
2575
+
2556
2576
  def _fs_mvrm(
2557
2577
  log , src , dst , atomic , flags
2558
2578
  ) :
@@ -2591,7 +2611,7 @@ def _fs_mvrm(
2591
2611
  t = "something appeared at dst; aborting rename %r ==> %r"
2592
2612
  log(t % (src, dst), 1)
2593
2613
  return False
2594
- osfun(*args)
2614
+ osfun(*args) # type: ignore
2595
2615
  if attempt:
2596
2616
  now = time.time()
2597
2617
  t = "%sd in %.2f sec, attempt %d: %r"
@@ -2641,7 +2661,7 @@ def atomic_move(log , src , dst , flags ) :
2641
2661
  os.unlink(bdst)
2642
2662
  except:
2643
2663
  pass
2644
- shutil.move(bsrc, bdst)
2664
+ shutil.move(bsrc, bdst) # type: ignore
2645
2665
 
2646
2666
 
2647
2667
  def wunlink(log , abspath , flags ) :
@@ -3046,7 +3066,7 @@ def sendfile_kern(
3046
3066
  try:
3047
3067
  req = min(0x2000000, upper - ofs) # 32 MiB
3048
3068
  if use_poll:
3049
- poll.poll(10000)
3069
+ poll.poll(10000) # type: ignore
3050
3070
  else:
3051
3071
  select.select([], [out_fd], [], 10)
3052
3072
  n = os.sendfile(out_fd, in_fd, ofs, req)
@@ -3334,7 +3354,9 @@ NICEB = NICES.encode("utf-8")
3334
3354
 
3335
3355
 
3336
3356
  def runcmd(
3337
- argv , timeout = None, **ka
3357
+ argv ,
3358
+ timeout = None,
3359
+ **ka
3338
3360
  ) :
3339
3361
  isbytes = isinstance(argv[0], (bytes, bytearray))
3340
3362
  oom = ka.pop("oom", 0) # 0..1000
@@ -3353,19 +3375,19 @@ def runcmd(
3353
3375
  if ANYWIN:
3354
3376
  if isbytes:
3355
3377
  if argv[0] in CMD_EXEB:
3356
- argv[0] += b".exe"
3378
+ argv[0] += b".exe" # type: ignore
3357
3379
  else:
3358
3380
  if argv[0] in CMD_EXES:
3359
- argv[0] += ".exe"
3381
+ argv[0] += ".exe" # type: ignore
3360
3382
 
3361
3383
  if ka.pop("nice", None):
3362
3384
  if WINDOWS:
3363
3385
  ka["creationflags"] = 0x4000
3364
3386
  elif NICEB:
3365
3387
  if isbytes:
3366
- argv = [NICEB] + argv
3388
+ argv = [NICEB] + argv # type: ignore
3367
3389
  else:
3368
- argv = [NICES] + argv
3390
+ argv = [NICES] + argv # type: ignore
3369
3391
 
3370
3392
  p = sp.Popen(argv, stdout=cout, stderr=cerr, **ka)
3371
3393
 
@@ -3377,10 +3399,10 @@ def runcmd(
3377
3399
  pass
3378
3400
 
3379
3401
  if not timeout or PY2:
3380
- bout, berr = p.communicate(sin)
3402
+ bout, berr = p.communicate(sin) # type: ignore
3381
3403
  else:
3382
3404
  try:
3383
- bout, berr = p.communicate(sin, timeout=timeout)
3405
+ bout, berr = p.communicate(sin, timeout=timeout) # type: ignore
3384
3406
  except sp.TimeoutExpired:
3385
3407
  if kill == "n":
3386
3408
  return -18, "", "" # SIGCONT; leave it be
@@ -3390,7 +3412,7 @@ def runcmd(
3390
3412
  killtree(p.pid)
3391
3413
 
3392
3414
  try:
3393
- bout, berr = p.communicate(timeout=1)
3415
+ bout, berr = p.communicate(timeout=1) # type: ignore
3394
3416
  except:
3395
3417
  bout = b""
3396
3418
  berr = b""
@@ -3812,7 +3834,7 @@ def runhook(
3812
3834
  at ,
3813
3835
  txt ,
3814
3836
  ) :
3815
- args = (broker or up2k).args
3837
+ args = (broker or up2k).args # type: ignore
3816
3838
  verbose = args.hook_v
3817
3839
  vp = vp.replace("\\", "/")
3818
3840
  ret = {"rc": 0}
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/md.css.gz CHANGED
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.css.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/mde.css.gz CHANGED
Binary file
copyparty/web/mde.js.gz CHANGED
Binary file
copyparty/web/msg.css.gz CHANGED
Binary file
copyparty/web/rups.css.gz CHANGED
Binary file
copyparty/web/rups.js.gz CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/svcs.js.gz CHANGED
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.19.8
3
+ Version: 1.19.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
@@ -2770,6 +2770,10 @@ below are some tweaks roughly ordered by usefulness:
2770
2770
  * using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
2771
2771
  * and pypy can sometimes crash on startup with `-j0` (TODO make issue)
2772
2772
 
2773
+ * if you are running the copyparty server **on Windows or Macos:**
2774
+ * `--casechk=y` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
2775
+ * this is the same as `casechk: n` in a config-file
2776
+
2773
2777
 
2774
2778
  ## client-side
2775
2779
 
@@ -1,38 +1,38 @@
1
1
  copyparty/__init__.py,sha256=8nHuSqcIwZvExA-66pV6slJnDEunE8LwlmGzYnGnDBU,2647
2
- copyparty/__main__.py,sha256=C4xs0PeWevU3dgx9hY_iXlTRG51qQzVmZ6XBqP0Jho8,142863
3
- copyparty/__version__.py,sha256=7ulMrU4CU53mYJ4w6woyAK0rf0TGyJWpGan4Bu4_GEM,250
4
- copyparty/authsrv.py,sha256=y7k5BScNV5gLrq2EziWpof_mwlOAMgGlnUMlodi2TfI,128685
2
+ copyparty/__main__.py,sha256=b0eURrybXQRskCWSK4Y0wR2Su6tRHgFd2CAbNWLqvK4,144217
3
+ copyparty/__version__.py,sha256=x0kGBgJD-lVB_8ZPEnHxRPxds_whwQbXlPRKJh2kCKk,251
4
+ copyparty/authsrv.py,sha256=ccWyDpc4Pk07iRd7Zjs7GA8FDU1BXTsNQ6yj4DvXIV0,132201
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=oZ3dGdJXLYvmdV4KSFdUv_PGErkgU-7kgr1SIglkjCU,1663
9
9
  copyparty/cert.py,sha256=OGTUBxhqPbseG0Bd4cHD6e5T5T8JdGqp3q0KAYqX0Cc,8031
10
- copyparty/cfg.py,sha256=ARHRqoV-MGXyXpaAVH4L8LYA1q46ATI4e7n-r8bsjwk,16359
10
+ copyparty/cfg.py,sha256=xIZH90TbOb9AnTOfx_v-cx6ONQce_TL9tKp9srNxx2g,16455
11
11
  copyparty/dxml.py,sha256=VZADJS9z18LalENSvVfjk29ddnpaDQ-v8AVm_THwS1c,2607
12
12
  copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
13
- copyparty/ftpd.py,sha256=Hl-kgFe9_JpfopksPLFK-pZSPzn7DGr4jJsO_VLQoeE,18134
14
- copyparty/httpcli.py,sha256=fotAxIUDBMxnDEea9THV2mJAocmuDIdy13pf93vmVbQ,239881
13
+ copyparty/ftpd.py,sha256=XtHvgtZPSMm0YMTCeKrElb8ZeIceHWskM16B3HyRXjQ,18264
14
+ copyparty/httpcli.py,sha256=K-qyKwk9QChYTwqHcGFqugbCRni4YfswWT4yopk0Yf4,240218
15
15
  copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
16
  copyparty/httpsrv.py,sha256=tza1m3Ob8A_iPjJBRoh9O_8ruAM7l3bJ905GsHsVf2A,19039
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
18
- copyparty/mdns.py,sha256=usQLqWfFCz8Nqr5Z1x7dtxrnx_gORWpxAGAQ9tPMS_w,18413
18
+ copyparty/mdns.py,sha256=u0G1ho1Pqks5dtgsyn3qctgRhGHk3ufQ3Y8HJ2l4Nhk,18422
19
19
  copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
- copyparty/mtag.py,sha256=yePCWUwiW7G90gd8AsqtDTcyRZ54wfu-ayLRu0i7Hys,22832
20
+ copyparty/mtag.py,sha256=fz0QXGc4F63iormFLkFCDtpSNmo6aDX5DmtkHBbovow,22803
21
21
  copyparty/multicast.py,sha256=xjCBHbbFI7XEohXxgBCAeExkzXJede6TLc5oR7ov5vM,12322
22
22
  copyparty/pwhash.py,sha256=58txP8GXIHOtWd__Tni8qkilHEGpOFsKIPuzjdFLc6M,4426
23
23
  copyparty/smbd.py,sha256=1iLbJMwG84EswYOe2irTc-CSJkzkM3Tq4cuqRFJDHII,14671
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
- copyparty/svchub.py,sha256=Ju6L0EL3bhh5BSPVVGxlJ3M57Fmfk8ZXiW8QhdyjaGI,53809
27
+ copyparty/svchub.py,sha256=PTExaPzyztOHsMamPp-HNH8JKLdd4QSORNEov5KuPrA,54171
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
29
  copyparty/tcpsrv.py,sha256=v3L4vpNwLC-gZjXEdeyS2QyCujYjnGz8FnGfnRKIZzI,22099
30
30
  copyparty/tftpd.py,sha256=fXW0w2_fSbzKVQGTLgr4DbtxaIEfwB591bl0pYhwy6k,14272
31
31
  copyparty/th_cli.py,sha256=n3MMbnN7HTVSBHkTI2qLIjgwVvHkGsfTW7-aPJ-2o4s,5545
32
32
  copyparty/th_srv.py,sha256=iAWBHgtAaYAshXdCFrUx7IWq4y3NXrH79yzHChsy2dc,38335
33
- copyparty/u2idx.py,sha256=mTtUKyIv9putnkDel8cttqMlZXDxb3IAga8J7e0kHls,13666
34
- copyparty/up2k.py,sha256=X-MQG3Yw3F87FQtt31JU_Q5-jb4PKzK5zyrT7F3UhUg,182255
35
- copyparty/util.py,sha256=8lwgkx3rheFmmrp9qrVASAqttBKolgKBZKKIBbPueCE,107904
33
+ copyparty/u2idx.py,sha256=YiacETpyncxHcyzhu3ltujovdLV7PPC7pZ3Tkwtwoj8,14437
34
+ copyparty/up2k.py,sha256=__FwjSxXh1r8IywPqHU4118FCqZ_BQMuHGtuaZ_UnlQ,182779
35
+ copyparty/util.py,sha256=g4eCa23uoz7rVPEOj6dAyuYZ3xwVIHv7AUUmYvtqvW4,109002
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=aGnph4sLIXe7-SFLo5qo1V67Ab1ZtgiUNbAN8EnHvkA,3371
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -54,60 +54,60 @@ copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABw
54
54
  copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUuTOrU,2626
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
- copyparty/web/baguettebox.js.gz,sha256=7T2OmAX0V9nmrjrkMe0WxKinHpiNzZCoLcxE1rB-2KE,8368
58
- copyparty/web/browser.css.gz,sha256=NC7GvaDs2kAW88ijvSwgIc9ozAjjKtoLqVqzLoJyKy0,15703
57
+ copyparty/web/baguettebox.js.gz,sha256=IXzu3LvbDUX1XXxtN88YHPaxIoJQEN6fo3Mx9T1mgdI,8365
58
+ copyparty/web/browser.css.gz,sha256=NQJZWuyE2umXFakMrqE0Q0sVq55rORGdw_wA5PRz67o,15703
59
59
  copyparty/web/browser.html,sha256=DrhMtj-FUjDM0jYhQnsOkVMc8U0bFY_6uGMIY1HB9po,4791
60
- copyparty/web/browser.js.gz,sha256=pEwxeNBP7NkeLCrtarntSlwREniJ_kHJRBO_rdJ6QXc,315355
60
+ copyparty/web/browser.js.gz,sha256=tmGsp1xEJwM6o9ajSeIaZjQxc9FaLZUikAzM-HFe_Jk,315342
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
- copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
63
+ copyparty/web/dbg-audio.js.gz,sha256=fNf4wGVGdz5fDtn-97ks3XPIglTKnTKiYo4Dk2m-NoA,688
64
64
  copyparty/web/idp.html,sha256=z7nHFqfhOVYKRpST6o7IRp1Jt_xnImKYCYCub5IMmiM,1486
65
- copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
65
+ copyparty/web/md.css.gz,sha256=z6S4rOq2ImiSOBW43MrBXEuy-4FpkE2MFDGvbn-P-y0,2032
66
66
  copyparty/web/md.html,sha256=idHQhV8TYB0bWi9aBVoXGsySaQPlXZSvyFW6OSw-n18,4206
67
- copyparty/web/md.js.gz,sha256=DD1DwHfKWnz73YUSWkUl9pBMLpwlSYijBCzpJR041kY,4180
68
- copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
69
- copyparty/web/md2.js.gz,sha256=JjKFTu4aO3DVQ8LV9T6PAFYCEw86_JncAAnB9LUvb88,8465
70
- copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
67
+ copyparty/web/md.js.gz,sha256=ZCFPnMU0dOerxO0ccUzTtDjL5_1u9jRIGZ7NoLC3q4E,4180
68
+ copyparty/web/md2.css.gz,sha256=KycVC-vV0hAOaq1OD2YiS7y8mXYgucT8tCtynW0SfIA,699
69
+ copyparty/web/md2.js.gz,sha256=SfusONhUhLmSX7LSFZIyQag4H3_LhxqiyoVLwqmEqAE,8465
70
+ copyparty/web/mde.css.gz,sha256=cXTemEFV2boL6xhRQuVVV39CaNdIynDDJTtEDzfkOXE,942
71
71
  copyparty/web/mde.html,sha256=o9iRxSSxFlwHsei8zeG9hDGY0GAol-F6Hx575MyOuzI,1776
72
- copyparty/web/mde.js.gz,sha256=FQplRzzMJqC_xNb8gmzj79PrlCfcPdKIm85lM6cR-xA,2220
73
- copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
72
+ copyparty/web/mde.js.gz,sha256=nlDivV2PgbXjsJe_GCtknpb5nVfFjzv7gMd700v9dxE,2220
73
+ copyparty/web/msg.css.gz,sha256=lBx8YdzLqbqbFW8h-3gw6Daadsy83Z7EsK8TP_6oK8M,300
74
74
  copyparty/web/msg.html,sha256=aS47IjsxXFTZZCNck2ip3IwYKrHoYNZstnNND2WweCA,978
75
- copyparty/web/rups.css.gz,sha256=pWklsym27oGGr-8tYQR7WnZvGZElAgCwLzlwTDErNAM,647
75
+ copyparty/web/rups.css.gz,sha256=XV1nExg_g82bf-B6hiKxjQCYVr5_-YOPVSxkPR4bcCc,647
76
76
  copyparty/web/rups.html,sha256=t7aofu0JFH2zPcYhInNytxgNJNEVycUG0QaSw0TJDZQ,1299
77
- copyparty/web/rups.js.gz,sha256=GDIEaIrxqeD6GAAEMuVJt9-Wbvagt2zxp4gDH8-Wlb0,929
78
- copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,547
77
+ copyparty/web/rups.js.gz,sha256=oVAp1fCrnE6HERAAXvP9kDTkrBHQIMiJmQEtVY-TXM0,929
78
+ copyparty/web/shares.css.gz,sha256=ChgHt-2dPgvPCpwClNeijX39OWJIbwj7NBIZXLLvaT0,547
79
79
  copyparty/web/shares.html,sha256=FDFMP5G7DL9B7g2SPBfbHtm83z-VXDMSnSWMsBhVLzk,2583
80
- copyparty/web/shares.js.gz,sha256=QgtzZ6oKJqGVlAEbhVCn-35F6mnwLwOmndRqlgtJd74,942
81
- copyparty/web/splash.css.gz,sha256=VY7tlF__8rx9uuCZjYJCrKYpmP0Wpbx6eTMRDiOlc28,1100
80
+ copyparty/web/shares.js.gz,sha256=LGgFdYhtTwBp96x6CtWZpMWrGaZW7vn5LYvIhPQqm8g,942
81
+ copyparty/web/splash.css.gz,sha256=NCtj8oXbSRIn5tL9RaswvzBSTzmCpAAqDtcgmgf3gXE,1100
82
82
  copyparty/web/splash.html,sha256=6FQedU-mVZ5UEiwm-9Wq_A_UatbTf29IqN4lWtS_6n0,7463
83
- copyparty/web/splash.js.gz,sha256=yLYTIXcIZ5wz7bFX4A_VRby89xziL5fxFXuHfEWEmlk,15893
83
+ copyparty/web/splash.js.gz,sha256=35fsbq9PAN4p67BcXzJhkupDnM6W7qn48AUGYeaFPKI,15893
84
84
  copyparty/web/svcs.html,sha256=jILgJ2WZ9gWHFqOla1tXTPPrR7XI5vENctz4UWTx0Go,17577
85
- copyparty/web/svcs.js.gz,sha256=rnIhpuSGdJfLKHJGrLYzSY7x7m7X3eUg-7UlAZCeRFQ,971
86
- copyparty/web/ui.css.gz,sha256=0NOzup4KnBwcKGV7MZJvHaglEvLti6tgwnY4o21wMTo,2848
87
- copyparty/web/up2k.js.gz,sha256=6FPhdErS53685z7vK-KettwytmHvsn2HWuuRL9WnmB0,25033
88
- copyparty/web/util.js.gz,sha256=lNkJ5b2ree6A2CfGIs_j9boVYecwk2wbsB9ooj3XKro,15571
89
- copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
85
+ copyparty/web/svcs.js.gz,sha256=DapO-UcJYln4VPyZ6Pwi0Lzhlyx0Hul7y7E2ephu34c,971
86
+ copyparty/web/ui.css.gz,sha256=Txx36dDbFI7_9I2LPUO40nCFnxLFKVpdkBsHFwI7JoE,2848
87
+ copyparty/web/up2k.js.gz,sha256=PzZR6mqB796ETr_EZVob0XZOrNZLo8GN3E8TYW3q7TY,25033
88
+ copyparty/web/util.js.gz,sha256=CWy_7OGcsMo4RvQE21A4UF3BIP9hRJFXPAYTGjZOX2k,15571
89
+ copyparty/web/w.hash.js.gz,sha256=rI-78PyXCdVjOpULbD9raBRyxxaPls5OjR7wonzXZPE,1193
90
90
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  copyparty/web/a/partyfuse.py,sha256=QxBoe0iu4LTedmxWdIsuBUlwdxQ7zQh9LAFtjAFcPpo,28206
92
92
  copyparty/web/a/u2c.py,sha256=F78m5b7EPODJd1LgsAxWrwj3v_4z1hGsU53UKy-wCYA,53378
93
93
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
94
94
  copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
- copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
96
- copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
97
- copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
95
+ copyparty/web/deps/busy.mp3.gz,sha256=ySTZiSMbGLJLioVUzUR2eEVemZQ-ACSgawJmw7lFvms,106
96
+ copyparty/web/deps/easymde.css.gz,sha256=s4eN1eVggiAyuK_LZjvHPTBFizw7TRuMU8DvMwB9_aA,3053
97
+ copyparty/web/deps/easymde.js.gz,sha256=CDSEpsMEmYXSjRTqsWmRlcZ27pTh5GvhomvKWtiNoaM,77014
98
98
  copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
99
- copyparty/web/deps/marked.js.gz,sha256=UdxHVVlpRf9k1UivWpLKVQzpzZxfK_O3pleTi_B5f8k,22704
100
- copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
99
+ copyparty/web/deps/marked.js.gz,sha256=_Wkf2QWv3ooEAx5xUhyHvMQHPnzevQ1Fy84OFFdVgt0,22704
100
+ copyparty/web/deps/mini-fa.css.gz,sha256=iYbmyD75m_gFaUhJBT2tk0FfMsLWZ_BArESc4nXd4tg,584
101
101
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
102
- copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
103
- copyparty/web/deps/prism.js.gz,sha256=DR0OAfTUb6PkzSPgxjnjj7jqlpvuCHdMDJKbsQP7s5E,41428
104
- copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cVCO0,1637
105
- copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
106
- copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
102
+ copyparty/web/deps/prism.css.gz,sha256=XQqg2Ii9aX391H9-ozl-UdEdj0-aGsuk7cHa6Hz6Ceo,1478
103
+ copyparty/web/deps/prism.js.gz,sha256=TjgEbUZ7iLayORYkmi1LMklNJ7NIZdkQWAmPuIR7EJo,41438
104
+ copyparty/web/deps/prismd.css.gz,sha256=VPKuZhPmVyhTKHo2xN6IBX-C2A8Eo313vIPxQ2HxoI8,1648
105
+ copyparty/web/deps/scp.woff2,sha256=mmjmsoLDBPKa5VHcEdxZiJcDR1S1TOvCjIg5Cf1iL0s,8684
106
+ copyparty/web/deps/sha512.ac.js.gz,sha256=OLk7EjA5e-DtJGlV0IQbvokpmoyE5v7djezx9kZBabE,7046
107
107
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
108
- copyparty-1.19.8.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
109
- copyparty-1.19.8.dist-info/METADATA,sha256=Du_BYK58-LkJhHBS6-0SOpY3mVcjep0wb-va-AO92gQ,177350
110
- copyparty-1.19.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
- copyparty-1.19.8.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
112
- copyparty-1.19.8.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
113
- copyparty-1.19.8.dist-info/RECORD,,
108
+ copyparty-1.19.9.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
109
+ copyparty-1.19.9.dist-info/METADATA,sha256=35gSIe2RmfYMrNXpmjA2wt-Irvbi01lJZhpg_vpJAlo,177645
110
+ copyparty-1.19.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
+ copyparty-1.19.9.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
112
+ copyparty-1.19.9.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
113
+ copyparty-1.19.9.dist-info/RECORD,,