copyparty 1.16.6__py3-none-any.whl → 1.16.8__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/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 16, 6)
3
+ VERSION = (1, 16, 8)
4
4
  CODENAME = "COPYparty"
5
- BUILD_DT = (2024, 12, 19)
5
+ BUILD_DT = (2025, 1, 11)
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
@@ -2174,11 +2174,11 @@ class AuthSrv(object):
2174
2174
  if not self.args.no_voldump:
2175
2175
  self.log(t)
2176
2176
 
2177
- if have_e2d:
2177
+ if have_e2d or self.args.idp_h_usr:
2178
2178
  t = self.chk_sqlite_threadsafe()
2179
2179
  if t:
2180
2180
  self.log("\n\033[{}\033[0m\n".format(t))
2181
-
2181
+ if have_e2d:
2182
2182
  if not have_e2t:
2183
2183
  t = "hint: enable multimedia indexing (artist/title/...) with argument -e2ts"
2184
2184
  self.log(t, 6)
copyparty/httpcli.py CHANGED
@@ -141,6 +141,14 @@ A_FILE = os.stat_result(
141
141
  (0o644, -1, -1, 1, 1000, 1000, 8, 0x39230101, 0x39230101, 0x39230101)
142
142
  )
143
143
 
144
+ RE_CC = re.compile(r"[\x00-\x1f]") # search always faster
145
+ RE_HSAFE = re.compile(r"[\x00-\x1f<>\"'&]") # search always much faster
146
+ RE_HOST = re.compile(r"[^][0-9a-zA-Z.:_-]") # search faster <=17ch
147
+ RE_MHOST = re.compile(r"^[][0-9a-zA-Z.:_-]+$") # match faster >=18ch
148
+ RE_K = re.compile(r"[^0-9a-zA-Z_-]") # search faster <=17ch
149
+
150
+ UPARAM_CC_OK = set("doc move tree".split())
151
+
144
152
 
145
153
  class HttpCli(object):
146
154
  """
@@ -384,6 +392,15 @@ class HttpCli(object):
384
392
  self.host = self.headers.get("x-forwarded-host") or self.host
385
393
  trusted_xff = True
386
394
 
395
+ m = RE_HOST.search(self.host)
396
+ if m and self.host != self.args.name:
397
+ zs = self.host
398
+ t = "malicious user; illegal Host header; req(%r) host(%r) => %r"
399
+ self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
400
+ self.cbonk(self.conn.hsrv.gmal, zs, "bad_host", "illegal Host header")
401
+ self.terse_reply(b"illegal Host header", 400)
402
+ return False
403
+
387
404
  if self.is_banned():
388
405
  return False
389
406
 
@@ -429,6 +446,16 @@ class HttpCli(object):
429
446
  self.loud_reply(t, status=400)
430
447
  return False
431
448
 
449
+ ptn_cc = RE_CC
450
+ m = ptn_cc.search(self.req)
451
+ if m:
452
+ zs = self.req
453
+ t = "malicious user; Cc in req0 %r => %r"
454
+ self.log(t % (zs, zs[m.span()[0] :]), 1)
455
+ self.cbonk(self.conn.hsrv.gmal, zs, "cc_r0", "Cc in req0")
456
+ self.terse_reply(b"", 500)
457
+ return False
458
+
432
459
  # split req into vpath + uparam
433
460
  uparam = {}
434
461
  if "?" not in self.req:
@@ -441,8 +468,8 @@ class HttpCli(object):
441
468
  self.trailing_slash = vpath.endswith("/")
442
469
  vpath = undot(vpath)
443
470
 
444
- ptn = self.conn.hsrv.ptn_cc
445
- k_safe = self.conn.hsrv.uparam_cc_ok
471
+ re_k = RE_K
472
+ k_safe = UPARAM_CC_OK
446
473
  for k in arglist.split("&"):
447
474
  if "=" in k:
448
475
  k, zs = k.split("=", 1)
@@ -452,6 +479,14 @@ class HttpCli(object):
452
479
  else:
453
480
  sv = ""
454
481
 
482
+ m = re_k.search(k)
483
+ if m:
484
+ t = "malicious user; bad char in query key; req(%r) qk(%r) => %r"
485
+ self.log(t % (self.req, k, k[m.span()[0] :]), 1)
486
+ self.cbonk(self.conn.hsrv.gmal, self.req, "bc_q", "illegal qkey")
487
+ self.terse_reply(b"", 500)
488
+ return False
489
+
455
490
  k = k.lower()
456
491
  uparam[k] = sv
457
492
 
@@ -459,17 +494,26 @@ class HttpCli(object):
459
494
  continue
460
495
 
461
496
  zs = "%s=%s" % (k, sv)
462
- m = ptn.search(zs)
497
+ m = ptn_cc.search(zs)
463
498
  if not m:
464
499
  continue
465
500
 
466
- hit = zs[m.span()[0] :]
467
- t = "malicious user; Cc in query [{}] => [{!r}]"
468
- self.log(t.format(self.req, hit), 1)
501
+ t = "malicious user; Cc in query; req(%r) qp(%r) => %r"
502
+ self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
469
503
  self.cbonk(self.conn.hsrv.gmal, self.req, "cc_q", "Cc in query")
470
504
  self.terse_reply(b"", 500)
471
505
  return False
472
506
 
507
+ if "k" in uparam:
508
+ m = RE_K.search(uparam["k"])
509
+ if m:
510
+ zs = uparam["k"]
511
+ t = "malicious user; illegal filekey; req(%r) k(%r) => %r"
512
+ self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
513
+ self.cbonk(self.conn.hsrv.gmal, zs, "bad_k", "illegal filekey")
514
+ self.terse_reply(b"illegal filekey", 400)
515
+ return False
516
+
473
517
  if self.is_vproxied:
474
518
  if vpath.startswith(self.args.R):
475
519
  vpath = vpath[len(self.args.R) + 1 :]
@@ -513,7 +557,7 @@ class HttpCli(object):
513
557
  return self.tx_qr()
514
558
 
515
559
  if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
516
- self.log("invalid relpath %r" % ("/" + self.vpath,))
560
+ self.log("illegal relpath; req(%r) => %r" % (self.req, "/" + self.vpath))
517
561
  self.cbonk(self.conn.hsrv.gmal, self.req, "bad_vp", "invalid relpaths")
518
562
  return self.tx_404() and self.keepalive
519
563
 
@@ -866,12 +910,12 @@ class HttpCli(object):
866
910
  for k, zs in list(self.out_headers.items()) + self.out_headerlist:
867
911
  response.append("%s: %s" % (k, zs))
868
912
 
913
+ ptn_cc = RE_CC
869
914
  for zs in response:
870
- m = self.conn.hsrv.ptn_cc.search(zs)
915
+ m = ptn_cc.search(zs)
871
916
  if m:
872
- hit = zs[m.span()[0] :]
873
- t = "malicious user; Cc in out-hdr {!r} => [{!r}]"
874
- self.log(t.format(zs, hit), 1)
917
+ t = "malicious user; Cc in out-hdr; req(%r) hdr(%r) => %r"
918
+ self.log(t % (self.req, zs, zs[m.span()[0] :]), 1)
875
919
  self.cbonk(self.conn.hsrv.gmal, zs, "cc_hdr", "Cc in out-hdr")
876
920
  raise Pebkac(999)
877
921
 
@@ -997,7 +1041,7 @@ class HttpCli(object):
997
1041
  if not kv:
998
1042
  return ""
999
1043
 
1000
- r = ["%s=%s" % (k, quotep(zs)) if zs else k for k, zs in kv.items()]
1044
+ r = ["%s=%s" % (quotep(k), quotep(zs)) if zs else k for k, zs in kv.items()]
1001
1045
  return "?" + "&amp;".join(r)
1002
1046
 
1003
1047
  def ourlq(self) :
@@ -1149,8 +1193,8 @@ class HttpCli(object):
1149
1193
  return self.tx_res(res_path)
1150
1194
 
1151
1195
  if res_path != undot(res_path):
1152
- t = "malicious user; attempted path traversal %r => %r"
1153
- self.log(t % ("/" + self.vpath, res_path), 1)
1196
+ t = "malicious user; attempted path traversal; req(%r) vp(%r) => %r"
1197
+ self.log(t % (self.req, "/" + self.vpath, res_path), 1)
1154
1198
  self.cbonk(self.conn.hsrv.gmal, self.req, "trav", "path traversal")
1155
1199
 
1156
1200
  self.tx_404()
@@ -1293,8 +1337,8 @@ class HttpCli(object):
1293
1337
 
1294
1338
  pw = self.ouparam.get("pw")
1295
1339
  if pw:
1296
- q_pw = "?pw=%s" % (pw,)
1297
- a_pw = "&pw=%s" % (pw,)
1340
+ q_pw = "?pw=%s" % (html_escape(pw, True, True),)
1341
+ a_pw = "&pw=%s" % (html_escape(pw, True, True),)
1298
1342
  for i in hits:
1299
1343
  i["rp"] += a_pw if "?" in i["rp"] else q_pw
1300
1344
  else:
@@ -1655,7 +1699,7 @@ class HttpCli(object):
1655
1699
 
1656
1700
  token = str(uuid.uuid4())
1657
1701
 
1658
- if not lk.find(r"./{DAV:}depth"):
1702
+ if lk.find(r"./{DAV:}depth") is None:
1659
1703
  depth = self.headers.get("depth", "infinity")
1660
1704
  lk.append(mktnod("D:depth", depth))
1661
1705
 
@@ -1845,7 +1889,7 @@ class HttpCli(object):
1845
1889
  return self.handle_stash(False)
1846
1890
 
1847
1891
  if "save" in opt:
1848
- post_sz, _, _, _, path, _ = self.dump_to_file(False)
1892
+ post_sz, _, _, _, _, path, _ = self.dump_to_file(False)
1849
1893
  self.log("urlform: %d bytes, %r" % (post_sz, path))
1850
1894
  elif "print" in opt:
1851
1895
  reader, _ = self.get_body_reader()
@@ -1926,11 +1970,11 @@ class HttpCli(object):
1926
1970
  else:
1927
1971
  return read_socket(self.sr, bufsz, remains), remains
1928
1972
 
1929
- def dump_to_file(self, is_put ) :
1930
- # post_sz, sha_hex, sha_b64, remains, path, url
1973
+ def dump_to_file(self, is_put ) :
1974
+ # post_sz, halg, sha_hex, sha_b64, remains, path, url
1931
1975
  reader, remains = self.get_body_reader()
1932
1976
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
1933
- rnd, _, lifetime, xbu, xau = self.upload_flags(vfs)
1977
+ rnd, lifetime, xbu, xau = self.upload_flags(vfs)
1934
1978
  lim = vfs.get_dbv(rem)[0].lim
1935
1979
  fdir = vfs.canonical(rem)
1936
1980
  if lim:
@@ -2078,12 +2122,14 @@ class HttpCli(object):
2078
2122
  # small toctou, but better than clobbering a hardlink
2079
2123
  wunlink(self.log, path, vfs.flags)
2080
2124
 
2125
+ halg = "sha512"
2081
2126
  hasher = None
2082
2127
  copier = hashcopy
2083
2128
  if "ck" in self.ouparam or "ck" in self.headers:
2084
- zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2129
+ halg = zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2085
2130
  if not zs or zs == "no":
2086
2131
  copier = justcopy
2132
+ halg = ""
2087
2133
  elif zs == "md5":
2088
2134
  hasher = hashlib.md5(**USED4SEC)
2089
2135
  elif zs == "sha1":
@@ -2117,7 +2163,7 @@ class HttpCli(object):
2117
2163
  raise
2118
2164
 
2119
2165
  if self.args.nw:
2120
- return post_sz, sha_hex, sha_b64, remains, path, ""
2166
+ return post_sz, halg, sha_hex, sha_b64, remains, path, ""
2121
2167
 
2122
2168
  at = mt = time.time() - lifetime
2123
2169
  cli_mt = self.headers.get("x-oc-mtime")
@@ -2228,19 +2274,30 @@ class HttpCli(object):
2228
2274
  self.args.RS + vpath + vsuf,
2229
2275
  )
2230
2276
 
2231
- return post_sz, sha_hex, sha_b64, remains, path, url
2277
+ return post_sz, halg, sha_hex, sha_b64, remains, path, url
2232
2278
 
2233
2279
  def handle_stash(self, is_put ) :
2234
- post_sz, sha_hex, sha_b64, remains, path, url = self.dump_to_file(is_put)
2280
+ post_sz, halg, sha_hex, sha_b64, remains, path, url = self.dump_to_file(is_put)
2235
2281
  spd = self._spd(post_sz)
2236
2282
  t = "%s wrote %d/%d bytes to %r # %s"
2237
2283
  self.log(t % (spd, post_sz, remains, path, sha_b64[:28])) # 21
2238
2284
 
2239
- ac = self.uparam.get(
2240
- "want", self.headers.get("accept", "").lower().split(";")[-1]
2241
- )
2285
+ mime = "text/plain; charset=utf-8"
2286
+ ac = self.uparam.get("want") or self.headers.get("accept") or ""
2287
+ if ac:
2288
+ ac = ac.split(";", 1)[0].lower()
2289
+ if ac == "application/json":
2290
+ ac = "json"
2242
2291
  if ac == "url":
2243
2292
  t = url
2293
+ elif ac == "json" or "j" in self.uparam:
2294
+ jmsg = {"fileurl": url, "filesz": post_sz}
2295
+ if halg:
2296
+ jmsg[halg] = sha_hex[:56]
2297
+ jmsg["sha_b64"] = sha_b64
2298
+
2299
+ mime = "application/json"
2300
+ t = json.dumps(jmsg, indent=2, sort_keys=True)
2244
2301
  else:
2245
2302
  t = "{}\n{}\n{}\n{}\n".format(post_sz, sha_b64, sha_hex[:56], url)
2246
2303
 
@@ -2250,7 +2307,7 @@ class HttpCli(object):
2250
2307
  h["X-OC-MTime"] = "accepted"
2251
2308
  t = "" # some webdav clients expect/prefer this
2252
2309
 
2253
- self.reply(t.encode("utf-8"), 201, headers=h)
2310
+ self.reply(t.encode("utf-8", "replace"), 201, mime=mime, headers=h)
2254
2311
  return True
2255
2312
 
2256
2313
  def bakflip(
@@ -2923,7 +2980,7 @@ class HttpCli(object):
2923
2980
  self.redirect(vpath, "?edit")
2924
2981
  return True
2925
2982
 
2926
- def upload_flags(self, vfs ) :
2983
+ def upload_flags(self, vfs ) :
2927
2984
  if self.args.nw:
2928
2985
  rnd = 0
2929
2986
  else:
@@ -2931,10 +2988,6 @@ class HttpCli(object):
2931
2988
  if vfs.flags.get("rand"): # force-enable
2932
2989
  rnd = max(rnd, vfs.flags["nrand"])
2933
2990
 
2934
- ac = self.uparam.get(
2935
- "want", self.headers.get("accept", "").lower().split(";")[-1]
2936
- )
2937
- want_url = ac == "url"
2938
2991
  zs = self.uparam.get("life", self.headers.get("life", ""))
2939
2992
  if zs:
2940
2993
  vlife = vfs.flags.get("lifetime") or 0
@@ -2944,7 +2997,6 @@ class HttpCli(object):
2944
2997
 
2945
2998
  return (
2946
2999
  rnd,
2947
- want_url,
2948
3000
  lifetime,
2949
3001
  vfs.flags.get("xbu") or [],
2950
3002
  vfs.flags.get("xau") or [],
@@ -2997,7 +3049,14 @@ class HttpCli(object):
2997
3049
  if not nullwrite:
2998
3050
  bos.makedirs(fdir_base)
2999
3051
 
3000
- rnd, want_url, lifetime, xbu, xau = self.upload_flags(vfs)
3052
+ rnd, lifetime, xbu, xau = self.upload_flags(vfs)
3053
+ zs = self.uparam.get("want") or self.headers.get("accept") or ""
3054
+ if zs:
3055
+ zs = zs.split(";", 1)[0].lower()
3056
+ if zs == "application/json":
3057
+ zs = "json"
3058
+ want_url = zs == "url"
3059
+ want_json = zs == "json" or "j" in self.uparam
3001
3060
 
3002
3061
  files = []
3003
3062
  # sz, sha_hex, sha_b64, p_file, fname, abspath
@@ -3319,7 +3378,9 @@ class HttpCli(object):
3319
3378
  msg += "\n" + errmsg
3320
3379
 
3321
3380
  self.reply(msg.encode("utf-8", "replace"), status=sc)
3322
- elif "j" in self.uparam:
3381
+ elif want_json:
3382
+ if len(jmsg["files"]) == 1:
3383
+ jmsg["fileurl"] = jmsg["files"][0]["url"]
3323
3384
  jtxt = json.dumps(jmsg, indent=2, sort_keys=True).encode("utf-8", "replace")
3324
3385
  self.reply(jtxt, mime="application/json", status=sc)
3325
3386
  else:
@@ -3636,6 +3697,7 @@ class HttpCli(object):
3636
3697
  return logues, readmes
3637
3698
 
3638
3699
  def _expand(self, txt , phs ) :
3700
+ ptn_hsafe = RE_HSAFE
3639
3701
  for ph in phs:
3640
3702
  if ph.startswith("hdr."):
3641
3703
  sv = str(self.headers.get(ph[4:], ""))
@@ -3653,7 +3715,7 @@ class HttpCli(object):
3653
3715
  self.log("unknown placeholder in server config: [%s]" % (ph,), 3)
3654
3716
  continue
3655
3717
 
3656
- sv = self.conn.hsrv.ptn_hsafe.sub("_", sv)
3718
+ sv = ptn_hsafe.sub("_", sv)
3657
3719
  txt = txt.replace("{{%s}}" % (ph,), sv)
3658
3720
 
3659
3721
  return txt
@@ -4486,12 +4548,12 @@ class HttpCli(object):
4486
4548
  else self.conn.hsrv.nm.map(self.ip) or host
4487
4549
  )
4488
4550
  # safer than html_escape/quotep since this avoids both XSS and shell-stuff
4489
- pw = re.sub(r"[<>&$?`\"']", "_", self.pw or "pw")
4551
+ pw = re.sub(r"[<>&$?`\"']", "_", self.pw or "hunter2")
4490
4552
  vp = re.sub(r"[<>&$?`\"']", "_", self.uparam["hc"] or "").lstrip("/")
4491
4553
  pw = pw.replace(" ", "%20")
4492
4554
  vp = vp.replace(" ", "%20")
4493
4555
  if pw in self.asrv.sesa:
4494
- pw = "pwd"
4556
+ pw = "hunter2"
4495
4557
 
4496
4558
  html = self.j2s(
4497
4559
  "svcs",
@@ -4965,11 +5027,11 @@ class HttpCli(object):
4965
5027
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4966
5028
  ret = ret[:2000]
4967
5029
 
5030
+ ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
5031
+
4968
5032
  if len(ret) > 2000:
4969
5033
  ret = ret[:2000]
4970
5034
 
4971
- ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
4972
-
4973
5035
  for rv in ret:
4974
5036
  rv["vp"] = quotep(rv["vp"])
4975
5037
  nfk = rv.pop("nfk")
@@ -5057,10 +5119,6 @@ class HttpCli(object):
5057
5119
  if not dots and "/." in vp:
5058
5120
  continue
5059
5121
 
5060
- n -= 1
5061
- if not n:
5062
- break
5063
-
5064
5122
  rv = {
5065
5123
  "vp": vp,
5066
5124
  "sz": sz,
@@ -5078,13 +5136,17 @@ class HttpCli(object):
5078
5136
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
5079
5137
  ret = ret[:1000]
5080
5138
 
5081
- if len(ret) > 1000:
5082
- ret = ret[:1000]
5139
+ n -= 1
5140
+ if not n:
5141
+ break
5083
5142
 
5084
5143
  ret.sort(key=lambda x: x["at"], reverse=True) # type: ignore
5085
5144
 
5145
+ if len(ret) > 1000:
5146
+ ret = ret[:1000]
5147
+
5086
5148
  for rv in ret:
5087
- rv["evp"] = quotep(rv["vp"])
5149
+ rv["vp"] = quotep(rv["vp"])
5088
5150
  nfk = rv.pop("nfk")
5089
5151
  if not nfk:
5090
5152
  continue
@@ -5117,15 +5179,16 @@ class HttpCli(object):
5117
5179
  for v in ret:
5118
5180
  v["vp"] = self.args.SR + v["vp"]
5119
5181
 
5120
- self.log("%s #%d %.2fsec" % (lm, len(ret), time.time() - t0))
5182
+ now = time.time()
5183
+ self.log("%s #%d %.2fsec" % (lm, len(ret), now - t0))
5121
5184
 
5185
+ ret2 = {"now": int(now), "filter": sfilt, "ups": ret}
5186
+ jtxt = json.dumps(ret2, separators=(",\n", ": "))
5122
5187
  if "j" in self.ouparam:
5123
- jtxt = json.dumps(ret, separators=(",\n", ": "))
5124
5188
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
5125
5189
  return True
5126
5190
 
5127
- rows = [[x["vp"], x["evp"], x["sz"], x["ip"], x["at"]] for x in ret]
5128
- html = self.j2s("rups", this=self, rows=rows, filt=sfilt, now=int(time.time()))
5191
+ html = self.j2s("rups", this=self, v=jtxt)
5129
5192
  self.reply(html.encode("utf-8"), status=200)
5130
5193
  return True
5131
5194
 
copyparty/httpsrv.py CHANGED
@@ -191,10 +191,6 @@ class HttpSrv(object):
191
191
  self.xff_nm = build_netmap(self.args.xff_src)
192
192
  self.xff_lan = build_netmap("lan")
193
193
 
194
- self.ptn_cc = re.compile(r"[\x00-\x1f]")
195
- self.ptn_hsafe = re.compile(r"[\x00-\x1f<>\"'&]")
196
- self.uparam_cc_ok = set("doc move tree".split())
197
-
198
194
  self.mallow = "GET HEAD POST PUT DELETE OPTIONS".split()
199
195
  if not self.args.no_dav:
200
196
  zs = "PROPFIND PROPPATCH LOCK UNLOCK MKCOL COPY MOVE"
copyparty/tcpsrv.py CHANGED
@@ -403,12 +403,12 @@ class TcpSrv(object):
403
403
  rem = []
404
404
  for k, v in netdevs.items():
405
405
  if k not in self.netdevs:
406
- add.append("\n added %s = %s" % (k, v))
406
+ add.append("\n\033[32m added %s = %s" % (k, v))
407
407
  for k, v in self.netdevs.items():
408
408
  if k not in netdevs:
409
- rem.append("\nremoved %s = %s" % (k, v))
409
+ rem.append("\n\033[33mremoved %s = %s" % (k, v))
410
410
 
411
- t = "network change detected:\033[32m%s\033[33m%s"
411
+ t = "network change detected:%s%s"
412
412
  self.log("tcpsrv", t % ("".join(add), "".join(rem)), 3)
413
413
  self.netdevs = netdevs
414
414
  self._distribute_netdevs()
copyparty/up2k.py CHANGED
@@ -851,9 +851,9 @@ class Up2k(object):
851
851
  self.iacct = self.asrv.iacct
852
852
  self.grps = self.asrv.grps
853
853
 
854
+ have_e2d = self.args.idp_h_usr or self.args.chpw or self.args.shr
854
855
  vols = list(all_vols.values())
855
856
  t0 = time.time()
856
- have_e2d = False
857
857
 
858
858
  if self.no_expr_idx:
859
859
  modified = False
@@ -1112,6 +1112,7 @@ class Up2k(object):
1112
1112
  reg = {}
1113
1113
  drp = None
1114
1114
  emptylist = []
1115
+ dotpart = "." if self.args.dotpart else ""
1115
1116
  snap = os.path.join(histpath, "up2k.snap")
1116
1117
  if bos.path.exists(snap):
1117
1118
  with gzip.GzipFile(snap, "rb") as f:
@@ -1124,6 +1125,8 @@ class Up2k(object):
1124
1125
  except:
1125
1126
  pass
1126
1127
 
1128
+ reg = reg2 # diff-golf
1129
+
1127
1130
  if reg2 and "dwrk" not in reg2[next(iter(reg2))]:
1128
1131
  for job in reg2.values():
1129
1132
  job["dwrk"] = job["wark"]
@@ -1131,7 +1134,8 @@ class Up2k(object):
1131
1134
  rm = []
1132
1135
  for k, job in reg2.items():
1133
1136
  job["ptop"] = ptop
1134
- if "done" in job:
1137
+ is_done = "done" in job
1138
+ if is_done:
1135
1139
  job["need"] = job["hash"] = emptylist
1136
1140
  else:
1137
1141
  if "need" not in job:
@@ -1139,10 +1143,13 @@ class Up2k(object):
1139
1143
  if "hash" not in job:
1140
1144
  job["hash"] = []
1141
1145
 
1142
- fp = djoin(ptop, job["prel"], job["name"])
1146
+ if is_done:
1147
+ fp = djoin(ptop, job["prel"], job["name"])
1148
+ else:
1149
+ fp = djoin(ptop, job["prel"], dotpart + job["name"] + ".PARTIAL")
1150
+
1143
1151
  if bos.path.exists(fp):
1144
- reg[k] = job
1145
- if "done" in job:
1152
+ if is_done:
1146
1153
  continue
1147
1154
  job["poke"] = time.time()
1148
1155
  job["busy"] = {}
@@ -1150,11 +1157,18 @@ class Up2k(object):
1150
1157
  self.log("ign deleted file in snap: %r" % (fp,))
1151
1158
  if not n4g:
1152
1159
  rm.append(k)
1153
- continue
1154
1160
 
1155
1161
  for x in rm:
1156
1162
  del reg2[x]
1157
1163
 
1164
+ # optimize pre-1.15.4 entries
1165
+ if next((x for x in reg.values() if "done" in x and "poke" in x), None):
1166
+ zsl = "host tnam busy sprs poke t0c".split()
1167
+ for job in reg.values():
1168
+ if "done" in job:
1169
+ for k in zsl:
1170
+ job.pop(k, None)
1171
+
1158
1172
  if drp is None:
1159
1173
  drp = [k for k, v in reg.items() if not v["need"]]
1160
1174
  else:
@@ -2989,7 +3003,7 @@ class Up2k(object):
2989
3003
  if wark in reg:
2990
3004
  del reg[wark]
2991
3005
  job["hash"] = job["need"] = []
2992
- job["done"] = True
3006
+ job["done"] = 1
2993
3007
  job["busy"] = {}
2994
3008
 
2995
3009
  if lost:
copyparty/web/a/u2c.py CHANGED
@@ -1101,7 +1101,7 @@ class Ctl(object):
1101
1101
  nleft = self.nfiles - self.up_f
1102
1102
  tail = "\033[K\033[u" if VT100 and not self.ar.ns else "\r"
1103
1103
 
1104
- t = "%s eta @ %s/s, %s, %d# left\033[K" % (self.eta, spd, sleft, nleft)
1104
+ t = "%s eta @ %s/s, %s, %d# left" % (self.eta, spd, sleft, nleft)
1105
1105
  if not self.hash_b:
1106
1106
  t = " now hashing..."
1107
1107
  eprint(txt + "\033]0;{0}\033\\\r{0}{1}".format(t, tail))
@@ -131,7 +131,7 @@
131
131
  <div id="widget"></div>
132
132
 
133
133
  <script>
134
- var SR = {{ r|tojson }},
134
+ var SR = "{{ r }}",
135
135
  CGV1 = {{ cgv1 }},
136
136
  CGV = {{ cgv|tojson }},
137
137
  TS = "{{ ts }}",
Binary file
copyparty/web/md.html CHANGED
@@ -128,9 +128,9 @@ write markdown (most html is 🙆 too)
128
128
 
129
129
  <script>
130
130
 
131
- var SR = {{ r|tojson }},
131
+ var SR = "{{ r }}",
132
132
  last_modified = {{ lastmod }},
133
- have_emp = {{ have_emp|tojson }},
133
+ have_emp = {{ "true" if have_emp else "false" }},
134
134
  dfavico = "{{ favico }}";
135
135
 
136
136
  var md_opt = {
copyparty/web/mde.html CHANGED
@@ -26,9 +26,9 @@
26
26
  <a href="#" id="repl">π</a>
27
27
  <script>
28
28
 
29
- var SR = {{ r|tojson }},
29
+ var SR = "{{ r }}",
30
30
  last_modified = {{ lastmod }},
31
- have_emp = {{ have_emp|tojson }},
31
+ have_emp = {{ "true" if have_emp else "false" }},
32
32
  dfavico = "{{ favico }}";
33
33
 
34
34
  var md_opt = {
copyparty/web/rups.css.gz CHANGED
Binary file
copyparty/web/rups.html CHANGED
@@ -6,6 +6,7 @@
6
6
  <title>{{ s_doctitle }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
+ <meta name="robots" content="noindex, nofollow">
9
10
  <meta name="theme-color" content="#{{ tcolor }}">
10
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/rups.css?_={{ ts }}">
11
12
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
@@ -14,14 +15,10 @@
14
15
 
15
16
  <body>
16
17
  <div id="wrap">
17
- <a id="a" href="{{ r }}/?ru" class="af">refresh</a>
18
- <a id="a" href="{{ r }}/?h" class="af">control-panel</a>
19
- <form method="get" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ r }}">
20
- <input type="hidden" name="ru" value="a" />
21
- Filter: <input type="text" name="filter" size="20" placeholder="documents/passwords" value="{{ filt }}" />
22
- <input type="submit" />
23
- </form>
24
- <span id="hits"></span>
18
+ <a href="#" id="re">refresh</a>
19
+ <a href="{{ r }}/?h">control-panel</a>
20
+ &nbsp; Filter: <input type="text" id="filter" size="20" placeholder="documents/passwords" />
21
+ &nbsp; <span id="hits"></span>
25
22
  <table id="tab"><thead><tr>
26
23
  <th>size</th>
27
24
  <th>who</th>
@@ -29,27 +26,12 @@
29
26
  <th>age</th>
30
27
  <th>dir</th>
31
28
  <th>file</th>
32
- </tr></thead><tbody>
33
- {% for vp, evp, sz, ip, at in rows %}
34
- <tr>
35
- <td>{{ sz }}</td>
36
- <td>{{ ip }}</td>
37
- <td>{{ at }}</td>
38
- <td>{{ (now-at) }}</td>
39
- <td></td>
40
- <td><a href="{{ r }}{{ evp }}">{{ vp|e }}</a></td>
41
- </tr>
42
- {% endfor %}
43
- </tbody></table>
44
- {% if not rows %}
45
- (the database is not aware of any uploads)
46
- {% endif %}
29
+ </tr></thead><tbody id="tb"></tbody></table>
47
30
  </div>
48
31
  <a href="#" id="repl">π</a>
49
32
  <script>
50
33
 
51
- var SR = {{ r|tojson }},
52
- NOW = {{ now }},
34
+ var SR="{{ r }}",
53
35
  lang="{{ lang }}",
54
36
  dfavico="{{ favico }}";
55
37
 
@@ -58,6 +40,7 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
58
40
 
59
41
  </script>
60
42
  <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
43
+ <script>var V={{ v }};</script>
61
44
  <script src="{{ r }}/.cpr/rups.js?_={{ ts }}"></script>
62
45
  {%- if js %}
63
46
  <script src="{{ js }}_={{ ts }}"></script>
copyparty/web/rups.js.gz CHANGED
Binary file
copyparty/web/shares.html CHANGED
@@ -6,6 +6,7 @@
6
6
  <title>{{ s_doctitle }}</title>
7
7
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
+ <meta name="robots" content="noindex, nofollow">
9
10
  <meta name="theme-color" content="#{{ tcolor }}">
10
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
11
12
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
@@ -14,8 +15,8 @@
14
15
 
15
16
  <body>
16
17
  <div id="wrap">
17
- <a id="a" href="{{ r }}/?shares" class="af">refresh</a>
18
- <a id="a" href="{{ r }}/?h" class="af">control-panel</a>
18
+ <a href="{{ r }}/?shares">refresh</a>
19
+ <a href="{{ r }}/?h">control-panel</a>
19
20
 
20
21
  <span>axs = perms (read,write,move,delet)</span>
21
22
  <span>nf = numFiles (0=dir)</span>
@@ -62,7 +63,7 @@
62
63
  <a href="#" id="repl">π</a>
63
64
  <script>
64
65
 
65
- var SR = {{ r|tojson }},
66
+ var SR="{{ r }}",
66
67
  shr="{{ shr }}",
67
68
  lang="{{ lang }}",
68
69
  dfavico="{{ favico }}";
Binary file
copyparty/web/splash.html CHANGED
@@ -168,7 +168,7 @@
168
168
  {%- endif %}
169
169
  <script>
170
170
 
171
- var SR = {{ r|tojson }},
171
+ var SR="{{ r }}",
172
172
  lang="{{ lang }}",
173
173
  dfavico="{{ favico }}";
174
174
 
copyparty/web/svcs.html CHANGED
@@ -9,7 +9,7 @@
9
9
  <meta name="theme-color" content="#{{ tcolor }}">
10
10
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
11
11
  <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
12
- <style>ul{padding-left:1.3em}li{margin:.4em 0}</style>
12
+ <style>ul{padding-left:1.3em}li{margin:.4em 0}.txa{float:right;margin:0 0 0 1em}</style>
13
13
  {{ html_head }}
14
14
  </head>
15
15
 
@@ -31,15 +31,22 @@
31
31
  <br />
32
32
  <span class="os win lin mac">placeholders:</span>
33
33
  <span class="os win">
34
- {% if accs %}<code><b>{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
34
+ {% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>W:</b></code>=mountpoint
35
35
  </span>
36
36
  <span class="os lin mac">
37
- {% if accs %}<code><b>{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
37
+ {% if accs %}<code><b id="pw0">{{ pw }}</b></code>=password, {% endif %}<code><b>mp</b></code>=mountpoint
38
38
  </span>
39
+ <a href="#" id="setpw">use real password</a>
39
40
  </p>
40
41
 
41
42
 
42
43
 
44
+ {% if args.idp_h_usr %}
45
+ <p style="line-height:2em"><b>WARNING:</b> this server is using IdP-based authentication, so this stuff may not work as advertised. Depending on server config, these commands can probably only be used to access areas which don't require authentication, unless you auth using any non-IdP accounts defined in the copyparty config. Please see <a href="https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients">the IdP docs</a></p>
46
+ {% endif %}
47
+
48
+
49
+
43
50
  {% if not args.no_dav %}
44
51
  <h1>WebDAV</h1>
45
52
 
@@ -229,11 +236,65 @@
229
236
 
230
237
 
231
238
 
239
+ <div class="os win">
240
+ <h1>ShareX</h1>
241
+
242
+ <p>to upload screenshots using ShareX <a href="https://github.com/ShareX/ShareX/releases/tag/v12.4.1">v12</a> or <a href="https://getsharex.com/">v15+</a>, save this as <code>copyparty.sxcu</code> and run it:</p>
243
+
244
+ <pre class="dl" name="copyparty.sxcu">
245
+ { "Name": "copyparty",
246
+ "RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
247
+ "Headers": {
248
+ {% if accs %}"pw": "<b>{{ pw }}</b>",{% endif %}
249
+ "accept": "url"
250
+ },
251
+ "DestinationType": "ImageUploader, TextUploader, FileUploader",
252
+ "FileFormName": "f" }
253
+ </pre>
254
+ </div>
255
+
256
+
257
+
258
+ <div class="os mac">
259
+ <h1>ishare</h1>
260
+
261
+ <p>to upload screenshots using <a href="https://isharemac.app/">ishare</a>, save this as <code>copyparty.iscu</code> and run it:</p>
262
+
263
+ <pre class="dl" name="copyparty.iscu">
264
+ { "Name": "copyparty",
265
+ "RequestURL": "http{{ s }}://{{ ep }}/{{ rvp }}",
266
+ "Headers": {
267
+ {% if accs %}"pw": "<b>{{ pw }}</b>",{% endif %}
268
+ "accept": "json"
269
+ },
270
+ "ResponseURL": "{{ '{{fileurl}}' }}",
271
+ "FileFormName": "f" }
272
+ </pre>
273
+ </div>
274
+
275
+
276
+
277
+ <div class="os lin">
278
+ <h1>flameshot</h1>
279
+
280
+ <p>to upload screenshots using <a href="https://flameshot.org/">flameshot</a>, save this as <code>flameshot.sh</code> and run it:</p>
281
+
282
+ <pre class="dl" name="flameshot.sh">
283
+ #!/bin/bash
284
+ pw="<b>{{ pw }}</b>"
285
+ url="http{{ s }}://{{ ep }}/{{ rvp }}"
286
+ filename="$(date +%Y-%m%d-%H%M%S).png"
287
+ flameshot gui -s -r | curl -sT- "$url$filename?want=url&pw=$pw" | xsel -ib
288
+ </pre>
289
+ </div>
290
+
291
+
292
+
232
293
  </div>
233
294
  <a href="#" id="repl">π</a>
234
295
  <script>
235
296
 
236
- var SR = {{ r|tojson }},
297
+ var SR="{{ r }}",
237
298
  lang="{{ lang }}",
238
299
  dfavico="{{ favico }}";
239
300
 
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
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: copyparty
3
- Version: 1.16.6
3
+ Version: 1.16.8
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
@@ -147,6 +147,7 @@ turn almost any device into a file server with resumable uploads/downloads using
147
147
  * [listen on port 80 and 443](#listen-on-port-80-and-443) - become a *real* webserver
148
148
  * [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
149
149
  * [real-ip](#real-ip) - teaching copyparty how to see client IPs
150
+ * [reverse-proxy performance](#reverse-proxy-performance)
150
151
  * [prometheus](#prometheus) - metrics/stats can be enabled
151
152
  * [other extremely specific features](#other-extremely-specific-features) - you'll never find a use for these
152
153
  * [custom mimetypes](#custom-mimetypes) - change the association of a file extension
@@ -195,6 +196,7 @@ just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/
195
196
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
196
197
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
197
198
  * or if you are on android, [install copyparty in termux](#install-on-android)
199
+ * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
198
200
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
199
201
  * or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
200
202
  * docker has all deps built-in, so skip this step:
@@ -701,7 +703,7 @@ dragdrop is the recommended way, but you may also:
701
703
 
702
704
  * select some files (not folders) in your file explorer and press CTRL-V inside the browser window
703
705
  * use the [command-line uploader](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy)
704
- * upload using [curl or sharex](#client-examples)
706
+ * upload using [curl, sharex, ishare, ...](#client-examples)
705
707
 
706
708
  when uploading files through dragdrop or CTRL-V, this initiates an upload using `up2k`; there are two browser-based uploaders available:
707
709
  * `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
@@ -1159,6 +1161,8 @@ on macos, connect from finder:
1159
1161
 
1160
1162
  in order to grant full write-access to webdav clients, the volflag `daw` must be set and the account must also have delete-access (otherwise the client won't be allowed to replace the contents of existing files, which is how webdav works)
1161
1163
 
1164
+ > note: if you have enabled [IdP authentication](#identity-providers) then that may cause issues for some/most webdav clients; see [the webdav section in the IdP docs](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients)
1165
+
1162
1166
 
1163
1167
  ### connecting to webdav from windows
1164
1168
 
@@ -1724,10 +1728,16 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
1724
1728
 
1725
1729
  for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it)
1726
1730
 
1727
- example webserver configs:
1731
+ example webserver / reverse-proxy configs:
1728
1732
 
1729
- * [nginx config](contrib/nginx/copyparty.conf) -- entire domain/subdomain
1730
- * [apache2 config](contrib/apache/copyparty.conf) -- location-based
1733
+ * [apache config](contrib/apache/copyparty.conf)
1734
+ * caddy uds: `caddy reverse-proxy --from :8080 --to unix///dev/shm/party.sock`
1735
+ * caddy tcp: `caddy reverse-proxy --from :8081 --to http://127.0.0.1:3923`
1736
+ * [haproxy config](contrib/haproxy/copyparty.conf)
1737
+ * [lighttpd subdomain](contrib/lighttpd/subdomain.conf) -- entire domain/subdomain
1738
+ * [lighttpd subpath](contrib/lighttpd/subpath.conf) -- location-based (not optimal, but in case you need it)
1739
+ * [nginx config](contrib/nginx/copyparty.conf) -- recommended
1740
+ * [traefik config](contrib/traefik/copyparty.yaml)
1731
1741
 
1732
1742
 
1733
1743
  ### real-ip
@@ -1739,6 +1749,38 @@ if you (and maybe everybody else) keep getting a message that says `thank you fo
1739
1749
  for most common setups, there should be a helpful message in the server-log explaining what to do, but see [docs/xff.md](docs/xff.md) if you want to learn more, including a quick hack to **just make it work** (which is **not** recommended, but hey...)
1740
1750
 
1741
1751
 
1752
+ ### reverse-proxy performance
1753
+
1754
+ most reverse-proxies support connecting to copyparty either using uds/unix-sockets (`/dev/shm/party.sock`, faster/recommended) or using tcp (`127.0.0.1`)
1755
+
1756
+ with copyparty listening on a uds / unix-socket / unix-domain-socket and the reverse-proxy connecting to that:
1757
+
1758
+ | index.html | upload | download | software |
1759
+ | ------------ | ----------- | ----------- | -------- |
1760
+ | 28'900 req/s | 6'900 MiB/s | 7'400 MiB/s | no-proxy |
1761
+ | 18'750 req/s | 3'500 MiB/s | 2'370 MiB/s | haproxy |
1762
+ | 9'900 req/s | 3'750 MiB/s | 2'200 MiB/s | caddy |
1763
+ | 18'700 req/s | 2'200 MiB/s | 1'570 MiB/s | nginx |
1764
+ | 9'700 req/s | 1'750 MiB/s | 1'830 MiB/s | apache |
1765
+ | 9'900 req/s | 1'300 MiB/s | 1'470 MiB/s | lighttpd |
1766
+
1767
+ when connecting the reverse-proxy to `127.0.0.1` instead (the basic and/or old-fasioned way), speeds are a bit worse:
1768
+
1769
+ | index.html | upload | download | software |
1770
+ | ------------ | ----------- | ----------- | -------- |
1771
+ | 21'200 req/s | 5'700 MiB/s | 6'700 MiB/s | no-proxy |
1772
+ | 14'500 req/s | 1'700 MiB/s | 2'170 MiB/s | haproxy |
1773
+ | 11'100 req/s | 2'750 MiB/s | 2'000 MiB/s | traefik |
1774
+ | 8'400 req/s | 2'300 MiB/s | 1'950 MiB/s | caddy |
1775
+ | 13'400 req/s | 1'100 MiB/s | 1'480 MiB/s | nginx |
1776
+ | 8'400 req/s | 1'000 MiB/s | 1'000 MiB/s | apache |
1777
+ | 6'500 req/s | 1'270 MiB/s | 1'500 MiB/s | lighttpd |
1778
+
1779
+ in summary, `haproxy > caddy > traefik > nginx > apache > lighttpd`, and use uds when possible (traefik does not support it yet)
1780
+
1781
+ * if these results are bullshit because my config exampels are bad, please submit corrections!
1782
+
1783
+
1742
1784
  ## prometheus
1743
1785
 
1744
1786
  metrics/stats can be enabled at URL `/.cpr/metrics` for grafana / prometheus / etc (openmetrics 1.0.0)
@@ -2052,7 +2094,8 @@ interact with copyparty using non-browser clients
2052
2094
  * can be downloaded from copyparty: controlpanel -> connect -> [partyfuse.py](http://127.0.0.1:3923/.cpr/a/partyfuse.py)
2053
2095
  * [rclone](https://rclone.org/) as client can give ~5x performance, see [./docs/rclone.md](docs/rclone.md)
2054
2096
 
2055
- * sharex (screenshot utility): see [./contrib/sharex.sxcu](contrib/#sharexsxcu)
2097
+ * sharex (screenshot utility): see [./contrib/sharex.sxcu](./contrib/#sharexsxcu)
2098
+ * and for screenshots on macos, see [./contrib/ishare.iscu](./contrib/#ishareiscu)
2056
2099
  * and for screenshots on linux, see [./contrib/flameshot.sh](./contrib/flameshot.sh)
2057
2100
 
2058
2101
  * contextlet (web browser integration); see [contrib contextlet](contrib/#send-to-cppcontextletjson)
@@ -1,7 +1,7 @@
1
1
  copyparty/__init__.py,sha256=VR6ZZhB9IxaK5TDXDTBM_OIP5ydkrdbaEnstktLM__s,2649
2
2
  copyparty/__main__.py,sha256=acjihZUEvEwFp6d6BnT35oFqminMZBruDUfVU76YijA,113564
3
- copyparty/__version__.py,sha256=JJ3NgXxnmonfdKeivWXs-b4z3FsuOlq62WbiCAifwR4,252
4
- copyparty/authsrv.py,sha256=n8lQACRCzKnYlgzNv1RkGehv5qI4soYnyZhNw4IPHxA,104062
3
+ copyparty/__version__.py,sha256=O9UOPs_HcmEufkcuaes_ZA7U8kK_sVtXCGmVqE69BnA,251
4
+ copyparty/authsrv.py,sha256=h1OhdMR_hrqZA9NH2pMRZ-Axc_GSoUajioCZhimVVsQ,104109
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
@@ -11,9 +11,9 @@ copyparty/cfg.py,sha256=UUmFpFbTm750Nv9RnofS80-FTpWT37EggEtmkE1wbxE,10374
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=IVOFG8zBQPMQDDv7RIStSJHwHiAnVNROZS37O5k465A,4524
13
13
  copyparty/ftpd.py,sha256=T97SFS7JFtvRLbJX8C4fJSYwe13vhN3-E6emtlVmqLA,17608
14
- copyparty/httpcli.py,sha256=DAi4JrEKCGTMBMjobQZ5d9WpisetKwuXLjZr2z1idpE,212463
14
+ copyparty/httpcli.py,sha256=jiR7zHGGyVp-ZwK-j0I2eNkwbEOYDTc7xGrGzrSb0iQ,215225
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
- copyparty/httpsrv.py,sha256=a_WKCKlN5isIoy8bNdttlQFha_8X_E_cPoToZv2q5Cw,18255
16
+ copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
18
18
  copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
19
19
  copyparty/metrics.py,sha256=EOIiPOItEQmdK9YgNb75l0kCzanWb6RtJGwMI7ufifY,8966
@@ -26,12 +26,12 @@ copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
27
27
  copyparty/svchub.py,sha256=sAHkiPGzzKACLqKlem2V-bps9Xh-wHlcfwaNywxcd5A,40877
28
28
  copyparty/szip.py,sha256=HFtnwOiBgx0HMLUf-h_T84zSlRijPxmhRo5PM613kRA,8602
29
- copyparty/tcpsrv.py,sha256=H7z9tzYRCm-kZwktnm_wP-v2MXaIOygx9yfdk1xkqRo,19889
29
+ copyparty/tcpsrv.py,sha256=2q18dGR8jnezA4SMfUXa-wrGRGX3nHIwkxkWvkTzF2A,19889
30
30
  copyparty/tftpd.py,sha256=PXgG4rTmiaU_TavSyZWD5cFphdfChs9YvNY21qfExt8,13611
31
31
  copyparty/th_cli.py,sha256=1B8el4NNs5cNyJyjOPiAdvLOX2HQXaebsHn6VLdZ_gU,4630
32
32
  copyparty/th_srv.py,sha256=uAcz-wZJQEG5KavcZDkwToONZujyoOeC4oCxxKTD5us,29575
33
33
  copyparty/u2idx.py,sha256=G6MDbD4I_sJSOwaNFZ6XLTQhnEDrB12pVKuKhzQ_leE,13676
34
- copyparty/up2k.py,sha256=u7yLOD9ypqTEmA3bMPXqXrvebo7Lq62NjUdSZ_oGw0E,174556
34
+ copyparty/up2k.py,sha256=0YPdP2UHoJr0c3QhS6EsOIUPSq2JztCD43Y3D7lN6Po,175144
35
35
  copyparty/util.py,sha256=qhNakLtygJveVbZUuPvDY7Tm1E48E67CS9XBmosu5i4,95101
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
@@ -56,39 +56,39 @@ copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
57
  copyparty/web/baguettebox.js.gz,sha256=_amC3ipOrXKEFz8DsVP-JEl49VjMQYiKyF78eWfG-uk,7965
58
58
  copyparty/web/browser.css.gz,sha256=kurx_iA-KxLYx8PqJsn0bJVjkAxP-0YTOHSV9l_oouo,11645
59
- copyparty/web/browser.html,sha256=KCkZ_LwzQnj9xhXKYptAxp6W3nVGiDoSJ4ioZDo7rQ0,4827
60
- copyparty/web/browser.js.gz,sha256=7YbcHLByMAc71j3nKKUrCEExAzfenYnHPBSrnJ8iJFA,89833
59
+ copyparty/web/browser.html,sha256=dekrZQ6w8ciB-QPlp-mjcuzUVKlsCYcvvi6efmXRfQE,4822
60
+ copyparty/web/browser.js.gz,sha256=HejO4Brwmw-NmLVmqbK1I31LPP9arf7G9JaHOUpNcfY,89876
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
- copyparty/web/md.html,sha256=isNbIzWbp_oqkhQSXm0uf3st3h4uB3LLTXFcHHcvgPQ,4189
65
+ copyparty/web/md.html,sha256=hz-xJVfKtaeTUQn3tGh7ebIMvLbOjVKkrMhsCTr3lGM,4200
66
66
  copyparty/web/md.js.gz,sha256=tw9vS9yZzyVW4FNOTv0MMHBdBEedfahZRZ8WhVJDu4A,4180
67
67
  copyparty/web/md2.css.gz,sha256=uIVHKScThdbcfhXNSHgKZnALYpxbnXC-WuEzOJ20Lpc,699
68
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
- copyparty/web/mde.html,sha256=ImBhQAaEUCke2M85QU_fl4X2XQExRLcEzgCEN8RNe9o,1759
70
+ copyparty/web/mde.html,sha256=fRGUlnNhK6ra8P4jskQLUw6Aaef6g6Heh4EEgYhJkxU,1770
71
71
  copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
72
72
  copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
73
73
  copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
74
- copyparty/web/rups.css.gz,sha256=slfIxkdQ4vmYfMw0hlqX9bl-L8MpGsNF8HOIJvYcGWM,674
75
- copyparty/web/rups.html,sha256=XEdGH-h-McwCXAx7POF0atyyLDUdDeBL0OnM3vBnyzU,2006
76
- copyparty/web/rups.js.gz,sha256=wVEV-jrXLj0jrrmxtZqTQVpvF9KpDnkvujw6Tx8Bk3o,469
74
+ copyparty/web/rups.css.gz,sha256=pWklsym27oGGr-8tYQR7WnZvGZElAgCwLzlwTDErNAM,647
75
+ copyparty/web/rups.html,sha256=iPuz53jBT_mIWIfl1yrjjg5-P7oO2ada6fTFq8PgjGk,1479
76
+ copyparty/web/rups.js.gz,sha256=nvvcG8L-fkm7zkhjnlTGhBp_KD0j08mtHEW0sB7zy-Y,854
77
77
  copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,547
78
- copyparty/web/shares.html,sha256=IhZnADkNoD_2-zEJnoiMG68iOXB58Ros1UNFVXjEwWE,2535
78
+ copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,2545
79
79
  copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,940
80
- copyparty/web/splash.css.gz,sha256=VxFqPvNdZtWb0u1C_GW2yYwrHq0lqPMitft9GYcv56k,1087
81
- copyparty/web/splash.html,sha256=2iTamber6qv84SvbQzibL-onvRUJwyfthUaFgeYdBRA,6256
80
+ copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
+ copyparty/web/splash.html,sha256=QEnTH9QZXFmAuyVtgqOuuHKBtIdi7uclpRqe0ZMewj4,6249
82
82
  copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
- copyparty/web/svcs.html,sha256=5cAp6RAkvqfDAbO9kxuDgXcmuDH20ZN2fse0uzCXb5s,11564
84
- copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
85
- copyparty/web/ui.css.gz,sha256=v8U-1tetZzuzTpITjq8NWj1gg3jEiYDIIE8aE0dx63k,2800
86
- copyparty/web/up2k.js.gz,sha256=CC1MM_FBfxpsjgtNWupzCdnlVNI3hBbWbNSCnOSpCTY,23460
87
- copyparty/web/util.js.gz,sha256=aXgf3U-0TL9Sc7uMQhA_sMyTXLsZCH3YDe8qLJ4hpEM,15082
83
+ copyparty/web/svcs.html,sha256=s2uMblxDpYo8l-M--KB1BAF1ZiQrWf5p4v1sDjSs1MQ,14140
84
+ copyparty/web/svcs.js.gz,sha256=rcc75HMmoc3KA7Ld2j8X9AKX_elZgwUD6Vnm2F-yj_U,805
85
+ copyparty/web/ui.css.gz,sha256=0sHIwGsL3_xH8Uu6N0Ag3bKBTjf-e_yfFbKynEZXAnk,2800
86
+ copyparty/web/up2k.js.gz,sha256=f00wmaThk1CoFYzFBG_6KLG0acq60cAn3p12tngqprk,23843
87
+ copyparty/web/util.js.gz,sha256=uCCnKWT_FeG-ShKPoj0MtXnBwtdAbyDSM9AP9KiBDjw,15098
88
88
  copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
91
- copyparty/web/a/u2c.py,sha256=1XcH5XfC2UuGrpTnLVHCe4KFU0Vl6uC02CZz04rvKOc,51612
91
+ copyparty/web/a/u2c.py,sha256=OuA0UMtPDSrMHgd4ebANJXoLeIa1RKxzbBXUV3H8jIw,51606
92
92
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
93
93
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
94
94
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.16.6.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.16.6.dist-info/METADATA,sha256=YD_Fomt3BWkdN4PksbjdhOTgjdqO1nDew-_XdTYjiKU,141509
114
- copyparty-1.16.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
115
- copyparty-1.16.6.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.16.6.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.16.6.dist-info/RECORD,,
112
+ copyparty-1.16.8.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.16.8.dist-info/METADATA,sha256=-4246pvuZxdG_MtxGM7euDMxpPVVGiXgIzBEdvlRGLk,144011
114
+ copyparty-1.16.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
115
+ copyparty-1.16.8.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.16.8.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.16.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5