copyparty 1.19.16__py3-none-any.whl → 1.19.18__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 (55) hide show
  1. copyparty/__init__.py +25 -1
  2. copyparty/__main__.py +32 -9
  3. copyparty/__version__.py +2 -2
  4. copyparty/authsrv.py +78 -31
  5. copyparty/bos/bos.py +5 -1
  6. copyparty/cfg.py +20 -0
  7. copyparty/ftpd.py +6 -4
  8. copyparty/httpcli.py +166 -45
  9. copyparty/httpsrv.py +2 -2
  10. copyparty/mtag.py +2 -2
  11. copyparty/smbd.py +1 -1
  12. copyparty/svchub.py +3 -0
  13. copyparty/tftpd.py +1 -1
  14. copyparty/up2k.py +39 -15
  15. copyparty/util.py +15 -5
  16. copyparty/web/a/partyfuse.py.gz +0 -0
  17. copyparty/web/a/u2c.py.gz +0 -0
  18. copyparty/web/a/webdav-cfg.txt.gz +0 -0
  19. copyparty/web/baguettebox.js.gz +0 -0
  20. copyparty/web/browser.css.gz +0 -0
  21. copyparty/web/browser.html +3 -0
  22. copyparty/web/browser.js.gz +0 -0
  23. copyparty/web/splash.html +3 -0
  24. copyparty/web/splash.js.gz +0 -0
  25. copyparty/web/svcs.html +1 -1
  26. copyparty/web/tl/chi.js.gz +0 -0
  27. copyparty/web/tl/cze.js.gz +0 -0
  28. copyparty/web/tl/deu.js.gz +0 -0
  29. copyparty/web/tl/epo.js.gz +0 -0
  30. copyparty/web/tl/fin.js.gz +0 -0
  31. copyparty/web/tl/fra.js.gz +0 -0
  32. copyparty/web/tl/grc.js.gz +0 -0
  33. copyparty/web/tl/ita.js.gz +0 -0
  34. copyparty/web/tl/kor.js.gz +0 -0
  35. copyparty/web/tl/nld.js.gz +0 -0
  36. copyparty/web/tl/nno.js.gz +0 -0
  37. copyparty/web/tl/nor.js.gz +0 -0
  38. copyparty/web/tl/pol.js.gz +0 -0
  39. copyparty/web/tl/por.js.gz +0 -0
  40. copyparty/web/tl/rus.js.gz +0 -0
  41. copyparty/web/tl/spa.js.gz +0 -0
  42. copyparty/web/tl/swe.js.gz +0 -0
  43. copyparty/web/tl/tur.js.gz +0 -0
  44. copyparty/web/tl/ukr.js.gz +0 -0
  45. copyparty/web/up2k.js.gz +0 -0
  46. copyparty/web/util.js.gz +0 -0
  47. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/METADATA +26 -4
  48. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/RECORD +52 -33
  49. copyparty/web/a/partyfuse.py +0 -947
  50. copyparty/web/a/u2c.py +0 -1718
  51. copyparty/web/a/webdav-cfg.bat +0 -45
  52. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/WHEEL +0 -0
  53. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/entry_points.txt +0 -0
  54. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/licenses/LICENSE +0 -0
  55. {copyparty-1.19.16.dist-info → copyparty-1.19.18.dist-info}/top_level.txt +0 -0
copyparty/__init__.py CHANGED
@@ -52,7 +52,7 @@ except:
52
52
  zs = """
53
53
  web/a/partyfuse.py
54
54
  web/a/u2c.py
55
- web/a/webdav-cfg.bat
55
+ web/a/webdav-cfg.txt
56
56
  web/baguettebox.js
57
57
  web/browser.css
58
58
  web/browser.html
@@ -97,12 +97,36 @@ 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
103
122
  web/w.hash.js
104
123
  """
105
124
  RES = set(zs.strip().split("\n"))
125
+ RESM = {
126
+ "web/a/partyfuse.txt": "web/a/partyfuse.py",
127
+ "web/a/u2c.txt": "web/a/u2c.py",
128
+ "web/a/webdav-cfg.bat": "web/a/webdav-cfg.txt",
129
+ }
106
130
 
107
131
 
108
132
  class EnvParams(object):
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):
@@ -1779,6 +1788,7 @@ def add_ui(ap, retry ):
1779
1788
  ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
1780
1789
  ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m 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)")
1781
1790
  ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
1791
+ ap2.add_argument("--notooltips", action="store_true", help="tooltips disabled as default")
1782
1792
  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]")
1783
1793
  ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
1784
1794
  ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
@@ -1805,6 +1815,15 @@ def add_ui(ap, retry ):
1805
1815
  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
1816
  ap2.add_argument("--no-sb-md", action="store_true", help="don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md)")
1807
1817
  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")
1818
+ ap2.add_argument("--ui-nombar", action="store_true", help="hide top-menu in the UI (volflag=ui_nombar)")
1819
+ ap2.add_argument("--ui-noacci", action="store_true", help="hide account-info in the UI (volflag=ui_noacci)")
1820
+ ap2.add_argument("--ui-nosrvi", action="store_true", help="hide server-info in the UI (volflag=ui_nosrvi)")
1821
+ ap2.add_argument("--ui-nonav", action="store_true", help="hide navpane+breadcrumbs (volflag=ui_nonav)")
1822
+ ap2.add_argument("--ui-notree", action="store_true", help="hide navpane in the UI (volflag=ui_nonav)")
1823
+ ap2.add_argument("--ui-nocpla", action="store_true", help="hide cpanel-link in the UI (volflag=ui_nocpla)")
1824
+ ap2.add_argument("--ui-nolbar", action="store_true", help="hide link-bar in the UI (volflag=ui_nolbar)")
1825
+ ap2.add_argument("--ui-noctxb", action="store_true", help="hide context-buttons in the UI (volflag=ui_noctxb)")
1826
+ ap2.add_argument("--ui-norepl", action="store_true", help="hide repl-button in the UI (volflag=ui_norepl)")
1808
1827
  ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS)
1809
1828
 
1810
1829
 
@@ -1932,15 +1951,19 @@ def run_argparse(
1932
1951
 
1933
1952
 
1934
1953
  def main(argv = None) :
1954
+ if argv is None:
1955
+ argv = sys.argv
1956
+
1957
+ if "--versionb" in argv:
1958
+ print(S_VERSION)
1959
+ sys.exit(0)
1960
+
1935
1961
  time.strptime("19970815", "%Y%m%d") # python#7980
1936
1962
  if WINDOWS:
1937
1963
  os.system("rem") # enables colors
1938
1964
 
1939
1965
  init_E(E)
1940
1966
 
1941
- if argv is None:
1942
- argv = sys.argv
1943
-
1944
1967
  f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0;36m\n sqlite {} | jinja {} | pyftpd {} | tftp {}\n\033[0m'
1945
1968
  f = f.format(
1946
1969
  S_VERSION,
@@ -2067,7 +2090,7 @@ def main(argv = None) :
2067
2090
 
2068
2091
  # propagate implications
2069
2092
  for k1, k2 in IMPLICATIONS:
2070
- if getattr(al, k1):
2093
+ if getattr(al, k1, None):
2071
2094
  setattr(al, k2, True)
2072
2095
 
2073
2096
  # 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, 18)
4
4
  CODENAME = "usernames"
5
- BUILD_DT = (2025, 10, 5)
5
+ BUILD_DT = (2025, 10, 25)
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
@@ -391,6 +391,9 @@ class VFS(object):
391
391
  self.vpath = vpath # absolute path in the virtual filesystem
392
392
  self.vpath0 = vpath0 # original vpath (before idp expansion)
393
393
  self.axs = axs
394
+ self.uaxs = {}
395
+
396
+
394
397
  self.flags = flags # config options
395
398
  self.root = self
396
399
  self.dev = 0 # st_dev
@@ -548,29 +551,19 @@ class VFS(object):
548
551
 
549
552
  def can_access(
550
553
  self, vpath , uname
551
- ) :
552
- """can Read,Write,Move,Delete,Get,Upget,Admin,Dot"""
554
+ ) :
555
+ """can Read,Write,Move,Delete,Get,Upget,Html,Admin,Dot"""
556
+ # NOTE: only used by get_perms, which is only used by hooks; the lowest of fruits
553
557
  if vpath:
554
558
  vn, _ = self._find(undot(vpath))
555
559
  else:
556
560
  vn = self
557
561
 
558
- c = vn.axs
559
- return (
560
- uname in c.uread,
561
- uname in c.uwrite,
562
- uname in c.umove,
563
- uname in c.udel,
564
- uname in c.uget,
565
- uname in c.upget,
566
- uname in c.uadmin,
567
- uname in c.udot,
568
- )
569
- # skip uhtml because it's rarely needed
562
+ return vn.uaxs[uname]
570
563
 
571
564
  def get_perms(self, vpath , uname ) :
572
565
  zbl = self.can_access(vpath, uname)
573
- ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok)
566
+ ret = "".join(ch for ch, ok in zip("rwmdgGha.", zbl) if ok)
574
567
  if "rwmd" in ret and "a." in ret:
575
568
  ret += "A"
576
569
  return ret
@@ -763,20 +756,17 @@ class VFS(object):
763
756
  virt_vis[name] = vn2
764
757
  continue
765
758
 
766
- ok = False
767
- zx = vn2.axs
768
- axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
759
+ u_has = vn2.uaxs.get(uname) or [False] * 9
769
760
  for pset in permsets:
770
761
  ok = True
771
- for req, lst in zip(pset, axs):
772
- if req and uname not in lst:
762
+ for req, zb in zip(pset, u_has):
763
+ if req and not zb:
773
764
  ok = False
765
+ break
774
766
  if ok:
767
+ virt_vis[name] = vn2
775
768
  break
776
769
 
777
- if ok:
778
- virt_vis[name] = vn2
779
-
780
770
  if ".hist" in abspath:
781
771
  p = abspath.replace("\\", "/") if WINDOWS else abspath
782
772
  if p.endswith("/.hist"):
@@ -1961,9 +1951,18 @@ class AuthSrv(object):
1961
1951
  axs_key = "u" + perm
1962
1952
  for vp, vol in vfs.all_vols.items():
1963
1953
  zx = getattr(vol.axs, axs_key)
1964
- if "*" in zx:
1954
+ if "*" in zx and "-@acct" not in zx:
1965
1955
  for usr in unames:
1966
1956
  zx.add(usr)
1957
+ for zs in list(zx):
1958
+ if zs.startswith("-"):
1959
+ zx.discard(zs)
1960
+ zs = zs[1:]
1961
+ zx.discard(zs)
1962
+ if zs.startswith("@"):
1963
+ zs = zs[1:]
1964
+ for zs in grps.get(zs) or []:
1965
+ zx.discard(zs)
1967
1966
 
1968
1967
  # aread,... = dict[uname, list[volnames] or []]
1969
1968
  umap = {x: [] for x in unames}
@@ -1975,6 +1974,23 @@ class AuthSrv(object):
1975
1974
  umap[usr].sort()
1976
1975
  setattr(vfs, "a" + perm, umap)
1977
1976
 
1977
+ for vol in vfs.all_nodes.values():
1978
+ za = vol.axs
1979
+ vol.uaxs = {
1980
+ un: (
1981
+ un in za.uread,
1982
+ un in za.uwrite,
1983
+ un in za.umove,
1984
+ un in za.udel,
1985
+ un in za.uget,
1986
+ un in za.upget,
1987
+ un in za.uhtml,
1988
+ un in za.uadmin,
1989
+ un in za.udot,
1990
+ )
1991
+ for un in unames
1992
+ }
1993
+
1978
1994
  all_users = {}
1979
1995
  missing_users = {}
1980
1996
  associated_users = {}
@@ -2308,6 +2324,10 @@ class AuthSrv(object):
2308
2324
  free_umask = False
2309
2325
  have_reflink = False
2310
2326
  for vol in vfs.all_nodes.values():
2327
+ if os.path.isfile(vol.realpath):
2328
+ vol.flags["is_file"] = True
2329
+ vol.flags["d2d"] = True
2330
+
2311
2331
  if (self.args.e2ds and vol.axs.uwrite) or self.args.e2dsa:
2312
2332
  vol.flags["e2ds"] = True
2313
2333
 
@@ -2577,6 +2597,15 @@ class AuthSrv(object):
2577
2597
  for x in drop:
2578
2598
  vol.flags.pop(x)
2579
2599
 
2600
+ zi = vol.flags.get("lifetime") or 0
2601
+ zi2 = time.time() // (86400 * 365)
2602
+ zi3 = zi2 * 86400 * 365
2603
+ if zi < 0 or zi > zi3:
2604
+ t = "the lifetime of volume [/%s] (%d) exceeds max value (%d years; %d)"
2605
+ t = t % (vol.vpath, zi, zi2, zi3)
2606
+ self.log(t, 1)
2607
+ raise Exception(t)
2608
+
2580
2609
  # verify tags mentioned by -mt[mp] are used by -mte
2581
2610
  local_mtp = {}
2582
2611
  local_only_mtp = {}
@@ -2613,7 +2642,14 @@ class AuthSrv(object):
2613
2642
  errors = True
2614
2643
 
2615
2644
  for vol in vfs.all_nodes.values():
2616
- if not vol.realpath or os.path.isfile(vol.realpath):
2645
+ if not vol.flags.get("is_file"):
2646
+ continue
2647
+ zs = "og opds xlink"
2648
+ for zs in zs.split():
2649
+ vol.flags.pop(zs, None)
2650
+
2651
+ for vol in vfs.all_nodes.values():
2652
+ if not vol.realpath or vol.flags.get("is_file"):
2617
2653
  continue
2618
2654
  ccs = vol.flags["casechk"][:1].lower()
2619
2655
  if ccs in ("y", "n"):
@@ -2743,9 +2779,13 @@ class AuthSrv(object):
2743
2779
  ["uadmin", "uadmin"],
2744
2780
  ]:
2745
2781
  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"
2782
+ if u == ["*"] and acct:
2783
+ u = ["\033[35monly-anonymous\033[0m"]
2784
+ elif "*" in u:
2785
+ u = ["\033[35meverybody\033[0m"]
2786
+ if not u:
2787
+ u = ["\033[36m--none--\033[0m"]
2788
+ u = ", ".join(u)
2749
2789
  t += "\n| {}: {}".format(txt, u)
2750
2790
 
2751
2791
  if "e2d" in zv.flags:
@@ -2857,8 +2897,6 @@ class AuthSrv(object):
2857
2897
 
2858
2898
  if have_reflink:
2859
2899
  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
2900
  if not sys.platform.startswith("linux"):
2863
2901
  self.log(t % "your OS is not Linux", 1)
2864
2902
 
@@ -3016,6 +3054,7 @@ class AuthSrv(object):
3016
3054
  "have_shr": self.args.shr,
3017
3055
  "shr_who": vf["shr_who"],
3018
3056
  "have_zip": not self.args.no_zip,
3057
+ "have_zls": not self.args.no_zls,
3019
3058
  "have_mv": not self.args.no_mv,
3020
3059
  "have_del": not self.args.no_del,
3021
3060
  "have_unpost": int(self.args.unpost),
@@ -3052,6 +3091,14 @@ class AuthSrv(object):
3052
3091
  "lifetime": vn.js_ls["lifetime"],
3053
3092
  "u2sort": self.args.u2sort,
3054
3093
  }
3094
+ zs = "ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi"
3095
+ for zs in zs.split():
3096
+ if vf.get(zs):
3097
+ js_htm[zs] = 1
3098
+ zs = "notooltips"
3099
+ for zs in zs.split():
3100
+ if getattr(self.args, zs, False):
3101
+ js_htm[zs] = 1
3055
3102
  vn.js_htm = json_hesc(json.dumps(js_htm))
3056
3103
 
3057
3104
  vols = list(vfs.all_nodes.values())
@@ -3396,7 +3443,7 @@ class AuthSrv(object):
3396
3443
  raise Exception("volume not found: " + zs)
3397
3444
 
3398
3445
  self.log(str({"users": users, "vols": vols, "flags": flags}))
3399
- t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) uadmin({})"
3446
+ t = "/{}: read({}) write({}) move({}) del({}) dots({}) get({}) upGet({}) html({}) uadmin({})"
3400
3447
  for k, zv in self.vfs.all_vols.items():
3401
3448
  vc = zv.axs
3402
3449
  vs = [
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
@@ -194,7 +194,7 @@ class FtpFs(AbstractedFS):
194
194
  if not avfs:
195
195
  raise FSE(t.format(vpath), 1)
196
196
 
197
- cr, cw, cm, cd, _, _, _, _ = avfs.can_access("", self.h.uname)
197
+ cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
198
198
  if r and not cr or w and not cw or m and not cm or d and not cd:
199
199
  raise FSE(t.format(vpath), 1)
200
200
 
@@ -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: