copyparty 1.16.2__py3-none-any.whl → 1.16.4__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
@@ -1116,6 +1116,8 @@ def add_zc_mdns(ap):
1116
1116
  ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
1117
1117
  ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
1118
1118
  ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
1119
+ ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)")
1120
+ ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)")
1119
1121
  ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
1120
1122
  ap2.add_argument("--zm-ld", metavar="PATH", type=u, default="", help="link a specific folder for webdav shares")
1121
1123
  ap2.add_argument("--zm-lh", metavar="PATH", type=u, default="", help="link a specific folder for http shares")
@@ -1308,7 +1310,7 @@ def add_logging(ap):
1308
1310
  ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
1309
1311
  ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
1310
1312
  ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
1311
- ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
1313
+ ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
1312
1314
 
1313
1315
 
1314
1316
  def add_admin(ap):
@@ -1393,6 +1395,7 @@ def add_db_general(ap, hcores):
1393
1395
  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, ...)")
1394
1396
  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")
1395
1397
  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")
1398
+ 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)")
1396
1399
  ap2.add_argument("--dotsrch", action="store_true", help="show dotfiles in search results (volflags: dotsrch | nodotsrch)")
1397
1400
 
1398
1401
 
@@ -1450,6 +1453,7 @@ def add_ui(ap, retry):
1450
1453
  ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
1451
1454
  ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
1452
1455
  ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
1456
+ ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
1453
1457
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1454
1458
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1455
1459
  ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 2)
3
+ VERSION = (1, 16, 4)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2024, 11, 23)
5
+ BUILD_DT = (2024, 12, 7)
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
@@ -1873,6 +1873,7 @@ class AuthSrv(object):
1873
1873
  ["no_hash", "nohash"],
1874
1874
  ["no_idx", "noidx"],
1875
1875
  ["og_ua", "og_ua"],
1876
+ ["srch_excl", "srch_excl"],
1876
1877
  ]:
1877
1878
  if vf in vol.flags:
1878
1879
  ptn = re.compile(vol.flags.pop(vf))
@@ -2079,6 +2080,22 @@ class AuthSrv(object):
2079
2080
  self.log(t.format(mtp), 1)
2080
2081
  errors = True
2081
2082
 
2083
+ for vol in vfs.all_vols.values():
2084
+ re1 = vol.flags.get("srch_excl")
2085
+ excl = [re1.pattern] if re1 else []
2086
+
2087
+ vpaths = []
2088
+ vtop = vol.vpath
2089
+ for vp2 in vfs.all_vols.keys():
2090
+ if vp2.startswith((vtop + "/").lstrip("/")) and vtop != vp2:
2091
+ vpaths.append(re.escape(vp2[len(vtop) :].lstrip("/")))
2092
+ if vpaths:
2093
+ excl.append("^(%s)/" % ("|".join(vpaths),))
2094
+
2095
+ vol.flags["srch_re_dots"] = re.compile("|".join(excl or ["^$"]))
2096
+ excl.extend([r"^\.", r"/\."])
2097
+ vol.flags["srch_re_nodot"] = re.compile("|".join(excl))
2098
+
2082
2099
  have_daw = False
2083
2100
  for vol in vfs.all_nodes.values():
2084
2101
  daw = vol.flags.get("daw") or self.args.daw
@@ -2307,6 +2324,7 @@ class AuthSrv(object):
2307
2324
  "idx": "e2d" in vf,
2308
2325
  "itag": "e2t" in vf,
2309
2326
  "dnsort": "nsort" in vf,
2327
+ "dhsortn": vf["hsortn"],
2310
2328
  "dsort": vf["sort"],
2311
2329
  "dcrop": vf["crop"],
2312
2330
  "dth3x": vf["th3x"],
@@ -2332,6 +2350,7 @@ class AuthSrv(object):
2332
2350
  "dgrid": "grid" in vf,
2333
2351
  "dgsel": "gsel" in vf,
2334
2352
  "dnsort": "nsort" in vf,
2353
+ "dhsortn": vf["hsortn"],
2335
2354
  "dsort": vf["sort"],
2336
2355
  "dcrop": vf["crop"],
2337
2356
  "dth3x": vf["th3x"],
copyparty/cfg.py CHANGED
@@ -70,6 +70,7 @@ def vf_vmap() :
70
70
  }
71
71
  for k in (
72
72
  "dbd",
73
+ "hsortn",
73
74
  "html_head",
74
75
  "lg_sbf",
75
76
  "md_sbf",
@@ -191,6 +192,7 @@ flagcats = {
191
192
  "xvol": "do not follow symlinks leaving the volume root",
192
193
  "dotsrch": "show dotfiles in search results",
193
194
  "nodotsrch": "hide dotfiles in search results (default)",
195
+ "srch_excl": "exclude search results with URL matching this regex",
194
196
  },
195
197
  'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
196
198
  "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
copyparty/httpcli.py CHANGED
@@ -14,6 +14,7 @@ import re
14
14
  import socket
15
15
  import stat
16
16
  import string
17
+ import sys
17
18
  import threading # typechk
18
19
  import time
19
20
  import uuid
@@ -76,6 +77,7 @@ from .util import (
76
77
  html_escape,
77
78
  humansize,
78
79
  ipnorm,
80
+ justcopy,
79
81
  load_resource,
80
82
  loadpy,
81
83
  log_reloc,
@@ -120,6 +122,8 @@ if not hasattr(socket, "AF_UNIX"):
120
122
 
121
123
  _ = (argparse, threading)
122
124
 
125
+ USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
126
+
123
127
  NO_CACHE = {"Cache-Control": "no-cache"}
124
128
 
125
129
  ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
@@ -133,6 +137,10 @@ READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
133
137
 
134
138
  RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
135
139
 
140
+ A_FILE = os.stat_result(
141
+ (0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
142
+ )
143
+
136
144
 
137
145
  class HttpCli(object):
138
146
  """
@@ -1238,7 +1246,7 @@ class HttpCli(object):
1238
1246
  self.log("RSS %s @%s" % (self.req, self.uname))
1239
1247
 
1240
1248
  if not self.can_read:
1241
- return self.tx_404()
1249
+ return self.tx_404(True)
1242
1250
 
1243
1251
  vn = self.vn
1244
1252
  if not vn.flags.get("rss"):
@@ -1419,7 +1427,8 @@ class HttpCli(object):
1419
1427
 
1420
1428
  depth = self.headers.get("depth", "infinity").lower()
1421
1429
  if depth == "infinity":
1422
- if not self.can_read:
1430
+ # allow depth:0 from unmapped root, but require read-axs otherwise
1431
+ if not self.can_read and (self.vpath or self.asrv.vfs.realpath):
1423
1432
  t = "depth:infinity requires read-access in /%s"
1424
1433
  t = t % (self.vpath,)
1425
1434
  self.log(t, 3)
@@ -1479,7 +1488,7 @@ class HttpCli(object):
1479
1488
  t2 = " or 'infinity'" if self.args.dav_inf else ""
1480
1489
  raise Pebkac(412, t.format(depth, t2))
1481
1490
 
1482
- if not self.can_read and not self.can_write and not self.can_get and not fgen:
1491
+ if not self.can_read and not self.can_write and not fgen:
1483
1492
  self.log("inaccessible: [%s]" % (self.vpath,))
1484
1493
  raise Pebkac(401, "authenticate")
1485
1494
 
@@ -1758,7 +1767,7 @@ class HttpCli(object):
1758
1767
 
1759
1768
  if not self.can_write:
1760
1769
  t = "user %s does not have write-access under /%s"
1761
- raise Pebkac(403, t % (self.uname, self.vn.vpath))
1770
+ raise Pebkac(403 if self.pw else 401, t % (self.uname, self.vn.vpath))
1762
1771
 
1763
1772
  if not self.args.no_dav and self._applesan():
1764
1773
  return self.headers.get("content-length") == "0"
@@ -2049,10 +2058,31 @@ class HttpCli(object):
2049
2058
  # small toctou, but better than clobbering a hardlink
2050
2059
  wunlink(self.log, path, vfs.flags)
2051
2060
 
2061
+ hasher = None
2062
+ copier = hashcopy
2063
+ if "ck" in self.ouparam or "ck" in self.headers:
2064
+ zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2065
+ if not zs or zs == "no":
2066
+ copier = justcopy
2067
+ elif zs == "md5":
2068
+ hasher = hashlib.md5(**USED4SEC)
2069
+ elif zs == "sha1":
2070
+ hasher = hashlib.sha1(**USED4SEC)
2071
+ elif zs == "sha256":
2072
+ hasher = hashlib.sha256(**USED4SEC)
2073
+ elif zs in ("blake2", "b2"):
2074
+ hasher = hashlib.blake2b(**USED4SEC)
2075
+ elif zs in ("blake2s", "b2s"):
2076
+ hasher = hashlib.blake2s(**USED4SEC)
2077
+ elif zs == "sha512":
2078
+ pass
2079
+ else:
2080
+ raise Pebkac(500, "unknown hash alg")
2081
+
2052
2082
  f, fn = ren_open(fn, *open_a, **params)
2053
2083
  try:
2054
2084
  path = os.path.join(fdir, fn)
2055
- post_sz, sha_hex, sha_b64 = hashcopy(reader, f, None, 0, self.args.s_wr_slp)
2085
+ post_sz, sha_hex, sha_b64 = copier(reader, f, hasher, 0, self.args.s_wr_slp)
2056
2086
  finally:
2057
2087
  f.close()
2058
2088
 
@@ -2289,8 +2319,8 @@ class HttpCli(object):
2289
2319
  # kinda silly but has the least side effects
2290
2320
  return self.handle_new_md()
2291
2321
 
2292
- if act == "bput":
2293
- return self.handle_plain_upload(file0)
2322
+ if act in ("bput", "uput"):
2323
+ return self.handle_plain_upload(file0, act == "uput")
2294
2324
 
2295
2325
  if act == "tput":
2296
2326
  return self.handle_text_upload()
@@ -2901,13 +2931,41 @@ class HttpCli(object):
2901
2931
  )
2902
2932
 
2903
2933
  def handle_plain_upload(
2904
- self, file0
2934
+ self,
2935
+ file0 ,
2936
+ nohash ,
2905
2937
  ) :
2906
2938
  assert self.parser
2907
2939
  nullwrite = self.args.nw
2908
2940
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
2909
2941
  self._assert_safe_rem(rem)
2910
2942
 
2943
+ halg = "sha512"
2944
+ hasher = None
2945
+ copier = hashcopy
2946
+ if nohash:
2947
+ halg = ""
2948
+ copier = justcopy
2949
+ elif "ck" in self.ouparam or "ck" in self.headers:
2950
+ halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
2951
+ if not halg or halg == "no":
2952
+ copier = justcopy
2953
+ halg = ""
2954
+ elif halg == "md5":
2955
+ hasher = hashlib.md5(**USED4SEC)
2956
+ elif halg == "sha1":
2957
+ hasher = hashlib.sha1(**USED4SEC)
2958
+ elif halg == "sha256":
2959
+ hasher = hashlib.sha256(**USED4SEC)
2960
+ elif halg in ("blake2", "b2"):
2961
+ hasher = hashlib.blake2b(**USED4SEC)
2962
+ elif halg in ("blake2s", "b2s"):
2963
+ hasher = hashlib.blake2s(**USED4SEC)
2964
+ elif halg == "sha512":
2965
+ pass
2966
+ else:
2967
+ raise Pebkac(500, "unknown hash alg")
2968
+
2911
2969
  upload_vpath = self.vpath
2912
2970
  lim = vfs.get_dbv(rem)[0].lim
2913
2971
  fdir_base = vfs.canonical(rem)
@@ -3037,8 +3095,8 @@ class HttpCli(object):
3037
3095
  try:
3038
3096
  tabspath = os.path.join(fdir, tnam)
3039
3097
  self.log("writing to {}".format(tabspath))
3040
- sz, sha_hex, sha_b64 = hashcopy(
3041
- p_data, f, None, max_sz, self.args.s_wr_slp
3098
+ sz, sha_hex, sha_b64 = copier(
3099
+ p_data, f, hasher, max_sz, self.args.s_wr_slp
3042
3100
  )
3043
3101
  if sz == 0:
3044
3102
  raise Pebkac(400, "empty files in post")
@@ -3170,10 +3228,15 @@ class HttpCli(object):
3170
3228
  jmsg["error"] = errmsg
3171
3229
  errmsg = "ERROR: " + errmsg
3172
3230
 
3231
+ if halg:
3232
+ file_fmt = '{0}: {1} // {2} // {3} bytes // <a href="/{4}">{5}</a> {6}\n'
3233
+ else:
3234
+ file_fmt = '{3} bytes // <a href="/{4}">{5}</a> {6}\n'
3235
+
3173
3236
  for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
3174
3237
  vsuf = ""
3175
3238
  if (self.can_read or self.can_upget) and "fk" in vfs.flags:
3176
- st = bos.stat(ap)
3239
+ st = A_FILE if nullwrite else bos.stat(ap)
3177
3240
  alg = 2 if "fka" in vfs.flags else 1
3178
3241
  vsuf = "?k=" + self.gen_fk(
3179
3242
  alg,
@@ -3188,7 +3251,8 @@ class HttpCli(object):
3188
3251
 
3189
3252
  vpath = "{}/{}".format(upload_vpath, lfn).strip("/")
3190
3253
  rel_url = quotep(self.args.RS + vpath) + vsuf
3191
- msg += 'sha512: {} // {} // {} bytes // <a href="/{}">{}</a> {}\n'.format(
3254
+ msg += file_fmt.format(
3255
+ halg,
3192
3256
  sha_hex[:56],
3193
3257
  sha_b64,
3194
3258
  sz,
@@ -3204,13 +3268,14 @@ class HttpCli(object):
3204
3268
  self.host,
3205
3269
  rel_url,
3206
3270
  ),
3207
- "sha512": sha_hex[:56],
3208
- "sha_b64": sha_b64,
3209
3271
  "sz": sz,
3210
3272
  "fn": lfn,
3211
3273
  "fn_orig": ofn,
3212
3274
  "path": rel_url,
3213
3275
  }
3276
+ if halg:
3277
+ jpart[halg] = sha_hex[:56]
3278
+ jpart["sha_b64"] = sha_b64
3214
3279
  jmsg["files"].append(jpart)
3215
3280
 
3216
3281
  vspd = self._spd(sz_total, False)
@@ -4603,6 +4668,18 @@ class HttpCli(object):
4603
4668
  if "th" in self.ouparam:
4604
4669
  return self.tx_svg("e" + pt[:3])
4605
4670
 
4671
+ # most webdav clients will not send credentials until they
4672
+ # get 401'd, so send a challenge if we're Absolutely Sure
4673
+ # that the client is not a graphical browser
4674
+ if (
4675
+ rc == 403
4676
+ and not self.pw
4677
+ and not self.ua.startswith("Mozilla/")
4678
+ and "sec-fetch-site" not in self.headers
4679
+ ):
4680
+ rc = 401
4681
+ self.out_headers["WWW-Authenticate"] = 'Basic realm="a"'
4682
+
4606
4683
  t = t.format(self.args.SR)
4607
4684
  qv = quotep(self.vpaths) + self.ourlq()
4608
4685
  html = self.j2s(
@@ -5243,7 +5320,7 @@ class HttpCli(object):
5243
5320
  st = bos.stat(abspath)
5244
5321
  except:
5245
5322
  if "on404" not in vn.flags:
5246
- return self.tx_404()
5323
+ return self.tx_404(not self.can_read)
5247
5324
 
5248
5325
  ret = self.on40x(vn.flags["on404"], vn, rem)
5249
5326
  if ret == "true":
@@ -5254,9 +5331,9 @@ class HttpCli(object):
5254
5331
  try:
5255
5332
  st = bos.stat(abspath)
5256
5333
  except:
5257
- return self.tx_404()
5334
+ return self.tx_404(not self.can_read)
5258
5335
  else:
5259
- return self.tx_404()
5336
+ return self.tx_404(not self.can_read)
5260
5337
 
5261
5338
  if rem.startswith(".hist/up2k.") or (
5262
5339
  rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
@@ -5383,13 +5460,13 @@ class HttpCli(object):
5383
5460
  vrem = vjoin(vrem, fn)
5384
5461
  abspath = ap2
5385
5462
  break
5386
- elif self.vpath.rsplit("/", 1)[1] in ("index.htm", "index.html"):
5463
+ elif self.vpath.rsplit("/", 1)[-1] in ("index.htm", "index.html"):
5387
5464
  fk_pass = True
5388
5465
 
5389
5466
  if not is_dir and (self.can_read or self.can_get):
5390
5467
  if not self.can_read and not fk_pass and "fk" in vn.flags:
5391
5468
  if not use_filekey:
5392
- return self.tx_404()
5469
+ return self.tx_404(True)
5393
5470
 
5394
5471
  if add_og and not abspath.lower().endswith(".md"):
5395
5472
  if og_ua or self.host not in self.headers.get("referer", ""):
copyparty/mdns.py CHANGED
@@ -25,6 +25,7 @@ from .stolen.dnslib import (
25
25
  DNSHeader,
26
26
  DNSQuestion,
27
27
  DNSRecord,
28
+ set_avahi_379,
28
29
  )
29
30
  from .util import CachedSet, Daemon, Netdev, list_ips, min_ex
30
31
 
@@ -68,6 +69,9 @@ class MDNS(MCast):
68
69
  self.ngen = ngen
69
70
  self.ttl = 300
70
71
 
72
+ if not self.args.zm_nwa_1:
73
+ set_avahi_379()
74
+
71
75
  zs = self.args.name + ".local."
72
76
  zs = zs.encode("ascii", "replace").decode("ascii", "replace")
73
77
  self.hn = "-".join(x for x in zs.split("?") if x) or (
@@ -332,6 +336,9 @@ class MDNS(MCast):
332
336
  self.log("stopped", 2)
333
337
  return
334
338
 
339
+ if self.args.zm_no_pe:
340
+ continue
341
+
335
342
  t = "{} {} \033[33m|{}| {}\n{}".format(
336
343
  self.srv[sck].name, addr, len(buf), repr(buf)[2:-1], min_ex()
337
344
  )
copyparty/res/COPYING.txt CHANGED
@@ -75,21 +75,78 @@ Copyright (c) 2022 Fonticons, Inc.
75
75
  License: SIL OFL 1.1
76
76
 
77
77
 
78
- --- MIT License ---
79
78
 
80
79
 
81
80
 
82
- --- ISC License ---
81
+ --- MIT License ---
82
+
83
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
83
84
 
85
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
84
86
 
87
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
88
 
86
89
  --- BSD 2-Clause License ---
87
90
 
91
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
92
+
93
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
88
94
 
95
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
96
+
97
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
89
98
 
90
99
  --- BSD 3-Clause License ---
91
100
 
101
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
102
+
103
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
104
+
105
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
92
106
 
107
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
108
+
109
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
93
110
 
94
111
  --- SIL Open Font License v1.1 ---
95
112
 
113
+ PREAMBLE
114
+
115
+ The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
116
+
117
+ The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
118
+
119
+ DEFINITIONS
120
+
121
+ "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
122
+
123
+ "Reserved Font Name" refers to any names specified as such after the copyright statement(s).
124
+
125
+ "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
126
+
127
+ "Modified Version" refers to any derivative made by adding to, deleting, or substituting - in part or in whole - any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
128
+
129
+ "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
130
+
131
+ PERMISSION & CONDITIONS
132
+
133
+ Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
134
+
135
+ 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
136
+
137
+ 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
138
+
139
+ 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
140
+
141
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
142
+
143
+ 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
144
+
145
+ TERMINATION
146
+
147
+ This license becomes null and void if any of the above conditions are not met.
148
+
149
+ DISCLAIMER
150
+
151
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
152
+
@@ -8,7 +8,7 @@ from itertools import chain
8
8
  from .bimap import Bimap, BimapError
9
9
  from .bit import get_bits, set_bits
10
10
  from .buffer import BufferError
11
- from .label import DNSBuffer, DNSLabel
11
+ from .label import DNSBuffer, DNSLabel, set_avahi_379
12
12
  from .ranges import IP4, IP6, H, I, check_bytes
13
13
 
14
14
 
@@ -426,7 +426,7 @@ class RR(object):
426
426
  if rdlength:
427
427
  rdata = RDMAP.get(QTYPE.get(rtype), RD).parse(buffer, rdlength)
428
428
  else:
429
- rdata = ""
429
+ rdata = RD(b"a")
430
430
  return cls(rname, rtype, rclass, ttl, rdata)
431
431
  except (BufferError, BimapError) as e:
432
432
  raise DNSError("Error unpacking RR [offset=%d]: %s" % (buffer.offset, e))
@@ -11,6 +11,23 @@ LDH = set(range(33, 127))
11
11
  ESCAPE = re.compile(r"\\([0-9][0-9][0-9])")
12
12
 
13
13
 
14
+ avahi_379 = 0
15
+
16
+
17
+ def set_avahi_379():
18
+ global avahi_379
19
+ avahi_379 = 1
20
+
21
+
22
+ def log_avahi_379(args):
23
+ global avahi_379
24
+ if avahi_379 == 2:
25
+ return
26
+ avahi_379 = 2
27
+ t = "Invalid pointer in DNSLabel [offset=%d,pointer=%d,length=%d];\n\033[35m NOTE: this is probably avahi-bug #379, packet corruption in Avahi's mDNS-reflection feature. Copyparty has a workaround and is OK, but other devices need either --zm4 or --zm6"
28
+ raise BufferError(t % args)
29
+
30
+
14
31
  class DNSLabelError(Exception):
15
32
  pass
16
33
 
@@ -96,8 +113,11 @@ class DNSBuffer(Buffer):
96
113
  )
97
114
  if pointer < self.offset:
98
115
  self.offset = pointer
116
+ elif avahi_379:
117
+ log_avahi_379((self.offset, pointer, len(self.data)))
118
+ label.extend(b"a")
119
+ break
99
120
  else:
100
-
101
121
  raise BufferError(
102
122
  "Invalid pointer in DNSLabel [offset=%d,pointer=%d,length=%d]"
103
123
  % (self.offset, pointer, len(self.data))
copyparty/svchub.py CHANGED
@@ -783,7 +783,7 @@ class SvcHub(object):
783
783
  al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
784
784
  al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
785
785
 
786
- for k in ["no_hash", "no_idx", "og_ua"]:
786
+ for k in ["no_hash", "no_idx", "og_ua", "srch_excl"]:
787
787
  ptn = getattr(self.args, k)
788
788
  if ptn:
789
789
  setattr(self.args, k, re.compile(ptn))
copyparty/u2idx.py CHANGED
@@ -318,7 +318,8 @@ class U2idx(object):
318
318
  sort ,
319
319
  lim ,
320
320
  ) :
321
- if self.args.srch_dbg:
321
+ dbg = self.args.srch_dbg
322
+ if dbg:
322
323
  t = "searching across all %s volumes in which the user has 'r' (full read access):\n %s"
323
324
  zs = "\n ".join(["/%s = %s" % (x.vpath, x.realpath) for x in vols])
324
325
  self.log(t % (len(vols), zs), 5)
@@ -361,14 +362,14 @@ class U2idx(object):
361
362
  if not cur:
362
363
  continue
363
364
 
364
- excl = []
365
- for vp2 in self.asrv.vfs.all_vols.keys():
366
- if vp2.startswith((vtop + "/").lstrip("/")) and vtop != vp2:
367
- excl.append(vp2[len(vtop) :].lstrip("/"))
365
+ dots = flags.get("dotsrch") and uname in vol.axs.udot
366
+ zs = "srch_re_dots" if dots else "srch_re_nodot"
367
+ rex = flags.get(zs) # type: ignore
368
368
 
369
- if self.args.srch_dbg:
370
- t = "searching in volume /%s (%s), excludelist %s"
371
- self.log(t % (vtop, ptop, excl), 5)
369
+ if dbg:
370
+ t = "searching in volume /%s (%s), excluding %s"
371
+ self.log(t % (vtop, ptop, rex.pattern), 5)
372
+ rex_cfg = flags.get("srch_excl")
372
373
 
373
374
  self.active_cur = cur
374
375
 
@@ -381,7 +382,6 @@ class U2idx(object):
381
382
 
382
383
  sret = []
383
384
  fk = flags.get("fk")
384
- dots = flags.get("dotsrch") and uname in vol.axs.udot
385
385
  fk_alg = 2 if "fka" in flags else 1
386
386
  c = cur.execute(uq, tuple(vuv))
387
387
  for hit in c:
@@ -390,20 +390,23 @@ class U2idx(object):
390
390
  if rd.startswith("//") or fn.startswith("//"):
391
391
  rd, fn = s3dec(rd, fn)
392
392
 
393
- if rd in excl or any([x for x in excl if rd.startswith(x + "/")]):
394
- if self.args.srch_dbg:
395
- zs = vjoin(vjoin(vtop, rd), fn)
396
- t = "database inconsistency in volume '/%s'; ignoring: %s"
397
- self.log(t % (vtop, zs), 1)
398
- continue
393
+ vp = vjoin(vjoin(vtop, rd), fn)
399
394
 
400
- rp = quotep("/".join([x for x in [vtop, rd, fn] if x]))
401
- if not dots and "/." in ("/" + rp):
395
+ if vp in seen_rps:
402
396
  continue
403
397
 
404
- if rp in seen_rps:
398
+ if rex.search(vp):
399
+ if dbg:
400
+ if rex_cfg and rex_cfg.search(vp): # type: ignore
401
+ self.log("filtered by srch_excl: %s" % (vp,), 6)
402
+ elif not dots and "/." in ("/" + vp):
403
+ pass
404
+ else:
405
+ t = "database inconsistency in volume '/%s'; ignoring: %s"
406
+ self.log(t % (vtop, vp), 1)
405
407
  continue
406
408
 
409
+ rp = quotep(vp)
407
410
  if not fk:
408
411
  suf = ""
409
412
  else:
@@ -425,7 +428,7 @@ class U2idx(object):
425
428
  if lim < 0:
426
429
  break
427
430
 
428
- if self.args.srch_dbg:
431
+ if dbg:
429
432
  t = "in volume '/%s': hit: %s"
430
433
  self.log(t % (vtop, rp), 5)
431
434
 
@@ -455,7 +458,7 @@ class U2idx(object):
455
458
  ret.extend(sret)
456
459
  # print("[{}] {}".format(ptop, sret))
457
460
 
458
- if self.args.srch_dbg:
461
+ if dbg:
459
462
  t = "in volume '/%s': got %d hits, %d total so far"
460
463
  self.log(t % (vtop, len(sret), len(ret)), 5)
461
464
 
copyparty/up2k.py CHANGED
@@ -85,16 +85,23 @@ if TYPE_CHECKING:
85
85
  zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
86
86
  CV_EXTS = set(zsg.split(","))
87
87
 
88
+ zsg = "nohash noidx xdev xvol"
89
+ VF_AFFECTS_INDEXING = set(zsg.split(" "))
90
+
88
91
 
89
92
  SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
90
93
 
91
94
  HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
92
95
 
93
96
 
97
+ NULLSTAT = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
98
+
99
+
94
100
  class Dbw(object):
95
- def __init__(self, c , n , t ) :
101
+ def __init__(self, c , n , nf , t ) :
96
102
  self.c = c
97
103
  self.n = n
104
+ self.nf = nf
98
105
  self.t = t
99
106
 
100
107
 
@@ -1067,7 +1074,8 @@ class Up2k(object):
1067
1074
  ft = "\033[0;32m{}{:.0}"
1068
1075
  ff = "\033[0;35m{}{:.0}"
1069
1076
  fv = "\033[0;36m{}:\033[90m{}"
1070
- fx = set(("html_head", "rm_re_t", "rm_re_r", "mv_re_t", "mv_re_r"))
1077
+ zs = "html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot"
1078
+ fx = set(zs.split())
1071
1079
  fd = vf_bmap()
1072
1080
  fd.update(vf_cmap())
1073
1081
  fd.update(vf_vmap())
@@ -1224,10 +1232,17 @@ class Up2k(object):
1224
1232
  def _verify_db_cache(self, cur , vpath ) :
1225
1233
  # check if list of intersecting volumes changed since last use; drop caches if so
1226
1234
  prefix = (vpath + "/").lstrip("/")
1227
- zsl = [x for x in self.vfs.all_vols if x.startswith(prefix)]
1228
- zsl = [x[len(prefix) :] for x in zsl]
1229
- zsl.sort()
1230
- zb = hashlib.sha1("\n".join(zsl).encode("utf-8", "replace")).digest()
1235
+ vps = [x for x in self.vfs.all_vols if x.startswith(prefix)]
1236
+ vps.sort()
1237
+ seed = [x[len(prefix) :] for x in vps]
1238
+
1239
+ # also consider volflags which affect indexing
1240
+ for vp in vps:
1241
+ vf = self.vfs.all_vols[vp].flags
1242
+ vf = {k: v for k, v in vf.items() if k in VF_AFFECTS_INDEXING}
1243
+ seed.append(str(sorted(vf.items())))
1244
+
1245
+ zb = hashlib.sha1("\n".join(seed).encode("utf-8", "replace")).digest()
1231
1246
  vcfg = ub64enc(zb[:18]).decode("ascii")
1232
1247
 
1233
1248
  c = cur.execute("select v from kv where k = 'volcfg'")
@@ -1259,7 +1274,7 @@ class Up2k(object):
1259
1274
 
1260
1275
  cur, db_path = reg
1261
1276
 
1262
- db = Dbw(cur, 0, time.time())
1277
+ db = Dbw(cur, 0, 0, time.time())
1263
1278
  self.pp.n = next(db.c.execute("select count(w) from up"))[0]
1264
1279
 
1265
1280
  excl = [
@@ -1311,7 +1326,7 @@ class Up2k(object):
1311
1326
  self.hub.log_stacks()
1312
1327
 
1313
1328
  if db.n:
1314
- self.log("commit {} new files".format(db.n))
1329
+ self.log("commit %d new files; %d updates" % (db.nf, db.n))
1315
1330
 
1316
1331
  if self.args.no_dhash:
1317
1332
  if db.c.execute("select d from dh").fetchone():
@@ -1612,12 +1627,13 @@ class Up2k(object):
1612
1627
  # skip upload hooks by not providing vflags
1613
1628
  self.db_add(db.c, {}, rd, fn, lmod, sz, "", "", wark, wark, "", "", ip, at)
1614
1629
  db.n += 1
1630
+ db.nf += 1
1615
1631
  tfa += 1
1616
1632
  td = time.time() - db.t
1617
1633
  if db.n >= 4096 or td >= 60:
1618
- self.log("commit {} new files".format(db.n))
1634
+ self.log("commit %d new files; %d updates" % (db.nf, db.n))
1619
1635
  db.c.connection.commit()
1620
- db.n = 0
1636
+ db.n = db.nf = 0
1621
1637
  db.t = time.time()
1622
1638
 
1623
1639
  if not self.args.no_dhash:
@@ -1630,7 +1646,7 @@ class Up2k(object):
1630
1646
  # drop shadowed folders
1631
1647
  for sh_rd in unreg:
1632
1648
  n = 0
1633
- q = "select count(w) from up where (rd=? or rd like ?||'/%') and +at == 0"
1649
+ q = "select count(w) from up where (rd=? or rd like ?||'/%')"
1634
1650
  for sh_erd in [sh_rd, "//" + w8b64enc(sh_rd)]:
1635
1651
  try:
1636
1652
  erd_erd = (sh_erd, sh_erd)
@@ -1647,7 +1663,7 @@ class Up2k(object):
1647
1663
  q = "delete from dh where (d = ? or d like ?||'/%')"
1648
1664
  db.c.execute(q, erd_erd)
1649
1665
 
1650
- q = "delete from up where (rd=? or rd like ?||'/%') and +at == 0"
1666
+ q = "delete from up where (rd=? or rd like ?||'/%')"
1651
1667
  db.c.execute(q, erd_erd)
1652
1668
  tfa += n
1653
1669
 
@@ -2914,7 +2930,7 @@ class Up2k(object):
2914
2930
  raise Exception()
2915
2931
  except Exception as ex:
2916
2932
  if n4g:
2917
- st = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
2933
+ st = NULLSTAT
2918
2934
  else:
2919
2935
  lost.append((cur, dp_dir, dp_fn))
2920
2936
  continue
@@ -3061,7 +3077,8 @@ class Up2k(object):
3061
3077
  if cur:
3062
3078
  dupe = (cj["prel"], cj["name"], cj["lmod"])
3063
3079
  try:
3064
- self.dupesched[src].append(dupe)
3080
+ if dupe not in self.dupesched[src]:
3081
+ self.dupesched[src].append(dupe)
3065
3082
  except:
3066
3083
  self.dupesched[src] = [dupe]
3067
3084
 
@@ -4636,6 +4653,13 @@ class Up2k(object):
4636
4653
  t = "forgetting partial upload {} ({})"
4637
4654
  p = self._vis_job_progress(job)
4638
4655
  self.log(t.format(wark, p))
4656
+
4657
+ src = djoin(ptop, vrem)
4658
+ zi = len(self.dupesched.pop(src, []))
4659
+ if zi:
4660
+ t = "...and forgetting %d links in dupesched"
4661
+ self.log(t % (zi,))
4662
+
4639
4663
  assert wark
4640
4664
  del reg[wark]
4641
4665
 
copyparty/util.py CHANGED
@@ -2709,6 +2709,26 @@ def yieldfile(fn , bufsz ) :
2709
2709
  yield buf
2710
2710
 
2711
2711
 
2712
+ def justcopy(
2713
+ fin ,
2714
+ fout ,
2715
+ hashobj ,
2716
+ max_sz ,
2717
+ slp ,
2718
+ ) :
2719
+ tlen = 0
2720
+ for buf in fin:
2721
+ tlen += len(buf)
2722
+ if max_sz and tlen > max_sz:
2723
+ continue
2724
+
2725
+ fout.write(buf)
2726
+ if slp:
2727
+ time.sleep(slp)
2728
+
2729
+ return tlen, "checksum-disabled", "checksum-disabled"
2730
+
2731
+
2712
2732
  def hashcopy(
2713
2733
  fin ,
2714
2734
  fout ,
@@ -3416,7 +3436,6 @@ def runhook(
3416
3436
  at ,
3417
3437
  txt ,
3418
3438
  ) :
3419
- asrv = (broker or up2k).asrv
3420
3439
  args = (broker or up2k).args
3421
3440
  vp = vp.replace("\\", "/")
3422
3441
  ret = {"rc": 0}
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.6"
5
- S_BUILD_DT = "2024-11-10"
4
+ S_VERSION = "2.7"
5
+ S_BUILD_DT = "2024-12-06"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1031,8 +1031,8 @@ class Ctl(object):
1031
1031
  handshake(self.ar, file, False)
1032
1032
 
1033
1033
  def _fancy(self):
1034
+ atexit.register(self.cleanup_vt100)
1034
1035
  if VT100 and not self.ar.ns:
1035
- atexit.register(self.cleanup_vt100)
1036
1036
  ss.scroll_region(3)
1037
1037
 
1038
1038
  Daemon(self.hasher)
@@ -1040,6 +1040,7 @@ class Ctl(object):
1040
1040
  Daemon(self.handshaker)
1041
1041
  Daemon(self.uploader)
1042
1042
 
1043
+ last_sp = -1
1043
1044
  while True:
1044
1045
  with self.exit_cond:
1045
1046
  self.exit_cond.wait(0.07)
@@ -1078,6 +1079,12 @@ class Ctl(object):
1078
1079
  else:
1079
1080
  txt = " "
1080
1081
 
1082
+ if not VT100: # OSC9;4 (taskbar-progress)
1083
+ sp = int(self.up_b * 100 / self.nbytes) or 1
1084
+ if last_sp != sp:
1085
+ last_sp = sp
1086
+ txt += "\033]9;4;1;%d\033\\" % (sp,)
1087
+
1081
1088
  if not self.up_br:
1082
1089
  spd = self.hash_b / ((time.time() - self.t0) or 1)
1083
1090
  eta = (self.nbytes - self.hash_b) / (spd or 1)
@@ -1095,6 +1102,8 @@ class Ctl(object):
1095
1102
  tail = "\033[K\033[u" if VT100 and not self.ar.ns else "\r"
1096
1103
 
1097
1104
  t = "%s eta @ %s/s, %s, %d# left\033[K" % (self.eta, spd, sleft, nleft)
1105
+ if not self.hash_b:
1106
+ t = " now hashing..."
1098
1107
  eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
1099
1108
 
1100
1109
  if self.ar.wlist:
@@ -1115,7 +1124,10 @@ class Ctl(object):
1115
1124
  handshake(self.ar, file, False)
1116
1125
 
1117
1126
  def cleanup_vt100(self):
1118
- ss.scroll_region(None)
1127
+ if VT100:
1128
+ ss.scroll_region(None)
1129
+ else:
1130
+ eprint("\033]9;4;0\033\\")
1119
1131
  eprint("\033[J\033]0;\033\\")
1120
1132
 
1121
1133
  def cb_hasher(self, file, ofs):
@@ -1130,7 +1142,9 @@ class Ctl(object):
1130
1142
  isdir = stat.S_ISDIR(inf.st_mode)
1131
1143
  if self.ar.z or self.ar.drd:
1132
1144
  rd = rel if isdir else os.path.dirname(rel)
1133
- srd = rd.decode("utf-8", "replace").replace("\\", "/")
1145
+ srd = rd.decode("utf-8", "replace").replace("\\", "/").rstrip("/")
1146
+ if srd:
1147
+ srd += "/"
1134
1148
  if prd != rd:
1135
1149
  prd = rd
1136
1150
  ls = {}
@@ -1165,11 +1179,11 @@ class Ctl(object):
1165
1179
  bnames = [x for x in ls if x not in lnodes and x != b".hist"]
1166
1180
  vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
1167
1181
  names = [x.decode("utf-8", WTF8) for x in bnames]
1168
- locs = [vpath + srd + "/" + x for x in names]
1182
+ locs = [vpath + srd + x for x in names]
1169
1183
  while locs:
1170
1184
  req = locs
1171
1185
  while req:
1172
- print("DELETING ~%s/#%s" % (srd, len(req)))
1186
+ print("DELETING ~%s#%s" % (srd, len(req)))
1173
1187
  body = json.dumps(req).encode("utf-8")
1174
1188
  sc, txt = web.req(
1175
1189
  "POST", self.ar.url + "?delete", {}, body, MJ
@@ -1534,6 +1548,38 @@ source file/folder selection uses rsync syntax, meaning that:
1534
1548
  except:
1535
1549
  pass
1536
1550
 
1551
+ # msys2 doesn't uncygpath absolute paths with whitespace
1552
+ if not VT100:
1553
+ zsl = []
1554
+ for fn in ar.files:
1555
+ if re.search("^/[a-z]/", fn):
1556
+ fn = r"%s:\%s" % (fn[1:2], fn[3:])
1557
+ zsl.append(fn.replace("/", "\\"))
1558
+ ar.files = zsl
1559
+
1560
+ fok = []
1561
+ fng = []
1562
+ for fn in ar.files:
1563
+ if os.path.exists(fn):
1564
+ fok.append(fn)
1565
+ elif VT100:
1566
+ fng.append(fn)
1567
+ else:
1568
+ # windows leaves glob-expansion to the invoked process... okayyy let's get to work
1569
+ from glob import glob
1570
+
1571
+ fns = glob(fn)
1572
+ if fns:
1573
+ fok.extend(fns)
1574
+ else:
1575
+ fng.append(fn)
1576
+
1577
+ if fng:
1578
+ t = "some files/folders were not found:\n %s"
1579
+ raise Exception(t % ("\n ".join(fng),))
1580
+
1581
+ ar.files = fok
1582
+
1537
1583
  if ar.drd:
1538
1584
  ar.dr = True
1539
1585
 
Binary file
Binary file
Binary file
Binary file
Binary file
copyparty/web/md.js.gz CHANGED
Binary file
copyparty/web/md2.js.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.16.2
3
+ Version: 1.16.4
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
@@ -1152,11 +1152,12 @@ using the GUI (winXP or later):
1152
1152
  * on winXP only, click the `Sign up for online storage` hyperlink instead and put the URL there
1153
1153
  * providing your password as the username is recommended; the password field can be anything or empty
1154
1154
 
1155
- known client bugs:
1155
+ the webdav client that's built into windows has the following list of bugs; you can avoid all of these by connecting with rclone instead:
1156
1156
  * win7+ doesn't actually send the password to the server when reauthenticating after a reboot unless you first try to login with an incorrect password and then switch to the correct password
1157
1157
  * or just type your password into the username field instead to get around it entirely
1158
1158
  * connecting to a folder which allows anonymous read will make writing impossible, as windows has decided it doesn't need to login
1159
1159
  * workaround: connect twice; first to a folder which requires auth, then to the folder you actually want, and leave both of those mounted
1160
+ * or set the server-option `--dav-auth` to force password-auth for all webdav clients
1160
1161
  * win7+ may open a new tcp connection for every file and sometimes forgets to close them, eventually needing a reboot
1161
1162
  * maybe NIC-related (??), happens with win10-ltsc on e1000e but not virtio
1162
1163
  * windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`
@@ -1323,7 +1324,7 @@ note:
1323
1324
 
1324
1325
  ### exclude-patterns
1325
1326
 
1326
- to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash \.iso$` or the volflag `:c,nohash=\.iso$`, this has the following consequences:
1327
+ to save some time, you can provide a regex pattern for filepaths to only index by filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash '\.iso$'` or the volflag `:c,nohash=\.iso$`, this has the following consequences:
1327
1328
  * initial indexing is way faster, especially when the volume is on a network disk
1328
1329
  * makes it impossible to [file-search](#file-search)
1329
1330
  * if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
@@ -1334,6 +1335,8 @@ similarly, you can fully ignore files/folders using `--no-idx [...]` and `:c,noi
1334
1335
 
1335
1336
  if you set `--no-hash [...]` globally, you can enable hashing for specific volumes using flag `:c,nohash=`
1336
1337
 
1338
+ to exclude certain filepaths from search-results, use `--srch-excl` or volflag `srch_excl` instead of `--no-idx`, for example `--srch-excl 'password|logs/[0-9]'`
1339
+
1337
1340
  ### filesystem guards
1338
1341
 
1339
1342
  avoid traversing into other filesystems using `--xdev` / volflag `:c,xdev`, skipping any symlinks or bind-mounts to another HDD for example
@@ -1,21 +1,21 @@
1
1
  copyparty/__init__.py,sha256=iRCNvMPg6k9WG_O2uCtlkD4vWogH8EgP9elp9XwSIJs,2610
2
- copyparty/__main__.py,sha256=fbbMVr-sxhrn2fb9l44OnQ3XStmOoZJbq8FLrwZeel4,112528
3
- copyparty/__version__.py,sha256=qkSYWTcO84wN8R56ATzvkttx7joci-cjVx1QjrUWw-8,252
4
- copyparty/authsrv.py,sha256=QQGbliw2jOI86sNPUcx7rYeG39-LLSsfbJ1yWszFx5Y,102621
2
+ copyparty/__main__.py,sha256=fA4D0yAs6twyBcawN9lM7CtHuFagTsQloljWX9MZ1j0,113240
3
+ copyparty/__version__.py,sha256=PSHPFmB4WReQ90GVjo2tS3ZE1I6F3Z2igIEoAOtf4ZQ,251
4
+ copyparty/authsrv.py,sha256=CIK5OKl38LehdAt4hidCMpsATdJhPABJPkJStpQpp9U,103405
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=0v_ZK3NCfzqBVLW7cheJr53IwIOIbHpgd-uYAeseyUc,10280
10
+ copyparty/cfg.py,sha256=UUmFpFbTm750Nv9RnofS80-FTpWT37EggEtmkE1wbxE,10374
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=5CshJWO7CflfaRRNOb3JxghUH7W5rmS_HWNmKfx42MM,4538
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=s0W2R7l-ZWAJi2A6pQhtF7E7iNKXeVuyk--ybNJhPhE,204841
14
+ copyparty/httpcli.py,sha256=hwHJ8EwV30MHZ6hGPGfmCuow5Nu2daMV2EN7Ups5jb8,207704
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=PXLZlT6iuJZYG9ajNsAaNgRK3UtS4CrOGFGXOpkRtOk,18235
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
18
- copyparty/mdns.py,sha256=vC078llnL1v0pvL3mnwacuStFHPJUQuxo9Opj-IbHL4,18155
18
+ copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
19
19
  copyparty/metrics.py,sha256=EOIiPOItEQmdK9YgNb75l0kCzanWb6RtJGwMI7ufifY,8966
20
20
  copyparty/mtag.py,sha256=o-rxu4LajWF4H3HFmfCbXnKfs0GCQh1FQ0XywuW6wOc,19934
21
21
  copyparty/multicast.py,sha256=Ha27l2oATEa-Qo2WOzkeRgjAm6G_YDCfbVJWR-ao2UE,12319
@@ -24,19 +24,19 @@ copyparty/smbd.py,sha256=Or7RF13cl1r3ncnpVh8BqyAGqH2Oa04O9iPZWCoB0Bo,14609
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=JTMrQwcWH85hXB_cKG206eDZ967WZDGaP00AWvl_gB0,3214
27
- copyparty/svchub.py,sha256=op6BtcsSa0OLmnJ_dslPswaNTOZaqScM8tRZnQrMkDs,40864
27
+ copyparty/svchub.py,sha256=sAHkiPGzzKACLqKlem2V-bps9Xh-wHlcfwaNywxcd5A,40877
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
29
  copyparty/tcpsrv.py,sha256=VuW_aVDcyXIhIMZ8I-wpIouX8MI9TGp7KKSRohrMTtk,19897
30
30
  copyparty/tftpd.py,sha256=FRCALO3PigoBlwUrqxoKHM_xk7wT2O0GPG1TvNRtjOY,13606
31
31
  copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
32
32
  copyparty/th_srv.py,sha256=LBcB4LpsF-H7L52Z0Dhn9LogRjJVPp1GKa8MeIMIBnw,29596
33
- copyparty/u2idx.py,sha256=_2nJlJb6a77Dt5KwK-TY5EnpIxWRVoiXTvk2sCGe6JI,13671
34
- copyparty/up2k.py,sha256=LWq_VqZdrBDHNLX2pyK221hZfoQFDabWD5vOEBbP-xs,173731
35
- copyparty/util.py,sha256=fUfkSBZwOjL4RbZ9UxP5Hi0TvRqy8GbL2oGEny_ikvw,94840
33
+ copyparty/u2idx.py,sha256=kF4TE-j78Faq_f4vGh5So5raYDYLt2UDpza6yBA3f54,13687
34
+ copyparty/up2k.py,sha256=KQZcpqWRwjvmJLFEo3ejVWDroQi10jQdvgxuASDpUlY,174476
35
+ copyparty/util.py,sha256=UyaYXfVO4nOkqhSMi9SZn2zK2W9texb9CL9rsqMak8I,95135
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
39
- copyparty/res/COPYING.txt,sha256=rrIzm8MDinQXNkaBZ_ipr3mAQNzRgTff9tPJNdjaPtY,2193
39
+ copyparty/res/COPYING.txt,sha256=1LnBxkwJuC8mRBxuoMF2UIcpCjcteIzFHotSUE1Xte0,9776
40
40
  copyparty/res/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  copyparty/res/insecure.pem,sha256=FEt7jgrn6ZHTlFopq_LFAN027YIoaHi0HQFBbzYnEwc,2876
42
42
  copyparty/stolen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -46,8 +46,8 @@ copyparty/stolen/dnslib/__init__.py,sha256=SmYdaHdL5czxKxrOFGvzbfmojOthRky1GXKgm
46
46
  copyparty/stolen/dnslib/bimap.py,sha256=j_ah40P3kOw0AQT5vvMcv7XbMtS0jV6Oe9YGCh7J57s,1182
47
47
  copyparty/stolen/dnslib/bit.py,sha256=uDECHT5wM-QObxxRgc4lJq9CUcd7hglRLgs0K1X8mnU,348
48
48
  copyparty/stolen/dnslib/buffer.py,sha256=bvj9rTn4EcccVuMWe1b5j57ex6elba3NCDBdevsnoSc,1407
49
- copyparty/stolen/dnslib/dns.py,sha256=D7Csp3Vryf8IVjpDmVxyFKNDiPKklajNnnZ3lj-3VVc,20999
50
- copyparty/stolen/dnslib/label.py,sha256=ohFQJAqbA806g7jDeTEUtmhan1e7Exyb5j2D7Bg2V-Q,4893
49
+ copyparty/stolen/dnslib/dns.py,sha256=wEaStjfDhT2RI6lS6StwDrkS40afGnbHPue1duOT3vs,21020
50
+ copyparty/stolen/dnslib/label.py,sha256=GKN4JkmZPdsHIyt70oFQYb4KB4Muh2zvGvla-6HiMQ0,5536
51
51
  copyparty/stolen/dnslib/lex.py,sha256=XuXvz4PHzSaEdRRl8xnYaM8cAupzns0QW3oeFn-4isM,2615
52
52
  copyparty/stolen/dnslib/ranges.py,sha256=XqR6NaRMm0xiEht590KhOjKjIuUFgW4I9_zPmPsGV0c,1810
53
53
  copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABwkoeJB0,706
@@ -55,17 +55,17 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
58
- copyparty/web/browser.css.gz,sha256=1BN6qfX6Zai3osSbW0Y5WFcqD4D7wiWYOVEb8zXYu9w,11643
58
+ copyparty/web/browser.css.gz,sha256=kurx_iA-KxLYx8PqJsn0bJVjkAxP-0YTOHSV9l_oouo,11645
59
59
  copyparty/web/browser.html,sha256=KCkZ_LwzQnj9xhXKYptAxp6W3nVGiDoSJ4ioZDo7rQ0,4827
60
- copyparty/web/browser.js.gz,sha256=YNp0XMA-fvOiEBymBv3Iti33cdrp3hP9WMqj_wL7g_U,88603
60
+ copyparty/web/browser.js.gz,sha256=7YbcHLByMAc71j3nKKUrCEExAzfenYnHPBSrnJ8iJFA,89833
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
64
64
  copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
65
65
  copyparty/web/md.html,sha256=isNbIzWbp_oqkhQSXm0uf3st3h4uB3LLTXFcHHcvgPQ,4189
66
- copyparty/web/md.js.gz,sha256=AHRQ3a-PZq_UiGh4CjNwXRllJCvA0IqqYmeHhFWhCig,4179
66
+ copyparty/web/md.js.gz,sha256=tw9vS9yZzyVW4FNOTv0MMHBdBEedfahZRZ8WhVJDu4A,4180
67
67
  copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
68
- copyparty/web/md2.js.gz,sha256=0fTA3lahQ1iDvJR4Q3W9v8dX5hc5VP8nunTtoDwFySs,8363
68
+ copyparty/web/md2.js.gz,sha256=w0Ve06BUcyZfcWR8DAPpCj-kI-uL_wTL2aX_0PsNc-I,8363
69
69
  copyparty/web/mde.css.gz,sha256=2SkAEDKIRPqywNJ8t_heQaeBQ_R73Rf-pQI_bDoKF6o,942
70
70
  copyparty/web/mde.html,sha256=ImBhQAaEUCke2M85QU_fl4X2XQExRLcEzgCEN8RNe9o,1759
71
71
  copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
@@ -80,12 +80,12 @@ copyparty/web/splash.js.gz,sha256=EEfsi9YGtPTYRB6MPX8Dfg4YyfqncI9ldJS7_MGVOhs,27
80
80
  copyparty/web/svcs.html,sha256=P5YZimYLeQMT0uz6u3clQSNZRc5Zs0Ok-ffcbcGSYuc,11762
81
81
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
82
82
  copyparty/web/ui.css.gz,sha256=v8U-1tetZzuzTpITjq8NWj1gg3jEiYDIIE8aE0dx63k,2800
83
- copyparty/web/up2k.js.gz,sha256=J8QY7CZH5Df3DN0ouDRDAM5derRgO_gRNzy5R5NilwM,23291
84
- copyparty/web/util.js.gz,sha256=cciG6bEOr0bC4uQ0O5xTrcgjqolu6lKX-la81VfS_B8,14855
83
+ copyparty/web/up2k.js.gz,sha256=vB3hLpRZzVStcN2-NAGjFdfroxFPbFvKss4vxVYyy1c,23371
84
+ copyparty/web/util.js.gz,sha256=MMDIZbxzBwBYE5eYLzhGGpC7h257oEFZaBKZwJ4uhc8,15077
85
85
  copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
86
86
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
88
- copyparty/web/a/u2c.py,sha256=n5CxOhwZ2C_GhiDMhUcbwuc6v-f4tS2xfoAF-djjIQU,50297
88
+ copyparty/web/a/u2c.py,sha256=1XcH5XfC2UuGrpTnLVHCe4KFU0Vl6uC02CZz04rvKOc,51612
89
89
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
90
90
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
91
91
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -95,9 +95,9 @@ copyparty/web/dd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
95
95
  copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
97
97
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
98
- copyparty/web/deps/easymde.js.gz,sha256=1FykpDM7_FiL4EeZAg4Qcggjoo4PE_MBTgRcBWvjD90,77000
98
+ copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
99
99
  copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
100
- copyparty/web/deps/marked.js.gz,sha256=8hEtz6O_fcajKb8Pl3bb6BRDHkrt0c7LBqb6OKTOyLo,22557
100
+ copyparty/web/deps/marked.js.gz,sha256=3sviQ05gVpZF2s43evtFQZZSMSHV6K3OsS15OoBqb10,22610
101
101
  copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
102
102
  copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
103
103
  copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
@@ -105,10 +105,10 @@ copyparty/web/deps/prism.js.gz,sha256=DR0OAfTUb6PkzSPgxjnjj7jqlpvuCHdMDJKbsQP7s5
105
105
  copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cVCO0,1637
106
106
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
107
107
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
108
- copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
109
- copyparty-1.16.2.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
110
- copyparty-1.16.2.dist-info/METADATA,sha256=55fGnCZfk0w7XNKhojXjou_rx5RSGFnOLkF-nUeqp_8,140284
111
- copyparty-1.16.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
112
- copyparty-1.16.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
113
- copyparty-1.16.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
114
- copyparty-1.16.2.dist-info/RECORD,,
108
+ copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
109
+ copyparty-1.16.4.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
110
+ copyparty-1.16.4.dist-info/METADATA,sha256=yo6iBPRZrSl4VBK6h32czwBccoFewYTRoSFVaOOYr5s,140656
111
+ copyparty-1.16.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
112
+ copyparty-1.16.4.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
113
+ copyparty-1.16.4.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
114
+ copyparty-1.16.4.dist-info/RECORD,,