copyparty 1.18.8__py3-none-any.whl → 1.18.10__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/__init__.py CHANGED
@@ -60,10 +60,6 @@ web/browser.js
60
60
  web/browser2.html
61
61
  web/cf.html
62
62
  web/copyparty.gif
63
- web/dd/2.png
64
- web/dd/3.png
65
- web/dd/4.png
66
- web/dd/5.png
67
63
  web/deps/busy.mp3
68
64
  web/deps/easymde.css
69
65
  web/deps/easymde.js
copyparty/__main__.py CHANGED
@@ -87,6 +87,10 @@ u = unicode
87
87
  printed = []
88
88
  zsid = uuid.uuid4().urn[4:]
89
89
 
90
+ CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
91
+ if not CFG_DEF[0]:
92
+ CFG_DEF.pop()
93
+
90
94
 
91
95
  class RiceFormatter(argparse.HelpFormatter):
92
96
  def __init__(self, *args , **kwargs ) :
@@ -566,7 +570,7 @@ def get_sects():
566
570
 
567
571
  --grp takes groupname:username1,username2,...
568
572
  and groupnames can be used instead of usernames in -v
569
- by prefixing the groupname with %
573
+ by prefixing the groupname with @
570
574
 
571
575
  list of permissions:
572
576
  "r" (read): list folder contents, download files
@@ -982,7 +986,7 @@ def build_flags_desc():
982
986
 
983
987
  def add_general(ap, nc, srvname):
984
988
  ap2 = ap.add_argument_group('general options')
985
- ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
989
+ ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file")
986
990
  ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
987
991
  ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
988
992
  ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
@@ -1101,7 +1105,7 @@ def add_tls(ap, cert_path):
1101
1105
  ap2 = ap.add_argument_group('SSL/TLS options')
1102
1106
  ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
1103
1107
  ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
1104
- ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to TLS certificate")
1108
+ ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
1105
1109
  ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
1106
1110
  ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
1107
1111
  ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
@@ -1138,6 +1142,7 @@ def add_auth(ap):
1138
1142
  ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)")
1139
1143
  ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
1140
1144
  ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
1145
+ ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
1141
1146
  ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
1142
1147
  ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
1143
1148
  ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
@@ -1435,6 +1440,8 @@ def add_transcoding(ap):
1435
1440
  ap2 = ap.add_argument_group('transcoding options')
1436
1441
  ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
1437
1442
  ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
1443
+ ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)")
1444
+ ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)")
1438
1445
  ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead")
1439
1446
  ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead")
1440
1447
  ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
@@ -1540,6 +1547,7 @@ def add_ui(ap, retry):
1540
1547
  ap2 = ap.add_argument_group('ui options')
1541
1548
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1542
1549
  ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
1550
+ ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
1543
1551
  ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
1544
1552
  ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1545
1553
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
@@ -1552,7 +1560,7 @@ def add_ui(ap, retry):
1552
1560
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1553
1561
  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")
1554
1562
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
1555
- 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])")
1563
+ ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1556
1564
  ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
1557
1565
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1558
1566
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
@@ -1777,7 +1785,7 @@ def main(argv = None) :
1777
1785
  argv[idx] = nk + ov
1778
1786
  time.sleep(2)
1779
1787
 
1780
- da = len(argv) == 1
1788
+ da = len(argv) == 1 and not CFG_DEF
1781
1789
  try:
1782
1790
  if da:
1783
1791
  argv.extend(["--qr"])
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 8)
3
+ VERSION = (1, 18, 10)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 7, 31)
5
+ BUILD_DT = (2025, 8, 4)
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
@@ -1705,7 +1705,8 @@ class AuthSrv(object):
1705
1705
  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"
1706
1706
  self.log(t, 1)
1707
1707
  axs = AXS()
1708
- vfs = VFS(self.log_func, absreal("."), "", "", axs, self.vf0())
1708
+ zvf = {"tcolor": self.args.tcolor}
1709
+ vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
1709
1710
  if not axs.uread:
1710
1711
  self.badcfg1 = True
1711
1712
  elif "" not in mount:
@@ -2742,6 +2743,8 @@ class AuthSrv(object):
2742
2743
  "s_name": self.args.bname,
2743
2744
  "have_up2k_idx": "e2d" in vf,
2744
2745
  "have_acode": not self.args.no_acode,
2746
+ "have_c2flac": self.args.allow_flac,
2747
+ "have_c2wav": self.args.allow_wav,
2745
2748
  "have_shr": self.args.shr,
2746
2749
  "have_zip": not self.args.no_zip,
2747
2750
  "have_mv": not self.args.no_mv,
@@ -2766,6 +2769,7 @@ class AuthSrv(object):
2766
2769
  "dth3x": vf["th3x"],
2767
2770
  "dvol": self.args.au_vol,
2768
2771
  "idxh": int(self.args.ih),
2772
+ "dutc": not self.args.localtime,
2769
2773
  "themes": self.args.themes,
2770
2774
  "turbolvl": self.args.turbo,
2771
2775
  "nosubtle": self.args.nosubtle,
@@ -2853,7 +2857,10 @@ class AuthSrv(object):
2853
2857
 
2854
2858
  n = []
2855
2859
  q = "insert into us values (?,?,?)"
2856
- for uname in self.acct:
2860
+ accs = list(self.acct)
2861
+ if self.args.idp_h_usr and self.args.idp_cookie:
2862
+ accs.extend(self.idp_accs.keys())
2863
+ for uname in accs:
2857
2864
  if uname not in ases:
2858
2865
  sid = ub64enc(os.urandom(blen)).decode("ascii")
2859
2866
  cur.execute(q, (uname, sid, int(time.time())))
@@ -3444,7 +3451,7 @@ def expand_config_file(
3444
3451
  ipath += " -> " + fp
3445
3452
  ret.append("#\033[36m opening cfg file{}\033[0m".format(ipath))
3446
3453
 
3447
- cfg_lines = read_utf8(log, fp, True).split("\n")
3454
+ cfg_lines = read_utf8(log, fp, True).replace("\t", " ").split("\n")
3448
3455
  if True: # diff-golf
3449
3456
  for oln in [x.rstrip() for x in cfg_lines]:
3450
3457
  ln = oln.split(" #")[0].strip()
copyparty/httpcli.py CHANGED
@@ -107,6 +107,7 @@ from .util import (
107
107
  sendfile_py,
108
108
  set_fperms,
109
109
  stat_resource,
110
+ str_anchor,
110
111
  ub64dec,
111
112
  ub64enc,
112
113
  ujoin,
@@ -652,6 +653,9 @@ class HttpCli(object):
652
653
  self.pw = ""
653
654
  self.uname = idp_usr
654
655
  self.html_head += "<script>var is_idp=1</script>\n"
656
+ zs = self.asrv.ases.get(idp_usr)
657
+ if zs:
658
+ self.set_idp_cookie(zs)
655
659
  else:
656
660
  self.log("unknown username: %r" % (idp_usr,), 1)
657
661
 
@@ -906,7 +910,7 @@ class HttpCli(object):
906
910
  if status == 304:
907
911
  self.out_headers.pop("Content-Length", None)
908
912
  self.out_headers.pop("Content-Type", None)
909
- self.out_headerlist.clear()
913
+ self.out_headerlist[:] = []
910
914
  if self.k304():
911
915
  self.keepalive = False
912
916
  else:
@@ -1190,15 +1194,6 @@ class HttpCli(object):
1190
1194
  self.reply(b"ssdp is disabled in server config", 404)
1191
1195
  return False
1192
1196
 
1193
- if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
1194
- if self.args.mpmc == ".":
1195
- raise Pebkac(404)
1196
-
1197
- loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
1198
- h = {"Location": loc, "Cache-Control": "max-age=39"}
1199
- self.reply(b"", 301, headers=h)
1200
- return True
1201
-
1202
1197
  if self.vpath == ".cpr/metrics":
1203
1198
  return self.conn.hsrv.metrics.tx(self)
1204
1199
 
@@ -2074,16 +2069,16 @@ class HttpCli(object):
2074
2069
  rnd, lifetime, xbu, xau = self.upload_flags(vfs)
2075
2070
  lim = vfs.get_dbv(rem)[0].lim
2076
2071
  fdir = vfs.canonical(rem)
2077
- if lim:
2078
- fdir, rem = lim.all(
2079
- self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2080
- )
2081
-
2082
2072
  fn = None
2083
2073
  if rem and not self.trailing_slash and not bos.path.isdir(fdir):
2084
2074
  fdir, fn = os.path.split(fdir)
2085
2075
  rem, _ = vsplit(rem)
2086
2076
 
2077
+ if lim:
2078
+ fdir, rem = lim.all(
2079
+ self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
2080
+ )
2081
+
2087
2082
  bos.makedirs(fdir, vf=vfs.flags)
2088
2083
 
2089
2084
  open_ka = {"fun": open}
@@ -2928,7 +2923,7 @@ class HttpCli(object):
2928
2923
  msg = "new password OK"
2929
2924
 
2930
2925
  redir = (self.args.SRS + "?h") if ok else ""
2931
- h2 = '<a href="' + self.args.SRS + '?h">ack</a>'
2926
+ h2 = '<a href="' + self.args.SRS + '?h">continue</a>'
2932
2927
  html = self.j2s("msg", h1=msg, h2=h2, redir=redir)
2933
2928
  self.reply(html.encode("utf-8"))
2934
2929
  return True
@@ -2956,7 +2951,8 @@ class HttpCli(object):
2956
2951
  dst += "_=1#" + html_escape(uhash, True, True)
2957
2952
 
2958
2953
  _, msg = self.get_pwd_cookie(pwd)
2959
- html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
2954
+ h2 = '<a href="' + dst + '">continue</a>'
2955
+ html = self.j2s("msg", h1=msg, h2=h2, redir=dst)
2960
2956
  self.reply(html.encode("utf-8"))
2961
2957
  return True
2962
2958
 
@@ -2969,7 +2965,7 @@ class HttpCli(object):
2969
2965
  self.get_pwd_cookie("x")
2970
2966
 
2971
2967
  dst = self.args.SRS + "?h"
2972
- h2 = '<a href="' + dst + '">ack</a>'
2968
+ h2 = '<a href="' + dst + '">continue</a>'
2973
2969
  html = self.j2s("msg", h1="ok bye", h2=h2, redir=dst)
2974
2970
  self.reply(html.encode("utf-8"))
2975
2971
  return True
@@ -3022,6 +3018,19 @@ class HttpCli(object):
3022
3018
 
3023
3019
  return dur > 0, msg
3024
3020
 
3021
+ def set_idp_cookie(self, ases) :
3022
+ k = "cppws" if self.is_https else "cppwd"
3023
+ ck = gencookie(
3024
+ k,
3025
+ ases,
3026
+ self.args.R,
3027
+ self.args.cookie_lax,
3028
+ self.is_https,
3029
+ self.args.idp_cookie,
3030
+ "; HttpOnly",
3031
+ )
3032
+ self.out_headers["Set-Cookie"] = ck
3033
+
3025
3034
  def handle_mkdir(self) :
3026
3035
  new_dir = self.parser.require("name", 512)
3027
3036
  self.parser.drop()
@@ -3093,6 +3102,20 @@ class HttpCli(object):
3093
3102
  if "fperms" in vfs.flags:
3094
3103
  set_fperms(f, vfs.flags)
3095
3104
 
3105
+ dbv, vrem = vfs.get_dbv(rem)
3106
+ self.conn.hsrv.broker.say(
3107
+ "up2k.hash_file",
3108
+ dbv.realpath,
3109
+ dbv.vpath,
3110
+ dbv.flags,
3111
+ vrem,
3112
+ sanitized,
3113
+ self.ip,
3114
+ bos.stat(fn).st_mtime,
3115
+ self.uname,
3116
+ True,
3117
+ )
3118
+
3096
3119
  vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
3097
3120
  self.redirect(vpath, "?edit")
3098
3121
  return True
@@ -4646,7 +4669,7 @@ class HttpCli(object):
4646
4669
  # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
4647
4670
  cfmt = ""
4648
4671
  if self.thumbcli and not self.args.no_bacode:
4649
- for zs in ("opus", "mp3", "w", "j", "p"):
4672
+ for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"):
4650
4673
  if zs in self.ouparam or uarg == zs:
4651
4674
  cfmt = zs
4652
4675
 
@@ -5331,15 +5354,16 @@ class HttpCli(object):
5331
5354
  raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
5332
5355
  raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
5333
5356
 
5334
- zs = self.uparam.get("filter") or ""
5335
- filt = re.compile(zs, re.I) if zs else None
5336
- lm = "ups %r" % (zs,)
5357
+ sfilt = self.uparam.get("filter") or ""
5358
+ nfi, vfi = str_anchor(sfilt)
5359
+ lm = "ups %d%r" % (nfi, sfilt)
5337
5360
 
5338
5361
  if self.args.shr and self.vpath.startswith(self.args.shr1):
5339
5362
  shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
5340
5363
  else:
5341
5364
  shr_dbv = None
5342
5365
 
5366
+ wret = {}
5343
5367
  ret = []
5344
5368
  t0 = time.time()
5345
5369
  lim = time.time() - self.args.unpost
@@ -5361,7 +5385,13 @@ class HttpCli(object):
5361
5385
  x = self.conn.hsrv.broker.ask(
5362
5386
  "up2k.get_unfinished_by_user", self.uname, "" if bad_xff else self.ip
5363
5387
  )
5364
- uret = x.get()
5388
+ zdsa = x.get()
5389
+ uret = []
5390
+ if "timeout" in zdsa:
5391
+ wret["nou"] = 1
5392
+ else:
5393
+ uret = zdsa["f"]
5394
+ nu = len(uret)
5365
5395
 
5366
5396
  if not self.args.unpost:
5367
5397
  allvols = []
@@ -5386,8 +5416,14 @@ class HttpCli(object):
5386
5416
  q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
5387
5417
  for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
5388
5418
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5389
- if filt and not filt.search(vp):
5390
- continue
5419
+ if nfi == 0 or (nfi == 1 and vfi in vp):
5420
+ pass
5421
+ elif nfi == 2:
5422
+ if not vp.startswith(vfi):
5423
+ continue
5424
+ elif nfi == 3:
5425
+ if not vp.endswith(vfi):
5426
+ continue
5391
5427
 
5392
5428
  n -= 1
5393
5429
  if not n:
@@ -5407,6 +5443,8 @@ class HttpCli(object):
5407
5443
 
5408
5444
  if len(ret) > 2000:
5409
5445
  ret = ret[:2000]
5446
+ if len(ret) >= 2000:
5447
+ wret["oc"] = 1
5410
5448
 
5411
5449
  for rv in ret:
5412
5450
  rv["vp"] = quotep(rv["vp"])
@@ -5426,6 +5464,13 @@ class HttpCli(object):
5426
5464
  )
5427
5465
  rv["vp"] += "?k=" + fk[:nfk]
5428
5466
 
5467
+ if not allvols:
5468
+ wret["noc"] = 1
5469
+ ret = []
5470
+
5471
+ nc = len(ret)
5472
+ ret = uret + ret
5473
+
5429
5474
  if shr_dbv:
5430
5475
  # translate vpaths from share-target to share-url
5431
5476
  # to satisfy access checks
@@ -5439,12 +5484,11 @@ class HttpCli(object):
5439
5484
  for v in ret:
5440
5485
  v["vp"] = self.args.SR + v["vp"]
5441
5486
 
5442
- if not allvols:
5443
- ret = [{"kinshi": 1}]
5444
-
5445
- jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, separators=(",\n", ": ")))
5446
- zi = len(uret.split('\n"pd":')) - 1
5447
- self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
5487
+ wret["f"] = ret
5488
+ wret["nu"] = nu
5489
+ wret["nc"] = nc
5490
+ jtxt = json.dumps(wret, separators=(",\n", ": "))
5491
+ self.log("%s #%d+%d %.2fsec" % (lm, nu, nc, time.time() - t0))
5448
5492
  self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
5449
5493
  return True
5450
5494
 
@@ -5459,8 +5503,8 @@ class HttpCli(object):
5459
5503
  raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
5460
5504
 
5461
5505
  sfilt = self.uparam.get("filter") or ""
5462
- filt = re.compile(sfilt, re.I) if sfilt else None
5463
- lm = "ru %r" % (sfilt,)
5506
+ nfi, vfi = str_anchor(sfilt)
5507
+ lm = "ru %d%r" % (nfi, sfilt)
5464
5508
  self.log(lm)
5465
5509
 
5466
5510
  ret = []
@@ -5495,8 +5539,14 @@ class HttpCli(object):
5495
5539
  q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
5496
5540
  for sz, rd, fn, ip, at in cur.execute(q):
5497
5541
  vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
5498
- if filt and not filt.search(vp):
5499
- continue
5542
+ if nfi == 0 or (nfi == 1 and vfi in vp):
5543
+ pass
5544
+ elif nfi == 2:
5545
+ if not vp.startswith(vfi):
5546
+ continue
5547
+ elif nfi == 3:
5548
+ if not vp.endswith(vfi):
5549
+ continue
5500
5550
 
5501
5551
  if not dots and "/." in vp:
5502
5552
  continue
copyparty/httpsrv.py CHANGED
@@ -319,7 +319,8 @@ class HttpSrv(object):
319
319
  spins = 0
320
320
  while self.ncli >= self.nclimax:
321
321
  if not spins:
322
- self.log(self.name, "at connection limit; waiting", 3)
322
+ t = "at connection limit (global-option 'nc'); waiting"
323
+ self.log(self.name, t, 3)
323
324
 
324
325
  spins += 1
325
326
  time.sleep(0.1)
copyparty/mtag.py CHANGED
@@ -61,6 +61,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
61
61
  CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
62
62
  CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
63
63
 
64
+ FMT_AU = set("mp3 ogg flac wav".split())
65
+
64
66
 
65
67
  class MParser(object):
66
68
  def __init__(self, cmdline ) :
@@ -236,7 +238,7 @@ def parse_ffprobe(txt ) :
236
238
  ret = {} # processed
237
239
  md = {} # raw tags
238
240
 
239
- is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"]
241
+ is_audio = fmt.get("format_name") in FMT_AU
240
242
  if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
241
243
  is_audio = True
242
244
 
@@ -264,6 +266,8 @@ def parse_ffprobe(txt ) :
264
266
  ["channel_layout", "chs"],
265
267
  ["sample_rate", ".hz"],
266
268
  ["bit_rate", ".aq"],
269
+ ["bits_per_sample", ".bps"],
270
+ ["bits_per_raw_sample", ".bprs"],
267
271
  ["duration", ".dur"],
268
272
  ]
269
273
 
copyparty/svchub.py CHANGED
@@ -1016,6 +1016,8 @@ class SvcHub(object):
1016
1016
  except:
1017
1017
  raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
1018
1018
 
1019
+ al.js_utc = "false" if al.localtime else "true"
1020
+
1019
1021
  al.tcolor = al.tcolor.lstrip("#")
1020
1022
  if len(al.tcolor) == 3: # fc5 => ffcc55
1021
1023
  al.tcolor = "".join([x * 2 for x in al.tcolor])
copyparty/tcpsrv.py CHANGED
@@ -580,8 +580,7 @@ class TcpSrv(object):
580
580
  if not ip:
581
581
  return ""
582
582
 
583
- if ":" in ip:
584
- ip = "[{}]".format(ip)
583
+ hip = "[%s]" % (ip,) if ":" in ip else ip
585
584
 
586
585
  if self.args.http_only:
587
586
  https = ""
@@ -593,7 +592,7 @@ class TcpSrv(object):
593
592
  ports = t1.get(ip, t2.get(ip, []))
594
593
  dport = 443 if https else 80
595
594
  port = "" if dport in ports or not ports else ":{}".format(ports[0])
596
- txt = "http{}://{}{}/{}".format(https, ip, port, self.args.qrl)
595
+ txt = "http{}://{}{}/{}".format(https, hip, port, self.args.qrl)
597
596
 
598
597
  btxt = txt.encode("utf-8")
599
598
  if PY2:
copyparty/th_cli.py CHANGED
@@ -85,7 +85,7 @@ class ThumbCli(object):
85
85
  if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
86
86
  return os.path.join(ptop, rem)
87
87
 
88
- if fmt[:1] in "jw":
88
+ if fmt[:1] in "jw" and fmt != "wav":
89
89
  sfmt = fmt[:1]
90
90
 
91
91
  if sfmt == "j" and self.args.th_no_jpg:
@@ -126,7 +126,7 @@ class ThumbCli(object):
126
126
 
127
127
  tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
128
128
  tpaths = [tpath]
129
- if fmt[:1] == "w":
129
+ if fmt[:1] == "w" and fmt != "wav":
130
130
  # also check for jpg (maybe webp is unavailable)
131
131
  tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
132
132
 
copyparty/th_srv.py CHANGED
@@ -47,7 +47,7 @@ HAVE_AVIF = False
47
47
  HAVE_WEBP = False
48
48
 
49
49
  EXTS_TH = set(["jpg", "webp", "png"])
50
- EXTS_AC = set(["opus", "owa", "caf", "mp3"])
50
+ EXTS_AC = set(["opus", "owa", "caf", "mp3", "flac", "wav"])
51
51
  EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
52
52
 
53
53
  PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
@@ -352,8 +352,10 @@ class ThumbSrv(object):
352
352
  tex = tpath.rsplit(".", 1)[-1]
353
353
  want_mp3 = tex == "mp3"
354
354
  want_opus = tex in ("opus", "owa", "caf")
355
+ want_flac = tex == "flac"
356
+ want_wav = tex == "wav"
355
357
  want_png = tex == "png"
356
- want_au = want_mp3 or want_opus
358
+ want_au = want_mp3 or want_opus or want_flac or want_wav
357
359
  for lib in self.args.th_dec:
358
360
  can_au = lib == "ff" and (
359
361
  ext in self.fmt_ffa or ext in self.fmt_ffv
@@ -368,6 +370,10 @@ class ThumbSrv(object):
368
370
  funs.append(self.conv_opus)
369
371
  elif want_mp3:
370
372
  funs.append(self.conv_mp3)
373
+ elif want_flac:
374
+ funs.append(self.conv_flac)
375
+ elif want_wav:
376
+ funs.append(self.conv_wav)
371
377
  elif want_png:
372
378
  funs.append(self.conv_waves)
373
379
  png_ok = True
@@ -803,6 +809,66 @@ class ThumbSrv(object):
803
809
  # fmt: on
804
810
  self._run_ff(cmd, vn, oom=300)
805
811
 
812
+ def conv_flac(self, abspath , tpath , fmt , vn ) :
813
+ if self.args.no_acode or not self.args.allow_flac:
814
+ raise Exception("flac not permitted in server config")
815
+
816
+ self.wait4ram(0.2, tpath)
817
+ tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
818
+ if "ac" not in tags:
819
+ raise Exception("not audio")
820
+
821
+ self.log("conv2 flac", 6)
822
+
823
+ # fmt: off
824
+ cmd = [
825
+ b"ffmpeg",
826
+ b"-nostdin",
827
+ b"-v", b"error",
828
+ b"-hide_banner",
829
+ b"-i", fsenc(abspath),
830
+ b"-map", b"0:a:0",
831
+ b"-c:a", b"flac",
832
+ fsenc(tpath)
833
+ ]
834
+ # fmt: on
835
+ self._run_ff(cmd, vn, oom=300)
836
+
837
+ def conv_wav(self, abspath , tpath , fmt , vn ) :
838
+ if self.args.no_acode or not self.args.allow_wav:
839
+ raise Exception("wav not permitted in server config")
840
+
841
+ self.wait4ram(0.2, tpath)
842
+ tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
843
+ if "ac" not in tags:
844
+ raise Exception("not audio")
845
+
846
+ bits = tags[".bps"][1]
847
+ if bits == 0.0:
848
+ bits = tags[".bprs"][1]
849
+
850
+ codec = b"pcm_s32le"
851
+ if bits <= 16.0:
852
+ codec = b"pcm_s16le"
853
+ elif bits <= 24.0:
854
+ codec = b"pcm_s24le"
855
+
856
+ self.log("conv2 wav", 6)
857
+
858
+ # fmt: off
859
+ cmd = [
860
+ b"ffmpeg",
861
+ b"-nostdin",
862
+ b"-v", b"error",
863
+ b"-hide_banner",
864
+ b"-i", fsenc(abspath),
865
+ b"-map", b"0:a:0",
866
+ b"-c:a", codec,
867
+ fsenc(tpath)
868
+ ]
869
+ # fmt: on
870
+ self._run_ff(cmd, vn, oom=300)
871
+
806
872
  def conv_opus(self, abspath , tpath , fmt , vn ) :
807
873
  if self.args.no_acode or not self.args.q_opus:
808
874
  raise Exception("disabled in server config")
copyparty/up2k.py CHANGED
@@ -83,7 +83,10 @@ if TYPE_CHECKING:
83
83
  from .svchub import SvcHub
84
84
 
85
85
  zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
86
- CV_EXTS = set(zsg.split(","))
86
+ ICV_EXTS = set(zsg.split(","))
87
+
88
+ zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
89
+ VCV_EXTS = set(zsg.split(","))
87
90
 
88
91
  zsg = "nohash noidx xdev xvol"
89
92
  VF_AFFECTS_INDEXING = set(zsg.split(" "))
@@ -395,12 +398,14 @@ class Up2k(object):
395
398
 
396
399
  return "{}"
397
400
 
398
- def get_unfinished_by_user(self, uname, ip) :
401
+ def get_unfinished_by_user(self, uname, ip) :
402
+ # returns dict due to ExceptionalQueue
399
403
  if PY2 or not self.reg_mutex.acquire(timeout=2):
400
- return '[{"timeout":1}]'
404
+ return {"timeout": 1}
401
405
 
402
406
  ret = []
403
407
  userset = set([(uname or "\n"), "*"])
408
+ n = 1000
404
409
  try:
405
410
  for ptop, tab2 in self.registry.items():
406
411
  cfg = self.flags.get(ptop, {}).get("u2abort", 1)
@@ -415,7 +420,6 @@ class Up2k(object):
415
420
  or (addr and addr != job["addr"])
416
421
  ):
417
422
  continue
418
-
419
423
  zt5 = (
420
424
  int(job["t0"]),
421
425
  djoin(job["vtop"], job["prel"], job["name"]),
@@ -424,6 +428,9 @@ class Up2k(object):
424
428
  len(job["hash"]),
425
429
  )
426
430
  ret.append(zt5)
431
+ n -= 1
432
+ if not n:
433
+ break
427
434
  finally:
428
435
  self.reg_mutex.release()
429
436
 
@@ -440,7 +447,7 @@ class Up2k(object):
440
447
  }
441
448
  for (at, vp, sz, nn, nh) in ret
442
449
  ]
443
- return json.dumps(ret2, separators=(",\n", ": "))
450
+ return {"f": ret2}
444
451
 
445
452
  def get_unfinished(self) :
446
453
  if PY2 or not self.reg_mutex.acquire(timeout=0.5):
@@ -1466,7 +1473,7 @@ class Up2k(object):
1466
1473
  unreg = []
1467
1474
  files = []
1468
1475
  fat32 = True
1469
- cv = ""
1476
+ cv = vcv = ""
1470
1477
 
1471
1478
  th_cvd = self.args.th_coversd
1472
1479
  th_cvds = self.args.th_coversd_set
@@ -1560,25 +1567,24 @@ class Up2k(object):
1560
1567
 
1561
1568
  rsz += sz
1562
1569
  files.append((sz, lmod, iname))
1563
- liname = iname.lower()
1564
- if (
1565
- sz
1566
- and (
1570
+ if sz:
1571
+ liname = iname.lower()
1572
+ ext = liname.rsplit(".", 1)[-1]
1573
+ if (
1567
1574
  liname in th_cvds
1568
- or (
1569
- not cv
1570
- and liname.rsplit(".", 1)[-1] in CV_EXTS
1571
- and not iname.startswith(".")
1572
- )
1573
- )
1574
- and (
1575
+ or (not cv and ext in ICV_EXTS and not iname.startswith("."))
1576
+ ) and (
1575
1577
  not cv
1576
1578
  or liname not in th_cvds
1577
1579
  or cv.lower() not in th_cvds
1578
1580
  or th_cvd.index(liname) < th_cvd.index(cv.lower())
1579
- )
1580
- ):
1581
- cv = iname
1581
+ ):
1582
+ cv = iname
1583
+ elif not vcv and ext in VCV_EXTS and not iname.startswith("."):
1584
+ vcv = iname
1585
+
1586
+ if not cv:
1587
+ cv = vcv
1582
1588
 
1583
1589
  if not self.args.no_dirsz:
1584
1590
  tnf += len(files)
@@ -2812,7 +2818,7 @@ class Up2k(object):
2812
2818
  # v5a -> v5b
2813
2819
  # store rd+fn rather than warks to support nohash vols
2814
2820
  try:
2815
- cur.execute("select ws, rd, fn from iu limit 1").fetchone()
2821
+ cur.execute("select c, w, rd, fn from iu limit 1").fetchone()
2816
2822
  return
2817
2823
  except:
2818
2824
  pass
copyparty/util.py CHANGED
@@ -2310,6 +2310,21 @@ def ujoin(rd , fn ) :
2310
2310
  return rd or fn
2311
2311
 
2312
2312
 
2313
+ def str_anchor(txt) :
2314
+ if not txt:
2315
+ return 0, ""
2316
+ txt = txt.lower()
2317
+ a = txt.startswith("^")
2318
+ b = txt.endswith("$")
2319
+ if not b:
2320
+ if not a:
2321
+ return 1, txt # ~
2322
+ return 2, txt[1:] # ^
2323
+ if not a:
2324
+ return 3, txt[:-1] # $
2325
+ return 4, txt[1:-1] # ^$
2326
+
2327
+
2313
2328
  def log_reloc(
2314
2329
  log ,
2315
2330
  re ,
copyparty/web/a/u2c.py CHANGED
@@ -52,6 +52,7 @@ if PY2:
52
52
 
53
53
  sys.dont_write_bytecode = True
54
54
  bytes = str
55
+ files_decoder = lambda s: unicode(s, "utf8")
55
56
  else:
56
57
  from urllib.parse import quote_from_bytes as quote
57
58
  from urllib.parse import unquote_to_bytes as unquote
@@ -61,6 +62,7 @@ else:
61
62
  from queue import Queue
62
63
 
63
64
  unicode = str
65
+ files_decoder = unicode
64
66
 
65
67
 
66
68
  WTF8 = "replace" if PY2 else "surrogateescape"
@@ -1530,7 +1532,7 @@ source file/folder selection uses rsync syntax, meaning that:
1530
1532
  """)
1531
1533
 
1532
1534
  ap.add_argument("url", type=unicode, help="server url, including destination folder")
1533
- ap.add_argument("files", type=unicode, nargs="+", help="files and/or folders to process")
1535
+ ap.add_argument("files", type=files_decoder, nargs="+", help="files and/or folders to process")
1534
1536
  ap.add_argument("-v", action="store_true", help="verbose")
1535
1537
  ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
1536
1538
  ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
Binary file
@@ -110,7 +110,7 @@
110
110
  <tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
111
111
  {%- if f.tags is defined %}
112
112
  {%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
113
- {%- endif %}<td>{{ f.ext|e }}</td><td>{{ f.dt }}</td></tr>
113
+ {%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
114
114
  {%- endfor %}
115
115
 
116
116
  </tbody>
Binary file
copyparty/web/rups.html CHANGED
@@ -33,6 +33,7 @@
33
33
 
34
34
  var SR="{{ r }}",
35
35
  lang="{{ lang }}",
36
+ dutc={{ this.args.js_utc }},
36
37
  dfavico="{{ favico }}";
37
38
 
38
39
  var STG = window.localStorage;
copyparty/web/rups.js.gz CHANGED
Binary file
copyparty/web/shares.html CHANGED
@@ -66,6 +66,7 @@
66
66
  var SR="{{ r }}",
67
67
  shr="{{ shr }}",
68
68
  lang="{{ lang }}",
69
+ dutc={{ this.args.js_utc }},
69
70
  dfavico="{{ favico }}";
70
71
 
71
72
  var STG = window.localStorage;
Binary file
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.4
2
2
  Name: copyparty
3
- Version: 1.18.8
3
+ Version: 1.18.10
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
@@ -37,6 +37,13 @@ Requires-Python: >=3.3
37
37
  Description-Content-Type: text/markdown
38
38
  License-File: LICENSE
39
39
  Requires-Dist: Jinja2
40
+ Provides-Extra: all
41
+ Requires-Dist: argon2-cffi; extra == "all"
42
+ Requires-Dist: partftpy>=0.4.0; extra == "all"
43
+ Requires-Dist: Pillow; extra == "all"
44
+ Requires-Dist: pyftpdlib; extra == "all"
45
+ Requires-Dist: pyopenssl; extra == "all"
46
+ Requires-Dist: pyzmq; extra == "all"
40
47
  Provides-Extra: thumbnails
41
48
  Requires-Dist: Pillow; extra == "thumbnails"
42
49
  Provides-Extra: thumbnails2
@@ -205,11 +212,14 @@ made in Norway 🇳🇴
205
212
 
206
213
  just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
207
214
 
215
+ > ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
216
+
208
217
  * or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
209
218
  * or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
210
219
  * or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
211
220
  * or if you are on android, [install copyparty in termux](#install-on-android)
212
221
  * or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
222
+ * or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
213
223
  * or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
214
224
  * or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
215
225
  * or if you don't trust copyparty yet and want to isolate it a little, then...
@@ -570,12 +580,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
570
580
 
571
581
  and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
572
582
 
583
+ * you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
584
+
573
585
  ```yaml
574
586
  [accounts]
575
587
  u1: p1 # create account "u1" with password "p1"
576
588
  u2: p2 # (note that comments must have
577
589
  u3: p3 # two spaces before the # sign)
578
590
 
591
+ [groups]
592
+ g1: u1, u2 # create a group
593
+
579
594
  [/] # this URL will be mapped to...
580
595
  /srv # ...this folder on the server filesystem
581
596
  accs:
@@ -585,6 +600,7 @@ and if you want to use config files instead of commandline args (good!) then her
585
600
  /mnt/music # which is mapped to this folder
586
601
  accs:
587
602
  r: u1, u2 # only these accounts can read,
603
+ r: @g1 # (exactly the same, just with a group instead)
588
604
  rw: u3 # and only u3 can read-write
589
605
 
590
606
  [/inc]
@@ -1141,6 +1157,9 @@ open the `[🎺]` media-player-settings tab to configure it,
1141
1157
  * `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
1142
1158
  * `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
1143
1159
  * `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
1160
+ * `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
1161
+ * `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
1162
+ * `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
1144
1163
  * "tint" reduces the contrast of the playback bar
1145
1164
 
1146
1165
 
@@ -1482,6 +1501,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
1482
1501
 
1483
1502
  you can also hotlink files regardless by appending `?raw` to the url
1484
1503
 
1504
+ > WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
1505
+
1485
1506
  if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
1486
1507
 
1487
1508
 
@@ -1938,6 +1959,8 @@ you can disable the built-in password-based login system, and instead replace it
1938
1959
 
1939
1960
  * the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
1940
1961
 
1962
+ * if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
1963
+
1941
1964
  some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
1942
1965
 
1943
1966
  there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
@@ -2486,6 +2509,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
2486
2509
  | **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
2487
2510
  | **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
2488
2511
  | **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
2512
+ | **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
2489
2513
 
2490
2514
  <p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
2491
2515
 
@@ -2756,6 +2780,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
2756
2780
 
2757
2781
  the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
2758
2782
 
2783
+ when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
2784
+ * inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
2785
+ * setting the same `--ah-salt` in both environments
2786
+
2759
2787
 
2760
2788
  ## https
2761
2789
 
@@ -2900,7 +2928,7 @@ then again, if you are already into downloading shady binaries from the internet
2900
2928
 
2901
2929
  ## zipapp
2902
2930
 
2903
- another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2931
+ another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
2904
2932
 
2905
2933
  run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
2906
2934
 
@@ -1,7 +1,7 @@
1
- copyparty/__init__.py,sha256=4aJw_Mt3eSNMV8sJ95Nh4ris-tBUYhCOV094Rnxa5Xo,2651
2
- copyparty/__main__.py,sha256=vs0_3Ba4k4BbYF34V3loxPB0Dds_T1akBr44mpXv3TQ,127567
3
- copyparty/__version__.py,sha256=gGmXPkDwpbWAAYBiY30YUl4LnUf0n2C8Y-YiLOAYwuk,249
4
- copyparty/authsrv.py,sha256=luOWk3_6ZvCJI8bp6UmBPSeyQT02HH5Nf6vnakCwzGI,122483
1
+ copyparty/__init__.py,sha256=SJtQjM-9PP9K-IaoM9M3iNKvRApp0omOrAN6YtXTPNM,2599
2
+ copyparty/__main__.py,sha256=F9x45nQDLPe4s-QHMQhWhnZsdOqHZ8FuJrs-xN7WKvg,128395
3
+ copyparty/__version__.py,sha256=efITsXVBc-tiLxawt-9axHNIw551PrdvusgzacjpmRw,249
4
+ copyparty/authsrv.py,sha256=7DGDYZseSe7ghT9FY5M5dSQaYtkcRQs7Lv5fX0vvPRg,122824
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,28 +11,28 @@ copyparty/cfg.py,sha256=THceFupFmsZWF8iJKDDHR_XUEU3TNQgDRJB50iLiC1k,15734
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=S0w6iMR8AlzLc_Aqn-TKuUJ-vNbmeQF6SQs614-NFOE,18107
14
- copyparty/httpcli.py,sha256=hoc9p9iJ_WegTrUbLTAOkwwqltnvw35eQLO0_ZzvN3I,231645
14
+ copyparty/httpcli.py,sha256=HMEWwIgfSxhNVKLrZeGVa6lqXblDciW7UxiG2tppaYA,232884
15
15
  copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
- copyparty/httpsrv.py,sha256=MCNjOEH_xM2qXCLGcoN6W0RhWlikv68-zBx0nICIheU,18864
16
+ copyparty/httpsrv.py,sha256=Qyhna6GTIHnmpUBwUicdtDrXGwct8oKap0LqEtGD5X4,18911
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
18
18
  copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
19
19
  copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
- copyparty/mtag.py,sha256=ljqkiUblKzmLF6NVSXBRcFvy61j8QdVOjXToi2xQcyM,19939
20
+ copyparty/mtag.py,sha256=uAHixYCzB52dN9rhmRV_As_YX_baL5Ha-oRnTtmDdcg,20053
21
21
  copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
22
22
  copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
23
23
  copyparty/smbd.py,sha256=Czo8SRkkl4ndCwEUe9Cbr8v0YOnyQHzubGSguPizuTc,14651
24
24
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
25
25
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
26
26
  copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
- copyparty/svchub.py,sha256=LVhCj0IxHBlGdDbX-At_wAQioQ2MQD2WPbofHzA08B0,49083
27
+ copyparty/svchub.py,sha256=lAa-4HfD1LSv3Sn3pOtgLWGHyPhtGDXSCzGvpVB21E0,49140
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
- copyparty/tcpsrv.py,sha256=BCOqlT_mRu1ibHJpPzvf9c4h83AnIMEfd8nBBednCCg,20484
29
+ copyparty/tcpsrv.py,sha256=AND0QFPs8xD5-Jzr-_KcOa2dYx5AdZPboKo_M0cuh5g,20478
30
30
  copyparty/tftpd.py,sha256=QuPcdx77gLmEpit3lLc0x4Px6BrBBKJpJl4VqINc5O8,14254
31
- copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=S6ChazjXwXevUxkAajwIwHzA16PyNIwv6kYRFakvLmY,32759
31
+ copyparty/th_cli.py,sha256=1tq5yFTSa6ppy9xXuxUsm1SqF7Ps69b0cm4gAGM0vVo,5460
32
+ copyparty/th_srv.py,sha256=0WyPJRhKhCphGBe3gC53yNrlu0IT7WQ6WaWVSGxzpog,34749
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
- copyparty/up2k.py,sha256=n1ZSEopnFmXbjPEU_B84T3i2nCXvIuLuY308uM4UaJQ,179150
35
- copyparty/util.py,sha256=JYqpB6OM2l6J6sfPphgq5q7JzV95MxAtKIvGXMPAPhk,105423
34
+ copyparty/up2k.py,sha256=lN6jB_hv-dbdDwvm61MLKFTjazLZbQ-buXmh5ANi4UY,179456
35
+ copyparty/util.py,sha256=XQN86nmxN4RgWWw4k1dIbWeIAR_Dqh_7Z4X9WobwRFI,105732
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=DYt5mJJNt-935rU7HRm8kt_whpcVSI0uSphvD7PXrJo,2247
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=MxRofvhXjmUN7RtXtC17_9AlROVNUT-66WwJ_pHLY9c,8258
58
- copyparty/web/browser.css.gz,sha256=29D3F4uB-VMd6uJo-SxWAwLfn08jcETYZm0SOJFJLAE,11847
59
- copyparty/web/browser.html,sha256=FKyez1jN3I7iG7VlAjZDtNi6PenYue22mahThVv4BTA,4792
60
- copyparty/web/browser.js.gz,sha256=yMy_s-cgUH8Vo0cos3lIZTqaBxyfJpphOGK0oM02jAc,111316
58
+ copyparty/web/browser.css.gz,sha256=Q4tBdkN0TTjrc-NTKrJy3RUoaeh7mJOe7_a95kGKf9M,11767
59
+ copyparty/web/browser.html,sha256=lhelkXI8_HGfuqo_5b6XEGzf8VNodOMXE9kbv31JtbM,4790
60
+ copyparty/web/browser.js.gz,sha256=02kCf8mpq2N7R6Xcl7RVRqLdMepu2tdNhdOglXAOZog,199166
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
@@ -73,29 +73,24 @@ copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
73
73
  copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
74
74
  copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
75
75
  copyparty/web/rups.css.gz,sha256=pWklsym27oGGr-8tYQR7WnZvGZElAgCwLzlwTDErNAM,647
76
- copyparty/web/rups.html,sha256=iPuz53jBT_mIWIfl1yrjjg5-P7oO2ada6fTFq8PgjGk,1479
77
- copyparty/web/rups.js.gz,sha256=nvvcG8L-fkm7zkhjnlTGhBp_KD0j08mtHEW0sB7zy-Y,854
76
+ copyparty/web/rups.html,sha256=36UfDfHYMltw7_qAWGUGeRF8zaCSw2EjclhsymW94n0,1512
77
+ copyparty/web/rups.js.gz,sha256=99RaGb6fhpER4_eNyauN7yOhA8NFcO4qNKazlnyrano,854
78
78
  copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,547
79
- copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,2545
80
- copyparty/web/shares.js.gz,sha256=NQzrF57cikU38NzoaWQhbrINWkQBZhu1sH0jAC2SESQ,967
79
+ copyparty/web/shares.html,sha256=ZZ9BIuzhbVtJCAZOb_PAaEY_z9jo8i93QEJolNDHX3g,2578
80
+ copyparty/web/shares.js.gz,sha256=KEOx1OxQeEQNHNjsPXCvtWGFIRQSKy_tP7XSvreNCXk,966
81
81
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
82
82
  copyparty/web/splash.html,sha256=0MvDe1lKfGqczi7d4nKjWjG0cRVyvs8J6sDEj3DCPSI,6376
83
- copyparty/web/splash.js.gz,sha256=6ag8Szmjd_S1jr2D0-DgubM1F6Fq7dUU7A8E-qBY2gI,3702
83
+ copyparty/web/splash.js.gz,sha256=FyuMJyupQ-aLk9gemy8U0R6XT83hMRtarvPcKkmjoY0,7766
84
84
  copyparty/web/svcs.html,sha256=mamJdq0hsmHqG2BQsf9jg8G9bAl338wUhUZ2WtXOlGQ,14865
85
85
  copyparty/web/svcs.js.gz,sha256=AYatNKyT_bKRWX8sb3WD_iujBY3L4P7HWBrsuMctsLs,722
86
86
  copyparty/web/ui.css.gz,sha256=e3iIflzddmjoyPrun_1jsu9j7fbdonNQLyhEE2oKKOQ,2819
87
- copyparty/web/up2k.js.gz,sha256=_uOZzORAFO91SG3TUHd9xhKhAIXwL3fUFBNEUKnEVHY,24812
88
- copyparty/web/util.js.gz,sha256=zC_5OONJOR2g33uVlyn4GufUWIOvVZCleUY-VW3lnTc,15327
87
+ copyparty/web/up2k.js.gz,sha256=aoq6uXrL77zlYQnx9y9VNUVL7YBOHc4u-zFlIrQSX14,24811
88
+ copyparty/web/util.js.gz,sha256=IpbiLt7QS9o-Yzj_07nwz3SghQWxX69TN5cH-AIhDKU,15512
89
89
  copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
90
90
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
92
- copyparty/web/a/u2c.py,sha256=auXzLj04dt_lw4H70PhNUK0GjrQEThrybo2-77SLsUg,53165
92
+ copyparty/web/a/u2c.py,sha256=f_KR1ZhOjJYBnyYlJbBXY-TnNITeT7HOf0R3pR_9DIM,53248
93
93
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
94
- copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
95
- copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
96
- copyparty/web/dd/4.png,sha256=fIwEVmtZNZtloZuVEKPKnkx3SELwRJmB3US61y7t2lI,248
97
- copyparty/web/dd/5.png,sha256=Lfpu8-yOlhONuoMbygloKqQVPXSm9gjxH2gUYn5QQAE,250
98
- copyparty/web/dd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
94
  copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
95
  copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
101
96
  copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
@@ -110,9 +105,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
110
105
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
111
106
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
112
107
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
113
- copyparty-1.18.8.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
- copyparty-1.18.8.dist-info/METADATA,sha256=mB3n9kkOR5sdmH_0tpAPDDbQbsPd2nfzlvNRRpY8YzY,167745
115
- copyparty-1.18.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
- copyparty-1.18.8.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
- copyparty-1.18.8.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
- copyparty-1.18.8.dist-info/RECORD,,
108
+ copyparty-1.18.10.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
109
+ copyparty-1.18.10.dist-info/METADATA,sha256=nM2VOQmUUXuiEFQ-Yqkzs-Y4jDYJY4FJS0NOeTEnvyg,169618
110
+ copyparty-1.18.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
+ copyparty-1.18.10.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
112
+ copyparty-1.18.10.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
113
+ copyparty-1.18.10.dist-info/RECORD,,
copyparty/web/dd/2.png DELETED
Binary file
copyparty/web/dd/3.png DELETED
Binary file
copyparty/web/dd/4.png DELETED
Binary file
copyparty/web/dd/5.png DELETED
Binary file
File without changes