copyparty 1.13.3__py3-none-any.whl → 1.13.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -42,6 +42,7 @@ from .util import (
42
42
  DEF_EXP,
43
43
  DEF_MTE,
44
44
  DEF_MTH,
45
+ HAVE_IPV6,
45
46
  IMPLICATIONS,
46
47
  JINJA_VER,
47
48
  MIMES,
@@ -287,6 +288,9 @@ def get_ah_salt() :
287
288
 
288
289
 
289
290
  def ensure_locale() :
291
+ if ANYWIN and PY2:
292
+ return # maybe XP, so busted 65001
293
+
290
294
  safe = "en_US.UTF-8"
291
295
  for x in [
292
296
  safe,
@@ -624,12 +628,12 @@ def get_sects():
624
628
  \033[36mxban\033[35m executes CMD if someone gets banned
625
629
  \033[0m
626
630
  can be defined as --args or volflags; for example \033[36m
627
- --xau notify-send
628
- -v .::r:c,xau=notify-send
631
+ --xau foo.py
632
+ -v .::r:c,xau=bar.py
629
633
  \033[0m
630
- commands specified as --args are appended to volflags;
631
- each --arg and volflag can be specified multiple times,
632
- each command will execute in order unless one returns non-zero
634
+ hooks specified as commandline --args are appended to volflags;
635
+ each commandline --arg and volflag can be specified multiple times,
636
+ each hook will execute in order unless one returns non-zero
633
637
 
634
638
  optionally prefix the command with comma-sep. flags similar to -mtp:
635
639
 
@@ -640,6 +644,10 @@ def get_sects():
640
644
  \033[36mtN\033[35m sets an N sec timeout before the command is abandoned
641
645
  \033[36miN\033[35m xiu only: volume must be idle for N sec (default = 5)
642
646
 
647
+ \033[36mar\033[35m only run hook if user has read-access
648
+ \033[36marw\033[35m only run hook if user has read-write-access
649
+ \033[36marwmd\033[35m ...and so on... (doesn't work for xiu or xban)
650
+
643
651
  \033[36mkt\033[35m kills the entire process tree on timeout (default),
644
652
  \033[36mkm\033[35m kills just the main process
645
653
  \033[36mkn\033[35m lets it continue running until copyparty is terminated
@@ -649,6 +657,21 @@ def get_sects():
649
657
  \033[36mc2\033[35m show only stdout
650
658
  \033[36mc3\033[35m mute all process otput
651
659
  \033[0m
660
+ examples:
661
+
662
+ \033[36m--xm some.py\033[35m runs \033[33msome.py msgtxt\033[35m on each 📟 message;
663
+ \033[33mmsgtxt\033[35m is the message that was written into the web-ui
664
+
665
+ \033[36m--xm j,some.py\033[35m runs \033[33msome.py jsontext\033[35m on each 📟 message;
666
+ \033[33mjsontext\033[35m is the message info (ip, user, ..., msg-text)
667
+
668
+ \033[36m--xm aw,j,some.py\033[35m requires user to have write-access
669
+
670
+ \033[36m--xm aw,,notify-send,hey,--\033[35m shows an OS alert on linux;
671
+ the \033[33m,,\033[35m stops copyparty from reading the rest as flags and
672
+ the \033[33m--\033[35m stops notify-send from reading the message as args
673
+ and the alert will be "hey" followed by the messagetext
674
+ \033[0m
652
675
  each hook is executed once for each event, except for \033[36mxiu\033[0m
653
676
  which builds up a backlog of uploads, running the hook just once
654
677
  as soon as the volume has been idle for iN seconds (5 by default)
@@ -675,7 +698,10 @@ def get_sects():
675
698
  \033[36mstash\033[35m dumps the data to file and returns length + checksum
676
699
  \033[36msave,get\033[35m dumps to file and returns the page like a GET
677
700
  \033[36mprint,get\033[35m prints the data in the log and returns GET
678
- (leave out the ",get" to return an error instead)
701
+ (leave out the ",get" to return an error instead)\033[0m
702
+
703
+ note that the \033[35m--xm\033[0m hook will only run if \033[35m--urlform\033[0m
704
+ is either \033[36mprint\033[0m or the default \033[36mprint,get\033[0m
679
705
  """
680
706
  ),
681
707
  ],
@@ -901,7 +927,7 @@ def add_upload(ap):
901
927
  ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
902
928
  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")
903
929
  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")
904
- ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
930
+ ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
905
931
  ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
906
932
  ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
907
933
  ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
@@ -929,12 +955,12 @@ def add_network(ap):
929
955
  else:
930
956
  ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
931
957
  ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
932
- ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=186, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
958
+ ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=186.0, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
933
959
  ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
934
960
  ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
935
- ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
936
- ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
937
- ap2.add_argument("--rsp-jtr", metavar="SEC", type=float, default=0, help="debug: response delay, random duration 0..\033[33mSEC\033[0m")
961
+ ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0.0, help="debug: socket write delay in seconds")
962
+ ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0.0, help="debug: response delay in seconds")
963
+ ap2.add_argument("--rsp-jtr", metavar="SEC", type=float, default=0.0, help="debug: response delay, random duration 0..\033[33mSEC\033[0m")
938
964
 
939
965
 
940
966
  def add_tls(ap, cert_path):
@@ -942,10 +968,10 @@ def add_tls(ap, cert_path):
942
968
  ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
943
969
  ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
944
970
  ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to TLS certificate")
945
- ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
946
- ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
971
+ ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
972
+ ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
947
973
  ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
948
- ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets for later decryption in wireshark")
974
+ ap2.add_argument("--ssl-log", metavar="PATH", type=u, default="", help="log master secrets for later decryption in wireshark")
949
975
 
950
976
 
951
977
  def add_cert(ap, cert_path):
@@ -958,12 +984,12 @@ def add_cert(ap, cert_path):
958
984
  ap2.add_argument("--crt-nolo", action="store_true", help="do not add 127.0.0.1 / localhost into cert")
959
985
  ap2.add_argument("--crt-nohn", action="store_true", help="do not add mDNS names / hostname into cert")
960
986
  ap2.add_argument("--crt-dir", metavar="PATH", default=cert_dir, help="where to save the CA cert")
961
- ap2.add_argument("--crt-cdays", metavar="D", type=float, default=3650, help="ca-certificate expiration time in days")
962
- ap2.add_argument("--crt-sdays", metavar="D", type=float, default=365, help="server-cert expiration time in days")
987
+ ap2.add_argument("--crt-cdays", metavar="D", type=float, default=3650.0, help="ca-certificate expiration time in days")
988
+ ap2.add_argument("--crt-sdays", metavar="D", type=float, default=365.0, help="server-cert expiration time in days")
963
989
  ap2.add_argument("--crt-cn", metavar="TXT", type=u, default="partyco", help="CA/server-cert common-name")
964
990
  ap2.add_argument("--crt-cnc", metavar="TXT", type=u, default="--crt-cn", help="override CA name")
965
991
  ap2.add_argument("--crt-cns", metavar="TXT", type=u, default="--crt-cn cpp", help="override server-cert name")
966
- ap2.add_argument("--crt-back", metavar="HRS", type=float, default=72, help="backdate in hours")
992
+ ap2.add_argument("--crt-back", metavar="HRS", type=float, default=72.0, help="backdate in hours")
967
993
  ap2.add_argument("--crt-alg", metavar="S-N", type=u, default="ecdsa-256", help="algorithm and keysize; one of these: \033[32mecdsa-256 rsa-4096 rsa-2048\033[0m")
968
994
 
969
995
 
@@ -1004,7 +1030,7 @@ def add_zc_mdns(ap):
1004
1030
  ap2.add_argument("--zm-mnic", action="store_true", help="merge NICs which share subnets; assume that same subnet means same network")
1005
1031
  ap2.add_argument("--zm-msub", action="store_true", help="merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not")
1006
1032
  ap2.add_argument("--zm-noneg", action="store_true", help="disable NSEC replies -- try this if some clients don't see copyparty")
1007
- ap2.add_argument("--zm-spam", metavar="SEC", type=float, default=0, help="send unsolicited announce every \033[33mSEC\033[0m; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues")
1033
+ ap2.add_argument("--zm-spam", metavar="SEC", type=float, default=0.0, help="send unsolicited announce every \033[33mSEC\033[0m; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues")
1008
1034
 
1009
1035
 
1010
1036
  def add_zc_ssdp(ap):
@@ -1019,14 +1045,15 @@ def add_zc_ssdp(ap):
1019
1045
 
1020
1046
  def add_ftp(ap):
1021
1047
  ap2 = ap.add_argument_group('FTP options (TCP only)')
1022
- ap2.add_argument("--ftp", metavar="PORT", type=int, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921")
1023
- ap2.add_argument("--ftps", metavar="PORT", type=int, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
1048
+ ap2.add_argument("--ftp", metavar="PORT", type=int, default=0, help="enable FTP server on \033[33mPORT\033[0m, for example \033[32m3921")
1049
+ ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
1024
1050
  ap2.add_argument("--ftpv", action="store_true", help="verbose")
1025
1051
  ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4")
1026
1052
  ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
1053
+ ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite")
1027
1054
  ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
1028
- ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections")
1029
- ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example \033[32m12000-13000")
1055
+ ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections")
1056
+ ap2.add_argument("--ftp-pr", metavar="P-P", type=u, default="", help="the range of TCP ports to use for passive connections, for example \033[32m12000-13000")
1030
1057
 
1031
1058
 
1032
1059
  def add_webdav(ap):
@@ -1040,14 +1067,15 @@ def add_webdav(ap):
1040
1067
 
1041
1068
  def add_tftp(ap):
1042
1069
  ap2 = ap.add_argument_group('TFTP options (UDP only)')
1043
- ap2.add_argument("--tftp", metavar="PORT", type=int, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969")
1070
+ ap2.add_argument("--tftp", metavar="PORT", type=int, default=0, help="enable TFTP server on \033[33mPORT\033[0m, for example \033[32m69 \033[0mor \033[32m3969")
1071
+ ap2.add_argument("--tftp4", action="store_true", help="only listen on IPv4")
1044
1072
  ap2.add_argument("--tftpv", action="store_true", help="verbose")
1045
1073
  ap2.add_argument("--tftpvv", action="store_true", help="verboser")
1046
1074
  ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
1047
1075
  ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...")
1048
1076
  ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing")
1049
1077
  ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
1050
- ap2.add_argument("--tftp-pr", metavar="P-P", type=u, help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000")
1078
+ ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000")
1051
1079
 
1052
1080
 
1053
1081
  def add_smb(ap):
@@ -1123,7 +1151,7 @@ def add_safety(ap):
1123
1151
  ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
1124
1152
  ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 -nih")
1125
1153
  ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
1126
- ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
1154
+ ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
1127
1155
  ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
1128
1156
  ap2.add_argument("--xdev", action="store_true", help="stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
1129
1157
  ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
@@ -1133,7 +1161,7 @@ def add_safety(ap):
1133
1161
  ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise)")
1134
1162
  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")
1135
1163
  ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
1136
- ap2.add_argument("--logout", metavar="H", type=float, default="8086", 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)")
1164
+ 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)")
1137
1165
  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]")
1138
1166
  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")
1139
1167
  ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
@@ -1169,7 +1197,7 @@ def add_shutdown(ap):
1169
1197
  def add_logging(ap):
1170
1198
  ap2 = ap.add_argument_group('logging options')
1171
1199
  ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
1172
- ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
1200
+ ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile, example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
1173
1201
  ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
1174
1202
  ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
1175
1203
  ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
@@ -1196,10 +1224,10 @@ def add_thumbnail(ap):
1196
1224
  ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
1197
1225
  ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
1198
1226
  ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
1199
- ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
1200
- ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1201
- ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32mfy\033[0m]=crop, [\033[32mfn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1202
- ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32mfy\033[0m]=yes, [\033[32mfn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1227
+ ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60.0, help="conversion timeout in seconds (volflag=convt)")
1228
+ ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=6.0, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
1229
+ ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
1230
+ ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
1203
1231
  ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
1204
1232
  ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
1205
1233
  ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
@@ -1238,8 +1266,8 @@ def add_db_general(ap, hcores):
1238
1266
  ap2.add_argument("-e2v", action="store_true", help="verify file integrity; rehash all files and compare with db")
1239
1267
  ap2.add_argument("-e2vu", action="store_true", help="on hash mismatch: update the database with the new hash")
1240
1268
  ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
1241
- ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs) (volflag=hist)")
1242
- ap2.add_argument("--no-hash", metavar="PTN", type=u, help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
1269
+ ap2.add_argument("--hist", metavar="PATH", type=u, default="", help="where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist)")
1270
+ ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
1243
1271
  ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
1244
1272
  ap2.add_argument("--no-dhash", action="store_true", help="disable rescan acceleration; do full database integrity check -- makes the db ~5%% smaller and bootup/rescans 3~10x slower")
1245
1273
  ap2.add_argument("--re-dhash", action="store_true", help="force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary)")
@@ -1248,7 +1276,7 @@ def add_db_general(ap, hcores):
1248
1276
  ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (volflag=xlink)")
1249
1277
  ap2.add_argument("--hash-mt", metavar="CORES", type=int, default=hcores, help="num cpu cores to use for file hashing; set 0 or 1 for single-core hashing")
1250
1278
  ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="rescan filesystem for changes every \033[33mSEC\033[0m seconds; 0=off (volflag=scan)")
1251
- ap2.add_argument("--db-act", metavar="SEC", type=float, default=10, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
1279
+ ap2.add_argument("--db-act", metavar="SEC", type=float, default=10.0, help="defer any scheduled volume reindexing until \033[33mSEC\033[0m seconds after last db write (uploads, renames, ...)")
1252
1280
  ap2.add_argument("--srch-time", metavar="SEC", type=int, default=45, help="search deadline -- terminate searches running for more than \033[33mSEC\033[0m seconds")
1253
1281
  ap2.add_argument("--srch-hits", metavar="N", type=int, default=7999, help="max search results to allow clients to fetch; 125 results will be shown initially")
1254
1282
  ap2.add_argument("--dotsrch", action="store_true", help="show dotfiles in search results (volflags: dotsrch | nodotsrch)")
@@ -1301,6 +1329,7 @@ def add_og(ap):
1301
1329
  def add_ui(ap, retry):
1302
1330
  ap2 = ap.add_argument_group('ui options')
1303
1331
  ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
1332
+ ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
1304
1333
  ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor\033[0m")
1305
1334
  ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
1306
1335
  ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
@@ -1309,8 +1338,8 @@ def add_ui(ap, retry):
1309
1338
  ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
1310
1339
  ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
1311
1340
  ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
1312
- ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
1313
- ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
1341
+ ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include")
1342
+ ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include")
1314
1343
  ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages; can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
1315
1344
  ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
1316
1345
  ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
@@ -1338,8 +1367,8 @@ def add_debug(ap):
1338
1367
  ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
1339
1368
  ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks")
1340
1369
  ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc")
1341
- ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60")
1342
- ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every \033[33mSEC\033[0m")
1370
+ ap2.add_argument("--stackmon", metavar="P,S", type=u, default="", help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60")
1371
+ ap2.add_argument("--log-thrs", metavar="SEC", type=float, default=0.0, help="list active threads every \033[33mSEC\033[0m")
1343
1372
  ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches \033[33mREGEX\033[0m; [\033[32m.\033[0m] (a single dot) = all files")
1344
1373
  ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to \033[33m--bf-nc\033[0m and \033[33m--bf-dir\033[0m")
1345
1374
  ap2.add_argument("--bf-nc", metavar="NUM", type=int, default=200, help="bak-flips: stop if there's more than \033[33mNUM\033[0m files at \033[33m--kf-dir\033[0m already; default: 6.3 GiB max (200*32M)")
@@ -1587,6 +1616,9 @@ def main(argv = None, rsrc = None) :
1587
1616
  if getattr(al, k1):
1588
1617
  setattr(al, k2, False)
1589
1618
 
1619
+ if not HAVE_IPV6 and al.i == "::":
1620
+ al.i = "0.0.0.0"
1621
+
1590
1622
  al.i = al.i.split(",")
1591
1623
  try:
1592
1624
  if "-" in al.p:
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 13, 3)
3
+ VERSION = (1, 13, 4)
4
4
  CODENAME = "race the beam"
5
- BUILD_DT = (2024, 6, 1)
5
+ BUILD_DT = (2024, 7, 16)
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
@@ -17,6 +17,8 @@ from .bos import bos
17
17
  from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
18
18
  from .pwhash import PWHash
19
19
  from .util import (
20
+ DEF_MTE,
21
+ DEF_MTH,
20
22
  EXTS,
21
23
  IMPLICATIONS,
22
24
  MIMES,
@@ -468,6 +470,13 @@ class VFS(object):
468
470
  )
469
471
  # skip uhtml because it's rarely needed
470
472
 
473
+ def get_perms(self, vpath , uname ) :
474
+ zbl = self.can_access(vpath, uname)
475
+ ret = "".join(ch for ch, ok in zip("rwmdgGa.", zbl) if ok)
476
+ if "rwmd" in ret and "a." in ret:
477
+ ret += "A"
478
+ return ret
479
+
471
480
  def get(
472
481
  self,
473
482
  vpath ,
@@ -2260,10 +2269,11 @@ class AuthSrv(object):
2260
2269
  "",
2261
2270
  ]
2262
2271
 
2263
- csv = set("i p".split())
2272
+ csv = set("i p th_covers zm_on zm_off zs_on zs_off".split())
2264
2273
  zs = "c ihead mtm mtp on403 on404 xad xar xau xiu xban xbd xbr xbu xm"
2265
2274
  lst = set(zs.split())
2266
- askip = set("a v c vc cgen theme".split())
2275
+ askip = set("a v c vc cgen exp_lg exp_md theme".split())
2276
+ fskip = set("exp_lg exp_md mv_re_r mv_re_t rm_re_r rm_re_t".split())
2267
2277
 
2268
2278
  # keymap from argv to vflag
2269
2279
  amap = vf_bmap()
@@ -2284,11 +2294,35 @@ class AuthSrv(object):
2284
2294
  for k, v in args.items():
2285
2295
  if k in askip:
2286
2296
  continue
2297
+
2298
+ try:
2299
+ v = v.pattern
2300
+ if k in ("idp_gsep", "tftp_lsf"):
2301
+ v = v[1:-1] # close enough
2302
+ except:
2303
+ pass
2304
+
2305
+ skip = False
2306
+ for k2, defstr in (("mte", DEF_MTE), ("mth", DEF_MTH)):
2307
+ if k != k2:
2308
+ continue
2309
+ s1 = list(sorted(list(v)))
2310
+ s2 = list(sorted(defstr.split(",")))
2311
+ if s1 == s2:
2312
+ skip = True
2313
+ break
2314
+ v = ",".join(s1)
2315
+
2316
+ if skip:
2317
+ continue
2318
+
2287
2319
  if k in csv:
2288
2320
  v = ", ".join([str(za) for za in v])
2289
2321
  try:
2290
2322
  v2 = getattr(self.dargs, k)
2291
- if v == v2:
2323
+ if k == "tcolor" and len(v2) == 3:
2324
+ v2 = "".join([x * 2 for x in v2])
2325
+ if v == v2 or v.replace(", ", ",") == v2:
2292
2326
  continue
2293
2327
  except:
2294
2328
  continue
@@ -2347,6 +2381,7 @@ class AuthSrv(object):
2347
2381
  pstr += pchar
2348
2382
  if "g" in pstr and "G" in pstr:
2349
2383
  pstr = pstr.replace("g", "")
2384
+ pstr = pstr.replace("rwmd.a", "A")
2350
2385
  try:
2351
2386
  vperms[pstr].append(uname)
2352
2387
  except:
@@ -2356,24 +2391,48 @@ class AuthSrv(object):
2356
2391
  trues = []
2357
2392
  vals = []
2358
2393
  for k, v in sorted(vol.flags.items()):
2394
+ if k in fskip:
2395
+ continue
2396
+
2397
+ try:
2398
+ v = v.pattern
2399
+ except:
2400
+ pass
2401
+
2359
2402
  try:
2360
2403
  ak = vmap[k]
2361
- if getattr(self.args, ak) is v:
2404
+ v2 = getattr(self.args, ak)
2405
+
2406
+ try:
2407
+ v2 = v2.pattern
2408
+ except:
2409
+ pass
2410
+
2411
+ if v2 is v:
2362
2412
  continue
2363
2413
  except:
2364
2414
  pass
2365
2415
 
2416
+ skip = False
2417
+ for k2, defstr in (("mte", DEF_MTE), ("mth", DEF_MTH)):
2418
+ if k != k2:
2419
+ continue
2420
+ s1 = list(sorted(list(v)))
2421
+ s2 = list(sorted(defstr.split(",")))
2422
+ if s1 == s2:
2423
+ skip = True
2424
+ break
2425
+ v = ",".join(s1)
2426
+
2427
+ if skip:
2428
+ continue
2429
+
2366
2430
  if k in lst:
2367
2431
  for ve in v:
2368
2432
  vals.append("{}: {}".format(k, ve))
2369
2433
  elif v is True:
2370
2434
  trues.append(k)
2371
2435
  elif v is not False:
2372
- try:
2373
- v = v.pattern
2374
- except:
2375
- pass
2376
-
2377
2436
  vals.append("{}: {}".format(k, v))
2378
2437
  pops = []
2379
2438
  for k1, k2 in IMPLICATIONS:
copyparty/cert.py CHANGED
@@ -79,6 +79,8 @@ def _read_crt(args, fn):
79
79
 
80
80
 
81
81
  def _gen_ca(log , args):
82
+ nlog = lambda msg, c=0: log("cert-gen-ca", msg, c)
83
+
82
84
  expiry = _read_crt(args, "ca.pem")[0]
83
85
  if time.time() + args.crt_cdays * 60 * 60 * 24 * 0.1 < expiry:
84
86
  return
@@ -109,16 +111,18 @@ def _gen_ca(log , args):
109
111
 
110
112
  bname = os.path.join(args.crt_dir, "ca")
111
113
  try:
112
- wunlink(log, bname + ".key", VF)
114
+ wunlink(nlog, bname + ".key", VF)
113
115
  except:
114
116
  pass
115
- wrename(log, bname + "-key.pem", bname + ".key", VF)
116
- wunlink(log, bname + ".csr", VF)
117
+ wrename(nlog, bname + "-key.pem", bname + ".key", VF)
118
+ wunlink(nlog, bname + ".csr", VF)
117
119
 
118
120
  log("cert", "new ca OK", 2)
119
121
 
120
122
 
121
123
  def _gen_srv(log , args, netdevs ):
124
+ nlog = lambda msg, c=0: log("cert-gen-srv", msg, c)
125
+
122
126
  names = args.crt_ns.split(",") if args.crt_ns else []
123
127
  if not args.crt_exact:
124
128
  for n in names[:]:
@@ -192,11 +196,11 @@ def _gen_srv(log , args, netdevs ):
192
196
 
193
197
  bname = os.path.join(args.crt_dir, "srv")
194
198
  try:
195
- wunlink(log, bname + ".key", VF)
199
+ wunlink(nlog, bname + ".key", VF)
196
200
  except:
197
201
  pass
198
- wrename(log, bname + "-key.pem", bname + ".key", VF)
199
- wunlink(log, bname + ".csr", VF)
202
+ wrename(nlog, bname + "-key.pem", bname + ".key", VF)
203
+ wunlink(nlog, bname + ".csr", VF)
200
204
 
201
205
  with open(os.path.join(args.crt_dir, "ca.pem"), "rb") as f:
202
206
  ca = f.read()
copyparty/cfg.py CHANGED
@@ -35,6 +35,7 @@ def vf_bmap() :
35
35
  "e2vp",
36
36
  "exp",
37
37
  "grid",
38
+ "gsel",
38
39
  "hardlink",
39
40
  "magic",
40
41
  "no_sb_md",
@@ -144,6 +145,7 @@ flagcats = {
144
145
  "maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g, t)",
145
146
  "vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
146
147
  "vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
148
+ "medialinks": "return medialinks for non-up2k uploads (not hotlinks)",
147
149
  "rand": "force randomized filenames, 9 chars long by default",
148
150
  "nrand=N": "randomized filenames are N chars long",
149
151
  "u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
@@ -213,6 +215,7 @@ flagcats = {
213
215
  },
214
216
  "client and ux": {
215
217
  "grid": "show grid/thumbnails by default",
218
+ "gsel": "select files in grid by ctrl-click",
216
219
  "sort": "default sort order",
217
220
  "unlist": "dont list files matching REGEX",
218
221
  "html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
copyparty/ftpd.py CHANGED
@@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING
19
19
  from .authsrv import VFS
20
20
  from .bos import bos
21
21
  from .util import (
22
+ VF_CAREFUL,
22
23
  Daemon,
23
24
  ODict,
24
25
  Pebkac,
@@ -30,6 +31,7 @@ from .util import (
30
31
  runhook,
31
32
  sanitize_fn,
32
33
  vjoin,
34
+ wunlink,
33
35
  )
34
36
 
35
37
  if TYPE_CHECKING:
@@ -134,6 +136,9 @@ class FtpFs(AbstractedFS):
134
136
  self.listdirinfo = self.listdir
135
137
  self.chdir(".")
136
138
 
139
+ def log(self, msg , c = 0) :
140
+ self.hub.log("ftpd", msg, c)
141
+
137
142
  def v2a(
138
143
  self,
139
144
  vpath ,
@@ -202,17 +207,37 @@ class FtpFs(AbstractedFS):
202
207
  w = "w" in mode or "a" in mode or "+" in mode
203
208
 
204
209
  ap = self.rv2a(filename, r, w)[0]
210
+ self.validpath(ap)
205
211
  if w:
206
212
  try:
207
213
  st = bos.stat(ap)
208
214
  td = time.time() - st.st_mtime
215
+ need_unlink = True
209
216
  except:
217
+ need_unlink = False
210
218
  td = 0
211
219
 
212
- if td < -1 or td > self.args.ftp_wt:
213
- raise FSE("Cannot open existing file for writing")
220
+ if w and need_unlink:
221
+ if td >= -1 and td <= self.args.ftp_wt:
222
+ # within permitted timeframe; unlink and accept
223
+ do_it = True
224
+ elif self.args.no_del or self.args.ftp_no_ow:
225
+ # file too old, or overwrite not allowed; reject
226
+ do_it = False
227
+ else:
228
+ # allow overwrite if user has delete permission
229
+ # (avoids win2000 freaking out and deleting the server copy without uploading its own)
230
+ try:
231
+ self.rv2a(filename, False, True, False, True)
232
+ do_it = True
233
+ except:
234
+ do_it = False
235
+
236
+ if not do_it:
237
+ raise FSE("File already exists")
238
+
239
+ wunlink(self.log, ap, VF_CAREFUL)
214
240
 
215
- self.validpath(ap)
216
241
  return open(fsenc(ap), mode, self.args.iobuf)
217
242
 
218
243
  def chdir(self, path ) :
@@ -277,9 +302,20 @@ class FtpFs(AbstractedFS):
277
302
  # display write-only folders as empty
278
303
  return []
279
304
 
280
- # return list of volumes
281
- r = {x.split("/")[0]: 1 for x in self.hub.asrv.vfs.all_vols.keys()}
282
- return list(sorted(list(r.keys())))
305
+ # return list of accessible volumes
306
+ ret = []
307
+ for vn in self.hub.asrv.vfs.all_vols.values():
308
+ if "/" in vn.vpath or not vn.vpath:
309
+ continue # only include toplevel-mounted vols
310
+
311
+ try:
312
+ self.hub.asrv.vfs.get(vn.vpath, self.uname, True, False)
313
+ ret.append(vn.vpath)
314
+ except:
315
+ pass
316
+
317
+ ret.sort()
318
+ return ret
283
319
 
284
320
  def rmdir(self, path ) :
285
321
  ap = self.rv2a(path, d=True)[0]
@@ -429,9 +465,10 @@ class FtpHandler(FTPHandler):
429
465
  None,
430
466
  xbu,
431
467
  ap,
432
- vfs.canonical(rem),
468
+ vp,
433
469
  "",
434
470
  self.uname,
471
+ self.hub.asrv.vfs.get_perms(vp, self.uname),
435
472
  0,
436
473
  0,
437
474
  self.cli_ip,