copyparty 1.17.0__py3-none-any.whl → 1.17.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -995,6 +995,9 @@ def add_upload(ap):
995
995
  ap2 = ap.add_argument_group('upload options')
996
996
  ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
997
997
  ap2.add_argument("--plain-ip", action="store_true", help="when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip")
998
+ ap2.add_argument("--put-name", metavar="TXT", type=u, default="put-{now.6f}-{cip}.bin", help="filename for nameless uploads (when uploader doesn't provide a name); default is [\033[32mput-UNIXTIME-IP.bin\033[0m] (the \033[32m.6f\033[0m means six decimal places) (volflag=put_name)")
999
+ ap2.add_argument("--put-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck)")
1000
+ ap2.add_argument("--bup-ck", metavar="ALG", type=u, default="sha512", help="default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck)")
998
1001
  ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h")
999
1002
  ap2.add_argument("--u2abort", metavar="NUM", type=int, default=1, help="clients can abort incomplete uploads by using the unpost tab (requires \033[33m-e2d\033[0m). [\033[32m0\033[0m] = never allowed (disable feature), [\033[32m1\033[0m] = allow if client has the same IP as the upload AND is using the same account, [\033[32m2\033[0m] = just check the IP, [\033[32m3\033[0m] = just check account-name (volflag=u2abort)")
1000
1003
  ap2.add_argument("--blank-wt", metavar="SEC", type=int, default=300, help="file write grace period (any client can write to a blank file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
@@ -1017,6 +1020,7 @@ def add_upload(ap):
1017
1020
  ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
1018
1021
  ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
1019
1022
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
1023
+ ap2.add_argument("--nosubtle", metavar="N", type=int, default=0, help="when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [\033[32m0\033[0m] = only when necessary (non-https), [\033[32m1\033[0m] = always (all browsers), [\033[32m2\033[0m] = always on chrome/firefox, [\033[32m3\033[0m] = always on chrome, [\033[32mN\033[0m] = chrome-version N and newer (recommendation: 137)")
1020
1024
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed")
1021
1025
  ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1022
1026
  ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
@@ -1371,8 +1375,8 @@ def add_thumbnail(ap):
1371
1375
  ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
1372
1376
  ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
1373
1377
  ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
1374
- ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1375
- ap2.add_argument("--th-spec-cnv", metavar="T,T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
1378
+ ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
1379
+ ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
1376
1380
  ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz", help="audio/image formats to decompress before passing to ffmpeg")
1377
1381
 
1378
1382
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 17, 0)
3
+ VERSION = (1, 17, 2)
4
4
  CODENAME = "mixtape.m3u"
5
- BUILD_DT = (2025, 4, 26)
5
+ BUILD_DT = (2025, 5, 27)
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
@@ -350,7 +350,6 @@ class VFS(object):
350
350
  self.flags = flags # config options
351
351
  self.root = self
352
352
  self.dev = 0 # st_dev
353
- self.badcfg1 = False
354
353
  self.nodes = {} # child nodes
355
354
  self.histtab = {} # all realpath->histpath
356
355
  self.dbpaths = {} # all realpath->dbpath
@@ -870,6 +869,7 @@ class AuthSrv(object):
870
869
  self.warn_anonwrite = warn_anonwrite
871
870
  self.line_ctr = 0
872
871
  self.indent = ""
872
+ self.is_lxc = args.c == ["/z/initcfg"]
873
873
 
874
874
  # fwd-decl
875
875
  self.vfs = VFS(log_func, "", "", "", AXS(), {})
@@ -880,6 +880,8 @@ class AuthSrv(object):
880
880
  self.defpw = {}
881
881
  self.grps = {}
882
882
  self.re_pwd = None
883
+ self.cfg_files_loaded = []
884
+ self.badcfg1 = False
883
885
 
884
886
  # all volumes observed since last restart
885
887
  self.idp_vols = {} # vpath->abspath
@@ -1475,8 +1477,10 @@ class AuthSrv(object):
1475
1477
  daxs = {}
1476
1478
  mflags = {} # vpath:flags
1477
1479
  mount = {} # dst:src (vp:(ap,vp0))
1480
+ cfg_files_loaded = []
1478
1481
 
1479
1482
  self.idp_vols = {} # yolo
1483
+ self.badcfg1 = False
1480
1484
 
1481
1485
  if self.args.a:
1482
1486
  # list of username:password
@@ -1537,6 +1541,7 @@ class AuthSrv(object):
1537
1541
  zst = [(max(0, len(x) - 2) * " ") + "└" + x[-1] for x in zstt]
1538
1542
  t = "loaded {} config files:\n{}"
1539
1543
  self.log(t.format(len(zst), "\n".join(zst)))
1544
+ cfg_files_loaded = zst
1540
1545
 
1541
1546
  except:
1542
1547
  lns = lns[: self.line_ctr]
@@ -1561,9 +1566,14 @@ class AuthSrv(object):
1561
1566
  if not mount and not self.args.idp_h_usr:
1562
1567
  # -h says our defaults are CWD at root and read/write for everyone
1563
1568
  axs = AXS(["*"], ["*"], None, None)
1564
- if os.path.exists("/z/initcfg"):
1565
- t = "Read-access has been disabled due to failsafe: Docker detected, but the config does not define any volumes. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
1566
- self.log(t, 1)
1569
+ if self.is_lxc:
1570
+ t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
1571
+ if len(cfg_files_loaded) == 1:
1572
+ self.log(t % ("no config-file was provided",), 1)
1573
+ t = "it is strongly recommended to add a config-file instead, for example based on https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/basic-docker-compose/copyparty.conf"
1574
+ self.log(t, 3)
1575
+ else:
1576
+ self.log(t % ("the config does not define any volumes",), 1)
1567
1577
  axs = AXS()
1568
1578
  elif self.args.c:
1569
1579
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
@@ -1571,7 +1581,7 @@ class AuthSrv(object):
1571
1581
  axs = AXS()
1572
1582
  vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1573
1583
  if not axs.uread:
1574
- vfs.badcfg1 = True
1584
+ self.badcfg1 = True
1575
1585
  elif "" not in mount:
1576
1586
  # there's volumes but no root; make root inaccessible
1577
1587
  zsd = {"d2d": True, "tcolor": self.args.tcolor}
@@ -2067,6 +2077,10 @@ class AuthSrv(object):
2067
2077
  if len(zs) == 3: # fc5 => ffcc55
2068
2078
  vol.flags["tcolor"] = "".join([x * 2 for x in zs])
2069
2079
 
2080
+ # volflag syntax currently doesn't allow for ':' in value
2081
+ zs = vol.flags["put_name"]
2082
+ vol.flags["put_name2"] = zs.replace("{now.", "{now:.")
2083
+
2070
2084
  if vol.flags.get("neversymlink"):
2071
2085
  vol.flags["hardlinkonly"] = True # was renamed
2072
2086
  if vol.flags.get("hardlinkonly"):
@@ -2422,6 +2436,7 @@ class AuthSrv(object):
2422
2436
  self.defpw = defpw
2423
2437
  self.grps = grps
2424
2438
  self.iacct = {v: k for k, v in acct.items()}
2439
+ self.cfg_files_loaded = cfg_files_loaded
2425
2440
 
2426
2441
  self.load_sessions()
2427
2442
 
@@ -2552,6 +2567,7 @@ class AuthSrv(object):
2552
2567
  "idxh": int(self.args.ih),
2553
2568
  "themes": self.args.themes,
2554
2569
  "turbolvl": self.args.turbo,
2570
+ "nosubtle": self.args.nosubtle,
2555
2571
  "u2j": self.args.u2j,
2556
2572
  "u2sz": self.args.u2sz,
2557
2573
  "u2ts": vf["u2ts"],
copyparty/cert.py CHANGED
@@ -1,13 +1,11 @@
1
1
  import calendar
2
2
  import errno
3
- import filecmp
4
3
  import json
5
4
  import os
6
- import shutil
7
5
  import time
8
6
 
9
7
  from .__init__ import ANYWIN
10
- from .util import Netdev, load_resource, runcmd, wrename, wunlink
8
+ from .util import Netdev, atomic_move, load_resource, runcmd, wunlink
11
9
 
12
10
  HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
13
11
 
@@ -118,7 +116,7 @@ def _gen_ca(log , args):
118
116
  wunlink(nlog, bname + ".key", VF)
119
117
  except:
120
118
  pass
121
- wrename(nlog, bname + "-key.pem", bname + ".key", VF)
119
+ atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
122
120
  wunlink(nlog, bname + ".csr", VF)
123
121
 
124
122
  log("cert", "new ca OK", 2)
@@ -211,7 +209,7 @@ def _gen_srv(log , args, netdevs ):
211
209
  wunlink(nlog, bname + ".key", VF)
212
210
  except:
213
211
  pass
214
- wrename(nlog, bname + "-key.pem", bname + ".key", VF)
212
+ atomic_move(nlog, bname + "-key.pem", bname + ".key", VF)
215
213
  wunlink(nlog, bname + ".csr", VF)
216
214
 
217
215
  with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
copyparty/cfg.py CHANGED
@@ -75,6 +75,7 @@ def vf_vmap() :
75
75
  "th_x3": "th3x",
76
76
  }
77
77
  for k in (
78
+ "bup_ck",
78
79
  "dbd",
79
80
  "forget_ip",
80
81
  "hsortn",
@@ -95,6 +96,8 @@ def vf_vmap() :
95
96
  "og_title_i",
96
97
  "og_tpl",
97
98
  "og_ua",
99
+ "put_ck",
100
+ "put_name",
98
101
  "mv_retry",
99
102
  "rm_retry",
100
103
  "sort",
@@ -165,6 +168,9 @@ flagcats = {
165
168
  "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
166
169
  "nosub": "forces all uploads into the top folder of the vfs",
167
170
  "magic": "enables filetype detection for nameless uploads",
171
+ "put_name": "fallback filename for nameless uploads",
172
+ "put_ck": "default checksum-hasher for PUT/WebDAV uploads",
173
+ "bup_ck": "default checksum-hasher for bup/basic uploads",
168
174
  "gz": "allows server-side gzip compression of uploads with ?gz",
169
175
  "xz": "allows server-side lzma compression of uploads with ?xz",
170
176
  "pk": "forces server-side compression, optional arg: xz,9",
copyparty/httpcli.py CHANGED
@@ -113,7 +113,6 @@ from .util import (
113
113
  vol_san,
114
114
  vroots,
115
115
  vsplit,
116
- wrename,
117
116
  wunlink,
118
117
  yieldfile,
119
118
  )
@@ -185,11 +184,11 @@ class HttpCli(object):
185
184
  self.log_src = conn.log_src # mypy404
186
185
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
187
186
  self.tls = hasattr(self.s, "cipher")
187
+ self.is_vproxied = bool(self.args.R)
188
188
 
189
189
  # placeholders; assigned by run()
190
190
  self.keepalive = False
191
191
  self.is_https = False
192
- self.is_vproxied = False
193
192
  self.in_hdr_recv = True
194
193
  self.headers = {}
195
194
  self.mode = " " # http verb
@@ -397,7 +396,6 @@ class HttpCli(object):
397
396
  self.bad_xff = True
398
397
  else:
399
398
  self.ip = cli_ip
400
- self.is_vproxied = bool(self.args.R)
401
399
  self.log_src = self.conn.set_rproxy(self.ip)
402
400
  self.host = self.headers.get("x-forwarded-host") or self.host
403
401
  trusted_xff = True
@@ -530,6 +528,7 @@ class HttpCli(object):
530
528
  else:
531
529
  t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
532
530
  self.log(t % (self.args.R, vpath), 1)
531
+ self.is_vproxied = False
533
532
 
534
533
  self.ouparam = uparam.copy()
535
534
 
@@ -1229,10 +1228,19 @@ class HttpCli(object):
1229
1228
  else:
1230
1229
  return self.tx_404(True)
1231
1230
  else:
1232
- vfs = self.asrv.vfs
1233
- if vfs.badcfg1:
1234
- t = "<h2>access denied due to failsafe; check server log</h2>"
1235
- html = self.j2s("splash", this=self, msg=t)
1231
+ if (
1232
+ self.asrv.badcfg1
1233
+ and "h" not in self.ouparam
1234
+ and "hc" not in self.ouparam
1235
+ ):
1236
+ zs1 = "copyparty refused to start due to a failsafe: invalid server config; check server log"
1237
+ zs2 = 'you may <a href="/?h">access the controlpanel</a> but nothing will work until you shutdown the copyparty container and %s config-file (or provide the configuration as command-line arguments)'
1238
+ if self.asrv.is_lxc and len(self.asrv.cfg_files_loaded) == 1:
1239
+ zs2 = zs2 % ("add a",)
1240
+ else:
1241
+ zs2 = zs2 % ("fix the",)
1242
+
1243
+ html = self.j2s("msg", h1=zs1, h2=zs2)
1236
1244
  self.reply(html.encode("utf-8", "replace"), 500)
1237
1245
  return True
1238
1246
 
@@ -1408,7 +1416,7 @@ class HttpCli(object):
1408
1416
  desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
1409
1417
  desc = html_escape(desc, True, True) if desc else title
1410
1418
  mime = html_escape(guess_mime(title))
1411
- lmod = formatdate(i["ts"])
1419
+ lmod = formatdate(max(0, i["ts"]))
1412
1420
  zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
1413
1421
  zs = (
1414
1422
  """\
@@ -1565,7 +1573,7 @@ class HttpCli(object):
1565
1573
  for x in fgen:
1566
1574
  rp = vjoin(vtop, x["vp"])
1567
1575
  st = x["st"]
1568
- mtime = st.st_mtime
1576
+ mtime = max(0, st.st_mtime)
1569
1577
  if stat.S_ISLNK(st.st_mode):
1570
1578
  try:
1571
1579
  st = bos.stat(os.path.join(tap, x["vp"]))
@@ -2091,8 +2099,7 @@ class HttpCli(object):
2091
2099
  suffix = "-{:.6f}-{}".format(time.time(), self.dip())
2092
2100
  nameless = not fn
2093
2101
  if nameless:
2094
- suffix += ".bin"
2095
- fn = "put" + suffix
2102
+ fn = vfs.flags["put_name2"].format(now=time.time(), cip=self.dip())
2096
2103
 
2097
2104
  params = {"suffix": suffix, "fdir": fdir}
2098
2105
  if self.args.nw:
@@ -2171,28 +2178,26 @@ class HttpCli(object):
2171
2178
  # small toctou, but better than clobbering a hardlink
2172
2179
  wunlink(self.log, path, vfs.flags)
2173
2180
 
2174
- halg = "sha512"
2175
2181
  hasher = None
2176
2182
  copier = hashcopy
2177
- if "ck" in self.ouparam or "ck" in self.headers:
2178
- halg = zs = self.ouparam.get("ck") or self.headers.get("ck") or ""
2179
- if not zs or zs == "no":
2180
- copier = justcopy
2181
- halg = ""
2182
- elif zs == "md5":
2183
- hasher = hashlib.md5(**USED4SEC)
2184
- elif zs == "sha1":
2185
- hasher = hashlib.sha1(**USED4SEC)
2186
- elif zs == "sha256":
2187
- hasher = hashlib.sha256(**USED4SEC)
2188
- elif zs in ("blake2", "b2"):
2189
- hasher = hashlib.blake2b(**USED4SEC)
2190
- elif zs in ("blake2s", "b2s"):
2191
- hasher = hashlib.blake2s(**USED4SEC)
2192
- elif zs == "sha512":
2193
- pass
2194
- else:
2195
- raise Pebkac(500, "unknown hash alg")
2183
+ halg = self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["put_ck"]
2184
+ if halg == "sha512":
2185
+ pass
2186
+ elif halg == "no":
2187
+ copier = justcopy
2188
+ halg = ""
2189
+ elif halg == "md5":
2190
+ hasher = hashlib.md5(**USED4SEC)
2191
+ elif halg == "sha1":
2192
+ hasher = hashlib.sha1(**USED4SEC)
2193
+ elif halg == "sha256":
2194
+ hasher = hashlib.sha256(**USED4SEC)
2195
+ elif halg in ("blake2", "b2"):
2196
+ hasher = hashlib.blake2b(**USED4SEC)
2197
+ elif halg in ("blake2s", "b2s"):
2198
+ hasher = hashlib.blake2s(**USED4SEC)
2199
+ else:
2200
+ raise Pebkac(500, "unknown hash alg")
2196
2201
 
2197
2202
  f, fn = ren_open(fn, *open_a, **params)
2198
2203
  try:
@@ -2580,10 +2585,6 @@ class HttpCli(object):
2580
2585
  x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
2581
2586
  ret = x.get()
2582
2587
 
2583
- if self.is_vproxied:
2584
- if "purl" in ret:
2585
- ret["purl"] = self.args.SR + ret["purl"]
2586
-
2587
2588
  if self.args.shr and self.vpath.startswith(self.args.shr1):
2588
2589
  # strip common suffix (uploader's folder structure)
2589
2590
  vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
@@ -2593,6 +2594,10 @@ class HttpCli(object):
2593
2594
  raise Pebkac(500, t % zt)
2594
2595
  ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
2595
2596
 
2597
+ if self.is_vproxied:
2598
+ if "purl" in ret:
2599
+ ret["purl"] = self.args.SR + ret["purl"]
2600
+
2596
2601
  ret = json.dumps(ret)
2597
2602
  self.log(ret)
2598
2603
  self.reply(ret.encode("utf-8"), mime="application/json")
@@ -2917,7 +2922,8 @@ class HttpCli(object):
2917
2922
  self.parser.drop()
2918
2923
 
2919
2924
  self.log("logout " + self.uname)
2920
- self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
2925
+ if not self.uname.startswith("s_"):
2926
+ self.asrv.forget_session(self.conn.hsrv.broker, self.uname)
2921
2927
  self.get_pwd_cookie("x")
2922
2928
 
2923
2929
  dst = self.args.SRS + "?h"
@@ -3068,15 +3074,18 @@ class HttpCli(object):
3068
3074
  vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
3069
3075
  self._assert_safe_rem(rem)
3070
3076
 
3071
- halg = "sha512"
3072
3077
  hasher = None
3073
- copier = hashcopy
3074
3078
  if nohash:
3075
3079
  halg = ""
3076
3080
  copier = justcopy
3077
- elif "ck" in self.ouparam or "ck" in self.headers:
3078
- halg = self.ouparam.get("ck") or self.headers.get("ck") or ""
3079
- if not halg or halg == "no":
3081
+ else:
3082
+ copier = hashcopy
3083
+ halg = (
3084
+ self.ouparam.get("ck") or self.headers.get("ck") or vfs.flags["bup_ck"]
3085
+ )
3086
+ if halg == "sha512":
3087
+ pass
3088
+ elif halg == "no":
3080
3089
  copier = justcopy
3081
3090
  halg = ""
3082
3091
  elif halg == "md5":
@@ -3089,8 +3098,6 @@ class HttpCli(object):
3089
3098
  hasher = hashlib.blake2b(**USED4SEC)
3090
3099
  elif halg in ("blake2s", "b2s"):
3091
3100
  hasher = hashlib.blake2s(**USED4SEC)
3092
- elif halg == "sha512":
3093
- pass
3094
3101
  else:
3095
3102
  raise Pebkac(500, "unknown hash alg")
3096
3103
 
@@ -3552,7 +3559,7 @@ class HttpCli(object):
3552
3559
  except:
3553
3560
  pass
3554
3561
  if dp:
3555
- wrename(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
3562
+ atomic_move(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
3556
3563
 
3557
3564
  p_field, _, p_data = next(self.parser.gen)
3558
3565
  if p_field != "body":
@@ -3961,7 +3968,7 @@ class HttpCli(object):
3961
3968
  for ext in ("", ".gz"):
3962
3969
  if ptop is not None:
3963
3970
  sz = job["size"]
3964
- file_ts = job["lmod"]
3971
+ file_ts = max(0, job["lmod"])
3965
3972
  editions["plain"] = (ap_data, sz)
3966
3973
  break
3967
3974
 
@@ -5482,6 +5489,7 @@ class HttpCli(object):
5482
5489
  raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
5483
5490
 
5484
5491
  pw = req.get("pw") or ""
5492
+ pw = self.asrv.ah.hash(pw)
5485
5493
  now = int(time.time())
5486
5494
  sexp = req["exp"]
5487
5495
  exp = int(sexp) if sexp else 0
@@ -6099,7 +6107,7 @@ class HttpCli(object):
6099
6107
  margin = "-"
6100
6108
 
6101
6109
  sz = inf.st_size
6102
- zd = datetime.fromtimestamp(linf.st_mtime, UTC)
6110
+ zd = datetime.fromtimestamp(max(0, linf.st_mtime), UTC)
6103
6111
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
6104
6112
  zd.year,
6105
6113
  zd.month,
copyparty/sutil.py CHANGED
@@ -11,6 +11,9 @@ from .bos import bos
11
11
  from .th_cli import ThumbCli
12
12
  from .util import UTC, vjoin, vol_san
13
13
 
14
+ TAR_NO_OPUS = set("aac|m4a|mp3|oga|ogg|opus|wma".split("|"))
15
+
16
+
14
17
  class StreamArc(object):
15
18
  def __init__(
16
19
  self,
@@ -76,9 +79,7 @@ def enthumb(
76
79
  ) :
77
80
  rem = f["vp"]
78
81
  ext = rem.rsplit(".", 1)[-1].lower()
79
- if (fmt == "mp3" and ext == "mp3") or (
80
- fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
81
- ):
82
+ if (fmt == "mp3" and ext == "mp3") or (fmt == "opus" and ext in TAR_NO_OPUS):
82
83
  raise Exception()
83
84
 
84
85
  vp = vjoin(vtop, rem.split("/", 1)[1])
copyparty/tftpd.py CHANGED
@@ -281,6 +281,7 @@ class Tftpd(object):
281
281
  if not ptn or not ptn.match(fn.lower()):
282
282
  return None
283
283
 
284
+ tsdt = datetime.fromtimestamp
284
285
  vn, rem = self.asrv.vfs.get(vpath, "*", True, False)
285
286
  fsroot, vfs_ls, vfs_virt = vn.ls(
286
287
  rem,
@@ -293,7 +294,7 @@ class Tftpd(object):
293
294
  dirs1 = [(v.st_mtime, v.st_size, k + "/") for k, v in vfs_ls if k in dnames]
294
295
  fils1 = [(v.st_mtime, v.st_size, k) for k, v in vfs_ls if k not in dnames]
295
296
  real1 = dirs1 + fils1
296
- realt = [(datetime.fromtimestamp(mt, UTC), sz, fn) for mt, sz, fn in real1]
297
+ realt = [(tsdt(max(0, mt), UTC), sz, fn) for mt, sz, fn in real1]
297
298
  reals = [
298
299
  (
299
300
  "%04d-%02d-%02d %02d:%02d:%02d"
copyparty/th_srv.py CHANGED
@@ -24,13 +24,13 @@ from .util import (
24
24
  Cooldown,
25
25
  Daemon,
26
26
  afsenc,
27
+ atomic_move,
27
28
  fsenc,
28
29
  min_ex,
29
30
  runcmd,
30
31
  statdir,
31
32
  ub64enc,
32
33
  vsplit,
33
- wrename,
34
34
  wunlink,
35
35
  )
36
36
 
@@ -409,7 +409,7 @@ class ThumbSrv(object):
409
409
  wunlink(self.log, ap_unpk, vn.flags)
410
410
 
411
411
  try:
412
- wrename(self.log, ttpath, tpath, vn.flags)
412
+ atomic_move(self.log, ttpath, tpath, vn.flags)
413
413
  except Exception as ex:
414
414
  if not os.path.exists(tpath):
415
415
  t = "failed to move [%s] to [%s]: %r"
@@ -673,7 +673,7 @@ class ThumbSrv(object):
673
673
  except:
674
674
  pass
675
675
  else:
676
- wrename(self.log, wtpath, tpath, vn.flags)
676
+ atomic_move(self.log, wtpath, tpath, vn.flags)
677
677
 
678
678
  def conv_spec(self, abspath , tpath , fmt , vn ) :
679
679
  ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
copyparty/up2k.py CHANGED
@@ -1112,7 +1112,7 @@ class Up2k(object):
1112
1112
  ft = "\033[0;32m{}{:.0}"
1113
1113
  ff = "\033[0;35m{}{:.0}"
1114
1114
  fv = "\033[0;36m{}:\033[90m{}"
1115
- zs = "ext_th_d html_head mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1115
+ zs = "ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
1116
1116
  fx = set(zs.split())
1117
1117
  fd = vf_bmap()
1118
1118
  fd.update(vf_cmap())
@@ -2110,11 +2110,12 @@ class Up2k(object):
2110
2110
  return -1
2111
2111
 
2112
2112
  w = bw[:-1].decode("ascii")
2113
+ w16 = w[:16]
2113
2114
 
2114
2115
  with self.mutex:
2115
2116
  try:
2116
2117
  q = "select rd, fn, ip, at from up where substr(w,1,16)=? and +w=?"
2117
- rd, fn, ip, at = cur.execute(q, (w[:16], w)).fetchone()
2118
+ rd, fn, ip, at = cur.execute(q, (w16, w)).fetchone()
2118
2119
  except:
2119
2120
  # file modified/deleted since spooling
2120
2121
  continue
@@ -2123,8 +2124,12 @@ class Up2k(object):
2123
2124
  rd, fn = s3dec(rd, fn)
2124
2125
 
2125
2126
  if "mtp" in flags:
2127
+ q = "select 1 from mt where w=? and +k='t:mtp' limit 1"
2128
+ if cur.execute(q, (w16,)).fetchone():
2129
+ continue
2130
+
2126
2131
  q = "insert into mt values (?,'t:mtp','a')"
2127
- cur.execute(q, (w[:16],))
2132
+ cur.execute(q, (w16,))
2128
2133
 
2129
2134
  abspath = djoin(ptop, rd, fn)
2130
2135
  self.pp.msg = "c%d %s" % (nq, abspath)
@@ -2180,7 +2185,7 @@ class Up2k(object):
2180
2185
  return tf, -1
2181
2186
 
2182
2187
  if flt == 1:
2183
- q = "select w from mt where w = ?"
2188
+ q = "select 1 from mt where w=? and +k != 't:mtp'"
2184
2189
  if c2.execute(q, (row[0][:16],)).fetchone():
2185
2190
  continue
2186
2191
 
@@ -3215,7 +3220,7 @@ class Up2k(object):
3215
3220
  if hr.get("reloc"):
3216
3221
  x = pathmod(self.vfs, dst, vp, hr["reloc"])
3217
3222
  if x:
3218
- zvfs = vfs
3223
+ ud1 = (vfs.vpath, job["prel"], job["name"])
3219
3224
  pdir, _, job["name"], (vfs, rem) = x
3220
3225
  dst = os.path.join(pdir, job["name"])
3221
3226
  job["vcfg"] = vfs.flags
@@ -3223,7 +3228,8 @@ class Up2k(object):
3223
3228
  job["vtop"] = vfs.vpath
3224
3229
  job["prel"] = rem
3225
3230
  job["name"] = sanitize_fn(job["name"], "")
3226
- if zvfs.vpath != vfs.vpath:
3231
+ ud2 = (vfs.vpath, job["prel"], job["name"])
3232
+ if ud1 != ud2:
3227
3233
  # print(json.dumps(job, sort_keys=True, indent=4))
3228
3234
  job["hash"] = cj["hash"]
3229
3235
  self.log("xbu reloc1:%d..." % (depth,), 6)
@@ -4970,14 +4976,15 @@ class Up2k(object):
4970
4976
  if hr.get("reloc"):
4971
4977
  x = pathmod(self.vfs, ap_chk, vp_chk, hr["reloc"])
4972
4978
  if x:
4973
- zvfs = vfs
4979
+ ud1 = (vfs.vpath, job["prel"], job["name"])
4974
4980
  pdir, _, job["name"], (vfs, rem) = x
4975
4981
  job["vcfg"] = vf = vfs.flags
4976
4982
  job["ptop"] = vfs.realpath
4977
4983
  job["vtop"] = vfs.vpath
4978
4984
  job["prel"] = rem
4979
4985
  job["name"] = sanitize_fn(job["name"], "")
4980
- if zvfs.vpath != vfs.vpath:
4986
+ ud2 = (vfs.vpath, job["prel"], job["name"])
4987
+ if ud1 != ud2:
4981
4988
  self.log("xbu reloc2:%d..." % (depth,), 6)
4982
4989
  return self._handle_json(job, depth + 1)
4983
4990
 
copyparty/util.py CHANGED
@@ -2497,6 +2497,11 @@ def _fs_mvrm(
2497
2497
  now = time.time()
2498
2498
  if ex.errno == errno.ENOENT:
2499
2499
  return False
2500
+ if not attempt and ex.errno == errno.EXDEV:
2501
+ t = "using copy+delete (%s)\n %s\n %s"
2502
+ log(t % (ex.strerror, src, dst))
2503
+ osfun = shutil.move
2504
+ continue
2500
2505
  if now - t0 > maxtime or attempt == 90209:
2501
2506
  raise
2502
2507
  if not attempt:
@@ -2521,15 +2526,18 @@ def atomic_move(log , src , dst , flags ) :
2521
2526
  elif flags.get("mv_re_t"):
2522
2527
  _fs_mvrm(log, src, dst, True, flags)
2523
2528
  else:
2524
- os.replace(bsrc, bdst)
2525
-
2526
-
2527
- def wrename(log , src , dst , flags ) :
2528
- if not flags.get("mv_re_t"):
2529
- os.rename(fsenc(src), fsenc(dst))
2530
- return True
2531
-
2532
- return _fs_mvrm(log, src, dst, False, flags)
2529
+ try:
2530
+ os.replace(bsrc, bdst)
2531
+ except OSError as ex:
2532
+ if ex.errno != errno.EXDEV:
2533
+ raise
2534
+ t = "using copy+delete (%s);\n %s\n %s"
2535
+ log(t % (ex.strerror, src, dst))
2536
+ try:
2537
+ os.unlink(bdst)
2538
+ except:
2539
+ pass
2540
+ shutil.move(bsrc, bdst)
2533
2541
 
2534
2542
 
2535
2543
  def wunlink(log , abspath , flags ) :
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.10"
5
- S_BUILD_DT = "2025-02-19"
4
+ S_VERSION = "2.11"
5
+ S_BUILD_DT = "2025-05-18"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -1287,7 +1287,7 @@ class Ctl(object):
1287
1287
  if self.ar.jw:
1288
1288
  print("%s %s" % (wark, vp))
1289
1289
  else:
1290
- zd = datetime.datetime.fromtimestamp(file.lmod, UTC)
1290
+ zd = datetime.datetime.fromtimestamp(max(0, file.lmod), UTC)
1291
1291
  dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
1292
1292
  zd.year,
1293
1293
  zd.month,
Binary file
Binary file
Binary file
copyparty/web/svcs.html CHANGED
@@ -101,6 +101,7 @@
101
101
  gio mount -a dav{{ s }}://{{ ep }}/{{ rvp }}
102
102
  {%- endif %}
103
103
  </pre>
104
+ <p>on KDE Dolphin, use <code>webdav{{ s }}://{{ ep }}/{{ rvp }}</code></p>
104
105
  </div>
105
106
 
106
107
  <div class="os mac">
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.17.0
3
+ Version: 1.17.2
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
@@ -72,6 +72,8 @@ turn almost any device into a file server with resumable uploads/downloads using
72
72
 
73
73
  🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
74
74
 
75
+ made in Norway 🇳🇴
76
+
75
77
 
76
78
  ## readme toc
77
79
 
@@ -162,7 +164,7 @@ turn almost any device into a file server with resumable uploads/downloads using
162
164
  * [feature chickenbits](#feature-chickenbits) - buggy feature? rip it out
163
165
  * [feature beefybits](#feature-beefybits) - force-enable features with known issues on your OS/env
164
166
  * [packages](#packages) - the party might be closer than you think
165
- * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
167
+ * [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
166
168
  * [fedora package](#fedora-package) - does not exist yet
167
169
  * [nix package](#nix-package) - `nix profile install github:9001/copyparty`
168
170
  * [nixos module](#nixos-module)
@@ -475,6 +477,9 @@ upgrade notes
475
477
 
476
478
  "frequently" asked questions
477
479
 
480
+ * CopyParty?
481
+ * nope! the name is either copyparty (all-lowercase) or Copyparty -- it's [one word](https://en.wiktionary.org/wiki/copyparty) after all :>
482
+
478
483
  * can I change the 🌲 spinning pine-tree loading animation?
479
484
  * [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
480
485
 
@@ -973,6 +978,7 @@ semi-intentional limitations:
973
978
 
974
979
  * cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
975
980
  * only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
981
+ * if you change [password hashing](#password-hashing) settings after creating a password-protected share, then that share will stop working
976
982
  * related to [IdP volumes being forgotten on shutdown](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#idp-volumes-are-forgotten-on-shutdown), any shares pointing into a user's IdP volume will be unavailable until that user makes their first request after a restart
977
983
  * no option to "delete after first access" because tricky
978
984
  * when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
@@ -2261,10 +2267,14 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on
2261
2267
 
2262
2268
  ## arch package
2263
2269
 
2264
- now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
2270
+ `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
2265
2271
 
2266
2272
  it comes with a [systemd service](./contrib/package/arch/copyparty.service) and expects to find one or more [config files](./docs/example.conf) in `/etc/copyparty.d/`
2267
2273
 
2274
+ after installing it, you may want to `cp /usr/lib/systemd/system/copyparty.service /etc/systemd/system/` and then `vim /etc/systemd/system/copyparty.service` to change what user/group it is running as (you only need to do this once)
2275
+
2276
+ NOTE: there used to be an aur package; this evaporated when copyparty was adopted by the official archlinux repos. If you're still using the aur package, please move
2277
+
2268
2278
 
2269
2279
  ## fedora package
2270
2280
 
@@ -2568,6 +2578,11 @@ below are some tweaks roughly ordered by usefulness:
2568
2578
 
2569
2579
  when uploading files,
2570
2580
 
2581
+ * when uploading from very fast storage (NVMe SSD) with chrome/firefox, enable `[wasm]` in the `[⚙️] settings` tab to more effectively use all CPU-cores for hashing
2582
+ * don't do this on Safari (runs faster without)
2583
+ * don't do this on older browsers; likely to provoke browser-bugs (browser eats all RAM and crashes)
2584
+ * can be made default-enabled serverside with `--nosubtle 137` (chrome v137+) or `--nosubtle 2` (chrome+firefox)
2585
+
2571
2586
  * chrome is recommended (unfortunately), at least compared to firefox:
2572
2587
  * up to 90% faster when hashing, especially on SSDs
2573
2588
  * up to 40% faster when uploading over extremely fast internets
@@ -1,17 +1,17 @@
1
1
  copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
2
- copyparty/__main__.py,sha256=dJaY-3WD04xo1rf2YsEhTsQ5ywg4qttImmL5PdO58Bo,120099
3
- copyparty/__version__.py,sha256=SayyDzASpFZQ7quvxJVu76GJKarGmrfz_Lfx8aEzc84,253
4
- copyparty/authsrv.py,sha256=S4HrUq-C6wpe_IT3KN75yo9GJw9Uzj5y558ooYCQqng,113286
2
+ copyparty/__main__.py,sha256=WRhqmrVEBU1_ejCNxEsn_5rhfrztjA-V04kZnUL_IIs,121255
3
+ copyparty/__version__.py,sha256=3LDY1k8_pNQJ6jOZqNHZXY9IYsLJzzrl1fDwi-GXsDw,253
4
+ copyparty/authsrv.py,sha256=aDPAs4XwchHA2CBor3K6CakXxljQ6AWOY1wTv5Jt2Iw,114130
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
- copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
10
- copyparty/cfg.py,sha256=EilhOIMLopgvn5j72MaRFS0q78K9Xf0NU0I7EAs3YAQ,14269
9
+ copyparty/cert.py,sha256=pSSeVYticrDsnsrdRtfpUQN-8WRObsqrYtSRroXmgxo,7992
10
+ copyparty/cfg.py,sha256=H2p2bHRWk2e4rOx5NZWUVHUcoETy3dtzDkeC8TdosRY,14522
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
13
13
  copyparty/ftpd.py,sha256=G7PApVIFeSzRo4-D-9uRb8NxYgz6nFwTEbrOk1ErYCU,17969
14
- copyparty/httpcli.py,sha256=2EEQfjIa2rDwcFslTKPAkPCS14a8Hj7TJj6c-khRIFk,222000
14
+ copyparty/httpcli.py,sha256=ze5Sl51itREFGiYM3p4at32Zol7RSJDHiDKNJel0MFk,222513
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
@@ -23,16 +23,16 @@ copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
23
23
  copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
- copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
26
+ copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
27
  copyparty/svchub.py,sha256=leERN449te_yBbyChcOenjY_nuNPoFJpncLHfDMvDYE,46618
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
29
  copyparty/tcpsrv.py,sha256=F5K4Qr4eBLfhdLT_39yDf6ftrhWuGTrd6DSqqp_6e-Q,20480
30
- copyparty/tftpd.py,sha256=HAXNbIM7I3yFng_a4ubLWGQ4trRTineAZsUPTZDWNQs,14001
30
+ copyparty/tftpd.py,sha256=tbnxUsilwyusrAUCVVjJUZnR9TIHDkE-99WLsUxAIGA,14029
31
31
  copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=ibEQL9LSYcwo7YXSaW5hNmH1SZex8Pud7-dsSkBRQ-A,32544
32
+ copyparty/th_srv.py,sha256=2omGprnKGWim6U7fjJAXI41UCZBSxRwZfS0rCDsUDps,32556
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
- copyparty/up2k.py,sha256=0Fj2yT2XnJ4MhwdEcDRs1T-gsdxVhdIxdaZSjGyGKBA,177682
35
- copyparty/util.py,sha256=SjG1pBPhv6XYa6uPbiOU8HRB6dDZqwjCRwUe22fLhdg,103117
34
+ copyparty/up2k.py,sha256=nnR_ZKaopSNBuAjSN3Q5G_UVe6GmYD1NkxJt5QQf02o,178079
35
+ copyparty/util.py,sha256=Keb-mlTq4rtWjv3utaGsKqwujHYPZLkaBZOeivnGxKc,103485
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
@@ -55,9 +55,9 @@ 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=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
- copyparty/web/browser.css.gz,sha256=bPS2U1pXtSBNjXFM9nZkg_exWP4N8kgYe4grEBOX1t8,11706
58
+ copyparty/web/browser.css.gz,sha256=-w-OUbKy0UdL9vYKAC95ayl4MesDAFjqrq60uAxPj4U,11728
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=RYVZRBltqZwUyDfBOrOz8S1iS-Bf0AKeI0lloXHrPs0,94249
60
+ copyparty/web/browser.js.gz,sha256=GEQR3qsVSFWoJun_cj-3ljE5ccL6pApSPwzNj36XToc,94957
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
@@ -79,16 +79,16 @@ copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,254
79
79
  copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,940
80
80
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
81
  copyparty/web/splash.html,sha256=ouFB1P9g0K-S3LZQtOLfNz3GLXIRjyETkxo9aJZhiYk,6249
82
- copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
83
- copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
82
+ copyparty/web/splash.js.gz,sha256=Qh0KoPWKoJ77cyzOwnhUaCTI5XUoPVV3YJURKklqpBg,2739
83
+ copyparty/web/svcs.html,sha256=cxgrhX9wD0Z_kvidry3aS9ubuGXYDj2f4ehq1X8T1EA,14227
84
84
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
85
  copyparty/web/ui.css.gz,sha256=iDjrmq32aDN6l2S5AjCQdKjD6bxmzP6ji2WjM1FjKiU,2819
86
- copyparty/web/up2k.js.gz,sha256=7AKmoJOtFh9tx3Ha7w2F-z69-XZo_LzyR3ilWnBO_D8,24524
87
- copyparty/web/util.js.gz,sha256=Sa7oyIKW0HtEQegmhUcrDwgty0lzqsMU0qn67pVvaZY,15231
88
- copyparty/web/w.hash.js.gz,sha256=JhJagnqIkcKng_hs6otEgzcuQE7keToG_r5dd2o3EfU,1108
86
+ copyparty/web/up2k.js.gz,sha256=vf9Kth2JQ4F-XmWhjcGXqy6gA4eOkg0-EfVjAGQsW5o,24794
87
+ copyparty/web/util.js.gz,sha256=VYXSNNvydhFx-LdPMNjSovMEi-9euYzH4tntPTH8wcg,15255
88
+ copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
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=oRKEApPuaXiOkmUyA-WGCDjtWsn8FirSW6nCJtx9sgk,53157
91
+ copyparty/web/a/u2c.py,sha256=auXzLj04dt_lw4H70PhNUK0GjrQEThrybo2-77SLsUg,53165
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.17.0.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.17.0.dist-info/METADATA,sha256=nKWqseMoVudhVi1HaLux7M1zJt1QLdpD4bXSUL4_eb4,162654
114
- copyparty-1.17.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
115
- copyparty-1.17.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.17.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.17.0.dist-info/RECORD,,
112
+ copyparty-1.17.2.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.17.2.dist-info/METADATA,sha256=kCbbfcG7Nl-bV-Opp56naw3fdqw3PYchKl5bXlamDXk,163776
114
+ copyparty-1.17.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
+ copyparty-1.17.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.17.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.17.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5