copyparty 1.18.0__py3-none-any.whl → 1.18.1__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
@@ -77,6 +77,7 @@ web/deps/prismd.css
77
77
  web/deps/scp.woff2
78
78
  web/deps/sha512.ac.js
79
79
  web/deps/sha512.hw.js
80
+ web/idp.html
80
81
  web/iiam.gif
81
82
  web/md.css
82
83
  web/md.html
copyparty/__main__.py CHANGED
@@ -1085,12 +1085,16 @@ def add_cert(ap, cert_path):
1085
1085
 
1086
1086
 
1087
1087
  def add_auth(ap):
1088
+ idp_db = os.path.join(E.cfg, "idp.db")
1088
1089
  ses_db = os.path.join(E.cfg, "sessions.db")
1089
1090
  ap2 = ap.add_argument_group('IdP / identity provider / user authentication options')
1090
1091
  ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
1091
1092
  ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
1092
1093
  ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
1093
1094
  ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
1095
+ 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)")
1096
+ 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")
1097
+ 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)")
1094
1098
  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")
1095
1099
  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")
1096
1100
  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)")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 18, 0)
3
+ VERSION = (1, 18, 1)
4
4
  CODENAME = "logtail"
5
- BUILD_DT = (2025, 6, 22)
5
+ BUILD_DT = (2025, 7, 7)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -21,6 +21,7 @@ from .util import (
21
21
  DEF_MTE,
22
22
  DEF_MTH,
23
23
  EXTS,
24
+ HAVE_SQLITE3,
24
25
  IMPLICATIONS,
25
26
  MIMES,
26
27
  SQLITE_VER,
@@ -32,6 +33,7 @@ from .util import (
32
33
  afsenc,
33
34
  get_df,
34
35
  humansize,
36
+ min_ex,
35
37
  odfusion,
36
38
  read_utf8,
37
39
  relchk,
@@ -44,6 +46,9 @@ from .util import (
44
46
  vsplit,
45
47
  )
46
48
 
49
+ if HAVE_SQLITE3:
50
+ import sqlite3
51
+
47
52
  if TYPE_CHECKING:
48
53
  from .broker_mp import BrokerMp
49
54
  from .broker_thr import BrokerThr
@@ -928,6 +933,10 @@ class AuthSrv(object):
928
933
  return False
929
934
 
930
935
  self.idp_accs[uname] = gnames
936
+ try:
937
+ self._update_idp_db(uname, gname)
938
+ except:
939
+ self.log("failed to update the --idp-db:\n%s" % (min_ex(),), 3)
931
940
 
932
941
  t = "reinitializing due to new user from IdP: [%r:%r]"
933
942
  self.log(t % (uname, gnames), 3)
@@ -940,6 +949,21 @@ class AuthSrv(object):
940
949
  broker.ask("reload", False, True).get()
941
950
  return True
942
951
 
952
+ def _update_idp_db(self, uname , gname ) :
953
+ if not self.args.idp_store:
954
+ return
955
+
956
+
957
+ db = sqlite3.connect(self.args.idp_db)
958
+ cur = db.cursor()
959
+
960
+ cur.execute("delete from us where un = ?", (uname,))
961
+ cur.execute("insert into us values (?,?)", (uname, gname))
962
+
963
+ db.commit()
964
+ cur.close()
965
+ db.close()
966
+
943
967
  def _map_volume_idp(
944
968
  self,
945
969
  src ,
@@ -1088,6 +1112,7 @@ class AuthSrv(object):
1088
1112
  * any non-zero value from IdP group header
1089
1113
  * otherwise take --grps / [groups]
1090
1114
  """
1115
+ self.load_idp_db(bool(self.idp_accs))
1091
1116
  ret = {un: gns[:] for un, gns in self.idp_accs.items()}
1092
1117
  ret.update({zs: [""] for zs in acct if zs not in ret})
1093
1118
  for gn, uns in grps.items():
@@ -1648,7 +1673,6 @@ class AuthSrv(object):
1648
1673
  shr = enshare[1:-1]
1649
1674
  shrs = enshare[1:]
1650
1675
  if enshare:
1651
- import sqlite3
1652
1676
 
1653
1677
  zsd = {"d2d": True, "tcolor": self.args.tcolor}
1654
1678
  shv = VFS(self.log_func, "", shr, shr, AXS(), zsd)
@@ -2613,6 +2637,42 @@ class AuthSrv(object):
2613
2637
  zs = str(vol.flags.get("tcolor") or self.args.tcolor)
2614
2638
  vol.flags["tcolor"] = zs.lstrip("#")
2615
2639
 
2640
+ def load_idp_db(self, quiet=False) :
2641
+ # mutex me
2642
+ level = self.args.idp_store
2643
+ if level < 2 or not self.args.idp_h_usr:
2644
+ return
2645
+
2646
+
2647
+ db = sqlite3.connect(self.args.idp_db)
2648
+ cur = db.cursor()
2649
+ from_cache = cur.execute("select un, gs from us").fetchall()
2650
+ cur.close()
2651
+ db.close()
2652
+
2653
+ self.idp_accs.clear()
2654
+ self.idp_usr_gh.clear()
2655
+
2656
+ gsep = self.args.idp_gsep
2657
+ n = []
2658
+ for uname, gname in from_cache:
2659
+ if level < 3:
2660
+ if uname in self.idp_accs:
2661
+ continue
2662
+ gname = ""
2663
+ gnames = [x.strip() for x in gsep.split(gname)]
2664
+ gnames.sort()
2665
+
2666
+ # self.idp_usr_gh[uname] = gname
2667
+ self.idp_accs[uname] = gnames
2668
+ n.append(uname)
2669
+
2670
+ if n and not quiet:
2671
+ t = ", ".join(n[:9])
2672
+ if len(n) > 9:
2673
+ t += "..."
2674
+ self.log("found %d IdP users in db (%s)" % (len(n), t))
2675
+
2616
2676
  def load_sessions(self, quiet=False) :
2617
2677
  # mutex me
2618
2678
  if self.args.no_ses:
@@ -2620,7 +2680,6 @@ class AuthSrv(object):
2620
2680
  self.sesa = {}
2621
2681
  return
2622
2682
 
2623
- import sqlite3
2624
2683
 
2625
2684
  ases = {}
2626
2685
  blen = (self.args.ses_len // 4) * 4 # 3 bytes in 4 chars
@@ -2667,7 +2726,6 @@ class AuthSrv(object):
2667
2726
  if self.args.no_ses:
2668
2727
  return
2669
2728
 
2670
- import sqlite3
2671
2729
 
2672
2730
  db = sqlite3.connect(self.args.ses_db)
2673
2731
  cur = db.cursor()
copyparty/httpcli.py CHANGED
@@ -1294,6 +1294,9 @@ class HttpCli(object):
1294
1294
  if "ru" in self.uparam:
1295
1295
  return self.tx_rups()
1296
1296
 
1297
+ if "idp" in self.uparam:
1298
+ return self.tx_idp()
1299
+
1297
1300
  if "h" in self.uparam:
1298
1301
  return self.tx_mounts()
1299
1302
 
@@ -5077,15 +5080,24 @@ class HttpCli(object):
5077
5080
  return "" # unhandled / fallthrough
5078
5081
 
5079
5082
  def scanvol(self) :
5080
- if not self.can_admin:
5081
- raise Pebkac(403, "'scanvol' not allowed for user " + self.uname)
5082
-
5083
5083
  if self.args.no_rescan:
5084
5084
  raise Pebkac(403, "the rescan feature is disabled in server config")
5085
5085
 
5086
- vn, _ = self.asrv.vfs.get(self.vpath, self.uname, True, True)
5086
+ vpaths = self.uparam["scan"].split(",/")
5087
+ if vpaths == [""]:
5088
+ vpaths = [self.vpath]
5089
+
5090
+ vols = []
5091
+ for vpath in vpaths:
5092
+ vn, _ = self.asrv.vfs.get(vpath, self.uname, True, True)
5093
+ vols.append(vn.vpath)
5094
+ if self.uname not in vn.axs.uadmin:
5095
+ self.log("rejected scanning [%s] => [%s];" % (vpath, vn.vpath), 3)
5096
+ raise Pebkac(403, "'scanvol' not allowed for user " + self.uname)
5097
+
5098
+ self.log("trying to rescan %d volumes: %r" % (len(vols), vols))
5087
5099
 
5088
- args = [self.asrv.vfs.all_vols, [vn.vpath], False, True]
5100
+ args = [self.asrv.vfs.all_vols, vols, False, True]
5089
5101
 
5090
5102
  x = self.conn.hsrv.broker.ask("up2k.rescan", *args)
5091
5103
  err = x.get()
@@ -5504,6 +5516,32 @@ class HttpCli(object):
5504
5516
  self.reply(html.encode("utf-8"), status=200)
5505
5517
  return True
5506
5518
 
5519
+ def tx_idp(self) :
5520
+ if self.uname.lower() not in self.args.idp_adm_set:
5521
+ raise Pebkac(403, "'idp' not allowed for user " + self.uname)
5522
+
5523
+ cmd = self.uparam["idp"]
5524
+ if cmd.startswith("rm="):
5525
+ import sqlite3
5526
+
5527
+ db = sqlite3.connect(self.args.idp_db)
5528
+ db.execute("delete from us where un=?", (cmd[3:],))
5529
+ db.commit()
5530
+ db.close()
5531
+
5532
+ self.conn.hsrv.broker.ask("reload", False, False).get()
5533
+
5534
+ self.redirect("", "?idp")
5535
+ return True
5536
+
5537
+ rows = [
5538
+ [k, "[%s]" % ("], [".join(v))]
5539
+ for k, v in sorted(self.asrv.idp_accs.items())
5540
+ ]
5541
+ html = self.j2s("idp", this=self, rows=rows, now=int(time.time()))
5542
+ self.reply(html.encode("utf-8"), status=200)
5543
+ return True
5544
+
5507
5545
  def tx_shares(self) :
5508
5546
  if self.uname == "*":
5509
5547
  self.loud_reply("you're not logged in")
@@ -5578,7 +5616,7 @@ class HttpCli(object):
5578
5616
  self.conn.hsrv.broker.ask("reload", False, False).get()
5579
5617
  self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
5580
5618
 
5581
- self.redirect(self.args.SRS + "?shares")
5619
+ self.redirect("", "?shares")
5582
5620
  return True
5583
5621
 
5584
5622
  def handle_share(self, req ) :
copyparty/httpconn.py CHANGED
@@ -219,3 +219,6 @@ class HttpConn(object):
219
219
  if self.u2idx:
220
220
  self.hsrv.put_u2idx(str(self.addr), self.u2idx)
221
221
  self.u2idx = None
222
+
223
+ if self.rproxy:
224
+ self.set_rproxy()
copyparty/httpsrv.py CHANGED
@@ -171,6 +171,7 @@ class HttpSrv(object):
171
171
  "browser",
172
172
  "browser2",
173
173
  "cf",
174
+ "idp",
174
175
  "md",
175
176
  "mde",
176
177
  "msg",
@@ -308,6 +309,8 @@ class HttpSrv(object):
308
309
 
309
310
  Daemon(self.broker.say, "sig-hsrv-up1", ("cb_httpsrv_up",))
310
311
 
312
+ saddr = ("", 0) # fwd-decl for `except TypeError as ex:`
313
+
311
314
  while not self.stopping:
312
315
  if self.args.log_conn:
313
316
  self.log(self.name, "|%sC-ncli" % ("-" * 1,), c="90")
@@ -389,6 +392,19 @@ class HttpSrv(object):
389
392
  self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
390
393
  time.sleep(0.02)
391
394
  continue
395
+ except TypeError as ex:
396
+ # on macOS, accept() may return a None saddr if blocked by LittleSnitch;
397
+ # unicode(saddr[0]) ==> TypeError: 'NoneType' object is not subscriptable
398
+ if tcp and not saddr:
399
+ t = "accept(%s): failed to accept connection from client due to firewall or network issue"
400
+ self.log(self.name, t % (fno,), c=3)
401
+ try:
402
+ sck.close() # type: ignore
403
+ except:
404
+ pass
405
+ time.sleep(0.02)
406
+ continue
407
+ raise
392
408
 
393
409
  if self.args.log_conn:
394
410
  t = "|{}C-acc2 \033[0;36m{} \033[3{}m{}".format(
copyparty/svchub.py CHANGED
@@ -82,6 +82,7 @@ if PY2:
82
82
  range = xrange # type: ignore
83
83
 
84
84
 
85
+ VER_IDP_DB = 1
85
86
  VER_SESSION_DB = 1
86
87
  VER_SHARES_DB = 2
87
88
 
@@ -252,11 +253,15 @@ class SvcHub(object):
252
253
  self.log("root", "effective %s is %s" % (zs, getattr(args, zs)))
253
254
 
254
255
  if args.ah_cli or args.ah_gen:
256
+ args.idp_store = 0
255
257
  args.no_ses = True
256
258
  args.shr = ""
257
259
 
260
+ if args.idp_store and args.idp_h_usr:
261
+ self.setup_db("idp")
262
+
258
263
  if not self.args.no_ses:
259
- self.setup_session_db()
264
+ self.setup_db("ses")
260
265
 
261
266
  args.shr1 = ""
262
267
  if args.shr:
@@ -415,25 +420,57 @@ class SvcHub(object):
415
420
  except:
416
421
  pass
417
422
 
418
- def setup_session_db(self) :
423
+ def _db_onfail_ses(self) :
424
+ self.args.no_ses = True
425
+
426
+ def _db_onfail_idp(self) :
427
+ self.args.idp_store = 0
428
+
429
+ def setup_db(self, which ) :
430
+ """
431
+ the "non-mission-critical" databases; if something looks broken then just nuke it
432
+ """
433
+ if which == "ses":
434
+ native_ver = VER_SESSION_DB
435
+ db_path = self.args.ses_db
436
+ desc = "sessions-db"
437
+ pathopt = "ses-db"
438
+ sanchk_q = "select count(*) from us"
439
+ createfun = self._create_session_db
440
+ failfun = self._db_onfail_ses
441
+ elif which == "idp":
442
+ native_ver = VER_IDP_DB
443
+ db_path = self.args.idp_db
444
+ desc = "idp-db"
445
+ pathopt = "idp-db"
446
+ sanchk_q = "select count(*) from us"
447
+ createfun = self._create_idp_db
448
+ failfun = self._db_onfail_idp
449
+ else:
450
+ raise Exception("unknown cachetype")
451
+
452
+ if not db_path.endswith(".db"):
453
+ zs = "config option --%s (the %s) was configured to [%s] which is invalid; must be a filepath ending with .db"
454
+ self.log("root", zs % (pathopt, desc, db_path), 1)
455
+ raise Exception(BAD_CFG)
456
+
419
457
  if not HAVE_SQLITE3:
420
- self.args.no_ses = True
421
- t = "WARNING: sqlite3 not available; disabling sessions, will use plaintext passwords in cookies"
422
- self.log("root", t, 3)
458
+ failfun()
459
+ if which == "ses":
460
+ zs = "disabling sessions, will use plaintext passwords in cookies"
461
+ elif which == "idp":
462
+ zs = "disabling idp-db, will be unable to remember IdP-volumes after a restart"
463
+ self.log("root", "WARNING: sqlite3 not available; %s" % (zs,), 3)
423
464
  return
424
465
 
425
466
 
426
- # policy:
427
- # the sessions-db is whatever, if something looks broken then just nuke it
428
-
429
- db_path = self.args.ses_db
430
467
  db_lock = db_path + ".lock"
431
468
  try:
432
469
  create = not os.path.getsize(db_path)
433
470
  except:
434
471
  create = True
435
472
  zs = "creating new" if create else "opening"
436
- self.log("root", "%s sessions-db %s" % (zs, db_path))
473
+ self.log("root", "%s %s %s" % (zs, desc, db_path))
437
474
 
438
475
  for tries in range(2):
439
476
  sver = 0
@@ -443,17 +480,19 @@ class SvcHub(object):
443
480
  try:
444
481
  zs = "select v from kv where k='sver'"
445
482
  sver = cur.execute(zs).fetchall()[0][0]
446
- if sver > VER_SESSION_DB:
447
- zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
448
- raise Exception(zs % (VER_SESSION_DB, sver))
483
+ if sver > native_ver:
484
+ zs = "this version of copyparty only understands %s v%d and older; the db is v%d"
485
+ raise Exception(zs % (desc, native_ver, sver))
449
486
 
450
- cur.execute("select count(*) from us").fetchone()
487
+ cur.execute(sanchk_q).fetchone()
451
488
  except:
452
489
  if sver:
453
490
  raise
454
- sver = 1
455
- self._create_session_db(cur)
456
- err = self._verify_session_db(cur, sver, db_path)
491
+ sver = createfun(cur)
492
+
493
+ err = self._verify_db(
494
+ cur, which, pathopt, db_path, desc, sver, native_ver
495
+ )
457
496
  if err:
458
497
  tries = 99
459
498
  self.args.no_ses = True
@@ -461,10 +500,10 @@ class SvcHub(object):
461
500
  break
462
501
 
463
502
  except Exception as ex:
464
- if tries or sver > VER_SESSION_DB:
503
+ if tries or sver > native_ver:
465
504
  raise
466
- t = "sessions-db is unusable; deleting and recreating: %r"
467
- self.log("root", t % (ex,), 3)
505
+ t = "%s is unusable; deleting and recreating: %r"
506
+ self.log("root", t % (desc, ex), 3)
468
507
  try:
469
508
  cur.close() # type: ignore
470
509
  except:
@@ -492,8 +531,31 @@ class SvcHub(object):
492
531
  for cmd in sch:
493
532
  cur.execute(cmd)
494
533
  self.log("root", "created new sessions-db")
534
+ return 1
495
535
 
496
- def _verify_session_db(self, cur , sver , db_path ) :
536
+ def _create_idp_db(self, cur ) :
537
+ sch = [
538
+ r"create table kv (k text, v int)",
539
+ r"create table us (un text, gs text)",
540
+ # username, groups
541
+ r"create index us_un on us(un)",
542
+ r"insert into kv values ('sver', 1)",
543
+ ]
544
+ for cmd in sch:
545
+ cur.execute(cmd)
546
+ self.log("root", "created new idp-db")
547
+ return 1
548
+
549
+ def _verify_db(
550
+ self,
551
+ cur ,
552
+ which ,
553
+ pathopt ,
554
+ db_path ,
555
+ desc ,
556
+ sver ,
557
+ native_ver ,
558
+ ) :
497
559
  # ensure writable (maybe owned by other user)
498
560
  db = cur.connection
499
561
 
@@ -505,9 +567,16 @@ class SvcHub(object):
505
567
  except:
506
568
  owner = 0
507
569
 
570
+ if which == "ses":
571
+ cons = "Will now disable sessions and instead use plaintext passwords in cookies."
572
+ elif which == "idp":
573
+ cons = "Each IdP-volume will not become available until its associated user sends their first request."
574
+ else:
575
+ raise Exception()
576
+
508
577
  if not lock_file(db_path + ".lock"):
509
- t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
510
- return t % (db_path, owner)
578
+ t = "the %s [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --%s or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. %s"
579
+ return t % (desc, db_path, owner, pathopt, cons)
511
580
 
512
581
  vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
513
582
  if owner:
@@ -519,9 +588,9 @@ class SvcHub(object):
519
588
  for k, v in vars:
520
589
  cur.execute("insert into kv values(?, ?)", (k, v))
521
590
 
522
- if sver < VER_SESSION_DB:
591
+ if sver < native_ver:
523
592
  cur.execute("delete from kv where k='sver'")
524
- cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
593
+ cur.execute("insert into kv values('sver',?)", (native_ver,))
525
594
 
526
595
  db.commit()
527
596
  cur.close()
@@ -870,6 +939,12 @@ class SvcHub(object):
870
939
  vs = os.path.expandvars(os.path.expanduser(vs))
871
940
  setattr(al, k, vs)
872
941
 
942
+ for k in "idp_adm".split(" "):
943
+ vs = getattr(al, k)
944
+ vsa = [x.strip() for x in vs.split(",")]
945
+ vsa = [x.lower() for x in vsa if x]
946
+ setattr(al, k + "_set", set(vsa))
947
+
873
948
  zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
874
949
  for k in zs.split(" "):
875
950
  vs = getattr(al, k)
copyparty/th_srv.py CHANGED
@@ -93,6 +93,10 @@ try:
93
93
  if os.environ.get("PRTY_NO_PIL_AVIF"):
94
94
  raise Exception()
95
95
 
96
+ if ".avif" in Image.registered_extensions():
97
+ HAVE_AVIF = True
98
+ raise Exception()
99
+
96
100
  import pillow_avif # noqa: F401 # pylint: disable=unused-import
97
101
 
98
102
  HAVE_AVIF = True
Binary file
Binary file
copyparty/web/idp.html ADDED
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>{{ s_doctitle }}</title>
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+ <meta name="viewport" content="width=device-width, initial-scale=0.8">
9
+ <meta name="robots" content="noindex, nofollow">
10
+ <meta name="theme-color" content="#{{ tcolor }}">
11
+ <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
12
+ <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
13
+ {{ html_head }}
14
+ </head>
15
+
16
+ <body>
17
+ <div id="wrap">
18
+ <a href="{{ r }}/?idp">refresh</a>
19
+ <a href="{{ r }}/?h">control-panel</a>
20
+
21
+ <table id="tab"><thead><tr>
22
+ <th>forget</th>
23
+ <th>user</th>
24
+ <th>groups</th>
25
+ </tr></thead><tbody>
26
+ {% for un, gn in rows %}
27
+ <tr>
28
+ <td><a href="{{ r }}/?idp=rm={{ un|e }}">forget</a></td>
29
+ <td>{{ un|e }}</td>
30
+ <td>{{ gn|e }}</td>
31
+ </tr>
32
+ {% endfor %}
33
+ </tbody></table>
34
+ {% if not rows %}
35
+ (there are no IdP users in the cache)
36
+ {% endif %}
37
+ </div>
38
+ <a href="#" id="repl">π</a>
39
+ <script>
40
+
41
+ var SR="{{ r }}",
42
+ lang="{{ lang }}",
43
+ dfavico="{{ favico }}";
44
+
45
+ var STG = window.localStorage;
46
+ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
47
+
48
+ </script>
49
+ <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
50
+ {%- if js %}
51
+ <script src="{{ js }}_={{ ts }}"></script>
52
+ {%- endif %}
53
+ </body>
54
+ </html>
55
+
copyparty/web/splash.html CHANGED
@@ -135,6 +135,10 @@
135
135
 
136
136
  <h1 id="cc">other stuff:</h1>
137
137
  <ul>
138
+ {%- if this.uname in this.args.idp_adm_set %}
139
+ <li><a id="ag" href="{{ r }}/?idp">view idp cache</a></li>
140
+ {% endif %}
141
+
138
142
  {%- if this.uname != '*' and this.args.shr %}
139
143
  <li><a id="y" href="{{ r }}/?shares">edit shares</a></li>
140
144
  {% endif %}
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.0
3
+ Version: 1.18.1
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
@@ -1559,7 +1559,6 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
1559
1559
  note:
1560
1560
  * upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
1561
1561
  * `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
1562
- * the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
1563
1562
 
1564
1563
  config file example (these options are recommended btw):
1565
1564
 
@@ -2786,7 +2785,7 @@ enable [thumbnails](#thumbnails) of...
2786
2785
  * **images:** `Pillow` and/or `pyvips` and/or `ffmpeg` (requires py2.7 or py3.5+)
2787
2786
  * **videos/audio:** `ffmpeg` and `ffprobe` somewhere in `$PATH`
2788
2787
  * **HEIF pictures:** `pyvips` or `ffmpeg` or `pyheif-pillow-opener` (requires Linux or a C compiler)
2789
- * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
2788
+ * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
2790
2789
  * **JPEG XL pictures:** `pyvips` or `ffmpeg`
2791
2790
 
2792
2791
  enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
@@ -2817,7 +2816,7 @@ set any of the following environment variables to disable its associated optiona
2817
2816
  | `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
2818
2817
  | `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
2819
2818
  | `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
2820
- | `PRTY_NO_PIL_AVIF` | disable 3rd-party Pillow plugin for [AVIF support](https://pypi.org/project/pillow-avif-plugin/) |
2819
+ | `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
2821
2820
  | `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pyheif-pillow-opener/) |
2822
2821
  | `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
2823
2822
  | `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
@@ -1,7 +1,7 @@
1
- copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
2
- copyparty/__main__.py,sha256=gJlKxpBmV39xfiWp8sx5iUZsoJPhFcCAWY5RVpWsrz0,122959
3
- copyparty/__version__.py,sha256=QNl11l-yovzXttFgMOYiqG-Rd-5GJHs6EcPWg-ZqCSI,249
4
- copyparty/authsrv.py,sha256=K3dIZxJvNhrcPbXJpVIkoxWaDrQf3CYz3X-FYaVlrYw,114905
1
+ copyparty/__init__.py,sha256=4aJw_Mt3eSNMV8sJ95Nh4ris-tBUYhCOV094Rnxa5Xo,2651
2
+ copyparty/__main__.py,sha256=9dif5oV-BdPEeTnuiEHLSTxqDoYHJkFyecnJ94N5IRA,123786
3
+ copyparty/__version__.py,sha256=ccuRyetxDT3Kz5z0Vg4CMZH0x07iFUmVRTMc3FX0Td4,248
4
+ copyparty/authsrv.py,sha256=0ZNfH6_9Br2cSe-vAgf_Vp6PFakeao-ylhlqNYvEfvo,116493
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
@@ -11,9 +11,9 @@ copyparty/cfg.py,sha256=6o6aLzLxZ59lusAn_PMGgldc3rEb9yyGnUnGIP5D9M8,15077
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=r_M67eEl-qKLR6pf79TTcf_G1o0fJiOcU4fTTll7B34,228170
15
- copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
- copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
14
+ copyparty/httpcli.py,sha256=ZeSkAhDGiu6K6k68DGnqH2u3wR0FqGzKCmcgIwQC6bU,229421
15
+ copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
+ copyparty/httpsrv.py,sha256=x6dl6ZjpwYREbm-eJZYnwdkhDeCA58hw_iwUMCD1Wz8,18819
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
@@ -24,12 +24,12 @@ 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
26
  copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
27
- copyparty/svchub.py,sha256=leERN449te_yBbyChcOenjY_nuNPoFJpncLHfDMvDYE,46618
27
+ copyparty/svchub.py,sha256=YbYJd5X0pXAyC-C2nt1rVwnwd56b8VEU1zvGkijgNeU,48921
28
28
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
29
29
  copyparty/tcpsrv.py,sha256=F5K4Qr4eBLfhdLT_39yDf6ftrhWuGTrd6DSqqp_6e-Q,20480
30
30
  copyparty/tftpd.py,sha256=tbnxUsilwyusrAUCVVjJUZnR9TIHDkE-99WLsUxAIGA,14029
31
31
  copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
32
- copyparty/th_srv.py,sha256=2omGprnKGWim6U7fjJAXI41UCZBSxRwZfS0rCDsUDps,32556
32
+ copyparty/th_srv.py,sha256=6JXHthaZtDreWHmyRxfxN_EyNC2aQOQS5wUFYxxX-3Y,32669
33
33
  copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
34
34
  copyparty/up2k.py,sha256=nnR_ZKaopSNBuAjSN3Q5G_UVe6GmYD1NkxJt5QQf02o,178079
35
35
  copyparty/util.py,sha256=S-dEme_1rKf3qYgxWWahEUQUzgq_kpoO_nPbuKCwLsY,103948
@@ -55,12 +55,13 @@ 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=cjHv6i6VDjCG66W9h0S37l2-Gv7-qKBH-HpTP7kZSr4,11769
58
+ copyparty/web/browser.css.gz,sha256=QRTGDf5GmNGCcly_pvxf0sKRCU9T9sQdhJB39_EoYkA,11789
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=qgL276U3d4GAZmkdq7YaWTBxGulnDvkevXDoCi9_GJk,96443
60
+ copyparty/web/browser.js.gz,sha256=1wAt4IULmQM0Mwn0NkbDgypXPumY28C97ViB-PWgAsI,96508
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
64
+ copyparty/web/idp.html,sha256=qOjjvZz6fVk0W8cXiP8fXeTp5KMa_dDV7BRzJNUModo,1481
64
65
  copyparty/web/md.css.gz,sha256=UZpN0J7ubVM05CZkbZYkQRJeGgJt_GNDEzKTGSQd8h4,2032
65
66
  copyparty/web/md.html,sha256=hz-xJVfKtaeTUQn3tGh7ebIMvLbOjVKkrMhsCTr3lGM,4200
66
67
  copyparty/web/md.js.gz,sha256=m5dpIskZZ8FD9X-L0EWpoZP-yLOo-PCWJ7j4AFtHVRI,4163
@@ -78,13 +79,13 @@ copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,5
78
79
  copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,2545
79
80
  copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,940
80
81
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
- copyparty/web/splash.html,sha256=ouFB1P9g0K-S3LZQtOLfNz3GLXIRjyETkxo9aJZhiYk,6249
82
- copyparty/web/splash.js.gz,sha256=Qh0KoPWKoJ77cyzOwnhUaCTI5XUoPVV3YJURKklqpBg,2739
82
+ copyparty/web/splash.html,sha256=0MvDe1lKfGqczi7d4nKjWjG0cRVyvs8J6sDEj3DCPSI,6376
83
+ copyparty/web/splash.js.gz,sha256=xMl4Rly-ykhTx7LCI2MK1DpCblFWmFU9uA1rgivgen8,2771
83
84
  copyparty/web/svcs.html,sha256=cxgrhX9wD0Z_kvidry3aS9ubuGXYDj2f4ehq1X8T1EA,14227
84
85
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
86
  copyparty/web/ui.css.gz,sha256=e3iIflzddmjoyPrun_1jsu9j7fbdonNQLyhEE2oKKOQ,2819
86
- copyparty/web/up2k.js.gz,sha256=vf9Kth2JQ4F-XmWhjcGXqy6gA4eOkg0-EfVjAGQsW5o,24794
87
- copyparty/web/util.js.gz,sha256=vQj__zSM0cbBN4AoZgwhFrSCh82ZTv4CbzeufXo8tw4,15248
87
+ copyparty/web/up2k.js.gz,sha256=_uOZzORAFO91SG3TUHd9xhKhAIXwL3fUFBNEUKnEVHY,24812
88
+ copyparty/web/util.js.gz,sha256=qa1B5w3bW7NNOTtuiiiL1ZWboqD9YVNZMKn3y1mf-oU,15247
88
89
  copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
89
90
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
91
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
@@ -109,9 +110,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
110
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
111
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
112
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.18.0.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.18.0.dist-info/METADATA,sha256=AgVke4zkEUcHf9Iw-CNV1myTB72KmxPHCua7tI3DYwg,165863
114
- copyparty-1.18.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- copyparty-1.18.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.18.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.18.0.dist-info/RECORD,,
113
+ copyparty-1.18.1.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
114
+ copyparty-1.18.1.dist-info/METADATA,sha256=kkkHwOVLPOVY9wUEQvNBoG2ZxxtGv0UmFsx1IIPeX1s,165791
115
+ copyparty-1.18.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ copyparty-1.18.1.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
117
+ copyparty-1.18.1.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
118
+ copyparty-1.18.1.dist-info/RECORD,,