copyparty 1.19.16__py3-none-any.whl → 1.19.17__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.
Files changed (46) hide show
  1. copyparty/__init__.py +19 -0
  2. copyparty/__main__.py +31 -9
  3. copyparty/__version__.py +2 -2
  4. copyparty/authsrv.py +31 -6
  5. copyparty/bos/bos.py +5 -1
  6. copyparty/cfg.py +20 -0
  7. copyparty/ftpd.py +5 -3
  8. copyparty/httpcli.py +105 -20
  9. copyparty/mtag.py +2 -2
  10. copyparty/smbd.py +1 -1
  11. copyparty/svchub.py +3 -0
  12. copyparty/tftpd.py +1 -1
  13. copyparty/up2k.py +39 -15
  14. copyparty/util.py +15 -5
  15. copyparty/web/baguettebox.js.gz +0 -0
  16. copyparty/web/browser.css.gz +0 -0
  17. copyparty/web/browser.html +3 -0
  18. copyparty/web/browser.js.gz +0 -0
  19. copyparty/web/splash.html +3 -0
  20. copyparty/web/splash.js.gz +0 -0
  21. copyparty/web/tl/chi.js.gz +0 -0
  22. copyparty/web/tl/cze.js.gz +0 -0
  23. copyparty/web/tl/deu.js.gz +0 -0
  24. copyparty/web/tl/epo.js.gz +0 -0
  25. copyparty/web/tl/fin.js.gz +0 -0
  26. copyparty/web/tl/fra.js.gz +0 -0
  27. copyparty/web/tl/grc.js.gz +0 -0
  28. copyparty/web/tl/ita.js.gz +0 -0
  29. copyparty/web/tl/kor.js.gz +0 -0
  30. copyparty/web/tl/nld.js.gz +0 -0
  31. copyparty/web/tl/nno.js.gz +0 -0
  32. copyparty/web/tl/nor.js.gz +0 -0
  33. copyparty/web/tl/pol.js.gz +0 -0
  34. copyparty/web/tl/por.js.gz +0 -0
  35. copyparty/web/tl/rus.js.gz +0 -0
  36. copyparty/web/tl/spa.js.gz +0 -0
  37. copyparty/web/tl/swe.js.gz +0 -0
  38. copyparty/web/tl/tur.js.gz +0 -0
  39. copyparty/web/tl/ukr.js.gz +0 -0
  40. copyparty/web/util.js.gz +0 -0
  41. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/METADATA +10 -2
  42. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/RECORD +46 -27
  43. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/WHEEL +0 -0
  44. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/entry_points.txt +0 -0
  45. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/licenses/LICENSE +0 -0
  46. {copyparty-1.19.16.dist-info → copyparty-1.19.17.dist-info}/top_level.txt +0 -0
copyparty/__init__.py CHANGED
@@ -97,6 +97,25 @@ web/splash.html
97
97
  web/splash.js
98
98
  web/svcs.html
99
99
  web/svcs.js
100
+ web/tl/chi.js
101
+ web/tl/cze.js
102
+ web/tl/deu.js
103
+ web/tl/epo.js
104
+ web/tl/fin.js
105
+ web/tl/fra.js
106
+ web/tl/grc.js
107
+ web/tl/ita.js
108
+ web/tl/kor.js
109
+ web/tl/nld.js
110
+ web/tl/nno.js
111
+ web/tl/nor.js
112
+ web/tl/pol.js
113
+ web/tl/por.js
114
+ web/tl/rus.js
115
+ web/tl/spa.js
116
+ web/tl/swe.js
117
+ web/tl/tur.js
118
+ web/tl/ukr.js
100
119
  web/ui.css
101
120
  web/up2k.js
102
121
  web/util.js
copyparty/__main__.py CHANGED
@@ -641,8 +641,11 @@ def get_sects():
641
641
  if no accounts or volumes are configured,
642
642
  current folder will be read/write for everyone
643
643
 
644
- the group @acct will always have every user with an account
645
- (the name of that group can be changed with --grp-all)
644
+ the group \033[33m@acct\033[0m will always have every user with an account
645
+ (the name of that group can be changed with \033[32m--grp-all\033[0m)
646
+
647
+ to hide a volume from authenticated users, specify \033[33m*,-@acct\033[0m
648
+ to subtract \033[33m@acct\033[0m from \033[33m*\033[0m (can subtract users from groups too)
646
649
 
647
650
  consider the config file for more flexible account/volume management,
648
651
  including dynamic reload at runtime (and being more readable w)
@@ -664,12 +667,12 @@ def get_sects():
664
667
 
665
668
  send the password in the '\033[36mPW\033[0m' http-header:
666
669
  \033[36mPW: \033[35mhunter2\033[0m
667
- or if you have \033[33m--accounts\033[0m enabled,
670
+ or if you have \033[33m--usernames\033[0m enabled,
668
671
  \033[36mPW: \033[35med:hunter2\033[0m
669
672
 
670
673
  send the password in the URL itself:
671
674
  \033[36mhttp://127.0.0.1:3923/\033[35m?pw=hunter2\033[0m
672
- or if you have \033[33m--accounts\033[0m enabled,
675
+ or if you have \033[33m--usernames\033[0m enabled,
673
676
  \033[36mhttp://127.0.0.1:3923/\033[35m?pw=ed:hunter2\033[0m
674
677
 
675
678
  use basic-authentication:
@@ -1166,6 +1169,7 @@ def add_general(ap, nc, srvname):
1166
1169
  ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
1167
1170
  ap2.add_argument("--license", action="store_true", help="show licenses and exit")
1168
1171
  ap2.add_argument("--version", action="store_true", help="show versions and exit")
1172
+ ap2.add_argument("--versionb", action="store_true", help="show version and exit")
1169
1173
 
1170
1174
 
1171
1175
  def add_qr(ap, tty):
@@ -1233,6 +1237,7 @@ def add_upload(ap):
1233
1237
  ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
1234
1238
  ap2.add_argument("--reflink", action="store_true", help="enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink)")
1235
1239
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
1240
+ ap2.add_argument("--no-dupe-m", action="store_true", help="also reject dupes when moving a file into another volume (volflag=nodupem)")
1236
1241
  ap2.add_argument("--no-clone", action="store_true", help="do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone)")
1237
1242
  ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
1238
1243
  ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
@@ -1520,6 +1525,7 @@ def add_optouts(ap):
1520
1525
  ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
1521
1526
  ap2.add_argument("--no-tail", action="store_true", help="disable streaming a growing files with ?tail (volflag=notail)")
1522
1527
  ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader-IP into the database; will also disable unpost, you may want \033[32m--forget-ip\033[0m instead (volflag=no_db_ip)")
1528
+ ap2.add_argument("--no-zls", action="store_true", help="disable browsing the contents of zip/cbz files, does not affect thumbnails")
1523
1529
 
1524
1530
 
1525
1531
  def add_safety(ap):
@@ -1538,6 +1544,7 @@ def add_safety(ap):
1538
1544
  ap2.add_argument("--force-js", action="store_true", help="don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore \033[33m--no-robots\033[0m")
1539
1545
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1540
1546
  ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
1547
+ ap2.add_argument("--dont-ban", metavar="TXT", type=u, default="no", help="anyone at this accesslevel or above will not get banned: [\033[32mav\033[0m]=admin-in-volume, [\033[32maa\033[0m]=has-admin-anywhere, [\033[32mrw\033[0m]=read-write, [\033[32mauth\033[0m]=authenticated, [\033[32many\033[0m]=disable-all-bans, [\033[32mno\033[0m]=anyone-can-get-banned")
1541
1548
  ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1542
1549
  ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
1543
1550
  ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
@@ -1602,12 +1609,14 @@ def add_admin(ap):
1602
1609
  ap2 = ap.add_argument_group("admin panel options")
1603
1610
  ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)")
1604
1611
  ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
1605
- ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)")
1612
+ ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks); same as --stack-who=no")
1606
1613
  ap2.add_argument("--no-ups-page", action="store_true", help="disable ?ru (list of recent uploads)")
1607
1614
  ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
1608
1615
  ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone")
1609
1616
  ap2.add_argument("--ups-who", metavar="LVL", type=int, default=2, help="who can see recent uploads on the ?ru page? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone (volflag=ups_who)")
1610
1617
  ap2.add_argument("--ups-when", action="store_true", help="let everyone see upload timestamps on the ?ru page, not just admins")
1618
+ ap2.add_argument("--stack-who", metavar="LVL", type=u, default="a", help="who can see the ?stack page (list of threads)? [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admins, [\033[32mrw\033[0m]=read+write, [\033[32mall\033[0m]=everyone")
1619
+ ap2.add_argument("--stack-v", action="store_true", help="verbose ?stack")
1611
1620
 
1612
1621
 
1613
1622
  def add_thumbnail(ap):
@@ -1805,6 +1814,15 @@ def add_ui(ap, retry ):
1805
1814
  ap2.add_argument("--lg-sba", metavar="TXT", type=u, default="", help="the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes")
1806
1815
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1807
1816
  ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")
1817
+ ap2.add_argument("--ui-nombar", action="store_true", help="hide top-menu in the UI (volflag=ui_nombar)")
1818
+ ap2.add_argument("--ui-noacci", action="store_true", help="hide account-info in the UI (volflag=ui_noacci)")
1819
+ ap2.add_argument("--ui-nosrvi", action="store_true", help="hide server-info in the UI (volflag=ui_nosrvi)")
1820
+ ap2.add_argument("--ui-nonav", action="store_true", help="hide navpane+breadcrumbs (volflag=ui_nonav)")
1821
+ ap2.add_argument("--ui-notree", action="store_true", help="hide navpane in the UI (volflag=ui_nonav)")
1822
+ ap2.add_argument("--ui-nocpla", action="store_true", help="hide cpanel-link in the UI (volflag=ui_nocpla)")
1823
+ ap2.add_argument("--ui-nolbar", action="store_true", help="hide link-bar in the UI (volflag=ui_nolbar)")
1824
+ ap2.add_argument("--ui-noctxb", action="store_true", help="hide context-buttons in the UI (volflag=ui_noctxb)")
1825
+ ap2.add_argument("--ui-norepl", action="store_true", help="hide repl-button in the UI (volflag=ui_norepl)")
1808
1826
  ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS)
1809
1827
 
1810
1828
 
@@ -1932,15 +1950,19 @@ def run_argparse(
1932
1950
 
1933
1951
 
1934
1952
  def main(argv = None) :
1953
+ if argv is None:
1954
+ argv = sys.argv
1955
+
1956
+ if "--versionb" in argv:
1957
+ print(S_VERSION)
1958
+ sys.exit(0)
1959
+
1935
1960
  time.strptime("19970815", "%Y%m%d") # python#7980
1936
1961
  if WINDOWS:
1937
1962
  os.system("rem") # enables colors
1938
1963
 
1939
1964
  init_E(E)
1940
1965
 
1941
- if argv is None:
1942
- argv = sys.argv
1943
-
1944
1966
  f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite {} | jinja {} | pyftpd {} | tftp {}\n\033[0m'
1945
1967
  f = f.format(
1946
1968
  S_VERSION,
@@ -2067,7 +2089,7 @@ def main(argv = None) :
2067
2089
 
2068
2090
  # propagate implications
2069
2091
  for k1, k2 in IMPLICATIONS:
2070
- if getattr(al, k1):
2092
+ if getattr(al, k1, None):
2071
2093
  setattr(al, k2, True)
2072
2094
 
2073
2095
  # propagate unplications
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 19, 16)
3
+ VERSION = (1, 19, 17)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 10, 5)
5
+ BUILD_DT = (2025, 10, 17)
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
@@ -1961,9 +1961,18 @@ class AuthSrv(object):
1961
1961
  axs_key = "u" + perm
1962
1962
  for vp, vol in vfs.all_vols.items():
1963
1963
  zx = getattr(vol.axs, axs_key)
1964
- if "*" in zx:
1964
+ if "*" in zx and "-@acct" not in zx:
1965
1965
  for usr in unames:
1966
1966
  zx.add(usr)
1967
+ for zs in list(zx):
1968
+ if zs.startswith("-"):
1969
+ zx.discard(zs)
1970
+ zs = zs[1:]
1971
+ zx.discard(zs)
1972
+ if zs.startswith("@"):
1973
+ zs = zs[1:]
1974
+ for zs in grps.get(zs) or []:
1975
+ zx.discard(zs)
1967
1976
 
1968
1977
  # aread,... = dict[uname, list[volnames] or []]
1969
1978
  umap = {x: [] for x in unames}
@@ -2577,6 +2586,15 @@ class AuthSrv(object):
2577
2586
  for x in drop:
2578
2587
  vol.flags.pop(x)
2579
2588
 
2589
+ zi = vol.flags.get("lifetime") or 0
2590
+ zi2 = time.time() // (86400 * 365)
2591
+ zi3 = zi2 * 86400 * 365
2592
+ if zi < 0 or zi > zi3:
2593
+ t = "the lifetime of volume [/%s] (%d) exceeds max value (%d years; %d)"
2594
+ t = t % (vol.vpath, zi, zi2, zi3)
2595
+ self.log(t, 1)
2596
+ raise Exception(t)
2597
+
2580
2598
  # verify tags mentioned by -mt[mp] are used by -mte
2581
2599
  local_mtp = {}
2582
2600
  local_only_mtp = {}
@@ -2743,9 +2761,13 @@ class AuthSrv(object):
2743
2761
  ["uadmin", "uadmin"],
2744
2762
  ]:
2745
2763
  u = list(sorted(getattr(zv.axs, attr)))
2746
- u = ["*"] if "*" in u else u
2747
- u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
2748
- u = u if u else "\033[36m--none--\033[0m"
2764
+ if u == ["*"] and acct:
2765
+ u = ["\033[35monly-anonymous\033[0m"]
2766
+ elif "*" in u:
2767
+ u = ["\033[35meverybody\033[0m"]
2768
+ if not u:
2769
+ u = ["\033[36m--none--\033[0m"]
2770
+ u = ", ".join(u)
2749
2771
  t += "\n| {}: {}".format(txt, u)
2750
2772
 
2751
2773
  if "e2d" in zv.flags:
@@ -2857,8 +2879,6 @@ class AuthSrv(object):
2857
2879
 
2858
2880
  if have_reflink:
2859
2881
  t = "WARNING: Reflink-based dedup was requested, but %s. This will not work; files will be full copies instead."
2860
- if sys.version_info < (3, 14):
2861
- self.log(t % "your python version is not new enough", 1)
2862
2882
  if not sys.platform.startswith("linux"):
2863
2883
  self.log(t % "your OS is not Linux", 1)
2864
2884
 
@@ -3016,6 +3036,7 @@ class AuthSrv(object):
3016
3036
  "have_shr": self.args.shr,
3017
3037
  "shr_who": vf["shr_who"],
3018
3038
  "have_zip": not self.args.no_zip,
3039
+ "have_zls": not self.args.no_zls,
3019
3040
  "have_mv": not self.args.no_mv,
3020
3041
  "have_del": not self.args.no_del,
3021
3042
  "have_unpost": int(self.args.unpost),
@@ -3052,6 +3073,10 @@ class AuthSrv(object):
3052
3073
  "lifetime": vn.js_ls["lifetime"],
3053
3074
  "u2sort": self.args.u2sort,
3054
3075
  }
3076
+ zs = "ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi"
3077
+ for zs in zs.split():
3078
+ if vf.get(zs):
3079
+ js_htm[zs] = 1
3055
3080
  vn.js_htm = json_hesc(json.dumps(js_htm))
3056
3081
 
3057
3082
  vols = list(vfs.all_nodes.values())
copyparty/bos/bos.py CHANGED
@@ -99,7 +99,11 @@ def utime(
99
99
 
100
100
 
101
101
  def utime_c(
102
- log , p , ts , follow_symlinks = True, throw = False
102
+ log ,
103
+ p ,
104
+ ts ,
105
+ follow_symlinks = True,
106
+ throw = False,
103
107
  ) :
104
108
  clamp = 0
105
109
  ov = ts
copyparty/cfg.py CHANGED
@@ -19,6 +19,7 @@ def vf_bmap() :
19
19
  "no_clone": "noclone",
20
20
  "no_dirsz": "nodirsz",
21
21
  "no_dupe": "nodupe",
22
+ "no_dupe_m": "nodupem",
22
23
  "no_forget": "noforget",
23
24
  "no_pipe": "nopipe",
24
25
  "no_robots": "norobots",
@@ -58,6 +59,15 @@ def vf_bmap() :
58
59
  "rm_partial",
59
60
  "rmagic",
60
61
  "rss",
62
+ "ui_noacci",
63
+ "ui_nocpla",
64
+ "ui_nolbar",
65
+ "ui_nombar",
66
+ "ui_nonav",
67
+ "ui_notree",
68
+ "ui_norepl",
69
+ "ui_nosrvi",
70
+ "ui_noctxb",
61
71
  "wo_up_readme",
62
72
  "wram",
63
73
  "xdev",
@@ -189,6 +199,7 @@ flagcats = {
189
199
  "safededup": "verify on-disk data before using it for dedup",
190
200
  "noclone": "take dupe data from clients, even if available on HDD",
191
201
  "nodupe": "rejects existing files (instead of linking/cloning them)",
202
+ "nodupem": "rejects existing files during moves as well",
192
203
  "chmod_d=755": "unix-permission for new dirs/folders",
193
204
  "chmod_f=644": "unix-permission for new files",
194
205
  "uid=573": "change owner of new files/folders to unix-user 573",
@@ -325,6 +336,15 @@ flagcats = {
325
336
  "md_sba": "value of iframe allow-prop for markdown-sandbox",
326
337
  "lg_sba": "value of iframe allow-prop for *logue-sandbox",
327
338
  "nohtml": "return html and markdown as text/html",
339
+ "ui_noacci": "hide account-info in the UI",
340
+ "ui_nocpla": "hide cpanel-link in the UI",
341
+ "ui_nolbar": "hide link-bar in the UI",
342
+ "ui_nombar": "hide top-menu in the UI",
343
+ "ui_nonav": "hide navpane+breadcrumbs in the UI",
344
+ "ui_notree": "hide navpane in the UI",
345
+ "ui_norepl": "hide repl-button in the UI",
346
+ "ui_nosrvi": "hide server-info in the UI",
347
+ "ui_noctxb": "hide context-buttons in the UI",
328
348
  },
329
349
  "opengraph (discord embeds)": {
330
350
  "og": "enable OG (disables hotlinking)",
copyparty/ftpd.py CHANGED
@@ -247,7 +247,7 @@ class FtpFs(AbstractedFS):
247
247
 
248
248
  if w and need_unlink:
249
249
  if td >= -1 and td <= self.args.ftp_wt:
250
- # within permitted timeframe; unlink and accept
250
+ # within permitted timeframe; allow overwrite or resume
251
251
  do_it = True
252
252
  elif self.args.no_del or self.args.ftp_no_ow:
253
253
  # file too old, or overwrite not allowed; reject
@@ -264,7 +264,9 @@ class FtpFs(AbstractedFS):
264
264
  if not do_it:
265
265
  raise FSE("File already exists")
266
266
 
267
- wunlink(self.log, ap, VF_CAREFUL)
267
+ # Don't unlink file for append mode
268
+ elif "a" not in mode:
269
+ wunlink(self.log, ap, VF_CAREFUL)
268
270
 
269
271
  ret = open(fsenc(ap), mode, self.args.iobuf)
270
272
  if w and "fperms" in vfs.flags:
@@ -505,7 +507,7 @@ class FtpHandler(FTPHandler):
505
507
  0,
506
508
  self.cli_ip,
507
509
  time.time(),
508
- "",
510
+ None,
509
511
  )
510
512
  t = hr.get("rejectmsg") or ""
511
513
  if t or not hr:
copyparty/httpcli.py CHANGED
@@ -270,7 +270,7 @@ class HttpCli(object):
270
270
  tpl = self.conn.hsrv.j2[name]
271
271
  ka["r"] = self.args.SR if self.is_vproxied else ""
272
272
  ka["ts"] = self.conn.hsrv.cachebuster()
273
- ka["lang"] = self.args.lang
273
+ ka["lang"] = self.cookies.get("cplng") or self.args.lang
274
274
  ka["favico"] = self.args.favico
275
275
  ka["s_doctitle"] = self.args.doctitle
276
276
  ka["tcolor"] = self.vn.flags["tcolor"]
@@ -857,6 +857,16 @@ class HttpCli(object):
857
857
  return self.conn.iphash.s(self.ip)
858
858
 
859
859
  def cbonk(self, g , v , reason , descr ) :
860
+ cond = self.args.dont_ban
861
+ if (
862
+ cond == "any"
863
+ or (cond == "auth" and self.uname != "*")
864
+ or (cond == "aa" and self.avol)
865
+ or (cond == "av" and self.can_admin)
866
+ or (cond == "rw" and self.can_read and self.can_write)
867
+ ):
868
+ return False
869
+
860
870
  self.conn.hsrv.nsus += 1
861
871
  if not g.lim:
862
872
  return False
@@ -881,7 +891,7 @@ class HttpCli(object):
881
891
  0,
882
892
  self.ip,
883
893
  time.time(),
884
- reason,
894
+ [reason, reason],
885
895
  ):
886
896
  self.log("client banned: %s" % (descr,), 1)
887
897
  self.conn.hsrv.bans[ip] = bonk
@@ -1516,6 +1526,64 @@ class HttpCli(object):
1516
1526
  self.log("rss: %d hits, %d bytes" % (len(hits), len(bret)))
1517
1527
  return True
1518
1528
 
1529
+ def tx_zls(self, abspath) :
1530
+ if self.do_log:
1531
+ self.log("zls %s @%s" % (self.req, self.uname))
1532
+ if self.args.no_zls:
1533
+ raise Pebkac(405, "zip browsing is disabled in server config")
1534
+
1535
+ import zipfile
1536
+
1537
+ try:
1538
+ with zipfile.ZipFile(abspath, "r") as zf:
1539
+ filelist = [{"fn": f.filename} for f in zf.infolist()]
1540
+ ret = json.dumps(filelist).encode("utf-8", "replace")
1541
+ self.reply(ret, mime="application/json")
1542
+ return True
1543
+ except (zipfile.BadZipfile, RuntimeError):
1544
+ raise Pebkac(404, "requested file is not a valid zip file")
1545
+
1546
+ def tx_zget(self, abspath) :
1547
+ maxsz = 1024 * 1024 * 64
1548
+
1549
+ inner_path = self.uparam.get("zget")
1550
+ if not inner_path:
1551
+ raise Pebkac(405, "inner path is required")
1552
+ if self.do_log:
1553
+ self.log(
1554
+ "zget %s \033[35m%s\033[0m @%s" % (self.req, inner_path, self.uname)
1555
+ )
1556
+ if self.args.no_zls:
1557
+ raise Pebkac(405, "zip browsing is disabled in server config")
1558
+
1559
+ import zipfile
1560
+
1561
+ try:
1562
+ with zipfile.ZipFile(abspath, "r") as zf:
1563
+ zi = zf.getinfo(inner_path)
1564
+ if zi.file_size >= maxsz:
1565
+ raise Pebkac(404, "zip bomb defused")
1566
+ with zf.open(zi, "r") as fi:
1567
+ self.send_headers(length=zi.file_size, mime=guess_mime(inner_path))
1568
+
1569
+ sendfile_py(
1570
+ self.log,
1571
+ 0,
1572
+ zi.file_size,
1573
+ fi,
1574
+ self.s,
1575
+ self.args.s_wr_sz,
1576
+ self.args.s_wr_slp,
1577
+ not self.args.no_poll,
1578
+ {},
1579
+ "",
1580
+ )
1581
+ except KeyError:
1582
+ raise Pebkac(404, "no such file in archive")
1583
+ except (zipfile.BadZipfile, RuntimeError):
1584
+ raise Pebkac(404, "requested file is not a valid zip file")
1585
+ return True
1586
+
1519
1587
  def handle_propfind(self) :
1520
1588
  if self.do_log:
1521
1589
  self.log("PFIND %s @%s" % (self.req, self.uname))
@@ -2079,7 +2147,7 @@ class HttpCli(object):
2079
2147
  t = "urlform_raw %d @ %r\n %r\n"
2080
2148
  self.log(t % (len(orig), "/" + self.vpath, orig))
2081
2149
  try:
2082
- zb = unquote(buf.replace(b"+", b" "))
2150
+ zb = unquote(buf.replace(b"+", b" ").replace(b"&", b"\n"))
2083
2151
  plain = zb.decode("utf-8", "replace")
2084
2152
  if buf.startswith(b"msg="):
2085
2153
  plain = plain[4:]
@@ -2100,7 +2168,7 @@ class HttpCli(object):
2100
2168
  len(buf),
2101
2169
  self.ip,
2102
2170
  time.time(),
2103
- plain,
2171
+ [plain, orig],
2104
2172
  )
2105
2173
 
2106
2174
  t = "urlform_dec %d @ %r\n %r\n"
@@ -2259,7 +2327,7 @@ class HttpCli(object):
2259
2327
  remains,
2260
2328
  self.ip,
2261
2329
  at,
2262
- "",
2330
+ None,
2263
2331
  )
2264
2332
  t = hr.get("rejectmsg") or ""
2265
2333
  if t or not hr:
@@ -2394,7 +2462,7 @@ class HttpCli(object):
2394
2462
  post_sz,
2395
2463
  self.ip,
2396
2464
  at,
2397
- "",
2465
+ None,
2398
2466
  )
2399
2467
  t = hr.get("rejectmsg") or ""
2400
2468
  if t or not hr:
@@ -3226,7 +3294,7 @@ class HttpCli(object):
3226
3294
  0,
3227
3295
  self.ip,
3228
3296
  time.time(),
3229
- "",
3297
+ None,
3230
3298
  )
3231
3299
  t = hr.get("rejectmsg") or ""
3232
3300
  if t or not hr:
@@ -3398,7 +3466,7 @@ class HttpCli(object):
3398
3466
  0,
3399
3467
  self.ip,
3400
3468
  at,
3401
- "",
3469
+ None,
3402
3470
  )
3403
3471
  t = hr.get("rejectmsg") or ""
3404
3472
  if t or not hr:
@@ -3505,7 +3573,7 @@ class HttpCli(object):
3505
3573
  sz,
3506
3574
  self.ip,
3507
3575
  at,
3508
- "",
3576
+ None,
3509
3577
  )
3510
3578
  t = hr.get("rejectmsg") or ""
3511
3579
  if t or not hr:
@@ -3816,7 +3884,7 @@ class HttpCli(object):
3816
3884
  0,
3817
3885
  self.ip,
3818
3886
  time.time(),
3819
- "",
3887
+ None,
3820
3888
  )
3821
3889
  t = hr.get("rejectmsg") or ""
3822
3890
  if t or not hr:
@@ -3864,7 +3932,7 @@ class HttpCli(object):
3864
3932
  sz,
3865
3933
  self.ip,
3866
3934
  new_lastmod,
3867
- "",
3935
+ None,
3868
3936
  )
3869
3937
  t = hr.get("rejectmsg") or ""
3870
3938
  if t or not hr:
@@ -4998,7 +5066,7 @@ class HttpCli(object):
4998
5066
  "edit": "edit" in self.uparam,
4999
5067
  "title": html_escape(self.vpath, crlf=True),
5000
5068
  "lastmod": int(ts_md * 1000),
5001
- "lang": self.args.lang,
5069
+ "lang": self.cookies.get("cplng") or self.args.lang,
5002
5070
  "favico": self.args.favico,
5003
5071
  "have_emp": int(self.args.emp),
5004
5072
  "md_no_br": int(vn.flags.get("md_no_br") or 0),
@@ -5383,13 +5451,20 @@ class HttpCli(object):
5383
5451
  return self.redirect("", "?h", x.get(), "return to", False)
5384
5452
 
5385
5453
  def tx_stack(self) :
5386
- if not self.avol and not [x for x in self.wvol if x in self.rvol]:
5454
+ zs = self.args.stack_who
5455
+ if zs == "all" or (
5456
+ (zs == "a" and self.avol)
5457
+ or (zs == "rw" and [x for x in self.wvol if x in self.rvol])
5458
+ ):
5459
+ pass
5460
+ else:
5387
5461
  raise Pebkac(403, "'stack' not allowed for user " + self.uname)
5388
5462
 
5389
- if self.args.no_stack:
5390
- raise Pebkac(403, "the stackdump feature is disabled in server config")
5391
-
5392
- ret = "<pre>{}\n{}".format(time.time(), html_escape(alltrace()))
5463
+ ret = html_escape(alltrace(self.args.stack_v))
5464
+ if self.args.stack_v:
5465
+ ret = "<pre>%s\n%s" % (time.time(), ret)
5466
+ else:
5467
+ ret = "<pre>%s" % (ret,)
5393
5468
  self.reply(ret.encode("utf-8"))
5394
5469
  return True
5395
5470
 
@@ -6456,14 +6531,23 @@ class HttpCli(object):
6456
6531
  ):
6457
6532
  return self.tx_md(vn, abspath)
6458
6533
 
6534
+ if "zls" in self.uparam:
6535
+ return self.tx_zls(abspath)
6536
+ if "zget" in self.uparam:
6537
+ return self.tx_zget(abspath)
6538
+
6459
6539
  if not add_og or not og_fn:
6460
- return self.tx_file(
6461
- abspath, None if st.st_size or "nopipe" in vn.flags else vn.realpath
6462
- )
6540
+ if st.st_size or "nopipe" in vn.flags:
6541
+ return self.tx_file(abspath, None)
6542
+ else:
6543
+ return self.tx_file(abspath, vn.get_dbv("")[0].realpath)
6463
6544
 
6464
6545
  elif is_dir and not self.can_read:
6465
6546
  if use_dirkey:
6466
6547
  is_dk = True
6548
+ elif self.can_get and "doc" in self.uparam:
6549
+ zs = vjoin(self.vpath, self.uparam["doc"]) + "?v"
6550
+ return self.redirect(zs, flavor="redirecting to", use302=True)
6467
6551
  elif not self.can_write:
6468
6552
  return self.tx_404(True)
6469
6553
 
@@ -6545,6 +6629,7 @@ class HttpCli(object):
6545
6629
  "acct": self.uname,
6546
6630
  "perms": perms,
6547
6631
  }
6632
+ # also see `js_htm` in authsrv.py
6548
6633
  j2a = {
6549
6634
  "cgv1": vn.js_htm,
6550
6635
  "cgv": cgv,
copyparty/mtag.py CHANGED
@@ -162,12 +162,12 @@ def au_unpk(
162
162
  znil = [x for x in znil if "cover" in x[0]] or znil
163
163
  znil = [x for x in znil if CBZ_01.search(x[0])] or znil
164
164
  t = "cbz: %d files, %d hits" % (nf, len(znil))
165
+ if not znil:
166
+ raise Exception("no images inside cbz")
165
167
  using = sorted(znil)[0][1].filename
166
168
  if znil:
167
169
  t += ", using " + using
168
170
  log(t)
169
- if not znil:
170
- raise Exception("no images inside cbz")
171
171
  fi = zf.open(using)
172
172
 
173
173
  elif pk == "epub":
copyparty/smbd.py CHANGED
@@ -259,7 +259,7 @@ class SMB(object):
259
259
  0,
260
260
  "1.7.6.2",
261
261
  time.time(),
262
- "",
262
+ None,
263
263
  )
264
264
  t = hr.get("rejectmsg") or ""
265
265
  if t or not hr:
copyparty/svchub.py CHANGED
@@ -285,6 +285,9 @@ class SvcHub(object):
285
285
  ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
286
286
  args.theme = "{0}{1} {0} {1}".format(ch, bri)
287
287
 
288
+ if args.no_stack:
289
+ args.stack_who = "no"
290
+
288
291
  if args.nid:
289
292
  args.du_who = "no"
290
293
  args.du_iwho = n_du_who(args.du_who)
copyparty/tftpd.py CHANGED
@@ -376,7 +376,7 @@ class Tftpd(object):
376
376
  0,
377
377
  "8.3.8.7",
378
378
  time.time(),
379
- "",
379
+ None,
380
380
  )
381
381
  t = hr.get("rejectmsg") or ""
382
382
  if t or not hr:
copyparty/up2k.py CHANGED
@@ -10,6 +10,7 @@ import re
10
10
  import shutil
11
11
  import stat
12
12
  import subprocess as sp
13
+ import sys
13
14
  import tempfile
14
15
  import threading
15
16
  import time
@@ -27,6 +28,7 @@ from .mtag import MParser, MTag
27
28
  from .util import (
28
29
  E_FS_CRIT,
29
30
  E_FS_MEH,
31
+ HAVE_FICLONE,
30
32
  HAVE_SQLITE3,
31
33
  SYMTIME,
32
34
  VF_CAREFUL,
@@ -85,6 +87,10 @@ DB_VER = 6
85
87
  if TYPE_CHECKING:
86
88
  from .svchub import SvcHub
87
89
 
90
+ USE_FICLONE = HAVE_FICLONE and sys.version_info < (3, 14)
91
+ if USE_FICLONE:
92
+ import fcntl
93
+
88
94
  zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
89
95
  ICV_EXTS = set(zsg.split(","))
90
96
 
@@ -3282,7 +3288,7 @@ class Up2k(object):
3282
3288
  job["size"],
3283
3289
  job["addr"],
3284
3290
  job["at"],
3285
- "",
3291
+ None,
3286
3292
  )
3287
3293
  t = hr.get("rejectmsg") or ""
3288
3294
  if t or not hr:
@@ -3514,11 +3520,26 @@ class Up2k(object):
3514
3520
 
3515
3521
  linked = False
3516
3522
  try:
3517
- if "reflink" in flags:
3518
- raise Exception("reflink")
3523
+ if rm and bos.path.exists(dst):
3524
+ wunlink(self.log, dst, flags)
3525
+
3519
3526
  if not is_mv and not flags.get("dedup"):
3520
3527
  raise Exception("dedup is disabled in config")
3521
3528
 
3529
+ if "reflink" in flags:
3530
+ if not USE_FICLONE:
3531
+ raise Exception("reflink") # python 3.14 or newer; no need
3532
+ try:
3533
+ with open(fsenc(src), "rb") as fi, open(fsenc(dst), "wb") as fo:
3534
+ fcntl.ioctl(fo.fileno(), fcntl.FICLONE, fi.fileno())
3535
+ except:
3536
+ if bos.path.exists(dst):
3537
+ wunlink(self.log, dst, flags)
3538
+ raise
3539
+ if lmod:
3540
+ bos.utime_c(self.log, dst, int(lmod), False)
3541
+ return
3542
+
3522
3543
  lsrc = src
3523
3544
  ldst = dst
3524
3545
  fs1 = bos.stat(os.path.dirname(src)).st_dev
@@ -3545,9 +3566,6 @@ class Up2k(object):
3545
3566
  lsrc = lsrc.replace("/", "\\")
3546
3567
  ldst = ldst.replace("/", "\\")
3547
3568
 
3548
- if rm and bos.path.exists(dst):
3549
- wunlink(self.log, dst, flags)
3550
-
3551
3569
  try:
3552
3570
  if "hardlink" in flags:
3553
3571
  os.link(fsenc(absreal(src)), fsenc(dst))
@@ -3962,7 +3980,7 @@ class Up2k(object):
3962
3980
  sz,
3963
3981
  ip,
3964
3982
  at or time.time(),
3965
- "",
3983
+ None,
3966
3984
  )
3967
3985
  t = hr.get("rejectmsg") or ""
3968
3986
  if t or not hr:
@@ -4197,7 +4215,7 @@ class Up2k(object):
4197
4215
  st.st_size,
4198
4216
  ip,
4199
4217
  time.time(),
4200
- "",
4218
+ None,
4201
4219
  ):
4202
4220
  t = "delete blocked by xbd server config: %r"
4203
4221
  self.log(t % (abspath,), 1)
@@ -4237,7 +4255,7 @@ class Up2k(object):
4237
4255
  st.st_size,
4238
4256
  ip,
4239
4257
  time.time(),
4240
- "",
4258
+ None,
4241
4259
  )
4242
4260
 
4243
4261
  if is_dir:
@@ -4365,7 +4383,7 @@ class Up2k(object):
4365
4383
  fsize,
4366
4384
  ip,
4367
4385
  time.time(),
4368
- "",
4386
+ None,
4369
4387
  ):
4370
4388
  t = "copy blocked by xbr server config: %r" % (svp,)
4371
4389
  self.log(t, 1)
@@ -4465,7 +4483,7 @@ class Up2k(object):
4465
4483
  fsize,
4466
4484
  ip,
4467
4485
  time.time(),
4468
- "",
4486
+ None,
4469
4487
  )
4470
4488
 
4471
4489
  return "k"
@@ -4616,7 +4634,7 @@ class Up2k(object):
4616
4634
  fsize,
4617
4635
  ip,
4618
4636
  time.time(),
4619
- "",
4637
+ None,
4620
4638
  ):
4621
4639
  t = "move blocked by xbr server config: %r" % (svp,)
4622
4640
  self.log(t, 1)
@@ -4656,7 +4674,7 @@ class Up2k(object):
4656
4674
  fsize,
4657
4675
  ip,
4658
4676
  time.time(),
4659
- "",
4677
+ None,
4660
4678
  )
4661
4679
 
4662
4680
  return "k"
@@ -4667,6 +4685,12 @@ class Up2k(object):
4667
4685
  has_dupes = False
4668
4686
  if w:
4669
4687
  if c2 and c2 != c1:
4688
+ if "nodupem" in dvn.flags:
4689
+ q = "select w from up where substr(w,1,16) = ?"
4690
+ for (w2,) in c2.execute(q, (w[:16],)):
4691
+ if w == w2:
4692
+ t = "file exists in target volume, and dupes are forbidden in config"
4693
+ raise Pebkac(400, t)
4670
4694
  self._copy_tags(c1, c2, w)
4671
4695
 
4672
4696
  xlink = bool(svn.flags.get("xlink"))
@@ -4775,7 +4799,7 @@ class Up2k(object):
4775
4799
  fsize,
4776
4800
  ip,
4777
4801
  time.time(),
4778
- "",
4802
+ None,
4779
4803
  )
4780
4804
 
4781
4805
  return "k"
@@ -5112,7 +5136,7 @@ class Up2k(object):
5112
5136
  job["size"],
5113
5137
  job["addr"],
5114
5138
  job["t0"],
5115
- "",
5139
+ None,
5116
5140
  )
5117
5141
  t = hr.get("rejectmsg") or ""
5118
5142
  if t or not hr:
copyparty/util.py CHANGED
@@ -129,8 +129,10 @@ try:
129
129
  import fcntl
130
130
 
131
131
  HAVE_FCNTL = True
132
+ HAVE_FICLONE = hasattr(fcntl, "FICLONE")
132
133
  except:
133
134
  HAVE_FCNTL = False
135
+ HAVE_FICLONE = False
134
136
 
135
137
  try:
136
138
  import ctypes
@@ -335,6 +337,8 @@ IMPLICATIONS = [
335
337
  ["hardlink_only", "hardlink"],
336
338
  ["hardlink", "dedup"],
337
339
  ["tftpvv", "tftpv"],
340
+ ["nodupem", "nodupe"],
341
+ ["no_dupe_m", "no_dupe"],
338
342
  ["smbw", "smb"],
339
343
  ["smb1", "smb"],
340
344
  ["smbvvv", "smbvv"],
@@ -1416,20 +1420,24 @@ def trace(*args , **kwargs ) :
1416
1420
  nuprint(msg)
1417
1421
 
1418
1422
 
1419
- def alltrace() :
1423
+ def alltrace(verbose = True) :
1420
1424
  threads = {}
1421
1425
  names = dict([(t.ident, t.name) for t in threading.enumerate()])
1422
1426
  for tid, stack in sys._current_frames().items():
1423
- name = "%s (%x)" % (names.get(tid), tid)
1427
+ if verbose:
1428
+ name = "%s (%x)" % (names.get(tid), tid)
1429
+ else:
1430
+ name = str(names.get(tid))
1424
1431
  threads[name] = stack
1425
1432
 
1426
1433
  rret = []
1427
1434
  bret = []
1435
+ np = -3 if verbose else -2
1428
1436
  for name, stack in sorted(threads.items()):
1429
1437
  ret = ["\n\n# %s" % (name,)]
1430
1438
  pad = None
1431
1439
  for fn, lno, name, line in traceback.extract_stack(stack):
1432
- fn = os.sep.join(fn.split(os.sep)[-3:])
1440
+ fn = os.sep.join(fn.split(os.sep)[np:])
1433
1441
  ret.append('File: "%s", line %d, in %s' % (fn, lno, name))
1434
1442
  if line:
1435
1443
  ret.append(" " + str(line.strip()))
@@ -3814,15 +3822,17 @@ def _runhook(
3814
3822
  "user": uname,
3815
3823
  "perms": perms,
3816
3824
  "src": src,
3817
- "txt": txt,
3818
3825
  }
3826
+ if txt:
3827
+ ja["txt"] = txt[0]
3828
+ ja["body"] = txt[1]
3819
3829
  if imp:
3820
3830
  ja["log"] = log
3821
3831
  mod = loadpy(acmd[0], False)
3822
3832
  return mod.main(ja)
3823
3833
  arg = json.dumps(ja)
3824
3834
  else:
3825
- arg = txt or ap
3835
+ arg = txt[0] if txt else ap
3826
3836
 
3827
3837
  if acmd[0].startswith("zmq:"):
3828
3838
  zi, zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
Binary file
Binary file
@@ -145,6 +145,9 @@
145
145
  document.documentElement.className = (STG && STG.cpp_thm) || dtheme;
146
146
  </script>
147
147
  <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
148
+ {%- if lang != "eng" %}
149
+ <script src="{{ r }}/.cpr/tl/{{ lang }}.js?_={{ ts }}"></script>
150
+ {%- endif %}
148
151
  <script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
149
152
  <script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script>
150
153
  <script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script>
Binary file
copyparty/web/splash.html CHANGED
@@ -211,6 +211,9 @@ document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme
211
211
 
212
212
  </script>
213
213
  <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
214
+ {%- if lang != "eng" %}
215
+ <script src="{{ r }}/.cpr/tl/{{ lang }}.js?_={{ ts }}"></script>
216
+ {%- endif %}
214
217
  <script src="{{ r }}/.cpr/splash.js?_={{ ts }}"></script>
215
218
  {%- if js %}
216
219
  <script src="{{ js }}_={{ ts }}"></script>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
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.19.16
3
+ Version: 1.19.17
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
@@ -588,6 +588,9 @@ examples:
588
588
 
589
589
  if you want to grant access to all users who are logged in, the group `acct` will always contain all known users, so for example `-v /mnt/music:music:r,@acct`
590
590
 
591
+ * to do the opposite, granting access to everyone who is NOT logged in. `*,-@acct` does the trick, for example `-v /srv/welcome:welcome:r,*,-@acct`
592
+ * single users can also be subtracted from a group: `@admins,-james`
593
+
591
594
  anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
592
595
 
593
596
  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`
@@ -1558,7 +1561,12 @@ tweaking the ui
1558
1561
  * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Circle,tags/.tn,tags/Artist,tags/Title,href`
1559
1562
  * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
1560
1563
 
1561
- see [./docs/rice](./docs/rice) for more, including how to add stuff (css/`<meta>`/...) to the html `<head>` tag, or to add your own translation
1564
+ see [./docs/rice](./docs/rice) for more, including:
1565
+ * how to [hide ui-elements](./docs/rice/README.md#hide-ui-elements)
1566
+ * [custom fonts](./docs/rice/README.md#custom-fonts)
1567
+ * [custom loading-spinner](./docs/rice/README.md#boring-loader-spinner)
1568
+ * adding stuff (css/`<meta>`/...) [to the html `<head>` tag](./docs/rice/README.md#head)
1569
+ * [adding your own translation](./docs/rice/README.md#translations)
1562
1570
 
1563
1571
 
1564
1572
  ## opengraph
@@ -1,41 +1,41 @@
1
- copyparty/__init__.py,sha256=wmO5wCR1yHdtWbYv1_8LZxLwS4W1s2OxpVYafvLHEBE,2660
2
- copyparty/__main__.py,sha256=ysSQEmxBQ40R5iZIVa9apLfR82LwkHPVtu1W4iU3hP4,147818
3
- copyparty/__version__.py,sha256=oIw4XsjXEqNflXJWZrLPYMKxBdC_Vb3EB-qU1BnCmjQ,252
4
- copyparty/authsrv.py,sha256=bs3Wa06KwyoJ5Dz_SEKkXlei1qbz_Wvc3tXwyICpf-o,134516
1
+ copyparty/__init__.py,sha256=7fjx-1nxaxr6UHo6A7MocA3-UPZTPP6jfgrNNfP38bk,2926
2
+ copyparty/__main__.py,sha256=uzOymQUZzDEFQa9SNZWLtbu7zbCd_6NEID0lmgrLtng,150194
3
+ copyparty/__version__.py,sha256=pQd30Cga5MCctT15TAcN5imWPCkMU0k4XYeP-zt5IGw,253
4
+ copyparty/authsrv.py,sha256=JDUjrEra0e9yUDT_xqWju4wWhuVZjEz3iBlr9sotkR8,135540
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=LXQlmxzOvdpqP1aZoiIvWHAYUV_PoewLORLD1BGRs0g,3449
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
8
8
  copyparty/broker_util.py,sha256=oZ3dGdJXLYvmdV4KSFdUv_PGErkgU-7kgr1SIglkjCU,1663
9
9
  copyparty/cert.py,sha256=OGTUBxhqPbseG0Bd4cHD6e5T5T8JdGqp3q0KAYqX0Cc,8031
10
- copyparty/cfg.py,sha256=8sW0KXoR1zkn9IrSLpbz8SDL0pGDJ0OS_2gn311RBnc,17035
10
+ copyparty/cfg.py,sha256=MPSVOamweuBhz65UdZQ7PVIzMle8BOyWQVBZBtesoZ4,17782
11
11
  copyparty/dxml.py,sha256=VZADJS9z18LalENSvVfjk29ddnpaDQ-v8AVm_THwS1c,2607
12
12
  copyparty/fsutil.py,sha256=kGvNM9G9YLeqcQjhDGfmT8gV7fn2WU0xye9BulL4h04,6469
13
- copyparty/ftpd.py,sha256=gXErlJgWsL2rw9l6REqE61GtLa8lgIaMShm86U9s05I,18531
14
- copyparty/httpcli.py,sha256=TcnghANMegZL5XMOhVhRvFS2xu0G1YUL5mXBTaTNSoA,246916
13
+ copyparty/ftpd.py,sha256=uw8HOpkBfDPX-MpUvWgzuZhT3VLx3PNdoCAXgGdgGSA,18627
14
+ copyparty/httpcli.py,sha256=XtNzx_aUP2VkXRurOB7GW2RpP2t6G4G53as6zN6pZa4,250071
15
15
  copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
16
16
  copyparty/httpsrv.py,sha256=6Al4seTbEqARBYkPwx_8YLS6qz6-oJQHwFdO1rqEgd4,19094
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
18
18
  copyparty/mdns.py,sha256=OfAms3kqolF5KFkorwwKH7ysd6_DRQzi3Ri62vTdCHA,19293
19
19
  copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
20
- copyparty/mtag.py,sha256=2mTQiMQOd6465jRpvX12E6cj1djquQb4rwRbGP44ltY,23184
20
+ copyparty/mtag.py,sha256=xfo6dm769Qq9kCGcYt9vyyS6774v0NPjNSXoUq3A3tw,23184
21
21
  copyparty/multicast.py,sha256=aqiY_ufCxcJ_VHn89jmdJFz61UukjEbQmHLGS6L-6JY,12423
22
22
  copyparty/pwhash.py,sha256=58txP8GXIHOtWd__Tni8qkilHEGpOFsKIPuzjdFLc6M,4426
23
23
  copyparty/qrkode.py,sha256=dkjcHDfEdxozFVwVAY8bFFf5P0cqieYmaMdnFixeiuM,2681
24
- copyparty/smbd.py,sha256=53GsMXXsQMHbGCkbUVoeQEo_JU5pV8FEtuJCKZHa93E,14890
24
+ copyparty/smbd.py,sha256=e-IBDEVzFgSHspQjpUm2d5K58koBT109ZOPJup1yBJ0,14892
25
25
  copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
26
26
  copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
27
27
  copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
28
- copyparty/svchub.py,sha256=jNYXOx5EQ8nJa2c26_zoJTGW7cwHt5gqOd-UMNL7K6Q,54903
28
+ copyparty/svchub.py,sha256=Qvcicxab_Xwy3pA22PIGTM2aIqVbmhRM7YwQDJ8aLpI,54964
29
29
  copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
30
30
  copyparty/tcpsrv.py,sha256=xQEJsDES9cYJnXaxy1GqN_M_8rYeAKM94Fkbg9sZWRI,22305
31
- copyparty/tftpd.py,sha256=Y-RFkwjcVGVYI8aHfvoir6eUz3Ai5uI6zax-M523C-o,14498
31
+ copyparty/tftpd.py,sha256=TA5-8b_JAmSWuN9YiHiRVBw_4Wusnr2ENidr06Uyej8,14500
32
32
  copyparty/th_cli.py,sha256=n3MMbnN7HTVSBHkTI2qLIjgwVvHkGsfTW7-aPJ-2o4s,5545
33
33
  copyparty/th_srv.py,sha256=r4I8PaMfg_eOf7Zo3GZpT22U-DsYnQO_R0fiZjB9R38,38573
34
34
  copyparty/u2idx.py,sha256=9Fcm8cDgLU4DdrH_2Q-lHAbMz4VfzLnANS0s4QgWZzE,14890
35
- copyparty/up2k.py,sha256=3AWXznWPCQVz15YUlciLu5IEmZFvDFwyLTaCyBccn6c,184191
36
- copyparty/util.py,sha256=3pORB5_fY0cTTJfy75HDYRZ620go_OPsXjPhXdYIkM4,110322
35
+ copyparty/up2k.py,sha256=-Cqg7g6iAshI0k-89-1yVOmFhbFNYHnOb83L6z9mFvc,185204
36
+ copyparty/util.py,sha256=b_f1Ff2K4hfjOB6uHyJ3gyr8xX6S2wCA_Vxy4mTaaVE,110639
37
37
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- copyparty/bos/bos.py,sha256=aGnph4sLIXe7-SFLo5qo1V67Ab1ZtgiUNbAN8EnHvkA,3371
38
+ copyparty/bos/bos.py,sha256=5Cp0Ob5XnCt8Zmr56QLQmsc9rkdz8Y1UBeB0026TqoE,3388
39
39
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
40
40
  copyparty/res/COPYING.txt,sha256=Zfcbw5KN_KJ7PQeLgoXwyWcAXXb0gc41iQiHcnGSNxg,19902
41
41
  copyparty/res/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,10 +55,10 @@ copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABw
55
55
  copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUuTOrU,2626
56
56
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
57
57
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
58
- copyparty/web/baguettebox.js.gz,sha256=7tJpm9DOxV1HWlRLARI2C8fcZX5IRCLS6vPaqRzAbzM,8714
59
- copyparty/web/browser.css.gz,sha256=MqGQ_VTy7zbXiZ-JoeSTdH7awif-ro7YsRxAaFJW3jM,15841
60
- copyparty/web/browser.html,sha256=EdHq96FJd3GSAvaTlLR8Icflmqby8U_GK1XGoOcGK8c,4760
61
- copyparty/web/browser.js.gz,sha256=3vaGeVUifCfr1CDvXluEgveWMN7pWOHaTwCd-PRGAjw,343024
58
+ copyparty/web/baguettebox.js.gz,sha256=B0gNOVblZWC3dcOektvuqT2jfVK99WY6ThRo9t1iu_A,9499
59
+ copyparty/web/browser.css.gz,sha256=DPIruSmxA-yw7yv6vBiQZoIKOCcDLOrxORm3ae5GNWQ,15878
60
+ copyparty/web/browser.html,sha256=IxHHqvGMlrG8B4hYkoIaH2cX6qCOCCZ6TQbAx9oB8KY,4865
61
+ copyparty/web/browser.js.gz,sha256=vfurFx7CklzzYDs_GDJ_AXkTJHN-O46jO7gx4qr7WfI,72501
62
62
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
63
63
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
64
64
  copyparty/web/dbg-audio.js.gz,sha256=fNf4wGVGdz5fDtn-97ks3XPIglTKnTKiYo4Dk2m-NoA,688
@@ -81,13 +81,13 @@ copyparty/web/shares.css.gz,sha256=ChgHt-2dPgvPCpwClNeijX39OWJIbwj7NBIZXLLvaT0,5
81
81
  copyparty/web/shares.html,sha256=SWgcgiAXEtV6xFqYDUlgg6WmrjedmgP3hCaVksNk1y4,2582
82
82
  copyparty/web/shares.js.gz,sha256=LGgFdYhtTwBp96x6CtWZpMWrGaZW7vn5LYvIhPQqm8g,942
83
83
  copyparty/web/splash.css.gz,sha256=Ez7MRPtPzYEiSsLsI7S9o8_jGFQxUFvK2j92ow5AoDg,1155
84
- copyparty/web/splash.html,sha256=7Q9rLKJalKWDNmvG60XXlyrUP7RqCJ5IkHgyCU5lPfQ,7476
85
- copyparty/web/splash.js.gz,sha256=xw5NnAsEthN_Yo1KGjlQxpBtw2mPUerVhzvYIBHKXtA,17721
84
+ copyparty/web/splash.html,sha256=NOYmsKmPpzJN0UbavTEq5Pfb9993URrOC9rBl6EJOBY,7578
85
+ copyparty/web/splash.js.gz,sha256=QPvYszEB_HAR98nb1pnEYnuuz23zHjH3wUTJGaa_Myo,1182
86
86
  copyparty/web/svcs.html,sha256=eBT6zq130hLHcD7OzYZD4ozFEd_bmU1-hTqDTt09Xs8,17576
87
87
  copyparty/web/svcs.js.gz,sha256=DapO-UcJYln4VPyZ6Pwi0Lzhlyx0Hul7y7E2ephu34c,971
88
88
  copyparty/web/ui.css.gz,sha256=Txx36dDbFI7_9I2LPUO40nCFnxLFKVpdkBsHFwI7JoE,2848
89
89
  copyparty/web/up2k.js.gz,sha256=3-_qOof3UR00_jVDxTGpQFv-DH3gfPS8k1QByHZtd14,25035
90
- copyparty/web/util.js.gz,sha256=UYOO3QCEs7qKeHcbxilQx-g_Q6CNGBHAai5bvZOR0dY,16321
90
+ copyparty/web/util.js.gz,sha256=YGNczP3-yjio2iU2lUBsMKSiikiji-vgZIljSANu_z0,16387
91
91
  copyparty/web/w.hash.js.gz,sha256=rI-78PyXCdVjOpULbD9raBRyxxaPls5OjR7wonzXZPE,1193
92
92
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  copyparty/web/a/partyfuse.py,sha256=QxBoe0iu4LTedmxWdIsuBUlwdxQ7zQh9LAFtjAFcPpo,28206
@@ -107,9 +107,28 @@ copyparty/web/deps/prismd.css.gz,sha256=VPKuZhPmVyhTKHo2xN6IBX-C2A8Eo313vIPxQ2Hx
107
107
  copyparty/web/deps/scp.woff2,sha256=mmjmsoLDBPKa5VHcEdxZiJcDR1S1TOvCjIg5Cf1iL0s,8684
108
108
  copyparty/web/deps/sha512.ac.js.gz,sha256=OLk7EjA5e-DtJGlV0IQbvokpmoyE5v7djezx9kZBabE,7046
109
109
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
110
- copyparty-1.19.16.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
111
- copyparty-1.19.16.dist-info/METADATA,sha256=4YNU_PlVT3alzEpmP-iPvDTryxv9imesLQfF57DnxJM,181533
112
- copyparty-1.19.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
113
- copyparty-1.19.16.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
114
- copyparty-1.19.16.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
115
- copyparty-1.19.16.dist-info/RECORD,,
110
+ copyparty/web/tl/chi.js.gz,sha256=TMOdOsT5e6g5JkJSNfxMBj8j9Lcgp5CPAPSU_TBrWI4,14686
111
+ copyparty/web/tl/cze.js.gz,sha256=tWbPL2LyQj95z1THu0oIhKZT0xDW3nC3QqqyBUsSmEo,15424
112
+ copyparty/web/tl/deu.js.gz,sha256=Xw-PejKnh8VNEMkS9yh7FUgZYn_pj7G9iqgWlhpLbPM,15567
113
+ copyparty/web/tl/epo.js.gz,sha256=d9b2MLwN6jstK522tZWRcf6j5c4Q6Vm3VrV-C5Z_lu0,14213
114
+ copyparty/web/tl/fin.js.gz,sha256=7SUK-xUwBe8aq49gPxdEX2GbjNCVzN4v_rJC7zZHgac,15168
115
+ copyparty/web/tl/fra.js.gz,sha256=9Jhn3878w-7BiMI9fRqZDJcWjs-8DJuLCza4LTC9bOU,15825
116
+ copyparty/web/tl/grc.js.gz,sha256=Q6vsE2hX8O7hv05AIjRXI46ozgHvsuifcz9S3Pse-e0,17752
117
+ copyparty/web/tl/ita.js.gz,sha256=IYjg4KqMayWSVr_jUaprRHf93Vvmpi64ZgGTaoXbisY,14661
118
+ copyparty/web/tl/kor.js.gz,sha256=XWlZvsX1nDVA_s-9vZXJh5gZpiScu4vcRkudmM0vXvg,15326
119
+ copyparty/web/tl/nld.js.gz,sha256=6d9tDZDK44IpUiEQqU7XLrzv4fKqakOBQzuoh-eyHdI,15430
120
+ copyparty/web/tl/nno.js.gz,sha256=i-bzHEOBfqyRV9fT5UmSZKZfNX_7XkU66E7mkBd-M00,14817
121
+ copyparty/web/tl/nor.js.gz,sha256=RADjEOWqkzm7JSZbGMZhoXhbw21ks8Df1DDPBe_f2Io,14598
122
+ copyparty/web/tl/pol.js.gz,sha256=IIhRwrdmJ29HWCQXO0aLQKT7K4Tql4KJnRw5rOG7Nx4,15694
123
+ copyparty/web/tl/por.js.gz,sha256=rwBCgtgdL24r-oNCPyODhA0ldv0dJnLTg273YHEDI5E,14886
124
+ copyparty/web/tl/rus.js.gz,sha256=SoH7G1VcAUnbv-yhYJB1x-ZSRiHCwwZ-xQ7ImykryaM,16909
125
+ copyparty/web/tl/spa.js.gz,sha256=pEqdC_qfbF1JNJQg5UX7bj6XXVZoLWzWCLkeT_oVNFk,14956
126
+ copyparty/web/tl/swe.js.gz,sha256=JAz_p2ceHLxsu-lBfc3zcukszbnCDkcvE4IU4nzW2Kk,14393
127
+ copyparty/web/tl/tur.js.gz,sha256=yOgKEuZ8zpGJo7E-wPXIKjwVcQMAoDLDiJow_B3yomo,14977
128
+ copyparty/web/tl/ukr.js.gz,sha256=3ZUR27PBTI_QyhEcoX0yo6_KawvehTRzqbJ0q1pc_bo,17243
129
+ copyparty-1.19.17.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
130
+ copyparty-1.19.17.dist-info/METADATA,sha256=-xwwzxdEnbrnryX-iV-Cx1mm0FVXGRqeTusKHmKa_kU,182009
131
+ copyparty-1.19.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
+ copyparty-1.19.17.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
133
+ copyparty-1.19.17.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
134
+ copyparty-1.19.17.dist-info/RECORD,,