Compare commits

...
449 Commits
Author SHA1 Message Date
Bart Oostveen 1af7efeca8 fix(nix): separate makeWrapperArgs properly (#1676)
* fix(nix): separate makeWrapperArgs properly

Since Python packages are now built with `__structuredAttrs = true;` in nixpkgs, this is neccesary.

See also: https://github.com/NixOS/nixpkgs/commit/a8383b633210925071f3d7c680c8d901939c7fd5
See also: https://github.com/NixOS/nixpkgs/pull/554025

* chore(nix): nixfmt
2026-10-21 08:09:00 +03:00
ed 8b4593c9a2 update pkgs to 1.20.26 2026-10-14 23:30:13 +00:00
ed 7ccebb6948 v1.20.26 2026-10-14 23:21:55 +00:00
ed ed0fcd608f th: fix .raw if not pil jxl 2026-10-14 21:55:12 +00:00
ed 9811cd3101 thx faelnor 2026-10-14 21:54:58 +00:00
ed fa8913c712 dumb 2026-10-14 21:53:36 +00:00
ed 4bd64d7456 shadowing: less false-positives 2026-10-14 21:51:52 +00:00
ed 7ce483e970 xvol: ls filter 2026-10-14 21:50:40 +00:00
ed 82eab3d2f8 xvol/xdev (GHSA-hg99-jqfp-rf76) 2026-10-14 21:49:04 +00:00
ed 0dc1cb3291 docker: drop openjpeg 2026-10-09 00:26:10 +00:00
ed 1a87dc3218 update pkgs to 1.20.25 2026-10-06 00:44:54 +00:00
ed dc3adc0f4d v1.20.25 2026-10-06 00:35:29 +00:00
ed 5f00c1631c return to ?b=u; closes #1659 2026-10-06 00:33:31 +00:00
ed e59fc05e73 add --sftp-chattr 2026-10-05 23:47:51 +00:00
ed e8de421174 shadowing + casefolding (GHSA-3c88-p299-7hvr) 2026-10-05 23:46:39 +00:00
ed 36aaa24974 file-rename instant preview on open 2026-10-05 23:02:30 +00:00
ed 5e0a88e355 ban-pw basic-auth; closes #1660 2026-10-05 22:36:30 +00:00
ed 4b283b4bfd bbox: fix #1650 (el null) 2026-10-05 22:32:49 +00:00
ed 64f574fe94 u2c: allow \ in filenames 2026-10-05 22:26:18 +00:00
Michael Rosenberg a31870cbd3 dk/fk constant-time (#1658) 2026-10-05 22:01:30 +00:00
ed 74b17374c2 DELETE: say 404 on enoent; closes #1656 2026-10-03 22:10:17 +00:00
ed 670e86228c tests: support new coreutils or whatever 2026-10-03 22:08:10 +00:00
ed 1341b3421f --smb now requres --hack-me-bro 2026-10-03 22:01:49 +00:00
ed 5961228c93 deadcode 2026-10-03 21:44:14 +00:00
ed 28f9f6f002 allow \ in filenames on linux/macos;
now possible to upload 09. t+pazolite - I still think about y%#'()_*{\\"'&36)%("'$&''&(15y!!!_??*`~.flac
2026-10-03 21:43:47 +00:00
ed 186cbf939f sftp: add fastloader;
startup on an 800mhz armv5 exceeded the 30sec timeout
2026-10-02 03:29:12 +00:00
ed 080935788b redup: skip conclusive hard/ref; closes #1652 2026-09-30 00:05:02 +00:00
ed c6918c2ce6 tests: macos 2026-09-30 00:15:08 +02:00
ed ab2299e3db add acao-re; closes #1498 2026-09-27 19:12:01 +00:00
edandTil Schmitter 334dccc55e --ih: fix nav to webroot
Co-authored-by: Til Schmitter <til.schmitter@gmail.com>
2026-09-27 18:23:08 +00:00
ed 8d7aa4f860 cosmetic 2026-09-27 18:20:47 +00:00
ed f73b37c6fa pipe-dl progress on /?h 2026-09-27 18:11:29 +00:00
ed 731dbbff84 u2c: fix exfat's http502 allergy;
addendum to 23e00c0a04
2026-09-26 23:43:26 +00:00
ed 23e00c0a04 up2k-js: fix exfat's http502 allergy;
normally, to avoid excessive handshaking, unexpected server errors are
handled by continuing onto the next chunk and dealing with it at eof

this doesn't work on filesystems that dont support sparse files, so
discard the chunk-queue and re-handshake to keep it sequential
2026-09-26 22:12:15 +00:00
ed 107dec9ed1 ifaddr: winxp fix 2026-09-24 19:31:21 +00:00
willie6666 f445bfa862 shares: delete sf entries too (#1647)
Signed-off-by: willie6666 <89621369+willie6666@users.noreply.github.com>
2026-09-20 13:12:56 +00:00
ed aff083fb28 update pkgs to 1.20.24 2026-09-19 21:39:09 +00:00
ed 5ccc999f39 v1.20.24 2026-09-19 21:33:56 +00:00
ed 16a3f5d51a add --help-redup + failsafe 2026-09-19 20:48:28 +00:00
ed 35af7c9fef js: add msel fn 2026-09-19 19:14:06 +00:00
ed 4d1f2a9301 js: fix contrived crash (closes #1516);
view textfile, sel, delete, sel, boom
2026-09-19 19:13:41 +00:00
ed 229159027b add --help-dedup 2026-09-19 17:43:51 +00:00
ed ec4a95e1a5 up2k nosub; closes #1611 2026-09-19 17:42:39 +00:00
ed eb4f1dddce lint 2026-09-18 20:59:16 +00:00
Mica Whiteanded 097b83056e textfile-viewer: parse shebang (#1643)
detect filetype based on #!/interpreter

---------

Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-09-18 19:47:09 +00:00
ed c688bb2e46 xiu: add trailing newline 2026-09-17 22:00:24 +00:00
ed c92435c2d2 add -i no 2026-09-17 21:53:50 +00:00
ed 22afaa7586 nocow histpath iff btrfs 2026-09-17 21:46:12 +00:00
ed 5ddbce31fc xlink-reflink only same-fs;
avoid waking other disks which wont work anyways
2026-09-17 21:24:10 +00:00
ed ec8ec0f1a6 un-cargocult bools 2026-09-17 21:12:46 +00:00
ed 558f6aee44 shallowclone up2k.flags;
update flags on config-reload while keeping up2k coherent
2026-09-17 19:56:58 +00:00
ed 6867ccc511 fix PRTY_NO_IFADDR + cleanup 2026-09-16 22:38:23 +00:00
ed 947d73eeb6 check xlink on copy 2026-09-16 22:35:11 +00:00
ed 735d6e00de up2k: reflink is xlink-safe 2026-09-16 22:34:19 +00:00
ed fd4f434192 add --redup 2026-09-16 22:33:38 +00:00
ed 971a62cc89 up2k: iOS: reduce jitter;
* xhr.abort() to close/free tcp
* onfoo=null fixes done-flicker
2026-09-15 19:29:47 +00:00
ed bfc3cff848 unsanctioned: sourceforge 2026-09-08 06:01:30 +02:00
ed 8967726f61 update pkgs to 1.20.23 2026-09-06 22:53:14 +00:00
ed ad9994bef3 v1.20.23 2026-09-06 22:49:08 +00:00
ed e5e2e97910 archlinux: add post_upgrade 2026-09-06 22:08:42 +00:00
ed e42e01563e update pkgs to 1.20.22 2026-09-06 21:18:21 +00:00
ed de21c210c6 v1.20.22 2026-09-06 21:12:32 +00:00
ed 55969b8e54 dk: block subdir zip (GHSA-mc69-pxc8-4xf4) 2026-09-06 21:07:39 +00:00
ed b80a210e0f sftp: xvol/xdev (GHSA-3fhv-rhjw-7hrg) 2026-09-06 21:07:21 +00:00
ed 8016512064 sfx: link explanation 2026-09-06 18:22:52 +00:00
ed 72eb03bf43 exe: drop license filter 2026-09-06 15:02:32 +00:00
ed f870cf3c05 sfx: prefer ~/.cache/; closes #1517 2026-09-05 22:49:01 +00:00
ed e6c9c6fbeb sfx: golf 2026-09-05 22:43:13 +00:00
ed dd9cf6456d btrfs: nocow .hist for sqlite perf 2026-09-05 20:54:37 +00:00
ed 185087ee05 systemd: use /etc/copyparty.conf; closes #1631 2026-09-05 17:56:19 +00:00
ed 3e3401f4ed allow dk download-as-zip 2026-09-05 12:55:58 +00:00
ed a78611d8c5 dont trust zip headers 2026-09-05 11:29:50 +00:00
ed 370a05ab3c add --no-mime / volflag nomime 2026-09-05 11:00:40 +00:00
ed d7d30f268e connect: rclone obscure; closes #1632 2026-09-04 19:14:06 +00:00
ed 34229337cc the font fell off 2026-09-04 19:01:44 +00:00
ed 0767d95674 up2k: fix !e2d reload blocking uploads 2026-09-04 18:51:15 +00:00
ed f00d19c5d3 avoid binary garbage in logs;
also stricten RE_HTTP1 against false-positives
2026-09-04 18:21:20 +00:00
ed ab791921ea fix csp-nonce on chrome<62 2026-09-04 17:54:20 +00:00
ed 2cebd8ce16 i18n: 2old 2026-09-04 17:54:04 +00:00
ed 662e3c5d59 indicate F4 instantly 2026-09-04 16:08:35 +00:00
ed 3ea671ff9a iOS Safari XHR has gone deaf;
workaround for iPhone bug in
iOS 18.6.2, 26.6, 26.6.1, ...

safari devtools has the full response,
it just never bothers calling xhr.onload
and xhr.readystate is unusably racey
(final value is any of 1..3)
2026-09-02 22:21:07 +00:00
ed f792a9529c add up2k mutex ping 2026-09-02 20:23:54 +00:00
ed 4e318d5165 deadcode 2026-09-01 23:02:48 +02:00
ed 80cf7bfdc0 certs: conditional whining 2026-09-01 23:00:10 +02:00
ed 704af697c7 packaging: license: rm missing/unvendored 2026-09-01 18:46:59 +02:00
ed b854b8f596 packaging: *.gif + pre-gzip js/css;
* web/*.gif was ignored by pyproject.toml
* pre-gzip js/css in dist source tarball
* systemd/archlinux: safer default-config
* readme: "config file example" labeling
2026-09-01 14:50:57 +02:00
Danila Kamaev 936e8a3bf3 rcm: wopi (#1607) 2026-08-31 16:36:55 +02:00
yuki 7796c2d06a add jxl support for autogrid (#1610) 2026-08-31 16:24:04 +02:00
Danila Kamaev adf5f639fa translation: rus review (#1612) 2026-08-31 16:22:46 +02:00
Danila Kamaev d4a1ce48e4 thumbex: collabora example (#1617) 2026-08-31 16:19:17 +02:00
Danila Kamaev 0a957bf264 thumbex: update example link (#1629) 2026-08-31 16:14:14 +02:00
ed bdf8b793d9 http206: fix footer (last-n-bytes) reqs 2026-08-31 16:08:05 +02:00
ed 69c022f565 misc 2026-08-31 15:41:01 +02:00
ed 7984ef9232 tests: fix macos 2026-08-31 15:40:21 +02:00
ed 9de090265f hooks: phonecam-sorter.py;
written 2025-04-27, just never left the nas
2026-08-23 22:28:45 +00:00
ed 6115eb452d update pkgs to 1.20.21 2026-08-17 22:05:44 +00:00
ed 32eadc0f14 v1.20.21 2026-08-17 21:57:27 +00:00
ed 566de65f17 option to allow js in svg 2026-08-17 21:04:45 +00:00
ed 78a4ee49a2 wopi-accs + support unmapped root 2026-08-17 19:40:22 +00:00
ed 9ff6a71a2a shares: hide md-hist too 2026-08-16 22:31:09 +00:00
ed 21c2c72889 shares: harden single-file shares some more;
motivated by a bug-report that couldn't be reproduced and
also didn't make sense, a "hallucination" as they call it?
(was submitted by an autonomous bot or something, man)

so this should make no difference aside from removing footguns
2026-08-16 22:22:51 +00:00
ed 18791c5768 up2k: on tcp loss, await !busy.up b4 re-hs;
avoids parallel reschedule of inflight chunks;
http400: that chunk is already being written to:
2026-08-16 20:48:48 +00:00
ed 2be85b67a9 lint 2026-08-16 18:23:12 +00:00
ed eeb399e3c9 fix dupe logue on !js hydrate 2026-08-16 18:20:11 +00:00
ed dcc0abd696 dsel: disable on bbox 2026-08-15 21:40:04 +00:00
ed 48d9de03fe whitespace 2026-08-15 21:38:51 +00:00
ed dbc6df812b th: add extractor example 2026-08-15 20:11:24 +00:00
Danila Kamaevanded 1864805072 th: custom thumbnail extractors (#1602)
Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-08-15 19:54:47 +00:00
shermanhlc 47297475bf u2c: password in U2C_PW (#1604) 2026-08-15 13:51:09 +00:00
ed 3098cf59cb wopi stuff:
* open in new tab
* print wopi url on error
* fix helptext to <= 15 ch
2026-08-15 13:25:36 +00:00
Danila Kamaev efcf96e3e3 wopi: collaborate (#1591)
persistent file IDs for real-time collaboration
2026-08-15 13:06:45 +00:00
Danila Kamaev 92c3f32e13 wopi: fix access_token_ttl (#1587) 2026-08-15 12:47:04 +00:00
ed 14e2d79b08 ignore PRTY_CONFIG if autodiscovered to prevent dupe vols 2026-08-15 09:56:13 +00:00
ed 825f1c9456 bbox: open svg; closes #1605 2026-08-15 09:48:22 +00:00
ed f19ef033ec up2k: u2sz: allow 1 as max + detect http413 2026-08-15 09:45:13 +00:00
ed 8b6e8972fe copyparty.exe: python 3.14 2026-08-12 18:38:53 +00:00
ed fbcb773307 failed the git skillcheck 2026-08-06 22:10:18 +00:00
ed 3eb7b46d83 optimize 2026-08-06 21:08:34 +00:00
h3 4f20738b71 haproxy: add X-Forwarded-Proto to example (codeberg#6)
Reviewed-on: https://codeberg.org/9001/copyparty/pulls/6
2026-08-05 01:52:58 +02:00
ed ef75220faf update pkgs to 1.20.20 2026-08-03 22:36:36 +00:00
ed a3acbfcd5b v1.20.20 2026-08-03 22:32:26 +00:00
Danila Kamaev fdf52a8c4e wopi: fix onlyoffice opening docs in readonly mode (#1585) 2026-08-03 22:02:18 +00:00
ed b9405a3e25 mute no-rss-warning for shares 2026-08-03 21:57:22 +00:00
ed 6f924829ee detect broken csp nonce 2026-08-03 21:56:45 +00:00
Danila Kamaev 24a4090fc7 wopi: add wopi-urls option (#1580);
map different copyparty hosts to different WOPI client addresses
2026-08-03 20:30:38 +00:00
ed c0c638c917 up2k: re-hs if unknown-wark or chunk-exists;
previous behavior:

* when a chunk already existed on the server, it would
   unstitch and continue retrying each individual chunk

* upon unknown-wark (file already completed), it would
   upload the remaining scheduled chunks before bailing

the unknown-wark behavior was accidental bullshit

the chunk-exists behavior was intentional, assumed optimal for
preserving tcp window-scaling, just not when running behind a
bufferbloating reverseproxy which may disconnect the client on
a timeout before the response is delivered, and "some clients"
still follow rfc2616-8.2.4 (retries the POST, wtf...)
wasting bandwidth + maybe further worsening conditions

"some clients" is specifically firefox with the devtools not open

another firefox joke is that it doesn't read the server-response
if the server does not drain the request-body; we end up in
xhr.onerror with no idea why, must assume the worst

new behavior:

in both scenarios, drop all scheduled chunks to be uploaded and
redo handshake, to as far as possible avoid retransmissions;
firefox will still eagerly retry 4 times but whatever

u2c was/is fine; nothing to be done

depending on network conditions and physical distance, this MAY
result in lower total speed, but the average case is likely a
net-positive, wasting less bandwidth on retransmissions,
in exchange for resetting tcp window-scaling
2026-08-03 20:08:19 +00:00
ed 2c6497b644 add hotkey F4 2026-08-03 18:55:24 +00:00
ed 2c4033831f js: while(apop) 2026-08-02 23:02:44 +00:00
ed 2880117265 fix ^a in search-results; closes #1581 2026-08-02 22:41:57 +00:00
yuki 63c61b6729 nixos: fix version checker (#1582)
Nixpkgs patches many libraries to read certificate bundle mentioned in
NIX_SSL_CERT_FILE, and /nix/store is already present in the sandbox.

Related to https://github.com/NixOS/nixpkgs/issues/409848
2026-08-02 21:48:24 +00:00
edandVictor Phillips 5c157be955 show subvolume size in listings;
* descends recursively to all nested volumes
* values are static, update on volume-rescan
* closes #634 #1033 #1390

Co-authored-by: Victor Phillips <victor.m.phillips@protonmail.com>
2026-08-01 22:23:58 +00:00
Wuerfel21 9f5f9d8884 th: .kra (krita) + .ora (openraster) (#1577) 2026-07-30 18:39:31 +00:00
ed 4003a47723 multipart boundary max-len 2026-07-30 18:22:45 +00:00
ed 398fcf1d18 update pkgs to 1.20.19 2026-07-27 22:08:01 +00:00
ed ac4be555f6 v1.20.19 2026-07-27 22:02:50 +00:00
ed 75c7502c34 mkdir: translit illegals instead of failing 2026-07-27 21:25:21 +00:00
ed 9912a95166 ftp: logging + fix xbu log 2026-07-27 19:06:25 +00:00
ed b331bb1c8c ftp: fix GHSA-phv8-wgjp-g4p9 (stou) 2026-07-27 18:50:44 +00:00
ed c122e103bd hooks: xar/xbr: old/new ap 2026-07-27 18:45:35 +00:00
ed 6226858b89 windows: fix fsnt detection 2026-07-27 17:35:45 +00:00
ed cea97ac6fd windows: restrict tool paths;
the warning in commit bb40804f2d is still true, but
this makes copyparty slightly safer in environments
where hostile binaries may appear in CWD
2026-07-27 17:34:52 +00:00
ed bc45299b86 windows: optimize sparse file creation;
previously shelled out to system32/fsutil.exe; expensive and
pointless since all it does is call DeviceIoControl (ioctl)

this is not a security fix; just like most software on windows,
copyparty is still vulnerable to DLL hijacking, so this change
makes little-to-no difference security-wise; commit bb40804f2d

plus, the default config (sharing current folder read-write
without auth) was meant to be obviously insecure, intended for
quick shares on a small LAN rather than untrusted networks
2026-07-27 17:20:25 +00:00
ed bb40804f2d windows: add DLL-hijacking warning;
famously, windows prefers cwd when resolving a binary to execute,
rather than a predefined PATH like linux / unix / every other OS

even worse, it also does this for DLLs, and mitigating *that*
is infeasible because it depends on variables we dont control

print a warning in case someone is unaware of this
2026-07-27 17:15:00 +00:00
ed c70dc7ac56 android: fix cross-fs mv; closes #1563 2026-07-26 21:19:33 +00:00
ed b6abc33fb3 lo: rlo: more spaghetti 2026-07-26 16:49:05 +00:00
ed 537a99df52 bump session-cookie from 20 to 24 ch 2026-07-26 16:47:17 +00:00
ed cdb474c8ec fix slow boot when a volume root has many files 2026-07-26 16:45:49 +00:00
ed 0f2040c383 ceil by integer math 2026-07-26 15:23:33 +00:00
ed e49f6cca17 py2: future division 2026-07-26 15:19:58 +00:00
ed 10db4236bf wopi: new-file, j2-template 2026-07-24 15:34:56 +00:00
Brandon Doornbosanded d57bb0c701 add wopi (#1495)
basic wopi integration

---------

Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-07-24 15:28:05 +00:00
Nia fface524c3 nix: use ffmpeg-headless (#1530) 2026-07-22 17:16:02 +02:00
ed bae77b9078 hooks: xau: fix argv (ap, not wark) 2026-07-21 18:40:47 +00:00
ed b2e4b1c46c shares: dont suggest write for fileshares;
the ui would still show the option to grant write-permission
when creating a fileshare; pointless since an existing file
can't be overwritten without the delete-permission

while this is obvious and nobody would intentionally create
a fileshare with write, avoid any confusion by hiding it
2026-07-18 20:36:41 +00:00
ed 4543ab31d3 cgen cleanup 2026-07-17 22:20:56 +00:00
ed 856fada3c7 th-covers volflag; no disables folderthumbs 2026-07-17 22:18:17 +00:00
stackxp aa86235360 hooks: xiu: fix fork (#1558) 2026-07-17 18:33:06 +00:00
ed 6a9437b77c shares: fix md-viewer 2026-07-16 20:32:35 +00:00
ed 5952401815 th: video: prefer embedded cover/pic 2026-07-15 22:00:33 +00:00
ed ed0be42a3b th: demote vips (#1556);
use libvips as last resort due to risk of OOM; example:

libvips defers to imagemagick for animated avif, and imagemagick
probably does an identify, iterating through each frame, itself
consuming 100+ MiB RAM, which libvips amplifies to 7+ GiB

similar behavior is observed with `identify a.mkv` in a shell
2026-07-15 21:59:45 +00:00
edandTil Schmitter daf144af61 up2k: fix dragdrop datatransfer type detection;
`text/uri-list` observed before `Files` once on wayland dolphin

Co-authored-by: Til Schmitter <til.schmitter@gmail.com>
2026-07-15 21:21:32 +00:00
ed a7c99094dd add --srch-nfkc (closes #1555);
option to unicode-normalize filepaths during search; useful for:
* macos servers with cjk filenames
* cjk stuff originating from a macos device
2026-07-15 20:09:11 +00:00
ed daee3e8500 docs: config include (#1554) 2026-07-12 18:47:15 +00:00
ed 96bafb8f4a update pkgs to 1.20.18 2026-07-09 22:59:43 +00:00
ed e700e4c421 v1.20.18 2026-07-09 22:54:41 +00:00
ed e15f292766 th: no keepalive-pokes if th-clean 0 2026-07-09 22:16:11 +00:00
ed 8884606138 shadowing: //NULL as unmap keyword 2026-07-09 21:42:59 +00:00
ed 12878e6c1c show_hist: shadowing gave false-positive 2026-07-09 20:16:29 +00:00
ed 29e145f5e4 rlo always unless no or blank 2026-07-09 19:07:18 +00:00
ed 85a8cc99ba bwrap: default-disable;
many distros dont care about FHS, so
th-bwrap cannot be autoconfigured
2026-07-09 18:56:25 +00:00
ed a523ba231f update pkgs to 1.20.17 2026-07-06 19:46:00 +00:00
ed f0feab0889 v1.20.17 2026-07-06 19:37:07 +00:00
ed e40755331b fix GHSA-x5pq-m9p8-f4vx; no dk with fk 2026-07-06 17:00:23 +00:00
ed 89040874f5 shr: remove upget (serves no purpose) 2026-07-06 16:17:38 +00:00
ed 9fa950bcfa plaintext readme/logue volflags 2026-07-06 16:11:20 +00:00
ed a3fbba10f7 genhelp: FULL_HELP 2026-07-06 15:51:14 +00:00
ed 741b00f342 fix tests 2026-07-06 10:18:29 +00:00
ed 2c8325a770 show-hist: nested volumes notice 2026-07-06 10:18:02 +00:00
ed db68353e22 docker blocks bwrap 2026-07-06 08:55:20 +00:00
ed 68568b7743 exe: deps: update pillow 2026-07-05 20:59:31 +00:00
ed 3888048755 windows fix; closes #1543 2026-07-05 19:29:32 +00:00
ed 379c0aa6b4 volflags: no_readme, no_logues 2026-07-05 19:28:51 +00:00
ed 8c4e931300 sftp: filter spam from portscanners (closes #1519);
also use a threadpool for accepting clients, to avoid
spinning up a new thread for each hit from a portscanner
2026-07-05 18:59:09 +00:00
ed 32d074ffc5 docker: version/ctime labels; closes #1532 2026-07-05 16:08:01 +00:00
ed 85be3b8de2 detect bwrap init-fail; prisonparty et al 2026-07-04 21:46:41 +00:00
ed 7b21e9f9ce r 2026-07-04 21:41:12 +00:00
suhas 56b6db0875 contrib: traefik + docker (#1511)
* add traefik compose file
* add copyparty compose file w/ traefik

Signed-off-by: suhas <hi@suhas.one>
2026-07-04 09:33:18 +00:00
Mobin fe006073a9 docs: s6-notify (#1500)
The way to use the S6_NOTIFY_FD is different between dinit and s6, but the README only mentioned dinit syntax with a link to the s6 documentation
2026-07-04 09:27:16 +00:00
doskelanded 8242e69359 ftp: banner (#1504)
Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-07-04 09:25:29 +00:00
ed 34c856e838 md-edit: md_nhist (max num backups) 2026-07-02 20:34:48 +00:00
ed fa1499ae15 md-edit: hide .hist by default 2026-07-02 20:20:24 +00:00
ed be31a74485 sqlite: make corruption more obvious 2026-07-02 16:24:26 +00:00
Damian Romanowskianded 12d877b0d0 bbox: cbz: go-to-page (#1535)
add "go-to-page" button + page-number in url

---------

Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-07-02 16:17:54 +00:00
ed d3b9599421 csp script nonce 2026-06-29 19:26:01 +00:00
ed 9a7f474750 deps: orbitron 2026-06-28 19:38:04 +00:00
ed 52c42b83e4 optimize md-edit 2026-06-28 15:52:46 +00:00
ed 4c8203016f docker: ffmpeg: chipping at the iceberg 2026-06-26 16:55:33 +00:00
ed a4f259dafb docker: ffmpeg: feature list 2026-06-26 16:06:10 +00:00
NecRaul d33d11321f fix directory sort-order (closes #1119) (#1528);
folders sorted incorrectly due to the trailing slash,
for example `a/` vs. `a b/`
2026-06-16 22:25:08 +00:00
NecRaul e017b1bc6e fix(rotf): use global rotf_tz as volume fallback 2026-06-16 20:40:37 +02:00
ed efa43f891b ffmpeg bwrap sandbox 2026-06-13 21:26:09 +00:00
ed 90639de984 u2c: hash stdin to wark 2026-06-13 11:47:47 +00:00
ed 5dbff4af41 js: mkdir: cd if existed; closes #1512 2026-06-13 11:45:21 +00:00
ed 5beecd6622 fix xbu reloc resume;
both up2k.js and u2c.py would ignore the "partial upload exists"
assuming it was self-inflicted, skipping the file
2026-06-13 11:20:13 +00:00
ed 5a8949d595 vjoins 2026-06-13 11:15:20 +00:00
ed 22c3a3dd46 pop job.vcfg; fixes up2k._snap_reg 2026-06-13 10:39:26 +00:00
ed a00bc93fe1 bsd: signal masking 2026-06-12 23:37:12 +00:00
ed a48afe87b9 docker: alpine 3.24 2026-06-12 21:47:04 +00:00
ed c3eb9ecec2 docs: docker archs 2026-06-12 21:29:35 +00:00
ed 6e75faa623 docker: drop arm32 iv;
deadlock on startup, probably bad cffi
2026-05-26 19:16:31 +00:00
ed 06af60276b update pkgs to 1.20.16 2026-05-26 18:49:07 +00:00
ed cc80ecc341 v1.20.16 2026-05-26 18:45:13 +00:00
ed ce33a88e25 v1.20.15 2026-05-26 18:16:37 +00:00
ed 62dc833273 misc linter 2026-05-26 17:47:52 +00:00
ed f0f6933b4e bpm mtp needs f32le 2026-05-26 17:47:08 +00:00
ed e32718303c xvol: clarify permissions 2026-05-25 20:16:23 +00:00
ed c7b80acd0d fix ?tar on xvol reject;
would panic and kill the connection
instead of just skipping the blocked symlink
2026-05-25 20:06:17 +00:00
ed cc5420a324 download-as-zip: toplevel optional 2026-05-25 18:32:42 +00:00
ed c28aa08b35 ihead filter on ohead too 2026-05-25 16:12:26 +00:00
ed 6183540c61 mde: fix crash on mobile 2026-05-25 15:57:28 +00:00
ed 5e806ec124 PRTY_FFMPEG_BIN 2026-05-25 13:41:24 +00:00
Danila Kamaev 9068ec6a8e improve opds compatibility (#1463)
use absolute paths and generate IDs
2026-05-25 11:27:01 +00:00
ed 27031f73be clamp lastmod to y6325 (closes #1470);
fromtimestamp fails on year>9999
2026-05-25 11:08:19 +00:00
ed 926c6e814d embedded-cover crop-control; closes #1474 2026-05-25 10:17:03 +00:00
ed 4e9ad781b6 advisory tiers 2026-05-25 10:10:01 +00:00
ed f4f97b6cc3 make signal-handler less shit;
previously: threading.Condition to wakeup the actual handler;
exciting chance of heisenbugs / deadlocks (theoretically)

almost went with os.pipe on unix and socketpairs on windows,
but turns out SimpleQueue is perfect and safe for this purpose

SimpleQueue is 3.7+ so use a regular queue on <3.7
(same problems as original approach)

also need dedicated thread for popping the queue on <3.7
to avoid deadlock on most platforms (--sig-thr)

new features:

--logrot-sig sets a signal for immediate log-rotate

--stack-sig sets a signal to dump stack to log/stdout

--reload-sig sets a signal to initiates config-reload
   (was hardcoded to USR1 previously)
2026-05-25 00:37:52 +00:00
ed f23ec5d9f8 tests: 0bb80e92 2026-05-23 20:56:44 +00:00
ed 348b4bb5c7 drop rawpy, use libraw/dcraw_emu directly;
rawpy is still supported but will not be bundled by default
due to security concerns

dcraw_emu reads more formats than rawpy + gives better quality
(we told rawpy to use embedded thumbs), so also much slower

dcraw_emu must be combined with libvips or pillow (equivalent)

other alternatives considered:

libvips + a full imagemagick does a different subset of formats,
less than dcraw_emu, yet is 3x slower and eats ram

magick wins wrt formats but is even slower (4x of dcraw_emu)
2026-05-23 20:55:33 +00:00
ed 2c778e0828 add font orbitron for ui-v1.5 2026-05-23 17:54:10 +00:00
exci 7d81b9e837 fix dsel error with bbox active (#1494)
stop error when dragging outside window with dsel active; fixes 1491
2026-05-23 14:15:00 +00:00
ed ca406472f4 readme: S6_NOTIFY_FD 2026-05-22 21:35:59 +00:00
ed 30b23c6ae8 black 2026-05-22 17:42:41 +00:00
ed 9b0268970c sd_notify only when set 2026-05-22 17:42:04 +00:00
Mobin 8c201b844e s6-notify: support fd-based s6 notification protocol (#1466)
Dinit and s6 supervision suite are known to support this protocol.

See https://skarnet.org/software/s6/notifywhenup.html and
https://davmac.org/projects/dinit/man-pages-html/dinit-service.5.html#ready

Signed-off-by: Mobin Aydinfar <mobin@mobintestserver.ir>
2026-05-22 17:26:47 +00:00
ed b2401ff15a partyfuse: prefer mfusepy (fuse.py fork);
now supports both fuse2 and fuse3

fallback on fuse.py (fuse2-only) if mfusepy unavailable

fuse3 is 20% faster on large files,
fuse2 == fuse3 on small files

motivated by nixos dropping fuse2 in NixOS/nixpkgs#522340
2026-05-21 23:31:57 +00:00
ed 1f4246c6bb support .mka; closes #1489 2026-05-21 00:27:41 +00:00
Lydia Vierkorn 83dc20f33e spectrograms: option to use log frequency scale (#1487) 2026-05-20 22:34:48 +00:00
Princess Grace f432ef6d51 freebsd: fix deps in rc.d (#1479)
delay startup until filesystems are in place

Signed-off-by: Princess Grace <10965841+Kansattica@users.noreply.github.com>
2026-05-20 22:32:55 +00:00
ilotoki0804 d7eb556cdd docs: macOS low-ports + sfx building (#1458)
Signed-off-by: ilotoki0804 <ilotoki0804@gmail.com>
2026-05-20 22:30:37 +00:00
AppleTheGolden 0bb80e9294 auth: read (and admin) implies get (#1485)
and hides redundant get-permission in webui; closes #1483
2026-05-20 22:27:25 +00:00
AppleTheGolden 3b53a228b0 invalidate get-only shares when creator no longer exists (#1482)
Fixes #1480
2026-05-15 19:51:12 +00:00
ed 139ef1851e docker-build: cache ffmpeg if stock 2026-05-08 00:02:54 +00:00
ed 2a4c82c742 fix docker makefile; broke from bitrot since nobody's using it
(the podman approach (make.sh) is recommended)
2026-05-07 18:15:59 +00:00
ed b009c585a6 rawpy-0.27 broke installing from github-tarball 2026-05-07 17:34:29 +00:00
ed da6e2ddca9 pyinstaller/win10exe: fix virtio/spice iso sha512;
forgot to update checksum when bumping version,
verified against internetarchive and old local copy
2026-05-02 17:52:33 +00:00
ed 6e25d648a9 update pkgs to 1.20.14 2026-04-24 22:27:49 +00:00
ed a09a0eadbb v1.20.14 2026-04-24 22:22:06 +00:00
ed 1066dc3908 thumb audio pretending to be video 2026-04-24 21:19:35 +00:00
ed 8d4363d147 list-nics / list-ips 2026-04-24 20:56:12 +00:00
ed 8b986888a9 logrotate-counter format 2026-04-24 20:09:52 +00:00
ed 1e7de5d14f new hooks: reloc-by-wark; closes #1395 2026-04-23 22:35:15 +00:00
ed e52bbed871 env-var compat 2026-04-23 19:27:03 +00:00
ed 46bd386a55 early logging 2026-04-23 19:26:39 +00:00
ed 8c7cdf8583 add --certkey 2026-04-22 20:32:18 +00:00
ed 43773f2c7e filesize units 6/7; closes #1389 2026-04-22 19:31:47 +00:00
ed 228c3dfa79 fix wrong dks in tree/navpane (closes #1392);
wrong key due to url-escaped foldername as input

also fixes "wrong dirkey" logspam as parent levels are built
2026-04-22 18:39:06 +00:00
ed ac05b4f1e6 helptext: mention --urlform get 2026-04-22 17:54:24 +00:00
ed 238887c774 u2c: macos-terminal would clearscreen on exit 2026-04-22 19:52:02 +02:00
ed 9a724b0124 misc logging/ux 2026-04-22 17:31:07 +00:00
ed 8173018926 tail: use counted offsets (closes #1449);
avoids spurious reopen on some fs
2026-04-21 23:35:37 +02:00
ed 37e68f60a6 update deps 2026-04-21 13:41:27 +00:00
ed cbd82b654a use ${ENV} syntax for env-vars;
only expand environment-variables of the form ${ENV}

by default, crash on startup if the old $ENV syntax is found,
explaning that the old syntax can be enabled with an option
2026-04-20 23:43:00 +02:00
ed b31f29024a audioplayer: opus: enforce 1ch or 2ch; downmix 3+ 2026-04-20 20:39:14 +02:00
ed 3a9ff67ab0 audioplayer: add bcstm/bfstm/brstm; closes #1447 2026-04-20 20:31:04 +02:00
excianded 874e0e7a01 fix rcm related errors (#1446)
fix error when rightclicking certain elements,
and disable hotkey ^A in inputboxes

Co-authored-by: ed <s@ocv.me>
2026-04-20 09:44:32 +00:00
ed a997455b5a bup: skip lim for nullwrite/nameless (perf) 2026-04-16 11:38:06 +00:00
ed 6fb1287e7f nameless uploads skip hooks+up2k; closes #1401 2026-04-16 11:03:51 +00:00
ed e00f2b46eb webdav: allow propfind depth:inf on files (closes #1437);
some webdav-clients (webdav4) sends Depth: infinite also for files,
assume Depth: 0 in this case
2026-04-16 10:01:22 +00:00
ed 745d82faf8 tests: support freebsd 2026-04-15 23:45:22 +02:00
Snoww f6dc1e2996 better ipv6 ratelimiting logic (#1439)
aligns ipv6 normalizatoin logic with the typical residental allocation of /56 instead of /64.
2026-04-15 22:14:35 +02:00
/dev/urandom d93fadd87e esperanto fixes (#1435) 2026-04-15 18:13:37 +02:00
ed a5d859d2b1 smb: add ipv6 support; closes #1417 2026-04-11 00:17:05 +00:00
AppleTheGolden 0b16e875da nixos: add override example (#1406) 2026-04-10 20:02:50 +00:00
chilledfrogs f5613187b4 improve *BSD compat (#1425)
reuse some macOS stuff since lsblk and /proc doesn't apply to *BSD
2026-04-10 20:02:01 +00:00
ed 003c68d027 readme: shadowing 2026-04-10 19:52:43 +00:00
ed 961a273764 autogrid global-option 2026-04-10 19:37:00 +00:00
ed 55d37364a6 indentation 2026-04-10 19:36:06 +00:00
ed 6b3419b38f mtl new strings 2026-04-10 19:11:09 +00:00
ed 660ed7a92e tl new strings 2026-04-10 19:09:59 +00:00
excianded 822fa71800 add autogrid (#1407)
Co-authored-by: ed <s@ocv.me>
2026-04-10 18:33:28 +00:00
AppleTheGolden ed516ddc20 make .txt the default extension for text/plain (#1428)
`MIMES.items()` iterates in insertion order, so the last-inserted entry
had priority, meaning that `text/plain` got `ssa` as default extension.
2026-04-09 23:20:53 +00:00
ed ede692925e reset language too 2026-04-04 20:28:16 +00:00
stackxpanded ec3e0e7e1d add --glang to use browser language (#1410)
Signed-off-by: ed <s@ocv.me>
Co-authored-by: ed <s@ocv.me>
2026-04-04 20:24:11 +00:00
ed d1517d0c65 catch the server-hdd phasing out of existence (and equally unexpected stuff) 2026-04-04 18:19:49 +00:00
mid-kid fb5384f412 readme: add gentoo packaging (#1387) 2026-04-02 20:36:52 +00:00
ed 971f8ef944 devnotes: vendored deps 2026-04-02 20:28:16 +00:00
exci 198f631ac8 fix playlist error on re-sorted filelists (#1403)
m3u files would get added (not good)
2026-03-31 19:02:11 +00:00
ed 5aaa4ff15b docs: volflags examples 2026-03-30 08:43:05 +00:00
Lomain 26e663d111 add fail2ban handler (#1352) 2026-03-25 04:08:35 +00:00
ed dcbdd9e665 versus: update pydio; closes #1385 2026-03-24 21:57:57 +00:00
ed 6a9e6da864 update pkgs to 1.20.13 2026-03-23 03:33:52 +00:00
ed aa23777385 v1.20.13 2026-03-23 03:28:27 +00:00
ed abdbd69a68 libvips jxl tweaks 2026-03-23 02:02:15 +00:00
ed edc201752b it is easter,, my dudes 2026-03-23 02:01:13 +00:00
ed 7d6b037dcc thumbnail pregen; closes #1381 2026-03-23 01:57:32 +00:00
ed 5f3b76c8f8 nohtml / noscript as global options 2026-03-22 17:11:45 +00:00
ed 66f9c95097 shares: dotfiles can be granted 2026-03-22 16:56:07 +00:00
ed 134e378e3a docs: dothidden, unlist, descript.ion 2026-03-22 16:22:22 +00:00
ed b4faff8e4a save some cycles 2026-03-22 15:44:36 +00:00
ed df78fc5732 throw ValueError for clarity 2026-03-22 15:43:21 +00:00
ed 23545116af sftp: .hidden 2026-03-22 15:40:04 +00:00
NecRaulanded beb634dc54 support .hidden file for dotfiles exclusion (#1351)
similar to many desktop file managers (Dolphin, Nautilus, Thunar), the
filenames in `.hidden` are cosmetically hidden in directory listings

the files are still directly accessible, and will be included in
download-as-zip/tar, and still appear in many other places in the UI

---------

Co-authored-by: ed <s@ocv.me>
2026-03-22 15:34:14 +00:00
ed 1f8ebd57dc macos/HFS+ cannot into floats 2026-03-21 23:49:09 +01:00
ed 311eb698a0 nginx: client_max_body_size is global 2026-03-21 21:18:32 +00:00
ed bb92ce5fc8 optimize connection:close 2026-03-21 19:47:44 +00:00
ed 1afe48b85d thumbs: fix th=x on non-jxl-capable servers;
if FFmpeg was compiled without jxl support, then every
request for a jxl thumbnail would result in a placeholder svg

detect this and switch to webp as fallback; the initial request
will still fail but successive ones are fine

closes #1333, closes #1372
2026-03-21 19:28:06 +00:00
ed bbe58105e7 shares: fix qr for huge links 2026-03-21 19:04:48 +00:00
ed ac60a1da46 shares: fix mkdir too 2026-03-21 19:04:14 +00:00
ed 2cebda3297 sfx.py: add MIT license; closes #1379 2026-03-20 00:32:10 +00:00
TWhiteShadow 4688410f8f readme: mention --shr-who (#1367)
Signed-off-by: TWhiteShadow <123359049+TWhiteShadow@users.noreply.github.com>
2026-03-19 01:15:51 +00:00
Evgeny e71e1900b4 versus: 2-letter headers (#1360) 2026-03-19 01:13:06 +00:00
ed 8c6d8a3c22 logfile colors 2026-03-19 00:57:09 +00:00
ed dcb0ffa72c cosmetic 2026-03-19 00:47:15 +00:00
ed eb028c92f8 fix js mimetype with noscript webroot 2026-03-17 23:51:25 +00:00
ed 6eb4f0ad9c ver-chk: fix admin-filter (closes #1363);
also drop cache if json invalid
2026-03-12 00:33:26 +01:00
ed 8a9066c35c update pkgs to 1.20.12 2026-03-11 00:46:49 +00:00
ed 7dce77c6d1 v1.20.12 2026-03-11 00:42:46 +00:00
ed 9f9d30f42c fix logues redered as-is with ?b (GHSA-rcp6-88mm-9vgf) 2026-03-10 23:36:35 +00:00
ed 66f1ef6354 shr_files fence ftp/sftp;
this fixes GHSA-67rw-2x62-mqqm which is the 2nd season of
e0a92ba72d / CVE-2025-58753
since that only fixed the http / https endpoints
2026-03-10 23:20:11 +00:00
ed 266d6e0ae6 fix exe build 2026-03-08 21:29:43 +00:00
ed c20c75cb76 update pkgs to 1.20.11 2026-03-08 21:12:26 +00:00
ed eca3ced160 v1.20.11 2026-03-08 21:06:24 +00:00
ed 8b5ac23efd fix au_unpk fd-leak (e.g. failed epub thumbing) 2026-03-08 20:17:28 +00:00
ed 0d71dde215 misc fixes 2026-03-08 20:15:19 +00:00
ed 1c9f894e14 fix GHSA-m6hv-x64c-27mm: svg nohtml 2026-03-08 19:48:12 +00:00
ed 981a7cd9dd bbox: probably fix #1348 (currentTime rhs was NaN) 2026-03-08 10:24:04 +00:00
ed 37123e33d2 forget_ip: fix cursor locking 2026-03-08 10:08:56 +00:00
ed 753ff548f2 move js/css into /.cpr/w/ to simplify allowlisting 2026-03-08 09:56:50 +00:00
ed fc39561b32 add m4a aliases m4b/m4r; closes #1317 2026-03-08 09:02:57 +00:00
ed 8941701a43 rclone: use network-mode on windows; closes #1324
https://rclone.org/commands/rclone_mount/#mounting-modes-on-windows
2026-03-08 08:51:22 +00:00
ed 4e32cb55d2 docker: fix min ver-chk 2026-03-08 08:49:53 +00:00
ed ffe6a9aa6f docs: fix wording 2026-03-08 00:11:55 +00:00
ed 1272de9de5 idp: fix groups for hybrid users;
if idp-store is 2 or less, and a user is defined both in config and by
idp, then config would reload for every request with groups attached
2026-03-08 00:01:58 +00:00
ed 7908d880da ver-chk: tests, docs, fix -j0 jank 2026-03-07 23:06:38 +00:00
excianded c6965f0614 add optional update-checker (#1315)
can check if the current version has a known vulnerability, with the option to panic and exit if so, and otherwise show a warning in the controlpanel for admins

---------

Co-authored-by: ed <s@ocv.me>
2026-03-07 21:54:49 +00:00
Сашка724ая 31b23843f2 rp-loc and site now combines better (#1323);
`--site` / `--up-site` / `--shr-site` now only specifies the
domain/origin, discarding the path (if any), keeping `rp-loc`
2026-03-06 23:21:19 +00:00
ed 1c0b5d26ea docker: aftermarket images 2026-03-06 22:49:50 +00:00
ed de1d9d8b75 fix hotkey headers (thx @satgo1546) 2026-03-06 22:02:32 +00:00
Frog Chen b01527413b improve Chinese translation (#1316)
in collaboration with @lxdlam
2026-03-06 21:47:12 +00:00
ed 00e821db23 rotn for dupes too 2026-03-04 06:29:40 +00:00
NecRaul 23d4a62e9f fix rotn leaf-level counting; closes #1336 (#1337) 2026-03-04 06:24:02 +00:00
NecRaul 8e20506de8 rotf: fix trailing slash; closes #1334 (#1335) 2026-03-04 05:09:16 +00:00
exci 7c60adc69c audioplayer: s-s: use clamp from util (#1313) 2026-02-25 17:07:45 +00:00
ed d3260b27a6 update pkgs to 1.20.10 2026-02-25 15:37:31 +00:00
ed e2ace1dab7 v1.20.10 2026-02-25 15:33:12 +00:00
ed a29037a0e2 warn that reload doesn't do global-options 2026-02-25 15:27:03 +00:00
ed ecdfd2d1ab fix cookie OWS; v1.20.9 broke login 2026-02-25 15:24:51 +00:00
ed 00ab7888d7 update pkgs to 1.20.9 2026-02-25 08:39:42 +00:00
ed e00e11225f v1.20.9 2026-02-25 08:34:57 +00:00
ed d21242fc62 dav-port as daw alternative 2026-02-25 08:30:41 +00:00
ed 31b2801fd0 fix GHSA-62cr-6wp5-q43h: setck xss 2026-02-25 08:29:51 +00:00
ed 9951e0333d readme: daw rephrase; closes #1305 2026-02-25 00:26:34 +00:00
ed ab8bd0178b docker: fix sftpd (ungolf); closes #1306 2026-02-23 08:13:47 +00:00
ed 7be73721fb update pkgs to 1.20.8 2026-02-22 15:13:27 +00:00
ed 1cc2c4e2fe v1.20.8 2026-02-22 15:07:22 +00:00
ed 1bec91d11c docker: no265 2026-02-22 14:31:56 +00:00
exci f5e70c7f67 audioplayer: s-s: clamp ffwd; closes #1302 (#1303)
* remove s-s fade setting;
   easier to just set this to 0.2, changing it doesn't
   make a whole lot of difference anyway

* limit s-s playback speed multi to 8;
   browsers don't play any audio if the playbackrate is
   too high or too low; on firefox, 0.15 ~ 8.0 seem to work
2026-02-21 21:25:54 +00:00
exci c2b71dab25 i18n: check/fix finnish (#1304) 2026-02-21 21:20:56 +00:00
ed 6f1d664754 tests: dks: assert parent dk not exposed 2026-02-19 17:53:32 +00:00
ed f1a433a617 audioplayer: fix s-s crash on folder change 2026-02-17 18:54:00 +00:00
ed d68028890d minify translations a little;
saves 7.4 KiB after zopfli; reduction from 343.8 to 336.4
2026-02-16 21:24:42 +00:00
ed f37c3b9608 enable hungarian translation 2026-02-16 19:11:13 +00:00
SoNaCl eefb181b33 Add Hungarian translation (#1298)
* Add Hungarian translation

---------

Signed-off-by: SoNaCl <81562723+sonacl@users.noreply.github.com>
2026-02-16 19:06:28 +00:00
Сашка724ая 5a7504fdbf Support 4-digit chmod (#1299)
* Support 4-digit chmod octal values

---------

Signed-off-by: Aleksandr Ksenofontov <github@sashok724.net>
2026-02-16 18:06:04 +00:00
/dev/urandom c39d149378 extra esperanto tweaks (#1297) 2026-02-16 17:14:52 +00:00
ed d067d2a638 update pkgs to 1.20.7 2026-02-14 23:19:29 +00:00
ed e1c22eaf0e v1.20.7 2026-02-14 23:15:27 +00:00
/dev/urandom 914f84ce5e update Esperanto translation (#1296)
translate skip-silence section

Signed-off-by: /dev/urandom <53902042+slashdevslashurandom@users.noreply.github.com>
2026-02-14 21:42:41 +00:00
ed ddd5ca84c1 s-s: s/atk/fade/ 2026-02-14 21:24:16 +00:00
ed 0ca4c1bda4 explain fika 2026-02-14 20:06:23 +00:00
ed 3c1bade452 exe: update deps 2026-02-14 19:44:31 +00:00
ed 27ae2e1e66 vips can read most RAWs (not .arw (sony)) 2026-02-14 19:43:33 +00:00
ed dc99c872e0 fix thumbnail names in archives 2026-02-14 19:41:09 +00:00
ed 40e13419e5 th_srv: fix a warning 2026-02-14 19:40:20 +00:00
ed 756494a1c3 audioplayer: hide s-s when unavailable 2026-02-14 19:32:18 +00:00
ed bf01ca4891 docker: add DI_PREPARTY and modding docs 2026-02-14 19:26:21 +00:00
ed cd927e7837 audioplayer: s-s: weave it in 2026-02-13 20:52:56 +00:00
ed 17145ba074 audioplayer: fix eq/s-s compat 2026-02-13 07:22:21 +00:00
ed 3089896be7 simplify <code> in tooltips + misc i18n 2026-02-12 21:27:56 +00:00
ed c8dfcc86f8 add missing localizations; closes #1293 2026-02-12 20:59:26 +00:00
/dev/urandom 418bf2f979 Update Esperanto translation (#1292)
Most `//m` lines are okay, but some suffered from english-isms or needed grammar/capitalization fixes.

Signed-off-by: /dev/urandom <53902042+slashdevslashurandom@users.noreply.github.com>
2026-02-12 19:24:32 +00:00
Brandon Philips 84e687a00d opds: add opensearch support (#1287)
tested with Moon Reader and Koreader; based on:
https://specs.opds.io/opds-1.2#3-search
https://github.com/koreader/koreader/pull/7380

Signed-off-by: Brandon Philips <brandon@ifup.org>
2026-02-11 22:47:24 +01:00
ed d44ea24530 add fika 2026-02-10 21:25:15 +00:00
ed a9a7c7e746 fix mimes: heics, heifs 2026-02-10 21:22:36 +00:00
exci e5d0a0572d custom url-origin for ctrl-c (#1288)
add check for alternative clipboard url origin on ctrl-c
2026-02-10 00:01:28 +00:00
ed 0e8913c2d0 jxl in cbz 2026-02-09 20:46:00 +00:00
ed 48c1017843 jxl-enc: add th-qvx (separate qscale) + misc 2026-02-09 20:18:00 +00:00
ed fef34b5465 black 2026-02-09 18:26:35 +00:00
ed 31e7d2d622 del is technically illegal too 2026-02-09 18:11:20 +00:00
Skye e8609b87af graalpy: workaround buggy inet_pton (#1282) 2026-02-09 08:46:10 +00:00
ed d692838095 rw_edit fixes 2026-02-08 00:43:21 +00:00
ed fd09638567 cfg: lowercase headernames 2026-02-07 23:58:53 +00:00
ed 312f48e17c add rw_edit (closes #1276);
previously, only .md files were editable with permissions read+write;
all other file-extensions required the delete-permission in addition

rw_edit is the list of file-extensions to allow read+write editing of
2026-02-07 23:55:23 +00:00
ed f02e9cf6d3 textfile-viewer: xhr large docs; closes #1279 2026-02-07 22:12:43 +00:00
stackxp fc8a4b8ea2 rcm: disable in textfile-viewer; fixes #1280 (#1281) 2026-02-07 21:47:27 +00:00
ed 76041fdba8 hide dotvols in more apis;
also default-set unlistcr/unlistcw to hide in controlpanel
2026-02-07 20:29:38 +00:00
ed a368fc66b3 tail/follow: add windows support; closes #1262 2026-02-06 18:57:00 +00:00
ed 4cb4e820f6 descript.ion without e2d/e2t; closes #1277 2026-02-05 22:27:56 +00:00
ed 6c96c64605 meta: adjust bug-report template (#1275) 2026-02-05 21:53:37 +00:00
ed dfadb5a7ba truncate chunked PUT at sizelimit; closes #1272 2026-02-05 19:56:04 +00:00
ed 16403d8c6c jxl-thumbs through ?tar/zip 2026-02-04 00:39:25 +00:00
Ivan Shapovalov 59de5e2ca8 th: deduplicate libvips logic between ThumbSrv.conv_{vips,raw}()
No functional change intended.

This would potentially be useful later if and when libvips gains
additional encoding/decoding options for other formats (like jxl)
and we decide to use them.
2026-02-04 01:34:04 +01:00
Ivan Shapovalov a7a25debe4 th: hack: configure pyvips cache and invalidate images
Attempt to reduce memory usage.
2026-02-04 01:34:04 +01:00
Ivan Shapovalov a65a30b1b4 web, th: rudimentary jxl support
Fixes #724.
2026-02-04 01:34:04 +01:00
Ivan Shapovalov 93ffc65c14 web: baguettebox: sort and add missing image extensions 2026-02-04 01:34:04 +01:00
Ivan Shapovalov 720c83b2ff th: factor out IMG_EXTS 2026-02-04 01:34:04 +01:00
Ivan Shapovalov b2711e05b6 th: account for HAVE_WEBP when computing PIL-able extensions 2026-02-04 01:34:04 +01:00
exci 6694998985 audioplayer: add skip-silence feature (#1265)
* add skip silence option
2026-02-02 22:45:17 +00:00
ed 598df44e87 cfg: warn on quotes 2026-02-02 19:08:56 +00:00
ed 96aeb898ae intuitive config-syntax in logs 2026-02-02 19:07:54 +00:00
ed 9b436eb52e update pkgs to 1.20.6 2026-01-31 20:06:34 +00:00
ed ff2200ff85 v1.20.6 2026-01-31 20:01:36 +00:00
ed 0a41d0c03b shares: require at least one permission 2026-01-31 19:57:48 +00:00
ed 72224d29d6 allow X-Forwarded-HTTP-Version;
the request-header safeguard added in v1.20.5 was too strict
2026-01-31 19:11:16 +00:00
ed 95b827f1a5 shares: add get perm (closes #1264);
option was available in ui but never implemented serverside
2026-01-31 18:06:22 +00:00
ed 32c11f142e update pkgs to 1.20.5 2026-01-30 21:49:40 +00:00
ed e2dc6d33bf v1.20.5 2026-01-30 21:45:16 +00:00
ed 9c14972da6 ctrl-c also copies links to clipboard; closes #1182 2026-01-30 20:39:48 +00:00
ed 530afddc6a readme: mention cbz reader 2026-01-30 20:16:16 +00:00
ed 08b0abdbdd small optimizations 2026-01-30 20:08:29 +00:00
ed ed6a8d5a73 optimize CL/TE check;
replace the heavyhanded connection:close added in b4fddbc3d
with a comparison of content-length to num bytes consumed

this approach also covers incorrectly configured servers
where the reverseproxy was not detected

also adds explicit TE/CL handling, even though most
(all?) reverseproxies already prevent such issues

also adds explicit sanchk of up2k chunk-receiver,
in case any bugs are ever added there
2026-01-30 20:06:02 +00:00
Skyeanded 73d06eaf84 add support for GraalPy (#1260)
Co-authored-by: ed <s@ocv.me>
2026-01-30 17:49:02 +00:00
stackxpanded 22cdc0f8c9 remove nth and fix nih description (#1257)
Co-authored-by: ed <s@ocv.me>
2026-01-29 18:28:21 +00:00
charel 79273a7d97 OpenRC: Add checkconfig function (#1259)
I found it slightly cumbersome to write a config when the only feedback OpenRC gives is `crashed`, so I added a small QOL function to run copyparty with the `--exit cfg` flag directly from the service.

Signed-off-by: charel <charel@lotsaspaghetti.com>
2026-01-29 18:16:25 +00:00
ed 2f57228fd4 webdeps: vendor busy.mp3 (#1252);
mainly with reproducible builds in mind,
and after all it's only 106 bytes
2026-01-26 23:34:06 +00:00
ed b6bf6d5f7a shares: fix lifetime-extend; closes #1248 2026-01-25 21:40:16 +00:00
ed 24141b494b another request-smuggling failsafe;
could concievably help when behind a buggy reverseproxy
2026-01-25 21:38:18 +00:00
ed 8e046fb6a8 webdav: fix macos-finder connect delay (closes #1242);
if both `quota-available-bytes` and `quotaused` are ignored
(not even returned as 404), then macos Finder is able to
connect instantly, avoiding this longstanding bug in macos

the presence of `quotaused` is the trigger for this logic, which
is a property apple invented and only apple uses, meaning we can
safely break the webdav spec as required in this case

thx @freddyheppell for the observation
2026-01-25 16:35:15 +00:00
Josh Willox 296362fc84 webdav: x-oc-mtime as float (#1240); closes #1239 2026-01-25 16:31:45 +00:00
ed b20d32593e prism: change language subset;
* add nasm (+0.3 K)
* rm autohotkey (-3.8 K)
* rm cmake (-4.0 K)
2026-01-25 16:26:23 +00:00
000yesnt 69fa1d10bc update Portuguese translation (#1245)
* portuguese tl: verify and adjust strings
* portuguese tl: fix newlines
* portuguese tl: change rc_zip wording
2026-01-25 16:25:30 +00:00
ed 5c4ba376a0 fix ie11 spinlock in write-only folders 2026-01-25 00:25:24 +00:00
ed bef30ac04d webdeps: vendor asmcrypto.js;
npm is no longer able to build it, and the build output
never changed since copyparty v0.11.20 (2021-06-20) /
asmcrypto 2821dd1dedd1196c378f5854037dda5c869313f3 (2018-12-02)

one final pass of reasonable size-golfing was done by hand

deflated with pigz -11 -I250
2026-01-25 00:22:54 +00:00
ed d734786c1d meta: gitattributes 2026-01-24 19:37:54 +00:00
George Rawlinson 59e2d77df9 update prism webdep (#1238)
* update prism to 1.30.0
* split out prism plugins
* deps: list all prism langs
2026-01-24 17:16:20 +00:00
ed d750f3f0d2 update pkgs to 1.20.4 2026-01-23 00:59:41 +00:00
ed c249ee457b v1.20.4 2026-01-23 00:55:08 +00:00
ed b4fddbc3d2 no keepalive when request is proxied;
consider each request individually
rather than the previous general approach
2026-01-23 00:46:05 +00:00
ed 81e5eb7b27 shares: lan button; closes #1232 2026-01-23 00:31:13 +00:00
/dev/urandom 1142ac2563 Esperanto translation update (#1229)
Important changes:

The "shift" key is called "MAJ" (short for "majuskla" for "uppercase") in the translation, so the new lines have been updated.

"beligi json" is technically a valid string, but the "-on" ending could be confused for a grammatical ending, with the whole string being interpreted as "beautify javascript" instead, so instead "JSON" is capitalized and another "on" ending is added.

A few commas are added for readability.

Signed-off-by: /dev/urandom <53902042+slashdevslashurandom@users.noreply.github.com>
2026-01-23 00:14:30 +00:00
Diego Passos Coutoanded ffca67f25a rcm: new file/folder in gridview (#1235)
enables creation of files/folders in grid-view

---------

Co-authored-by: ed <s@ocv.me>
2026-01-23 00:06:11 +00:00
ed e1eff21623 no keepalive when proxied (#1231);
might help prevent desync
2026-01-22 23:54:42 +00:00
ed dc8c229bcd update pkgs to 1.20.3 2026-01-21 05:09:47 +00:00
ed c41ee3fc27 v1.20.3 2026-01-21 05:05:50 +00:00
ed 6dcb1efb7c add ?smsg 2026-01-21 04:55:50 +00:00
ed 0a3a80725a fix jumpvol ?ls v2
the missing part of 66750391ae
2026-01-21 03:24:00 +00:00
ed ba67b27946 no racing 2026-01-21 03:19:41 +00:00
ed b4df8fa23c multipart-parser correctness (closes #1227);
makes it possible to login from the webbrowser dillo;

* unlike every other browser, dillo does NOT send a trailing "\r\n"
   after the terminating "--"; turns out that dillo got this right
   and every other browser didn't, fun

* dillo announces the boundary in quotes, which is spec-optional

the multipart-parser is now 2% slower
2026-01-21 03:19:32 +00:00
ed 78f6855f08 update pkgs to 1.20.2 2026-01-19 01:31:57 +00:00
216 changed files with 11707 additions and 3036 deletions

No files matched your search

+2
View File
@@ -4,3 +4,5 @@
*.png binary
*.gif binary
*.ico binary
*.gz binary
+3 -1
View File
@@ -9,7 +9,9 @@ assignees: '9001'
<!-- NOTE:
**please use english, or include an english translation.** aside from that,
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md -->
include all of the information that might possibly be necessary to help diagnose your issue,
especially things like the server config and server logs; that said,
any parts below which are obviously and definitely irrelevant can be skipped -->
### Describe the bug
a description of what the bug is
+5
View File
@@ -29,8 +29,13 @@ copyparty.egg-info/
copyparty/res/COPYING.txt
copyparty/web/deps/
srv/
scripts/docker/base/b/
scripts/docker/base/cver*
scripts/docker/base/test-aac/
scripts/docker/base/whl/
scripts/docker/i/
scripts/deps-docker/uncomment.py
scripts/deps-docker/unhint.py
contrib/package/arch/pkg/
contrib/package/arch/src/
+276 -30
View File
@@ -64,6 +64,8 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [other tricks](#other-tricks)
* [searching](#searching) - search by size, date, path/name, mp3-tags, ...
* [server config](#server-config) - using arguments or config files, or a mix of both
* [version-checker](#version-checker) - sleep better at night
* [logging](#logging) - serverlog is sent to stdout by default
* [zeroconf](#zeroconf) - announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png))
* [mdns](#mdns) - LAN domain-name and feature announcer
* [ssdp](#ssdp) - windows-explorer announcer
@@ -74,6 +76,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [connecting to webdav from windows](#connecting-to-webdav-from-windows) - using the GUI
* [tftp server](#tftp-server) - a TFTP server (read/write) can be started using `--tftp 3969`
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
* [wopi server](#wopi-server) - edit office documents in the web-ui
* [browser ux](#browser-ux) - tweaking the ui
* [opengraph](#opengraph) - discord and social-media embeds
* [file deduplication](#file-deduplication) - enable symlink-based upload deduplication
@@ -81,10 +84,14 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [exclude-patterns](#exclude-patterns) - to save some time
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
* [periodic rescan](#periodic-rescan) - filesystem monitoring
* [u2sz](#u2sz) - configure upload-chunking
* [upload rules](#upload-rules) - set upload rules using volflags
* [compress uploads](#compress-uploads) - files can be autocompressed on upload
* [chmod and chown](#chmod-and-chown) - per-volume filesystem-permissions and ownership
* [other flags](#other-flags)
* [descript.ion](#description) - add a description to each file in a folder
* [dothidden](#dothidden) - cosmetically hide specific files in a folder
* [thumbnail pregen](#thumbnail-pregen) - if you want to pre-generate everything on startup
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
* [metadata from xattrs](#metadata-from-xattrs) - unix extended file attributes
@@ -116,6 +123,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [packages](#packages) - the party might be closer than you think
* [arch package](#arch-package) - `pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
* [fedora package](#fedora-package) - does not exist yet
* [gentoo ::guru package](#gentoo-guru-package) - `emerge www-servers/copyparty::guru` (in [::guru](https://wiki.gentoo.org/wiki/Project:GURU))
* [homebrew formulae](#homebrew-formulae) - `brew install copyparty ffmpeg`
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
* [nixos module](#nixos-module)
@@ -130,6 +138,7 @@ built in Norway 🇳🇴 with contributions from [not-norway](https://github.com
* [client-side](#client-side) - when uploading files
* [security](#security) - there is a [discord server](https://discord.gg/25J8CdTT6G) with announcements
* [gotchas](#gotchas) - behavior that might be unexpected
* [csp nonce](#csp-nonce) - unauthorized javascript is blocked
* [cors](#cors) - cross-site request config
* [filekeys](#filekeys) - prevent filename bruteforcing
* [dirkeys](#dirkeys) - share specific folders in a volume
@@ -179,7 +188,7 @@ enable thumbnails (images/audio/video), media indexing, and audio transcoding by
* **Alpine:** `apk add py3-pillow ffmpeg`
* **Debian:** `apt install --no-install-recommends python3-pil ffmpeg`
* **Fedora:** rpmfusion + `dnf install python3-pillow ffmpeg --allowerasing`
* **FreeBSD:** `pkg install py39-sqlite3 py39-pillow ffmpeg`
* **FreeBSD:** `pkg install py311-sqlite3 py311-pillow ffmpeg`
* **MacOS:** `port install py-Pillow ffmpeg`
* **MacOS** (alternative): `brew install pillow ffmpeg`
* **Windows:** `python -m pip install --user -U Pillow`
@@ -210,6 +219,8 @@ other places to download copyparty from (non-github links):
* https://copyparty.eu/enz = https://copyparty.eu/copyparty-en.pyz = the enterprise pyz
* https://copyparty.eu/cli = online cli helptext
DO NOT download from [sourceforge](https://copyparty.eu/unsanctioned/sourceforge.txt)
### at home
@@ -236,6 +247,11 @@ you may also want these, especially on servers:
* [nixos module](#nixos-module) to run copyparty on NixOS hosts
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to [reverse-proxy](#reverse-proxy) behind nginx (for better https)
because the following environment variables are commonly used in service-scripts, they are understood by copyparty:
* `NOTIFY_SOCKET` as provided by systemd with service type=notify (see systemd/copyparty.service above)
* `S6_NOTIFY_FD` for dinit [`ready-notification = pipevar:S6_NOTIFY_FD`](https://davmac.org/projects/dinit/man-pages-html/dinit-service.5.html#ready) and [s6 supervision suite notification](https://skarnet.org/software/s6/notifywhenup.html) support
and remember to open the ports you want; here's a complete example including every feature copyparty has to offer:
```
firewall-cmd --permanent --add-port={80,443,3921,3922,3923,3945,3990}/tcp # --zone=libvirt
@@ -286,11 +302,12 @@ also see [comparison to similar software](./docs/versus.md)
* ☑ play video files as audio (converted on server)
* ☑ create and play [m3u8 playlists](#playlists)
* ☑ image gallery with webm player
* ☑ and cbz manga/comics reader
* ☑ [textfile browser](#textfile-viewer) with syntax highlighting
* ☑ realtime streaming of growing files (logfiles and such)
* ☑ [thumbnails](#thumbnails)
* ☑ ...of images using Pillow, pyvips, or FFmpeg
* ☑ ...of RAW images using rawpy
* ☑ ...of RAW images using libraw-dcraw_emu or rawpy
* ☑ ...of videos using FFmpeg
* ☑ ...of audio (spectrograms) using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually)
@@ -491,6 +508,9 @@ upgrade notes
* thumbnails are broken (you get a colorful square which says the filetype instead)
* you need to install `FFmpeg` or `Pillow`; see [thumbnails](#thumbnails)
* thumbnails are broken, specifically for photos and videos taken by iphones
* the [docker image](https://github.com/9001/copyparty/blob/hovudstraum/scripts/docker) and [bootable flashdrive](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) are not able to read heif/heic images and h265/HEVC video due to [legal reasons](docs/bad-codecs.md)
* thumbnails are broken (some images appear, but other files just get a blank box, and/or the broken-image placeholder)
* probably due to a reverse-proxy messing with the request URLs and stripping the query parameters (`?th=w`), so check your URL rewrite rules
* could also be due to incorrect caching settings in reverse-proxies and/or CDNs, so make sure that nothing is set to ignore the query string
@@ -508,6 +528,7 @@ upgrade notes
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if the user has `a`/admin in any volume)
* changes to the `[global]` config section requires a restart to take effect
* when using config-files, ignore all the `-a` and `-v` stuff below, and CTRL-F `config file example` on this page instead
a quick summary can be seen using [`--help-accounts`](https://copyparty.eu/cli/#accounts-help-page)
@@ -551,12 +572,14 @@ if you want to grant access to all users who are logged in, the group `acct` wil
* 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`
* single users can also be subtracted from a group: `@admins,-james`
anyone trying to bruteforce a password gets banned according to `--ban-pw`; default is 24h ban for 9 failed attempts in 1 hour
anyone trying to bruteforce a password gets banned according to `--ban-pw` (assuming [real-ip](#real-ip) is configured correctly); default is 24h ban for 9 failed attempts in 1 hour
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`
* you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
config file example:
```yaml
[accounts]
u1: p1 # create account "u1" with password "p1"
@@ -599,12 +622,30 @@ and if you want to use config files instead of commandline args (good!) then her
hiding specific subfolders by mounting another volume on top of them
for example `-v /mnt::r -v /var/empty:web/certs:r` mounts the server folder `/mnt` as the webroot, but another volume is mounted at `/web/certs` -- so visitors can only see the contents of `/mnt` and `/mnt/web` (at URLs `/` and `/web`), but not `/mnt/web/certs` because URL `/web/certs` is mapped to `/var/empty`
> **WARNING:** shadowing is NOT bulletproof when the underlying filesystem is casefolding (case-insensitive), in other words DON'T rely on this when copyparty is running on Windows and/or MacOS. This is also unsafe when combined with certain filesystems on Linux, especially FAT32/exFAT. Pay attention to the volume-listing in the startup log; `casechk:n` indicates probably-OK, but if it says `casechk:y` then DO NOT trust shadowing to fully prevent access to a file/subfolder.
for example `-v /mnt::r -v /var/empty:web/certs:` (note: no permissions) mounts the server folder `/mnt` as the webroot, but another volume is mounted at `/web/certs` -- so visitors can only see the contents of `/mnt` and `/mnt/web` (at URLs `/` and `/web`), but not `/mnt/web/certs` because URL `/web/certs` is mapped to `/var/empty`
to fully unmap it from the filesystem, specify `//NULL` instead of a real path such as `/var/empty`, so for example `-v /mnt::r -v //NULL:web/certs:` ensures `/web/certs` will never be accessible by anyone
the example config file right above this section may explain this better; the first volume `/` is mapped to `/srv` which means http://127.0.0.1:3923/music would try to read `/srv/music` on the server filesystem, but since there's another volume at `/music` mapped to `/mnt/music` then it'll go to `/mnt/music` instead
so, to shadow a file/folder, define a volume but leave out the `accs:` section
> ℹ️ this also works for single files, because files can also be volumes
config file example for unmapping folders by shadowing:
```yaml
[/drives]
/mnt # url "/drives" goes to "/mnt"
accs:
r: * # everyone can read
[/drives/foo/bar]
//NULL # blocks access to "/mnt/foo/bar"
```
## dotfiles
@@ -614,6 +655,8 @@ anyone can access these if they know the name, but they normally don't appear in
a client can request to see dotfiles in directory listings if global option `-ed` is specified, or the volume has volflag `dots`, or the user has permission `.`
> for [shares](#shares), the `dots` volflag is ignored
dotfiles do not appear in search results unless one of the above is true, **and** the global option / volflag `dotsrch` is set
> even if user has permission to see dotfiles, they are default-hidden unless `--see-dots` is set, and/or user has enabled the `dotfiles` option in the settings tab
@@ -740,7 +783,7 @@ press `g` or `田` to toggle grid-view instead of the file listing and `t` togg
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/129636211-abd20fa2-a953-4366-9423-1c88ebb96ba9.png)
it does static images with Pillow / pyvips / FFmpeg, and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
* pyvips is 3x faster than Pillow, Pillow is 3x faster than FFmpeg
* Pillow is 3x faster (and safer) than FFmpeg
* disable thumbnails for specific volumes with volflag `dthumb` for all, or `dvthumb` / `dathumb` / `dithumb` for video/audio/images only
* for installing FFmpeg on windows, see [optional dependencies](#optional-dependencies)
@@ -749,6 +792,7 @@ audio files are converted into spectrograms using FFmpeg unless you `--no-athumb
images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
* the order is significant, so if both `cover.png` and `folder.jpg` exist in a folder, it will pick the first matching `--th-covers` entry (`folder.jpg`)
* and, if you enable [file indexing](#file-indexing), it will also try those names as dotfiles (`.folder.jpg` and so), and then fallback on the first picture in the folder (if it has any pictures at all)
* disable folderthumbs with `--th-covers no`
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
@@ -760,8 +804,8 @@ to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` an
* be careful with svg; chrome will crash if you have too many unique svg files showing on the same page (the limit is 250 or so) -- showing the same handful of svg files thousands of times is ok however
note:
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) (available in the `iv` docker image, `withFastThumbnails` in nixos)
* technical trivia: FFmpeg has basic support for tiled heic as of v7.0; need `-show_stream_groups` for correct resolution
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) but this is not possible with the docker-images due to [legal reasons](docs/bad-codecs.md)
* if you do not want thumbnails to be generated on-the-fly, and instead wish to generate all of them on server startup, then see [thumbnail pregen](#thumbnail-pregen)
config file example:
@@ -817,9 +861,11 @@ you can also zip a selection of files or folders by clicking them in the browser
cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
* super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
* and url-param `&name=foo` changes the name of the toplevel folder in the archive to `foo`, and just `&name` removes the folder entirely
* and url-param `&nodot` skips dotfiles/dotfolders; they are included by default if your account has permission to see them
* and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
* can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
* but now there is also a real [thumbnail pregen](#thumbnail-pregen) so just use that
## uploading
@@ -884,7 +930,7 @@ note that since up2k has to read each file twice, `[🎈] bup` can *theoreticall
if you are resuming a massive upload and want to skip hashing the files which already finished, you can enable `turbo` in the `[⚙️] config` tab, but please read the tooltip on that button
if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option `--u2sz` (the default is 96 MiB to support Cloudflare)
if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option [--u2sz](#u2sz)
if you want to replace existing files on the server with new uploads by default, run with `--u2ow 2` (only works if users have the delete-permission, and can still be disabled with `🛡️` in the UI)
@@ -1001,6 +1047,8 @@ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
the volflag `--shr-who` lets you control who can create a share from that volume, either `no` (nobody), `a` (people with admin permission), or `auth` (people who are logged in)
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
@@ -1164,7 +1212,7 @@ open the `[🎺]` media-player-settings tab to configure it,
* `[flac]` converts `flac` and `wav` files into opus (if supported by browser) or mp3
* `[aac]` converts `aac` and `m4a` files into opus (if supported by browser) or mp3
* `[oth]` converts all other known formats into opus (if supported by browser) or mp3
* `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
* `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|m4b|m4r|mka|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
* "transcode to":
* `[opus]` produces an `opus` whenever transcoding is necessary (the best choice on Android and PCs)
* `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
@@ -1300,7 +1348,8 @@ for the above example to work, add the commandline argument `-e2ts` to also scan
# server config
using arguments or config files, or a mix of both:
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf)
* using a config-file (`-c some.conf`) is best, easier to read/maintain; see [./docs/example.conf](docs/example.conf)
* for complex and intricate setups, see how [./docs/example2.conf](docs/example2.conf) includes additional config-files/folders with `% copyparty.d` which loads [./docs/copyparty.d/some.conf](docs/copyparty.d/some.conf)
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
* or click the `[reload cfg]` button in the control-panel if the user has `a`/admin in any volume
* changes to the `[global]` config section requires a restart to take effect
@@ -1310,6 +1359,66 @@ using arguments or config files, or a mix of both:
* or if you prefer plaintext, https://copyparty.eu/helptext.txt
## version-checker
sleep better at night by telling copyparty to periodically check whether your version has a [known vulnerability](https://github.com/9001/copyparty/security/advisories)
this feature can be enabled by setting the global-option `--vc-url` to one of the following URLs; choose what severity level you want to be notified for:
* `https://api.copyparty.eu/advisories-panic` -- only really bad stuff, the "UPGRADE NOW" kind
* `https://api.copyparty.eu/advisories` -- everything important / noteworthy, "upgrade when you can"
* `https://api.copyparty.eu/advisories-all` -- *everything*, including stuff that's unlikely to affect anyone
* `https://api.github.com/repos/9001/copyparty/security-advisories?per_page=9` -- same as `advisories-all`
note that `https://api.copyparty.eu/advisories` may (for example) skip some advisories rated `High` but include some `Low`; that's because an easily-reachable `Low` in a default-enabled feature is more severe than a `High` which is a theoretical bug in a contrived use of a fringe feature, but the CVE calculator would still classify that as `High`
if you want to use the github advisory feed but only care about advisories rated `medium`/`moderate` or higher, then global-option `--vc-sev medium` does that, but see previous paragraph
> to see what happens when a bad version is detected, try `--vc-url https://api.copyparty.eu/advisories-test`
also consider the following options:
* global-option `--vc-age` is how often (in hours) to check that URL; default is 3
* global-option `--vc-exit` can be enabled to panic and immediately exit if a vulnerability is indicated
* if `--vc-exit` is not enabled, it just shows a warning on the controlpanel for all users with permission `a` or `A`
config file example:
```yaml
[global]
vc-url: https://api.copyparty.eu/advisories
vc-age: 3 # how many hours to wait between each check
vc-exit # emergency-exit if current version is vulnerable
vc-sev: medium # only care about severity 'Medium'/'Moderate' or higher (github-only; don't use this with api.copyparty.eu)
```
## logging
serverlog is sent to stdout by default (but logging to a file is also possible)
"stdout" usually means either the terminal, or journalctl, or whatever is collecting logs from your docker containers, so that depends on your setup
* [-q](https://copyparty.eu/cli/#g-q) disables logging to stdout, and may improve performance a little bit
* combine it with `-lo logfolder/cpp-%Y-%m-%d.txt` to log to a file instead
* the `%Y-%m-%d` makes it create a new logfile every day, with the date as filename
* global-option [--rlo](https://copyparty.eu/cli/#rlo-help-page) decides what happens if the filename is taken
* `-lo whatever.txt` can be used without `-q` to log to both at the same time
* by default, the logfile will have colors if the terminal does (usually the case)
* use the [textfile-viewer](https://github.com/user-attachments/assets/8a828947-2fae-4df9-bd2a-3de46f42d478) or `less -R` in a terminal to see colors correctly
* if you want [no colors](https://youtu.be/biW5UVGkPMA?t=148):
* `--flo 2` disables colors for just the logfile
* `--no-ansi` disables colors for both the terminal and logfile
config file example:
```yaml
[global]
log-date: %Y-%m-%d # show dates on stdout too
lo: /var/log/cpp/%Y-%m-%d.txt # logfile path
flo: 2 # just text (no colors) in logfile
q # disable stdout; use logfile only
```
## zeroconf
announce enabled services on the LAN ([pic](https://user-images.githubusercontent.com/241032/215344737-0eae8d98-9496-4256-9aa8-cd2f6971810d.png)) -- `-z` enables both [mdns](#mdns) and [ssdp](#ssdp)
@@ -1437,7 +1546,7 @@ general usage:
on macos, connect from finder:
* [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/
to upload or edit files with WebDAV clients, enable the `daw` volflag (because most WebDAV clients expect this) and give your account the delete-permission. This avoids getting several copies of the same file on the server. HOWEVER: This will also make all PUT-uploads overwrite existing files if the user has delete-access, so use with caution.
to be able to edit existing files, the client must have the Delete-permission, and some webdav clients will also require the [daw](https://copyparty.eu/cli/#g-daw) volflag or global-option (not necessary if the client sends the `x-oc-mtime` header). Without `daw`, those clients will fail to modify existing files and instead create new copies with names like `notes.txt-1771978661.726032-3i9GPghL.txt`. **NOTE:** Enabling `daw` will also make all PUT-uploads overwrite existing files if the user has delete-access, so use with caution. Another alternative is the [dav-port](https://copyparty.eu/cli/#g-dav-port) option
> note: if you have enabled [IdP authentication](#identity-providers) then that may cause issues for some/most webdav clients; see [the webdav section in the IdP docs](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients)
@@ -1532,6 +1641,15 @@ authenticate with one of the following:
* username `$password`, password `k`
## wopi server
edit office documents in the web-ui
needs a wopi client to work, and the only supported wopi client is a selfhosted collabora online server; eventually onlyoffice will probably also be supported: [issue #1574](https://github.com/9001/copyparty/issues/1574)
no further documentation or examples yet, [issue #1575](https://github.com/9001/copyparty/issues/1575)
## browser ux
tweaking the ui
@@ -1687,12 +1805,17 @@ avoid traversing into other filesystems using `--xdev` / volflag `:c,xdev`, ski
and/or you can `--xvol` / `:c,xvol` to ignore all symlinks leaving the volume's top directory, but still allow bind-mounts pointing elsewhere
* symlinks are permitted with `xvol` if they point into another volume where the user has the same level of access
* symlinks are permitted with `xvol` if they point into another volume where the user also has some sort of access
* usually keeps permissions from outer/initial volume, but don't take that for granted
these options will reduce performance; unlikely worst-case estimates are 14% reduction for directory listings, 35% for download-as-tar
these options will reduce performance; unlikely worst-case estimates are 27% reduction for directory listings, 35% for download-as-tar
* if the underlying storage is [a network disk](#using-the-cloud-as-storage) then xvol/xdev will be **very** slow (not to mention pointless/useless)
as of copyparty v1.7.0 these options also prevent file access at runtime -- in previous versions it was just hints for the indexer
> **WARNING:** the `xvol` and `xdev` options should NOT be relied on for security; these are originally and primarily intended for the [e2ds indexer](#file-indexing). Certain underlying filesystems (especially FUSE-based and casefolding ones) will be problematic, and symlinks inherently have various TOCTOUs, and there's probably also bugs in copyparty where xvol/xdev is not fully enforced for certain APIs. The developer does not care about these options and they do not receive much testing.
### periodic rescan
filesystem monitoring; if copyparty is not the only software doing stuff on your filesystem, you may want to enable periodic rescans to keep the index up to date
@@ -1716,6 +1839,23 @@ config file example:
```
## u2sz
configure upload-chunking; the default `64,96` is optimal for nearly every scenario, however:
* if you are seeing the message `bad server config; see "u2sz" in readme` then you have a proxy/CDN/whatever with a `max-request-body-size` which is too small, and you must either increase the limit in the reverseproxy config to at least 96 MiB (recommended) or reduce the target chunksize in copyparty (continue reading)
the global-option `--u2sz` is two numbers, `target,max` MiB of each chunk to send, aiming for `target` MiB and allowing up-to-and-including `max` MiB [depending on filesize](./docs/devnotes.md#list-of-chunk-sizes), and if you only specify one number then it sets that for both
cloudflare max is 96 MiB, but if you have a reverseproxy (or CDN/WAF/whatever) which is more restrictive, then try some of the following:
* `u2sz: 32` is still acceptable performance in most cases
* `u2sz: 8` is the lowest recommended value; anything lower will reduce performance over long physical distances and/or shitty routes
* `u2sz: 1` is the absolute minimum, and will be slow in many cases
the setting can also be changed in the web-UI; the textbox next to `date-chk` in the settings tab
## upload rules
set upload rules using volflags, some examples:
@@ -1818,6 +1958,55 @@ notes:
* adding `?cache` to a link will override this with "fully cache this for 69 seconds"; `?cache=321` is 321 seconds, and `?cache=i` is 7 days
## descript.ion
add a description to each file in a folder by adding them to a textfile named `descript.ion`
see https://copyparty.eu/beta/ for an example -- here's a basic `descript.ion` file:
```
bookmark.mp3 Taishi feat. Rita - Bookmark Memories
slowstep.mp3 Taishi feat. 向日葵 - Slow Step -F.L.C.A-
prsnlzr.mp3 Taishi feat. みとせのりこ - Personalizer
cosmos.mp3 Taishi feat. Rita - Into the cosmos
```
## dothidden
cosmetically hide specific files in a folder by adding them to a textfile named `.hidden`
this option is default-disabled; enable the volflag and/or global-option `dothidden`
this is **cosmetic only!** the files are still easily accessible in many ways, for example with download-as-zip/tar, so **do not** rely on this for security.
> also see the [--unlist](https://copyparty.eu/cli/#g-unlist) option which is somewhat similar -- `unlist` applies to the whole volume instead of just one folder; however, while dothidden also affects sftp and ftp, the `unlist` option is http/https-only
## thumbnail pregen
if you want to pre-generate everything on startup (usually a bad idea);
by default, thumbnails are created on-the-fly when a client needs it, and then cached on the server for [--th-maxage](https://copyparty.eu/cli/#g-th-maxage) seconds (default is one week), so most thumbnails only need to be created once, and are then eventually deleted from the cache to preserver disk space
but if you need every thumbnail instantly available when a folder is viewed, then first increase the thumbnail expiration time to something really big, and then set global-option `th-pregen` and volflag `th_pregen` to a comma-separated list of thumbnail formats to automatically generate on server startup;
the full list of all possible formats is: `j,jf,jf3,j3,w,wf,wf3,w3,x,xf,xf3,x3,opus,mp3,flac,wav` and I'll explain what those mean soon
* `j` = jpeg cropped, `jf` = jpeg uncropped, `jf3` = jpeg uncropped triplesize, `j3` jpeg cropped triplesize
* `w` = webm cropped, `wf` = webm uncropped, ..., `x` = jxl cropped, `xf` = jxl uncropped, ...
* and yes, audio-transcodes are technically thumbnails according to copyparty -- don't think too much about it ( ゚ ヮ゚)
* unlike thumbnails, the expiry time for audio-transcodes is configured with [--ac-maxage](https://copyparty.eu/cli/#g-ac-maxage)
anyways, obviously you **do not** want to pregenerate flac/wav because they're HUGE, and everything else also gets pretty big because it all adds up;
* each regular thumbnail ( j, jf, w, wf, x, xf ) takes about 16 KiB of disk space
* each triplesize thumb ( j3, jf3, w3, wf3, x3, xf3 ) takes about 96 KiB
* each opus / mp3 audiotranscode takes... idk, 6 MiB? depends on song length
so a thousand pictures converted to every possible regular-size image format (`j,jf,w,wf,x,xf`) takes **96 MiB,** and every possible 3x-size (`jf3,j3,wf3,w3,xf3,x3`) takes **562 MiB,** alternatively **658 MiB** in total for all, so that's why the default is to *not* pregenerate on startup, but instead do on-demand with a cache
## database location
in-volume (`.hist/up2k.db`, default) or somewhere else
@@ -2018,7 +2207,7 @@ as for client-side stuff, there is [plugins for modifying UI/UX](./contrib/plugi
autologin based on IP range (CIDR) , using the global-option `--ipu`
for example, if everyone with an IP that starts with `192.168.123` should automatically log in as the user `spartacus`, then you can either specify `--ipu=192.168.123.0/24=spartacus` as a commandline option, or put this in a config file:
for example, if everyone with an IP that starts with `192.168.123` should automatically log in as the user `spartacus`, then you can either specify `--ipu=192.168.123.0/24=spartacus` as a commandline option, same as this config file example:
```yaml
[global]
@@ -2034,7 +2223,7 @@ repeat the option to map additional subnets
limit a user to certain IP ranges (CIDR) , using the global-option `--ipr`
for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, or put this in a config file:
for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, same as this config file example:
```yaml
[global]
@@ -2071,7 +2260,7 @@ but if you just want to let users change their own passwords, then you probably
other ways to auth by header
if you have a middleware which adds a header with a user identifier, for example tailscale's `Tailscale-User-Login: alice.m@forest.net` then you can automatically auth as `alice` by defining that mapping with `--idp-hm-usr '^Tailscale-User-Login^alice.m@forest.net^alice'` or the following config file:
if you have a middleware which adds a header with a user identifier, for example tailscale's `Tailscale-User-Login: alice.m@forest.net` then you can automatically auth as `alice` by defining that mapping with `--idp-hm-usr '^Tailscale-User-Login^alice.m@forest.net^alice'` or the following config file example:
```yaml
[global]
@@ -2202,9 +2391,7 @@ if you want to change the fonts, see [./docs/rice/](./docs/rice/)
become a *real* webserver which people can access by just going to your IP or domain without specifying a port
**if you're on windows,** then you just need to add the commandline argument `-p 80,443` and you're done! nice
**if you're on macos,** sorry, I don't know
**if you're on windows or macos,** then you just need to add the commandline argument `-p 80,443` and you're done! nice
**if you're on Linux,** you have the following 4 options:
@@ -2394,7 +2581,7 @@ change the association of a file extension
using commandline args, you can do something like `--mime gif=image/jif` and `--mime ts=text/x.typescript` (can be specified multiple times)
in a config file, this is the same as:
that's the same as this config file example:
```yaml
[global]
@@ -2423,12 +2610,15 @@ buggy feature? rip it out by setting any of the following environment variables
| env-var | what it does |
| -------------------- | ------------ |
| `PRTY_NO_CTYPES` | do not use features from external libraries such as kernel32 |
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
| `PRTY_NO_ENVEXPAND` | do not expand environment-variables in configs and args |
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
| `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
| `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
| `PRTY_NO_UNIDATA` | disable loading the unicodedata c-extension; saves 400k ram, breaks `--srch-nfkc` |
| `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
| `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
| `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
@@ -2458,7 +2648,7 @@ if your distro/OS is not mentioned below, there might be some hints in the [«on
`pacman -S copyparty` (in [arch linux extra](https://archlinux.org/packages/extra/any/copyparty/))
it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well as a [user service](./contrib/systemd/copyparty-user.service), and expects to find a [config file](./contrib/systemd/copyparty.example.conf) in `/etc/copyparty/copyparty.conf` or `~/.config/copyparty/copyparty.conf`
it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well as a [user service](./contrib/systemd/copyparty-user.service), and expects to find a [config file](./contrib/systemd/copyparty.example.conf) in `/etc/copyparty.conf` or `~/.config/copyparty/copyparty.conf`
after installing, start either the system service or the user service and navigate to http://127.0.0.1:3923 for further instructions (unless you already edited the config files, in which case you are good to go, probably)
@@ -2470,6 +2660,23 @@ after installing, start either the system service or the user service and naviga
does not exist yet; there are rumours that it is being packaged! keep an eye on this space...
## gentoo ::guru package
`emerge www-servers/copyparty::guru` (in [::guru](https://wiki.gentoo.org/wiki/Project:GURU))
but first enable the `::guru` repo;
```bash
emerge -an app-eselect/eselect-repository
eselect repository enable guru
emerge --sync guru
```
to start the service as a user:
* OpenRC: `rc-service -U copyparty start && rc-update -U add copyparty default`
* systemd: [todo]
## homebrew formulae
`brew install copyparty ffmpeg` -- https://formulae.brew.sh/formula/copyparty
@@ -2487,8 +2694,6 @@ requires a [flake-enabled](https://nixos.wiki/wiki/Flakes) installation of nix
some recommended dependencies are enabled by default; [override the package](https://github.com/9001/copyparty/blob/hovudstraum/contrib/package/nix/copyparty/default.nix#L3-L22) if you want to add/remove some features/deps
`ffmpeg-full` was chosen over `ffmpeg-headless` mainly because we need `withWebp` (and `withOpenmpt` is also nice) and being able to use a cached build felt more important than optimizing for size at the time -- PRs welcome if you disagree 👍
## nixos module
@@ -2614,6 +2819,12 @@ services.copyparty = {
};
# you may increase the open file limit for the process
openFilesLimit = 8192;
# override the package used by the module to add dependencies, e.g. for hooks
package = pkgs.copyparty.override {
# provides exiftool for bin/hooks/image-noexif.py
extraPackages = [ pkgs.exiftool ];
};
};
```
@@ -2843,7 +3054,7 @@ below are some tweaks roughly ordered by usefulness:
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
* if you are running the copyparty server **on Windows or Macos:**
* `--casechk=n` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
* `--casechk=n` makes it much faster, but [reduces security](https://github.com/9001/copyparty/security/advisories/GHSA-3c88-p299-7hvr) and awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
* this is the same as `casechk: n` in a config-file
@@ -2877,8 +3088,13 @@ some notes on hardening
* set `--rproxy 0` *if and only if* your copyparty is directly facing the internet (not through a reverse-proxy)
* cors doesn't work right otherwise
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
* this returns html documents as plaintext, and also disables markdown rendering
* this returns html documents and svg images as plaintext, and also disables markdown rendering
* the `nohtml` volflag also enables `noscript` which, on its own, prevents *most* javascript from running; enabling just `noscript` without `nohtml` makes it probably-safe (see below) to view html and svg files, but `nohtml` is necessary to block javascript in markdown documents
* "probably-safe" because it relies on `Content-Security-Policy` so it depends on the reverseproxy to forward it, and the browser to understand it, but `nohtml` (the nuclear option) always works
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or [`--help-bind`](https://copyparty.eu/cli/#bind-help-page)
* put ffmpeg into a sandbox (helps against future ffmpeg vulns) by configuring [--th-bwrap](https://copyparty.eu/cli/#g-th-bwrap) and enable it with `--use-bwrap y`
* usually impossible when running in docker or podman
* the default value of `--th-bwrap` (see `--help` on your own server) is autogenerated best-effort guess based on your setup; installing `bwrap` and configuring this is up to you (good luck)
safety profiles:
@@ -2922,6 +3138,23 @@ behavior that might be unexpected
* or eliminate the problem entirely by only giving write-access to trustworthy people :^)
## csp nonce
unauthorized javascript is blocked by means of the default [content-security-policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) which enforces "script nonces", to help prevent XSS and similar
usually this Just Works and is correctly configured by default; you can stop reading this section and move on, nothing to see here
but if you are the unlucky 0.1% with a hosting-setup funky enough for this to not work as intended, then you will also be seeing this message: `Looks like javascript is broken; see "csp nonce" in the readme`
there is two possible reasons why this might happen:
1. your browser is simply too old to understand the javascript, which is not likely, because just about every browser is supported, even internet explorer 11
2. something in your hosting-setup is tampering with the responses from copyparty and destroying this security feature; the culprit is probably a misconfigured reverseproxy, a CDN, a WAF, or some other mitm, but a popular offender is **"cloudflare rocket loader"**
you can try to **temporarily** set global-options `--csp-ui no` and `--csp-dl no` and restart copyparty to check whether it is due to reason #2. Remember to refresh the webbrowser with CTRL-SHIFT-R to check properly. If that makes the problem go away, then turn them back off (**never** keep them enabled), restart copyparty, open the `Network` tab in the webbrowser, and refresh the website; you should see the `content-security-policy` header including a nonce, for example `nonce-BxcJbvdu1z3S/ycPBjVWuA==` which should also appear inside the HTML of that response like this: `<script nonce="BxcJbvdu1z3S/ycPBjVWuA==">` -- if it doesn't then that's the problem; something in your hosting stack is dangerously misconfigured
## cors
cross-site request config
@@ -2948,6 +3181,8 @@ permissions `wG` (write + upget) lets users upload files and receive their own f
share specific folders in a volume without giving away full read-access to the rest -- the visitor only needs the `g` (get) permission to view the link
> ℹ️ a better alternative to this is [shares](#shares)
volflag `dk` generates dirkeys (per-directory accesskeys) for all folders, granting read-access to that folder; by default only that folder itself, no subfolders
volflag `dky` disables the actual key-check, meaning anyone can see the contents of a folder where they have `g` access, but not its subdirectories
@@ -2983,9 +3218,10 @@ when generating hashes using `--ah-cli` for docker or systemd services, make sur
## https
both HTTP and HTTPS are accepted by default, but letting a [reverse proxy](#reverse-proxy) handle the https/tls/ssl would be better (probably more secure by default)
both HTTP and HTTPS are accepted by default, but please ignore copyparty's built-in https/tls support and instead use a [reverse proxy](#reverse-proxy) to handle https/tls/ssl
copyparty doesn't speak HTTP/2 or QUIC, so using a reverse proxy would solve that as well -- but note that HTTP/1 is usually faster than both HTTP/2 and HTTP/3
* reverseproxies do a better job following [best practices](https://cipherlist.eu/) meaning they are more secure, and probably also have higher performance
* also, copyparty doesn't speak HTTP/2 or QUIC, so using a reverse proxy would solve that as well -- but note that HTTP/1 is usually faster than both HTTP/2 and HTTP/3
if [cfssl](https://github.com/cloudflare/cfssl/releases/latest) is installed, copyparty will automatically create a CA and server-cert on startup
* the certs are written to `--crt-dir` for distribution, see `--help` for the other `--crt` options
@@ -2997,6 +3233,11 @@ to install cfssl on windows:
* rename them to `cfssl.exe`, `cfssljson.exe`, `cfssl-certinfo.exe`
* put them in PATH, for example inside `c:\windows\system32`
if you really wanna give copyparty an existing TLS certificate then do one of the following:
* `--no-crt --cert server.pem` where `server.pem` is a concatenation of key + cert + chain (in that order), or...
* `--no-crt --cert server.crt --certkey server.key` where `server.key` is the key, and `server.crt` is a concatenation of cert + chain (in that order)
* file-extensions don't matter, but all files are expected to be [PEM-style](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/res/insecure.pem)
# recovering from crashes
@@ -3051,18 +3292,20 @@ enable [thumbnails](#thumbnails) of...
* **HEIF pictures:** `pyvips` or `ffmpeg` or `pillow-heif`
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` or pillow v11.3+
* **JPEG XL pictures:** `pyvips` or `ffmpeg`
* **RAW images:** `rawpy`, plus one of `pyvips` or `Pillow` (for some formats)
* **RAW photos:** either `libraw dcraw_emu` or `rawpy`, plus either `pyvips` or `Pillow`
enable sending [zeromq messages](#zeromq) from event-hooks: `pyzmq`
enable [smb](#smb-server) support (**not** recommended): `impacket==0.13.0`
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram
`pyvips` adds support for some additional image formats, but can use extreme amounts of RAM
* to install `pyvips` on Linux: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
* to install `pyvips` on windows: `pip install --user -U "pyvips[binary]"`
to install FFmpeg on Windows, grab [a recent build](https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z) -- you need `ffmpeg.exe` and `ffprobe.exe` from inside the `bin` folder; copy them into `C:\Windows\System32` or any other folder that's in your `%PATH%`
if your ffmpeg/ffprobe binaries have nonstandard names -- such as `ffmpeg8` (macports) -- set environment variables `PRTY_FFMPEG_BIN` and `PRTY_FFPROBE_BIN` to the corret name (or full path)
### dependency chickenbits
@@ -3077,6 +3320,7 @@ set any of the following environment variables to disable its associated optiona
| -------------------- | ------------ |
| `PRTY_NO_ARGON2` | disable argon2-cffi password hashing |
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
| `PRTY_NO_DCRAW` | disable all [libraw](https://www.libraw.org/homepage)-based thumbnail support for RAW images |
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
| `PRTY_NO_MAGIC` | do not use [magic](https://pypi.org/project/python-magic/) for filetype detection |
@@ -3087,10 +3331,12 @@ set any of the following environment variables to disable its associated optiona
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
| `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
| `PRTY_NO_PIL_JXL` | disable 3rd-party Pillow plugin for [JXL support](https://pypi.org/project/pillow-jxl-plugin/) |
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
| `PRTY_NO_PYFTPD` | disable ftp(s) server ([pyftpdlib](https://pypi.org/project/pyftpdlib/)-based) |
| `PRTY_NO_RAW` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
| `PRTY_NO_RAW` | same as `PRTY_NO_DCRAW` plus `PRTY_NO_RAWPY` |
| `PRTY_NO_RAWPY` | disable all [rawpy](https://pypi.org/project/rawpy/)-based thumbnail support for RAW images |
| `PRTY_NO_VIPS` | disable all [libvips](https://pypi.org/project/pyvips/)-based thumbnail support; will fallback to Pillow or ffmpeg |
example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
+3 -2
View File
@@ -26,12 +26,12 @@ and consider using [../docs/rclone.md](../docs/rclone.md) instead; usually a bit
## to run this on windows:
* install [winfsp](https://github.com/billziss-gh/winfsp/releases/latest) and [python 3](https://www.python.org/downloads/)
* [x] add python 3.x to PATH (it asks during install)
* `python -m pip install --user fusepy` (or grab a copy of `fuse.py` from the `connect` page on your copyparty, and keep it in the same folder)
* `python -m pip install --user mfusepy` (or grab a copy of `mfusepy.py` from the `connect` page on your copyparty, and keep it in the same folder)
* `python ./partyfuse.py n: http://192.168.1.69:3923/`
10% faster in [msys2](https://www.msys2.org/), 700% faster if debug prints are enabled:
* `pacman -S mingw64/mingw-w64-x86_64-python{,-pip}`
* `/mingw64/bin/python3 -m pip install --user fusepy`
* `/mingw64/bin/python3 -m pip install --user mfusepy`
* `/mingw64/bin/python3 ./partyfuse.py [...]`
you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releases/latest), let me know if you [figure out how](https://github.com/dokan-dev/dokany/wiki/FUSE)
@@ -78,6 +78,7 @@ cd /mnt/nas/music/.hist
# [`prisonparty.sh`](prisonparty.sh)
* run copyparty in a chroot, preventing any accidental file access
* creates bindmounts for /bin, /lib, and so on, see `sysdirs=`
* **not compatible with:** `--th-bwrap`
# [`bubbleparty.sh`](bubbleparty.sh)
* run copyparty in an isolated process, preventing any accidental file access and more
+36
View File
@@ -0,0 +1,36 @@
# /!\ Warning: be careful, as webdav clients often generate a large number of 404 requets.
# In your `jail.local`, add:
# [copyparty]
# enabled = true
# logtimezone = UTC
# logpath = /path/to/log/file # or keep the default value if you're using systemd
# Create the `copyparty.conf` file in `filter.d` with the following:
# [Definition]
# failregex = ^ <ADDR>$
# ignoreregex =
# datepattern = ^fail2ban: %%Y-%%m-%%dT%%H:%%M:%%S
# First check `--dont-ban`, and if it doesn't match, log the line to be intercepted by fail2ban.
from datetime import datetime, UTC
def main(cli, vn="", rem=""):
now = datetime.now(UTC).isoformat()[:19]
msg = "\nfail2ban: %s %s"
if not vn and not rem:
# got called by --xban
cli["log"](msg % (now, cli["ip"]), 3)
return {"rc": 0}
cond = cli.args.dont_ban
if (
cond == "any"
or (cond == "auth" and cli.uname != "*")
or (cond == "aa" and cli.avol)
or (cond == "av" and cli.can_admin)
or (cond == "rw" and cli.can_read and cli.can_write)
):
return ""
cli.log(msg % (now, cli.ip), 3)
return ""
+2 -1
View File
@@ -22,10 +22,11 @@ each plugin must define a `main()` which takes 3 arguments;
* [redirect.py](redirect.py) sends an HTTP 301 or 302, redirecting the client to another page/file
* [randpic.py](randpic.py) redirects `/foo/bar/randpic.jpg` to a random pic in `/foo/bar/`
* [sorry.py](answer.py) replies with a custom message instead of the usual 404
* [sorry.py](sorry.py) replies with a custom message instead of the usual 404
* [nooo.py](nooo.py) replies with an endless noooooooooooooo
* [never404.py](never404.py) 100% guarantee that 404 will never be a thing again as it automatically creates dummy files whenever necessary
* [caching-proxy.py](caching-proxy.py) transforms copyparty into a squid/varnish knockoff
* [404-to-fail2ban.py](404-to-fail2ban.py) creates 404 logs for fail2ban
## on403
+1 -1
View File
@@ -8,7 +8,7 @@ def say_no():
def main(cli, vn, rem):
cli.send_headers(None, 404, "text/plain")
cli.send_headers("oh_f", None, 404, "text/plain")
for chunk in say_no():
cli.s.sendall(chunk)
+5
View File
@@ -36,6 +36,11 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
* [reject-and-explain.py](reject-and-explain.py) shows a custom error-message when it rejects an upload
* [reject-ramdisk.py](reject-ramdisk.py) rejects the upload if the destination is a ramdisk
* this hook uses the `I` flag which makes it 140x faster, but if the plugin has a bug it may crash copyparty
* [phonecam-sorter.py](phonecam-sorter.py) is for syncing backups of phone gallery into year/month folders
# more upload stuff
* combine [reloc-by-wark-xbu.py](reloc-by-wark-xbu.py) and [reloc-by-wark-xau.py](reloc-by-wark-xau.py) to rename uploads to the checksum of the file contents
# on message
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import os
import re
import sys
_ = r"""
purpose:
sync/backup phone gallery (pics/vids) to a nas which runs copyparty
what this hook does:
redirect photos/videos into date-based folders, named "%Y%m" according
to filename; that is, "/cam/sort/DCIM/D027/PXL_20250828_202820075.jpg"
will be redirected to "/cam/pics/ts/202508/PXL_20250828_202820075.jpg"
requires this setup on the server:
* /cam/ is a volume with all your pics/vids
* the phone must upload to somewhere below /cam/sort/
* photo/video filenames match the following regex:
^[a-z]{3,4}_([0-9]{6})[0-9]{2}_[0-9]{6}.*\.(jpg|mp4)$
* so for example these filenames are OK:
* PXL_20250828_202820075.jpg
* IMG_20251123_202405.jpg
* PANO_20180421_121245_03.t8o5.jpg
* VID_20250307_220250-crf34.mp4
example server config as a volflag:
-v /mnt/thedisk/gallery:cam:rwa,ed:c,e2ds,xbu=I,~/hooks/phonecam-sorter.py
example server config in a copyparty config file:
[/cam]
/mnt/thedisk/gallery
accs:
rwa: ed
flags:
e2ds
xbu: I,~/hooks/phonecam-sorter.py
command to run on phone to sync to server, sending new files only:
~/bin/u2c.py -te homenas.ca -a '$homenas.pw' https://10.0.0.2:3923/cam/sort/ ~/storage/dcim/Camera
the phone-command above assumes this setup on the phone:
* termux or another linux-like thing with access to gallery files
* homenas.ca is a textfile with the server's CA certificate
* homenas.pw is a textfile with the copyparty password inside
* ~/storage/dcim/ exists; if not, run termux-setup-storage
* ~/bin/u2c.py exists; if not, curl -LO https://10.0.0.2:3923/.cpr/a/u2c.py && chmod 755 u2c.py
unfortunate consequences:
* due to the redirection, u2c -z doesn't do anything, so every local
file is hashed every time, wasting some time and cpu on the phone
but otoh certainly avoids corruption on either side (phone/server)
"""
def main(inf):
log = inf["log"]
vdir, fn = os.path.split(inf["vp"])
if not vdir.startswith("cam/sort"):
return {"rc": 0} # 2nd call after 1st reloc; already done
m = re.match(r"^[a-z]{3,4}_([0-9]{6})[0-9]{2}_[0-9]{6}.*\.(jpg|mp4)$", fn.lower())
if not m:
return {"rc": 0} # not recognized; write to original path in /cam/sort/
ym, ext = m.groups()
if ext == "jpg":
dst = f"/cam/pics/ts/{ym}/"
elif ext == "mp4":
dst = f"/cam/vids/ts/{ym}/"
else:
return {"rc": 0}
ap = "%s%s" % (inf["ap"].split("/cam/sort/")[0], dst)
if not os.path.isdir(ap):
os.makedirs(ap)
ret = {"rc": 0, "reloc": {"vp": dst}}
return ret
if __name__ == "__main__":
main()
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
import os
import sys
_ = r"""
rename incoming uploads according to the "wark" (the file identifier)
which is basically but not exactly a sha512 hash of the file contents
NOTE: this does NOT work with up2k uploads (dragdrop into browser);
combine this hook with reloc-by-wark-xbu.py to fix that
example usage as global config:
-e2d --xau I,c,bin/hooks/reloc-by-wark-xau.py
parameters explained,
e2d = enable up2k database (mandatory for xau hooks)
xau = execute before upload
I = import this hook for performance; do not fork / subprocess
c = "check"; reject upload if this hook crashes due to a bug
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,e2d,xau=I,c,bin/hooks/reloc-by-wark-xau.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params explained above)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
e2d, xau: I,c,bin/hooks/reloc-by-wark-xau.py
"""
def main(inf):
if inf.get("wark"):
# this is an up2k upload; nothing to be done, just bail
return {}
abspath = inf["ap"] # filesystem path to the uploaded file
# we don't have the wark yet so need to calculate it;
# generating a regular sha512 would of course be much easier,
# but then filenames would be different depending on how the
# file was uploaded (laaame) so let's do it the hard way
# use the standard up2k-salt which nobody ever changes:
salt = "hunter2"
# to generate the wark we'll need some functions from copyparty;
# follow the trail to the copyparty module and grab them from there:
import inspect
libpath = inspect.getfile(inf["log"])
libpath = os.path.dirname(os.path.dirname(libpath))
sys.path.insert(0, libpath)
from copyparty.up2k import up2k_hashlist_from_file, up2k_wark_from_hashlist
chunklist, st = up2k_hashlist_from_file(abspath)
wark = up2k_wark_from_hashlist(salt, st.st_size, chunklist)
# okay nice
# the rest of the code below is just copied from reloc-by-wark-xbu.py
# -------------------------------------------------------------------------
# grab the original filename from the vpath...
vdir, fn = os.path.split(inf["vp"])
# ...to retain the original file extension, if any
try:
fn, ext = fn.rsplit(".", 1)
except:
ext = ""
# use the first 16 characters; 12 bytes of entropy,
# roughly one collision for every 26 million files
fn = wark[:16]
if ext:
ext = ext.lower()
fn += "." + ext
return {"reloc": {"fn": fn}}
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import os
_ = r"""
rename incoming uploads according to the "wark" (the file identifier)
which is basically but not exactly a sha512 hash of the file contents
NOTE: this only works for up2k uploads (dragdrop into browser);
combine this with reloc-by-wark-xau.py to cover the other protocols
example usage as global config:
-e2d --xbu I,c,bin/hooks/reloc-by-wark-xbu.py
parameters explained,
e2d = enable up2k database (mandatory for xbu hooks)
xbu = execute before upload
I = import this hook for performance; do not fork / subprocess
c = "check"; reject upload if this hook crashes due to a bug
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,e2d,xbu=I,c,bin/hooks/reloc-by-wark-xbu.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params explained above)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
e2d, xbu: I,c,bin/hooks/reloc-by-wark-xbu.py
"""
def main(inf):
wark = inf.get("wark")
if not wark:
# not an up2k upload, so we don't have the hash;
# option 1: let upload proceed with original filename
return {}
# option 2: reject the upload
return {"rejectmsg": "only up2k uploads are allowed in this volume"}
# grab the original filename from the vpath...
vdir, fn = os.path.split(inf["vp"])
# ...to retain the original file extension, if any
try:
fn, ext = fn.rsplit(".", 1)
except:
ext = ""
# use the first 16 characters; 12 bytes of entropy,
# roughly one collision for every 26 million files
fn = wark[:16]
if ext:
ext = ext.lower()
fn += "." + ext
return {"reloc": {"fn": fn}}
+16 -10
View File
@@ -21,7 +21,7 @@ usage:
python partyfuse.py http://192.168.1.69:3923/ ./music
dependencies:
python3 -m pip install --user fusepy # or grab it from the connect page
python3 -m pip install --user mfusepy # or grab it from the connect page
+ on Linux: sudo apk add fuse
+ on Macos: https://osxfuse.github.io/
+ on Windows: https://github.com/billziss-gh/winfsp/releases/latest
@@ -92,8 +92,13 @@ is_dbg = False
try:
from fuse import FUSE, FuseOSError, Operations
from mfusepy import FUSE, FuseOSError, Operations
except:
try:
from fuse import FUSE, FuseOSError, Operations
except:
FUSE = None
if WINDOWS:
libfuse = "install https://github.com/billziss-gh/winfsp/releases/latest"
elif MACOS:
@@ -102,12 +107,13 @@ except:
libfuse = "apt install libfuse2\n modprobe fuse"
m = """\033[33m
could not import fuse; these may help:
{} -m pip install --user fusepy
could not import mfusepy; these may help:
{} -m pip install --user mfusepy
{}
\033[0m"""
print(m.format(sys.executable, libfuse))
raise
if not FUSE:
print(m.format(sys.executable, libfuse))
raise
def termsafe(txt):
@@ -143,10 +149,10 @@ def fancy_log(fmt, *a):
def register_wtf8():
def wtf8_enc(text):
def wtf8_enc(text, errors=""):
return str(text).encode("utf-8", "surrogateescape"), len(text)
def wtf8_dec(binary):
def wtf8_dec(binary, errors=""):
return bytes(binary).decode("utf-8", "surrogateescape"), len(binary)
def wtf8_search(encoding_name):
@@ -620,7 +626,7 @@ class Gateway(object):
"st_atime": ts,
"st_mtime": ts,
"st_ctime": ts,
"st_blocks": int((sz + 511) / 512),
"st_blocks": -int(-sz // 512),
}
def stat_file(self, ts, sz):
@@ -632,7 +638,7 @@ class Gateway(object):
"st_atime": ts,
"st_mtime": ts,
"st_ctime": ts,
"st_blocks": int((sz + 511) / 512),
"st_blocks": -int(-sz // 512),
}
+3
View File
@@ -20,6 +20,8 @@ example:
example for running straight from source (instead of using an sfx):
PYTHONPATH=$PWD ./prisonparty.sh /var/lib/copyparty-jail cpp cpp /mnt/nas/music -- python3 -um copyparty -v /mnt/nas/music::rwmd
NOT COMPATIBLE WITH: --th-bwrap
note that if you have python modules installed as --user (such as bpm/key detectors),
you should add /home/foo/.local as a VOLDIR
@@ -97,6 +99,7 @@ jail="${jail%/}"
# bind-mount system directories and volumes
mkdir -p "$jail"
for a in {1..30}; do mkdir "$jail/.prisonlock" && break; sleep 0.1; done
printf '%s\n' "${sysdirs[@]}" "${vols[@]}" | sed -r 's`/$``' | LC_ALL=C sort | uniq |
while IFS= read -r v; do
+55
View File
@@ -0,0 +1,55 @@
# thumbnail extractors
extract (or generate) thumbnails for custom file formats or override default thumbnail extraction for standard media files
## usage
* load plugins with `--th-extract baz=~/dev/copyparty/bin/thumbs/randomcolor.py`
* `baz` is the file extension (may be comma-separated list of extensions)
* add multiple different extractor plugins by repeating the `--th-extract` argument
## api
### in
each plugin must define a function `main(abspath, **kwargs)`:
* `abspath` is the path to a file to extract thumbnail for
* `kwargs` currently receives these keyword arguments:
* `vn` is the VFS which contains the requested file
* `th_srv` is an instance of [copyparty/th_srv](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/th_srv.py)
### out
the `main()` function must
* return `None` or raise an `Exception` in case it is unable to extract thumbnail
* return tuple `fmt, stream, offset, whence, size` otherwise:
* `fmt: str` – format (filename extension) of the thumbnail; should be one of the image formats supported by copyparty (see `--th-r-*` options)
* `stream: IO[bytes]` – binary file-like object supporting `seek()`, `read()`, `close()` methods; should contain extracted thumbnail image
* `offset: int, whence: int` – arguments to `stream.seek()`; copyparty will call `stream.seek(offset, whence)` once before starting reading the stream contents
* `size: int | None` – number of bytes to read from the `stream`; if `size` is negative or `None`, copyparty will read until the end of the stream
### notes
it is possible to extract or generate custom thumbnails for standard media files too,
e.g. run copyparty with `--th-extract mp3,m4a,aac,flac,opus=~/dev/copyparty/bin/thumbs/my_custom_mp3_th_extractor.py` and do your magic with audio file thumbnails
in case copyparty doesn't like the `fmt` option, it will `close()` the `stream` without ever calling `seek()` or `read()`
if your scenario involves heavy computation or i/o, it is better to defer the heavy parts until the first `seek()` or `read()` to avoid redundant work in case the result gets rejected based on `fmt` value
if you provide custom `stream` object:
copyparty performs buffered reading, so expect multiple `read()` calls, respect `size` argument in `read(size)` calls, and maintain correct seek position
## examples
* [randomcolor](https://github.com/9001/copyparty/blob/hovudstraum/bin/thumbs/randomcolor.py) generates a random .gif image
* [collabora](https://github.com/9001/copyparty/blob/hovudstraum/bin/thumbs/collabora.py) uses external self-hosted service (collabora online) to generate previews for various office documents, pdf and fb2 books, html, markdown, text files and some image formats; collabora is also supported as office editor in copyparty, see [wopi](https://github.com/9001/copyparty#wopi-server)
## some other known plugins seen on the internets
* [fpkg_thumb](https://github.com/kamaeff/fpkg-vault/blob/1c248de62a9ff9c4088d4b3e7962023cde9d6426/old/fpkg_thumb.py) extracts cover images from playstation4 software installation packages ("pkg" and "fpkg" files)
+125
View File
@@ -0,0 +1,125 @@
"""
CAUTION!
looks like collabora spawns separate jailed libreoffice instance to do the conversion
this may eat up too much ram in folders with many docs or with --th-pregen enabled
consider limiting resources available for collabora
e.g. for me these lines in docker-compose.yml helped alot:
deploy:
resources:
limits:
cpus: '2'
memory: '2G'
/CAUTION!
if you use collabora office editor with copyparty, you already have a thumbnailer for office formats:
https://sdk.collaboraonline.com/docs/conversion_api.html
the 'convert-to' api accepts files in multipart/form-data format
just add this extractor to copyparty with an argument like this:
--th-extract=docx,xlsx,pptx,odt,ods,pdf,fb2=~/dev/copyparty/bin/thumbs/collabora.py
and set COLLABORA_INSTANCE_URL below to your collabora instace url
full list of supported file types:
https://sdk.collaboraonline.com/docs/conversion_api.html#supported-input-formats
this is also an example of deferred IO:
no files opened and no network requests made if copyparty doesn't like the file type
"""
import os
import string
import random
import mimetypes
from urllib.error import HTTPError
from urllib.request import Request, urlopen
COLLABORA_INSTANCE_URL = "http://collabora:9980"
BOUNDARY_CHARS = string.ascii_letters + string.digits
def main(abspath, **kwargs):
boundary = "CopypartyFormData" + "".join(random.choices(BOUNDARY_CHARS, k=53))
start, end = make_headers(abspath, boundary)
size = len(start) + os.path.getsize(abspath) + len(end)
request = Request(
COLLABORA_INSTANCE_URL + "/cool/convert-to/png",
method="POST",
data=iterate(start, abspath, end),
headers={
"Content-Type": f"multipart/form-data; boundary={boundary}",
"Content-Length": str(size),
},
)
file_like = Wrapper(request)
offset, whence, length = 0, 0, None
return "png", file_like, offset, whence, length
def make_headers(abspath, boundary):
"""stuff to wrap file contents by for multipart"""
fname = "file." + abspath.rsplit(".", 1)[-1]
ftype = mimetypes.guess_file_type(fname)[0] or "application/octet-stream"
start = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="data"; filename="{fname}"\r\n'
f"Content-Type: {ftype}\r\n\r\n"
)
end = f"\r\n--{boundary}--"
return bytes(start, "ascii"), bytes(end, "ascii")
def iterate(start, fpath, end):
"""docs may be big so read and send them in chunks"""
yield start
with open(fpath, "rb") as f:
while True:
b = f.read(32768)
if not b:
break
yield b
yield end
class Wrapper:
"""
This file-like wrapper defers expensive work until first .seek() call
Useful to not waste resources in case copyparty decides
to throw our thumbnail away based on returned file extension
"""
def __init__(self, request):
self.request = request
self.response = None
def seek(self, offset, whence=0):
if self.response:
return 0
try:
# the heavy part:
# - reading probably big file
# - sending it over the network
# - making collabora do some work
self.response = urlopen(self.request, timeout=10)
except HTTPError as e:
e.close()
raise e
except Exception as e:
self.close()
raise e
# actual response is not seekable
# just return 0
return 0
def read(self, amount):
return self.response.read(amount)
def close(self):
if self.response:
self.response.close()
+29
View File
@@ -0,0 +1,29 @@
import io
import os
import struct
from base64 import b64decode
def create_gif() -> bytes:
head = r"R0lGODdhIAAgAIAB"
tail = r"AAD/LAAAAAAgACAAAAIehI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC5sFADs="
#color = struct.pack(">L", random.randrange(0xffffff)) # wow, way too stable
color = b"\x00" + os.urandom(3)
return b64decode(head) + color + b64decode(tail)
def main(abspath, **ka) -> Optional[tuple[str, IO[bytes], int, int, Optional[int]]]:
print("hello from randomcolor.py pretending to extract a thumb from " + abspath)
# need to return a file-like object
gif_bytes = create_gif()
file_obj = io.BytesIO(gif_bytes)
# where copyparty should seek to in the "file" to start reading from
seek_offset = 0
seek_whence = 0
filesize = None # auto; read until end
# "gif" is the file-extension to decode the file as
return "gif", file_obj, seek_offset, seek_whence, filesize
+59 -20
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
S_VERSION = "2.19"
S_BUILD_DT = "2026-01-18"
S_VERSION = "2.25"
S_BUILD_DT = "2026-10-05"
"""
u2c.py: upload to copyparty
@@ -67,6 +67,8 @@ else:
WTF8 = "replace" if PY2 else "surrogateescape"
SEP = "{0}".format(os.sep).encode("ascii")
VT100 = platform.system() != "Windows"
@@ -249,7 +251,7 @@ class File(object):
def __init__(self, top, rel, size, lmod):
self.top = top # type: bytes
self.rel = rel.replace(b"\\", b"/") # type: bytes
self.rel = rel.replace(SEP, b"/") # type: bytes
self.size = size # type: int
self.lmod = lmod # type: float
@@ -267,6 +269,7 @@ class File(object):
self.wark = "" # type: str
self.url = "" # type: str
self.nhs = 0 # type: int
self.t_hs = 0.0 # type: float
# set by upload
self.t0_up = 0.0 # type: float
@@ -398,7 +401,7 @@ class MTHash(object):
self.csz = chunksz
chunks = {}
nchunks = int(math.ceil(fsz / chunksz))
nchunks = -int(-fsz // chunksz)
for nch in range(nchunks):
self.work_q.put(nch)
@@ -652,7 +655,7 @@ def walkdir(err, top, excl, seen):
def walkdirs(err, tops, excl):
"""recursive statdir for a list of tops, yields [top, relpath, stat]"""
sep = "{0}".format(os.sep).encode("ascii")
sep = SEP
if not VT100:
excl = excl.replace("/", r"\\")
za = []
@@ -725,7 +728,7 @@ def up2k_chunksize(filesize):
stepsize = 512 * 1024
while True:
for mul in [1, 2]:
nchunks = math.ceil(filesize * 1.0 / chunksize)
nchunks = -int(-filesize // chunksize)
if nchunks <= 256 or (chunksize >= 32 * 1024 * 1024 and nchunks <= 4096):
return chunksize
@@ -733,16 +736,22 @@ def up2k_chunksize(filesize):
stepsize *= mul
# mostly from copyparty/up2k.py
def get_hashlist(file, pcb, mth):
# type: (File, Any, Any) -> None
with open(file.abs, "rb", 512 * 1024) as f:
get_hashlist_f(file, pcb, mth, f)
# mostly from copyparty/up2k.py
def get_hashlist_f(file, pcb, mth, f):
# type: (File, Any, Any, Any) -> None
"""generates the up2k hashlist from file contents, inserts it into `file`"""
chunk_sz = up2k_chunksize(file.size)
file_rem = file.size
file_ofs = 0
ret = []
with open(file.abs, "rb", 512 * 1024) as f:
if True:
t0 = time.time()
if mth and file.size >= 1024 * 512:
@@ -778,6 +787,18 @@ def get_hashlist(file, pcb, mth):
file.kchunks[k] = [v1, v2]
def wark_stdin(ar):
file = File(b"", b"", int(ar.files[0]), 0)
get_hashlist_f(file, None, False, sys.stdin if PY2 else sys.stdin.buffer)
if ar.chs:
zsl = ["%s %d %d" % (zsii[0], n, zsii[1]) for n, zsii in enumerate(file.cids)]
print("chs:\n" + "\n".join(zsl))
zsl = [ar.wsalt, str(file.size)] + [x[0] for x in file.cids]
zb = hashlib.sha512("\n".join(zsl).encode("utf-8")).digest()[:33]
wark = ub64enc(zb).decode("utf-8")
print(wark)
def printlink(ar, purl, name, fk):
if not name:
url = purl # srch
@@ -886,6 +907,7 @@ def handshake(ar, file, search):
file.url = quotep(r["purl"].encode("utf-8", WTF8)).decode("utf-8")
file.name = r["name"]
file.wark = r["wark"]
file.t_hs = time.time()
if ar.uon and not r["hash"]:
printlink(ar, file.url, r["name"], r.get("fk"))
@@ -1009,7 +1031,7 @@ class Ctl(object):
self.up_b = 0 # num bytes handled
self.up_br = 0 # num bytes actually transferred
self.uploader_busy = 0
self.serialized = False
self.oneconn = False
self.t0 = time.time()
self.t0_up = None
@@ -1184,7 +1206,7 @@ class Ctl(object):
handshake(self.ar, file, False)
def cleanup_vt100(self):
if VT100:
if VT100 and not self.ar.ns:
ss.scroll_region(None)
else:
eprint("\033]9;4;0\033\\")
@@ -1195,14 +1217,15 @@ class Ctl(object):
def hasher(self):
ptn = re.compile(self.ar.x.encode("utf-8"), re.I) if self.ar.x else None
sep = "{0}".format(os.sep).encode("ascii")
usep = os.sep
sep = SEP
prd = None
ls = {}
for top, rel, inf in self.filegen:
isdir = stat.S_ISDIR(inf.st_mode)
if self.ar.z or self.ar.drd:
rd = rel if isdir else os.path.dirname(rel)
srd = rd.decode("utf-8", "replace").replace("\\", "/").rstrip("/")
srd = rd.decode("utf-8", "replace").replace(usep, "/").rstrip("/")
if srd:
srd += "/"
if prd != rd:
@@ -1212,7 +1235,7 @@ class Ctl(object):
print(" ls ~{0}".format(srd))
zt = (
self.ar.vtop,
quotep(rd.replace(b"\\", b"/")).decode("utf-8"),
quotep(rd.replace(sep, b"/")).decode("utf-8"),
)
sc, txt = web.req("GET", "%s%s?ls&lt&dots" % zt, {})
if sc >= 400:
@@ -1311,7 +1334,8 @@ class Ctl(object):
if self.ar.jw:
print("%s %s" % (wark, vp))
else:
zd = datetime.datetime.fromtimestamp(max(0, file.lmod), UTC)
tsdt = datetime.datetime.fromtimestamp
zd = tsdt(max(0, min(2 << 36, file.lmod)), UTC)
dt = "%04d-%02d-%02d %02d:%02d:%02d" % (
zd.year,
zd.month,
@@ -1384,10 +1408,10 @@ class Ctl(object):
self.recheck.append(file)
with self.mutex:
if hs and not sprs and not self.serialized:
t = "server filesystem does not support sparse files; serializing uploads\n"
if hs and not sprs and not self.oneconn:
t = "server filesystem does not support sparse files; cannot parallelize uploads\n"
eprint(t)
self.serialized = True
self.oneconn = True
for _ in range(self.ar.j - 1):
self.q_upload.put(None)
if not hs:
@@ -1399,7 +1423,7 @@ class Ctl(object):
if not file.recheck:
self.up_done(file)
if hs and file.up_c:
if hs and file.up_c and not self.oneconn:
# some chunks failed
self.up_c -= len(hs)
file.up_c -= len(hs)
@@ -1457,6 +1481,7 @@ class Ctl(object):
self.q_upload.put(fsl)
def uploader(self):
drop_hs = (0.0, "")
while True:
fsl = self.q_upload.get()
if not fsl:
@@ -1475,6 +1500,9 @@ class Ctl(object):
cids = fsl.cids
self.last_file = file
if self.oneconn and drop_hs == (file.t_hs, file.wark):
continue
with self.mutex:
if not self.uploader_busy:
self.at_upr = time.time()
@@ -1500,6 +1528,9 @@ class Ctl(object):
t = "upload failed, retrying: %s #%s+%d (%s)\n"
eprint(t % (file.name, cids[0][:8], len(cids) - 1, ex))
file.cd = time.time() + self.ar.cd
if self.oneconn:
file.ucids = []
drop_hs = (file.t_hs, file.wark)
# handshake will fix it
with self.mutex:
@@ -1557,7 +1588,7 @@ NOTE: if server has --usernames enabled, then password is "username:password"
ap.add_argument("url", type=unicode, help="server url, including destination folder")
ap.add_argument("files", type=files_decoder, nargs="+", help="files and/or folders to process")
ap.add_argument("-v", action="store_true", help="verbose")
ap.add_argument("-a", metavar="PASSWD", default="", help="password (or $filepath) for copyparty (is sent in header 'PW')")
ap.add_argument("-a", metavar="PASSWD", default="", help="password (or $filepath) for copyparty (is sent in header 'PW'), default is env.var U2C_PW")
ap.add_argument("--ba", metavar="PASSWD", default="", help="password (or $filepath) for basic-auth (usually not necessary)")
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
ap.add_argument("-x", type=unicode, metavar="REGEX", action="append", help="skip file if filesystem-abspath matches REGEX (option can be repeated), example: '.*/\\.hist/.*'")
@@ -1586,6 +1617,7 @@ NOTE: if server has --usernames enabled, then password is "username:password"
ap.add_argument("--wsalt", type=unicode, metavar="S", default="hunter2", help="salt to use when creating warks; must match server config")
ap.add_argument("--chs", action="store_true", help="verbose (print the hash/offset of each chunk in each file)")
ap.add_argument("--jw", action="store_true", help="just identifier+filepath, not mtime/size too")
ap.add_argument("--stdin", action="store_true", help="calculate file-ID of stdin; u2c.py --stdin - $LEN < a.mkv")
ap = app.add_argument_group("performance tweaks")
ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
@@ -1616,6 +1648,10 @@ NOTE: if server has --usernames enabled, then password is "username:password"
except:
pass
if ar.stdin:
wark_stdin(ar)
return
# msys2 doesn't uncygpath absolute paths with whitespace
if not VT100:
zsl = []
@@ -1705,6 +1741,9 @@ NOTE: if server has --usernames enabled, then password is "username:password"
with open(zs[1:], "rb") as f:
setattr(ar, k, f.read().decode("utf-8").strip())
if not ar.a:
ar.a = os.environ.get("U2C_PW", "")
if ar.ba:
ar.ba = "Basic " + base64.b64encode(ar.ba.encode("utf-8")).decode("utf-8")
+4
View File
@@ -17,8 +17,12 @@ defaults
listen foo1
bind *:8081
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-request add-header X-Forwarded-Proto http unless { ssl_fc }
server srv1 127.0.0.1:3923 maxconn 512
listen foo2
bind *:8082
http-request add-header X-Forwarded-Proto https if { ssl_fc }
http-request add-header X-Forwarded-Proto http unless { ssl_fc }
server srv1 /dev/shm/party.sock maxconn 512
+1 -2
View File
@@ -75,9 +75,8 @@ server {
# recommendation: replace cpp_tcp with cpp_uds below
proxy_pass http://cpp_tcp;
proxy_redirect off;
# disable buffering (next 4 lines)
# disable buffering (next 3 lines)
proxy_http_version 1.1;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
# improve download speed from 600 to 1500 MiB/s
+1
View File
@@ -285,6 +285,7 @@ in
environment = {
PYTHONUNBUFFERED = "true";
NIX_SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
XDG_CONFIG_HOME = externalStateDir;
};
+9
View File
@@ -9,10 +9,19 @@
# you may want to:
# change '/usr/bin/python' to another interpreter
# change '/mnt::rw' to another location or permission-set
# use a config file instead of command arguments, e.g.:
# command_args="-c /etc/copyparty.conf"
name="$SVCNAME"
command_background=true
extra_commands="checkconfig"
pidfile="/var/run/$SVCNAME.pid"
command="/usr/bin/python3 /usr/local/bin/copyparty-sfx.py"
command_args="-q -v /mnt::rw"
checkconfig() {
ebegin "Checking $RC_SVCNAME configuration"
$command $command_args --exit cfg
eend $?
}
+11 -13
View File
@@ -3,7 +3,7 @@
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty
pkgver="1.20.1"
pkgver="1.20.26"
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@@ -13,23 +13,22 @@ depends=("bash" "python" "lsof" "python-jinja")
makedepends=("python-wheel" "python-setuptools" "python-build" "python-installer" "make" "pigz")
optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tags"
"cfssl: generate TLS certificates on startup"
"python-mutagen: music tags (alternative)"
"python-paramiko: sftp server",
"python-mutagen: music tags (fast/inaccurate)"
"python-pillow: thumbnails for images"
"python-pyvips: thumbnails for images (higher quality, faster, uses more ram)"
"python-pyvips: thumbnails for images (eats ram, prefer pillow)"
"python-paramiko: sftp server"
"python-pyftpdlib: ftp server"
"python-pyopenssl: ftps server (needs pyftpdlib too)"
"libkeyfinder: detection of musical keys"
"python-pyopenssl: ftps functionality"
"python-pyzmq: send zeromq messages from event-hooks"
"python-argon2-cffi: hashed passwords in config"
)
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("etc/${pkgname}/copyparty.conf" )
sha256sums=("4f513ca9e3d1c11a7bb4e1a8a925dda2449b9565e91f6ef7cbe10367fa4e2935")
backup=("etc/copyparty.conf")
install=copyparty.install
sha256sums=("ac7ea05bde8d431b32cc2ff618f2754e46bd967bf10deaa6654dac9a449a6ff9")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
make
cd "${srcdir}/${pkgname}-${pkgver}"
python -m build --wheel --no-isolation
}
@@ -38,12 +37,11 @@ package() {
cd "${srcdir}/${pkgname}-${pkgver}"
python -m installer --destdir="$pkgdir" dist/*.whl
install -dm755 "${pkgdir}/etc/${pkgname}"
install -Dm755 "bin/prisonparty.sh" "${pkgdir}/usr/bin/prisonparty"
install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/${pkgname}/copyparty.conf"
install -Dm644 "contrib/systemd/${pkgname}.conf" "${pkgdir}/etc/copyparty.conf"
install -Dm644 "contrib/systemd/${pkgname}@.service" "${pkgdir}/usr/lib/systemd/system/${pkgname}@.service"
install -Dm644 "contrib/systemd/${pkgname}-user.service" "${pkgdir}/usr/lib/systemd/user/${pkgname}.service"
install -Dm644 "contrib/systemd/prisonparty@.service" "${pkgdir}/usr/lib/systemd/system/prisonparty@.service"
install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/README.md"
install -Dm644 "contrib/systemd/index.md" "${pkgdir}/var/lib/${pkgname}-jail/srv/README.md"
install -Dm644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
+27
View File
@@ -0,0 +1,27 @@
post_upgrade() {
# v1.20.22 moved the config-file, affecting the system-service only
if [[ $(vercmp "$2" 1.20.22) -lt 0 ]]; then
oldpath="/etc/copyparty/copyparty.conf"
newpath="/etc/copyparty.conf"
echo "==> Migrating config-file from $oldpath to $newpath"
if [[ ! -f "$oldpath" ]]; then
oldpath="$oldpath.pacsave"
fi
if [[ -f "$newpath.pacnew" ]]; then
echo "==> WARNING: A config-file already existed in the new location [$newpath]. The existing file will be used as-is."
elif [[ -f "$oldpath" ]]; then
mv -v "$oldpath" "$newpath"
rmdir /etc/copyparty 2>/dev/null || true
fi
systemctl cat copyparty@.service | awk '/=PRTY_CONFIG=/ && !/=\/etc\/copyparty.conf/ {exit 1}' || {
echo "==> WARNING: The copyparty system-service has been edited by hand since installation, so it will not read config from the new default config path [$newpath]. Please use PRTY_CONFIG=$newpath if that is correct for your setup."
}
fi
}
+2 -5
View File
@@ -2,7 +2,7 @@
pkgname=copyparty
pkgver=1.20.1
pkgver=1.20.26
pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any")
@@ -21,12 +21,9 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
)
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("/etc/${pkgname}.d/init" )
sha256sums=("4f513ca9e3d1c11a7bb4e1a8a925dda2449b9565e91f6ef7cbe10367fa4e2935")
sha256sums=("ac7ea05bde8d431b32cc2ff618f2754e46bd967bf10deaa6654dac9a449a6ff9")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
make
cd "${srcdir}/${pkgname}-${pkgver}"
python -m build --wheel --no-isolation
}
@@ -15,7 +15,7 @@ Description=copyparty file server
Type=notify
SyslogIdentifier=copyparty
Environment=PYTHONUNBUFFERED=x
WorkingDirectory=/var/lib/copyparty-jail
WorkingDirectory=/var/lib/copyparty-jail/srv
ExecReload=/bin/kill -s USR1 $MAINPID
# user to run as + where the TLS certificate is (if any)
+12 -2
View File
@@ -1,3 +1,13 @@
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
this is `/var/lib/copyparty-jail/srv/`, the fallback webroot when copyparty has not yet been configured
please add some `*.conf` files to `/etc/copyparty.d/`
please edit the config-file and restart copyparty;
* if running as a system service: `vim /etc/copyparty.conf`
* if running as a user service: `vim $HOME/.config/copyparty/copyparty.conf`
some inspiration:
* a basic example config: https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
* another example with focus on syntax: https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
* and CTRL-F `config file example` here: https://github.com/9001/copyparty
the full list of configuration options can be seen at https://copyparty.eu/cli/
or by running `copyparty --help`
@@ -17,7 +17,7 @@ Description=copyparty file server
[Service]
SyslogIdentifier=prisonparty
Environment=PYTHONUNBUFFERED=x
WorkingDirectory=/var/lib/copyparty-jail
WorkingDirectory=/var/lib/copyparty-jail/srv
ExecReload=/bin/kill -s USR1 $MAINPID
# stop systemd-tmpfiles-clean.timer from deleting copyparty while it's running
+37 -28
View File
@@ -30,7 +30,7 @@
# create thumbnails with Pillow; faster than FFmpeg / MediaProcessing
withThumbnails ? true,
# create thumbnails with PyVIPS; even faster, uses more memory
# create thumbnails with PyVIPS; uses more memory, pillow is preferred
# -- can be combined with Pillow to support more filetypes
withFastThumbnails ? false,
@@ -91,19 +91,20 @@ let
day = builtins.substring 6 2 lastModifiedDate;
};
# ex: "1970" "1" "1"
dateStringsShort = builtins.mapAttrs (_: val: toString (lib.toIntBase10 val)) dateStringsZeroPrefixed;
dateStringsShort = builtins.mapAttrs (
_: val: toString (lib.toIntBase10 val)
) dateStringsZeroPrefixed;
unstableVersion =
if copypartyFlake == null then
"${pinData.version}-unstable"
else
with dateStringsZeroPrefixed; "${pinData.version}-unstable-${year}-${month}-${day}"
;
with dateStringsZeroPrefixed; "${pinData.version}-unstable-${year}-${month}-${day}";
version = if stable then pinData.version else unstableVersion;
stableSrc = fetchurl {
inherit (pinData) url hash;
};
root = ../../../..;
unstableSrc = nix-gitignore.gitignoreSource [] root;
unstableSrc = nix-gitignore.gitignoreSource [ ] root;
src = if stable then stableSrc else unstableSrc;
rev = copypartyFlake.shortRev or copypartyFlake.dirtyShortRev or "unknown";
unstableCodename = "unstable" + (lib.optionalString (copypartyFlake != null) "-${rev}");
@@ -125,30 +126,38 @@ buildPythonApplication {
old_src_folder="''${folders[0]}"
cp -r "$old_src_folder"/copyparty/web/deps copyparty/web/deps
sed -i 's/^CODENAME =.*$/CODENAME = "${unstableCodename}"/' copyparty/__version__.py
${lib.optionalString (copypartyFlake != null) (with dateStringsShort; ''
sed -i 's/^BUILD_DT =.*$/BUILD_DT = (${year}, ${month}, ${day})/' copyparty/__version__.py
'')}
${lib.optionalString (copypartyFlake != null) (
with dateStringsShort;
''
sed -i 's/^BUILD_DT =.*$/BUILD_DT = (${year}, ${month}, ${day})/' copyparty/__version__.py
''
)}
'';
dependencies =
[
jinja2
fusepy
]
++ lib.optional withSMB impacket
++ lib.optional withSFTP paramiko
++ lib.optional withFTP pyftpdlib
++ lib.optional withFTPS pyopenssl
++ lib.optional withTFTP partftpy
++ lib.optional withCertgen cfssl
++ lib.optional withThumbnails pillow
++ lib.optional withFastThumbnails pyvips
++ lib.optional withMediaProcessing ffmpeg
++ lib.optional withBasicAudioMetadata mutagen
++ lib.optional withHashedPasswords argon2-cffi
++ lib.optional withZeroMQ pyzmq
++ lib.optional withMagic magic
++ (extraPythonPackages python.pkgs);
makeWrapperArgs = [ "--prefix PATH : ${lib.makeBinPath runtimeDeps}" ];
dependencies = [
jinja2
fusepy
]
++ lib.optional withSMB impacket
++ lib.optional withSFTP paramiko
++ lib.optional withFTP pyftpdlib
++ lib.optional withFTPS pyopenssl
++ lib.optional withTFTP partftpy
++ lib.optional withCertgen cfssl
++ lib.optional withThumbnails pillow
++ lib.optional withFastThumbnails pyvips
++ lib.optional withMediaProcessing ffmpeg
++ lib.optional withBasicAudioMetadata mutagen
++ lib.optional withHashedPasswords argon2-cffi
++ lib.optional withZeroMQ pyzmq
++ lib.optional withMagic magic
++ (extraPythonPackages python.pkgs);
makeWrapperArgs = [
"--prefix"
"PATH"
":"
(lib.makeBinPath runtimeDeps)
];
pyproject = true;
build-system = [
+3 -3
View File
@@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.20.1/copyparty-1.20.1.tar.gz",
"version": "1.20.1",
"hash": "sha256-T1E8qePRwRp7tOGoqSXdokSblWXpH273y+EDZ/pOKTU="
"url": "https://github.com/9001/copyparty/releases/download/v1.20.26/copyparty-1.20.26.tar.gz",
"version": "1.20.26",
"hash": "sha256-rH6gW96NQxsyzC/2GPJ1Tka9lnvxDeqmZU2smkSab/k="
}
+1 -1
View File
@@ -16,7 +16,7 @@ let
withMagic = true;
};
call = attrs: final.python3.pkgs.callPackage ./copyparty ({ ffmpeg = final.ffmpeg-full; } // attrs);
call = attrs: final.python3.pkgs.callPackage ./copyparty ({ ffmpeg = final.ffmpeg-headless; } // attrs);
in
{
copyparty = call { stable = true; };
-3
View File
@@ -26,9 +26,6 @@ See release at https://github.com/9001/copyparty/releases
%setup -q
%build
cd "copyparty/web"
make
cd -
%pyproject_wheel
%install
+1 -1
View File
@@ -36,7 +36,7 @@
for (var a = 0; a < files.length; a++) {
var file = files[a],
is_pic = /\.(jpe?g|png|gif|webp)$/i.exec(file.vp),
is_pic = /\.(jpe?g|png|gif|webp|jxl)$/i.exec(file.vp),
is_audio = re_au_all.exec(file.vp),
basename = file.vp.replace(/\.[^\.]+$/, ""),
entry = pairs[basename];
+4 -1
View File
@@ -66,11 +66,14 @@ var u2min = `
</style>
<a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>
<a href="#" id="u2min_off">show advanced options</a>
`;
if (!has(perms, 'read')) {
var e2 = mknod('div');
e2.innerHTML = u2min;
ebi('wrap').insertBefore(e2, QS('#wfp'));
ebi('u2min_off').onclick = function () {
this.parentNode.innerHTML='';
};
}
+9 -1
View File
@@ -8,7 +8,7 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
# q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
# q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
@@ -22,6 +22,12 @@
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
# no-robots, force-js # make it harder for search engines to read your server
# enable version-checking by uncommenting one of the vc-url lines below;
# shows a warning-banner in the controlpanel if your version has a known vulnerability
#vc-url: https://api.github.com/repos/9001/copyparty/security-advisories?per_page=9
#vc-url: https://api.copyparty.eu/advisories
vc-exit # panic and shutdown instead of just showing the warning
[accounts]
ed: wark # username: password
@@ -32,5 +38,7 @@
accs:
rw: * # everyone gets read-write access, but
rwmda: ed # the user "ed" gets read-write-move-delete-admin
flags:
e2ds # enable filesystem-scanning for this volume only
# uid: 1000 # If you're running as root, you can change the owner of this volume here
# gid: 1000 # If you're running as root, you can change the group of this volume here
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# PROVIDE: copyparty
# REQUIRE: networking
# REQUIRE: networking DAEMON FILESYSTEMS mountd
# KEYWORD:
. /etc/rc.subr
+3 -3
View File
@@ -1,5 +1,5 @@
# this will start `/usr/bin/copyparty`
# and read config from `$HOME/.config/copyparty.conf`
# and read config from `$HOME/.config/copyparty/copyparty.conf`
#
# unless you add -q to disable logging, you may want to remove the
# following line to allow buffering (slightly better performance):
@@ -11,13 +11,13 @@ Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
WorkingDirectory=/var/lib/copyparty-jail
WorkingDirectory=/var/lib/copyparty-jail/srv
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=%h/.config/copyparty/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID
# ensure there is a config
ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp /etc/copyparty/copyparty.conf %h/.config/copyparty/copyparty.conf; fi'
ExecStartPre=/bin/bash -c 'if [[ ! -f %h/.config/copyparty/copyparty.conf ]]; then mkdir -p %h/.config/copyparty; cp -v /etc/copyparty.conf %h/.config/copyparty/; fi'
# run copyparty
ExecStart=/usr/bin/python3 /usr/bin/copyparty
+13 -11
View File
@@ -1,13 +1,15 @@
[global]
i: 127.0.0.1
[accounts]
user: password
[/]
/var/lib/copyparty-jail
/var/lib/copyparty-jail/srv/
accs:
r: *
rwdma: user
flags:
grid
r: * # everyone can [r]ead (browse/download)
# above is a very simple config;
# the webroot is /var/lib/copyparty-jail/srv/
# readable by everyone, no login required
#
# you'll want to replace this file; some inspiration:
# - a basic example config: https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
# - another example with focus on syntax: https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
# - and CTRL-F "config file example" here: https://github.com/9001/copyparty
# - full list of options: https://copyparty.eu/cli/
+12 -4
View File
@@ -3,9 +3,6 @@
# vim: ft=yaml:
# put this file in /etc/
[global]
e2dsa # enable file indexing and filesystem scanning
e2ts # and enable multimedia indexing
@@ -16,7 +13,16 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# enable version-checker by uncommenting one of the 'vc-url' lines below; this will
# periodically check if your copyparty version has a known security vulnerability,
# showing a warning on /?h (control-panel) for all users with permission 'a' or 'A'
#vc-url: https://api.github.com/repos/9001/copyparty/security-advisories?per_page=9
#vc-url: https://api.copyparty.eu/advisories
vc-age: 3 # how many hours to wait between each version-check
vc-exit # emergency-exit if a version-check indicates that the current version is vulnerable
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
@@ -40,3 +46,5 @@
accs:
rw: * # everyone gets read-write access, but
rwmda: ed # the user "ed" gets read-write-move-delete-admin
flags:
e2ds # enable filesystem-scanning for this volume only
+3 -3
View File
@@ -1,5 +1,5 @@
# this will start `/usr/bin/copyparty`
# and read config from `/etc/copyparty/copyparty.conf`
# and read config from `/etc/copyparty.conf`
#
# the %i refers to whatever you put after the copyparty@
# so with copyparty@foo.service, %i == foo
@@ -14,9 +14,9 @@ Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
WorkingDirectory=/var/lib/copyparty-jail
WorkingDirectory=/var/lib/copyparty-jail/srv
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
Environment=PRTY_CONFIG=/etc/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID
# user to run as + where the TLS certificate is (if any)
+9 -6
View File
@@ -1,10 +1,13 @@
this is `/var/lib/copyparty-jail`, the fallback webroot when copyparty has not yet been configured
this is `/var/lib/copyparty-jail/srv/`, the fallback webroot when copyparty has not yet been configured
please edit `/etc/copyparty/copyparty.conf` (if running as a system service)
or `$HOME/.config/copyparty/copyparty.conf` if running as a user service
please edit the config-file and restart copyparty;
* if running as a system service: `vim /etc/copyparty.conf`
* if running as a user service: `vim $HOME/.config/copyparty/copyparty.conf`
a basic configuration example is available at https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
a configuration example that explains most flags is available at https://github.com/9001/copyparty/blob/hovudstraum/docs/chungus.conf
some inspiration:
* a basic example config: https://github.com/9001/copyparty/blob/hovudstraum/contrib/systemd/copyparty.example.conf
* another example with focus on syntax: https://github.com/9001/copyparty/blob/hovudstraum/docs/example.conf
* and CTRL-F `config file example` here: https://github.com/9001/copyparty
the full list of configuration options can be seen at https://ocv.me/copyparty/helptext.html
the full list of configuration options can be seen at https://copyparty.eu/cli/
or by running `copyparty --help`
+3 -3
View File
@@ -1,6 +1,6 @@
# this will start `/usr/bin/copyparty`
# in a chroot, preventing accidental access elsewhere,
# and read copyparty config from `/etc/copyparty/copyparty.conf`
# and read copyparty config from `/etc/copyparty.conf`
#
# expose additional filesystem locations to copyparty
# by listing them between the last `%i` and `--`
@@ -19,9 +19,9 @@ Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=prisonparty
WorkingDirectory=/var/lib/copyparty-jail
WorkingDirectory=/var/lib/copyparty-jail/srv
Environment=PYTHONUNBUFFERED=x
Environment=PRTY_CONFIG=/etc/copyparty/copyparty.conf
Environment=PRTY_CONFIG=/etc/copyparty.conf
ExecReload=/bin/kill -s USR1 $MAINPID
# user to run as + where the TLS certificate is (if any)
@@ -0,0 +1,36 @@
# docker-compose.yml for copyparty
services:
copyparty:
image: copyparty/ac:latest
container_name: copyparty
user: "1000:1000"
volumes:
- ./:/cfg:z
- /path/to/your/fileshare/top/folder:/w:z
environment:
LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
PYTHONUNBUFFERED: 1
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
stop_grace_period: 15s # thumbnailer is allowed to continue finishing up for 10s after the shutdown signal
healthcheck:
# hide it from logs with "/._" so it matches the default --lf-url filter
test: ["CMD-SHELL", "wget --spider -q 127.0.0.1:3923/?reset=/._"]
interval: 1m
timeout: 2s
retries: 5
start_period: 15s
networks:
- traefik_default
labels:
- traefik.http.routers.copyparty.rule=Host(`<DOMAIN>`)
- traefik.http.services.copyparty.loadbalancer.server.port=3923
- traefik.http.routers.copyparty.tls.certresolver=le
- traefik.http.routers.copyparty.entrypoints=websecure
networks:
traefik_default:
external: true
@@ -0,0 +1,43 @@
# note: copied from copyparty/docs/examples/docker/basic-docker-compose/copyparty.conf
# not actually YAML but lets pretend:
# -*- mode: yaml -*-
# vim: ft=yaml:
[global]
e2dsa # enable file indexing and filesystem scanning
e2ts # enable multimedia indexing
ansi # enable colors in log messages (both in logfiles and stdout)
# q, lo: /cfg/log/%Y-%m%d.log # log to file instead of docker
# p: 3939 # listen on another port
# ipa: 10.89. # only allow connections from 10.89.*
# ipa: 172.16.4.0/23 # ...or only 172.16.4.* and 172.16.5.*
# ipa: lan # ...or allow LAN only; reject internet IPs
# df: 16 # stop accepting uploads if less than 16 GB free disk space
# ver # show copyparty version in the controlpanel
# grid # show thumbnails/grid-view by default
# theme: 2 # monokai
# name: datasaver # change the server-name that's displayed in the browser
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
# no-robots, force-js # make it harder for search engines to read your server
# enable version-checking by uncommenting one of the vc-url lines below;
# shows a warning-banner in the controlpanel if your version has a known vulnerability
#vc-url: https://api.github.com/repos/9001/copyparty/security-advisories?per_page=9
#vc-url: https://api.copyparty.eu/advisories
vc-exit # panic and shutdown instead of just showing the warning
[accounts]
ed: wark # username: password
[/] # create a volume at "/" (the webroot), which will
/w # share /w (the docker data volume)
accs:
rw: * # everyone gets read-write access, but
rwmda: ed # the user "ed" gets read-write-move-delete-admin
flags:
e2ds # enable filesystem-scanning for this volume only
@@ -0,0 +1,34 @@
# docker-compose.yml for traefik
services:
traefik:
image: traefik:latest
command:
- --api.insecure=false
- --api.dashboard=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entryPoints.websecure.http.tls=true
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.le.acme.email=<your email here>
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt
labels:
# OPTIONAL: host your traefik dashboard on <DOMAIN>
# - traefik.http.routers.traefik-dash.rule=Host(`<DOMAIN>`)
# - traefik.http.routers.traefik-dash.entrypoints=websecure
# - traefik.http.routers.traefik-dash.service=api@internal
# - traefik.http.routers.traefik-dash.tls.certresolver=le
# - traefik.http.routers.traefik-dash.middlewares=traefik-dash-auth,traefik-dash-redirect
# - traefik.http.middlewares.traefik-dash-auth.basicauth.users=<USERNAME>:<HTPASSWD_ENCODED_PASSWD>
# - traefik.http.middlewares.traefik-dash-redirect.redirectregex.regex=^https://<SUBDOMAIN>\.<DOMAIN>\.TLD/?$
# - traefik.http.middlewares.traefik-dash-redirect.redirectregex.replacement=https://<DOMAIN>/dashboard/
networks: {}
+18 -3
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
import platform
@@ -40,10 +40,23 @@ VT100 = "--ansi" in sys.argv or (
)
# introduced in anniversary update
zs = os.environ.get("PRTY_CONFIG", "")
CFG_DEF = [zs] if zs else []
ANYWIN = WINDOWS or sys.platform in ["msys", "cygwin"]
MACOS = platform.system() == "Darwin"
FREEBSD = platform.system() == "FreeBSD"
OPENBSD = platform.system() == "OpenBSD"
ANYBSD = FREEBSD or OPENBSD
UNIX = MACOS or ANYBSD
GRAAL = platform.python_implementation() == "GraalVM"
EXE = bool(getattr(sys, "frozen", False))
try:
@@ -67,9 +80,10 @@ web/deps/busy.mp3
web/deps/easymde.css
web/deps/easymde.js
web/deps/marked.js
web/deps/fuse.py
web/deps/mfusepy.py
web/deps/mini-fa.css
web/deps/mini-fa.woff
web/deps/orbitron.woff2
web/deps/prism.css
web/deps/prism.js
web/deps/prismd.css
@@ -106,6 +120,7 @@ web/tl/epo.js
web/tl/fin.js
web/tl/fra.js
web/tl/grc.js
web/tl/hun.js
web/tl/ita.js
web/tl/jpn.js
web/tl/kor.js
@@ -123,7 +138,7 @@ web/tl/vie.js
web/ui.css
web/up2k.js
web/util.js
web/w.hash.js
web/wopi.html
"""
RES = set(zs.strip().split("\n"))
RESM = {
+455 -79
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
"""copyparty: http file sharing hub (py2/py3)"""
__author__ = "ed <copyparty@ocv.me>"
@@ -14,6 +14,7 @@ import locale
import os
import re
import select
import shutil
import socket
import sys
import threading
@@ -23,11 +24,13 @@ import uuid
from .__init__ import (
ANYWIN,
CFG_DEF,
CORES,
EXE,
MACOS,
PY2,
PY36,
UNIX,
VT100,
WINDOWS,
E,
@@ -37,7 +40,10 @@ from .__init__ import (
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt
from .bos import bos
from .cert import NO_TLS
from .cfg import flagcats, onedash
from .mdns import DNS_VND
from .qrkode import VENDORED as QR_VND
from .svchub import SvcHub
from .util import (
APPLESAN_TXT,
@@ -45,7 +51,10 @@ from .util import (
DEF_EXP,
DEF_MTE,
DEF_MTH,
HAVE_BWRAP,
HAVE_IPV6,
HAVE_MAGIC,
IFADDR_VND,
IMPLICATIONS,
JINJA_VER,
MIKO_VER,
@@ -61,15 +70,25 @@ from .util import (
URL_BUG,
URL_PRJ,
Daemon,
absreal,
align_tab,
b64enc,
ctypes,
dedent,
expand_osenv_c,
expand_osenv_cs,
expand_osenv_noop,
expand_osenv_s,
has_resource,
list_ips,
list_nics,
load_resource,
lprint,
min_ex,
pybin,
read_utf8,
termsize,
wk32,
wrap,
)
@@ -83,21 +102,19 @@ if PY2:
range = xrange # type: ignore
try:
if os.environ.get("PRTY_NO_TLS"):
if NO_TLS:
raise Exception()
HAVE_SSL = True
import ssl
HAVE_SSL = True
except:
HAVE_SSL = False
u = unicode
printed: list[str] = []
zsid = uuid.uuid4().urn[4:]
CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
if not CFG_DEF[0]:
CFG_DEF.pop()
FULL_HELP = os.environ.get("PRTY_FULL_HELP", "")
class RiceFormatter(argparse.HelpFormatter):
@@ -168,16 +185,6 @@ class BasicDodge11874(
super(BasicDodge11874, self).__init__(*args, **kwargs)
def lprint(*a: Any, **ka: Any) -> None:
eol = ka.pop("end", "\n")
txt: str = " ".join(unicode(x) for x in a) + eol
printed.append(txt)
if not VT100:
txt = RE_ANSI.sub("", txt)
print(txt, end="", **ka)
def warn(msg: str) -> None:
lprint("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
@@ -425,9 +432,22 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
sys.exit(0)
def expand_cvars(argv) -> list[str]:
n = 0
for v in argv:
if "=" in v:
a, b = v.split("=", 1)
v = "%s=%s" % (a, os.path.expanduser(expand_osenv_c(b)))
else:
v = os.path.expanduser(expand_osenv_c(v))
argv[n] = v
n += 1
return argv
def args_from_cfg(cfg_path: str) -> list[str]:
lines: list[str] = []
expand_config_file(None, lines, cfg_path, "")
expand_config_file(None, expand_osenv_c, lines, cfg_path, "")
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
ret: list[str] = []
@@ -451,10 +471,12 @@ def args_from_cfg(cfg_path: str) -> list[str]:
else:
ret.append(prefix + k + "=" + v)
return ret
return expand_cvars(ret)
def expand_cfg(argv) -> list[str]:
argv = expand_cvars(argv)
if CFG_DEF:
supp = args_from_cfg(CFG_DEF[0])
argv = argv[:1] + supp + argv[1:]
@@ -477,6 +499,21 @@ def expand_cfg(argv) -> list[str]:
return argv
def quotecheck(al):
for zs1, zco in vars(al).items():
zsl = [u(x) for x in zco] if isinstance(zco, list) else [u(zco)]
for zs2 in zsl:
zs2 = zs2.strip()
zs3 = zs2.strip("\"'")
if zs2 == zs3 or len(zs2) - len(zs3) < 2:
continue
if al.c:
t = "found the following global-config: %s: %s\n values should not be quoted; did you mean: %s: %s"
else:
t = 'found the following config-option: "--%s=%s"\n values should not be quoted; did you mean: "--%s=%s"'
warn(t % (zs1, zs2, zs1, zs3))
def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) -> None:
msg = [""] * 5
for th in threading.enumerate():
@@ -489,8 +526,10 @@ def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) ->
def disable_quickedit() -> None:
if not ctypes:
raise Exception("no ctypes")
import atexit
import ctypes
from ctypes import wintypes
def ecb(ok: bool, fun: Any, args: list[Any]) -> list[Any]:
@@ -500,10 +539,10 @@ def disable_quickedit() -> None:
raise ctypes.WinError(err) # type: ignore
return args
k32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
if PY2:
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
k32 = wk32
k32.GetStdHandle.errcheck = ecb # type: ignore
k32.GetConsoleMode.errcheck = ecb # type: ignore
k32.SetConsoleMode.errcheck = ecb # type: ignore
@@ -552,6 +591,17 @@ def sfx_tpoke(top: str):
lprint("<TPOKE> [%s] %r" % (f, ex))
files.remove(f)
r = os.path.dirname(top)
for d in os.listdir(r):
try:
p = r + "/" + d
f = p + "/copyparty/up2k.py"
t2 = os.path.getmtime(f)
if t - t2 > 777777 and d.startswith("pe-copyparty"):
shutil.rmtree(p)
except:
pass
time.sleep(78123)
@@ -562,12 +612,64 @@ def showlic() -> None:
except:
buf = b""
if buf:
print(buf.decode("utf-8", "replace"))
else:
if not buf:
print("no relevant license info to display")
return
lic = buf.decode("utf-8", "replace")
try:
import site
from importlib import import_module
from inspect import getsourcefile
bases = site.PREFIXES + [site.USER_BASE]
bases = [x.replace(os.sep, "/") + "/" for x in list(set(bases)) if x]
except:
bases = []
# remove missing and/or non-vendored
for url, zi, zx in (
("/dnslib/", 1, DNS_VND),
("/ifaddr/", 1, IFADDR_VND),
("/nayuki/QR", 1, QR_VND),
("/partftpy", 2, PARTFTPY_VER),
("/jinja/", 3, "jinja2"),
("/markupsafe/", 3, "jinja2"), # jinja2 sic
("/pyftpdlib/", 2, PYFTPD_VER), # heed PRTY_NO
("/pyftpdlib/", 3, "pyftpdlib"),
("/python-magic/", 1, HAVE_MAGIC), # heed PRTY_NO
("/python-magic/", 3, "magic"),
):
if url not in lic:
continue
elif zi == 1:
if zx:
continue
elif zi == 2:
if zx != "(None)":
continue
elif zi == 3:
mod = None
try:
assert bases and getsourcefile and import_module # type: ignore # !rm
mod = import_module(zx) # type: ignore
mp = getsourcefile(mod) or ""
for zs in bases:
if mp.startswith(zs):
mp = ""
if mp:
continue
except:
if mod:
continue
zs1, zs2 = lic.split(url, 1)
zs1 = zs1.rsplit("\n", 1)[0]
zs2 = zs2.split("\n\n", 1)[1]
lic = zs1 + "\n" + zs2
print(lic)
def get_sects():
return [
@@ -585,6 +687,7 @@ def get_sects():
\033[32m-i 127.0.0.1\033[0m listens on IPv4 localhost only
\033[32m-i 127.1\033[0m listens on IPv4 localhost only
\033[32m-i 127.1,192.168.123.1\033[0m = IPv4 localhost and 192.168.123.1
\033[32m-i no\033[0m disable webserver
\033[33m-p\033[0m takes a comma-separated list of tcp ports to listen on;
the default is \033[32m-p 3923\033[0m but as root you can \033[32m-p 80,443,3923\033[0m
@@ -606,6 +709,9 @@ def get_sects():
\033[32m-i fd:\033[33m3\033[0m uses the socket passed to copyparty on file descriptor 3
\033[33m-p\033[0m (tcp ports) is ignored for unix-sockets and FDs
\033[33m--list-nics\033[0m shows all network adapters (also offline ones);
\033[33m--list-ips\033[0m shows all LAN IPs
"""
),
],
@@ -742,6 +848,122 @@ def get_sects():
).rstrip()
+ build_flags_desc(),
],
[
"dedup",
"file deduplication on upload",
dedent(
"""
when just \033[36m--dedup\033[0m or volflag "\033[36mdedup\033[0m" is enabled, files will be
deduplicated by means of \033[1msymlinks,\033[0m which demand some caution:
the available dedup approaches:
* symlinks work on most filesystems, but if another software
moves either the symlink itself or the file that it points to,
the link will break and the file "disappears";
only safe if only copyparty is used to rename/move/delete
* hardlinks work on Linux filesystems and \033[1mlook\033[0m like regular files,
except if the contents of one copy of that file is edited, then
this will affect \033[1mall the dupes too!\033[0m
and they share the same last-modified-time
* reflinks only work on btrfs/zfs/xfs but is the safest/best choice;
deduplicated files behave just like regular files,
and can be modified without affecting other copies/dupes
to change approach, add one of the following in addition to "\033[36mdedup\033[0m":
\033[36m--reflink\033[0m or volflag "\033[36mreflink\033[0m"
if reflink is not possible (unsupp/cross-fs), a full copy is made
\033[36m--hardlink-only\033[0m or volflag "\033[36mhardlinkonly\033[0m"
if hardlink is not possible (unsupp/cross-fs), a full copy is made
\033[36m--hardlink\033[0m or volflag "\033[36mhardlink\033[0m"
if hardlink is not possible (unsupp/cross-fs), a symlink is made
note: dedup works best when combined with e2ds or e2dsa
to deduplicate existing files on-disk, see \033[36m--help-redup\033[0m
"""
),
],
[
"redup",
"deduplicating existing files on disk",
dedent(
"""
\033[36m--redup\033[0m can convert on-disk files between deduplication-approaches,
for example turning redundant full copies into reflinks to save space
converts \033[1mall files\033[0m in each volume! not just copyparty-created ones
example: --redup \033[32mno,sym,hard\033[0m=\033[35mref\033[0m will find all files of type
\033[32mfullcopy/symlink/hardlink\033[0m, and convert them into \033[35mreflinks\033[0m
(see \033[36m--help-dedup\033[0m for a summary on the types)
converting \033[1mfrom\033[0m symlinks/hardlinks is fairly fast and simple;
in this case, the obvious target type/approach is reflinks
(which only works on btrfs/zfs/xfs)
converting \033[1mto\033[0m symlinks or hardlinks is \033[3mpossible\033[0m, but
not-recommended due to risks explained in \033[36m--help-dedup\033[0m
(interaction with other softwares can result in data loss)
converting \033[1mfrom\033[0m 'no' (full redundant copies) is hella slow;
checksums each file to avoid corruption from bad RAM
"""
),
],
[
"thumb-ex",
"extract thumbnails from custom file formats with python scripts",
dedent(
"""
copyparty is able to extract thumbnails from various media formats:
images, audio, video, ebooks
if you want to extract thumbnails from unsupported file formats,
you can load a plugin, which extracts image data from custom formats
load the plugin using --args; for example \033[36m
--tx-extract iso,mdf,dmg=~/party-thumb-extractors/th_diskimg.py
\033[0m
the file must define the function \033[35mmain(abspath, **kwargs)\033[0m:
\033[35mabspath\033[0m: path to the file to extract thumbnail from
\033[35mkwargs\033[0m currently have:
\033[35mvn\033[0m: the VFS which contains the requested file
\033[35mrth_srv\033[0m: the copyparty ThumbSrv instance
if `main` couldn't extract a thumbnail, it must return None;
otherwise the return value must be a tuple:
\033[35mreturn fmt, stream, offset, whence, size\033[0m
> \033[32mfmt\033[0m: thumbnail format, should be one of the formats
supported by copyparty; e.g. "png", "jpg"
> \033[32mstream\033[0m: binary file-like object containing the thumbnail,
supporting seek(), read(), and close() methods
> \033[32moffset, whence\033[0m: arguments to seek() method;
stream.seek(offset, whence) will be called once before copyparty
starts reading from the stream
> \033[32msize\033[0m: number of bytes to read ftom the stream;
if negative, copyparty will read through the end of the stream;
you can also supply extractors for standard formats to override default
thumbnail extraction behaviour; for example \033[36m
--tx-extract mp3=~/party-thumb-extractors/th_mp3.py
\033[0mwill let you extract or generate custom thumbnails for mp3 files
\033[1;35mPS!\033[0m the folder that contains the python file should ideally
not contain many other python files, and especially nothing
with filenames that overlap with modules used by copyparty
"""
),
],
[
"handlers",
"use plugins to handle certain events",
@@ -931,6 +1153,7 @@ def get_sects():
values for --urlform:
\033[36mstash\033[35m dumps the data to file and returns length + checksum
\033[36msave,get\033[35m dumps to file and returns the page like a GET
\033[36mget\033[35m ignores the message and returns the page like a GET
\033[36mprint \033[35m prints the data to log and returns an error
\033[36mprint,xm \033[35m prints the data to log and returns --xm output
\033[36mprint,get\033[35m prints the data to log and returns GET\033[0m
@@ -991,6 +1214,28 @@ def get_sects():
"""
),
],
[
"rlo",
"logrotate format",
dedent(
"""
a logrotate-counter is added if the logfile filename is taken;
by default at the end, unless \033[32m%R\033[0m is somewhere in the \033[36m-lo\033[0m pattern,
for example: -lo /var/log/cpp/%Y-%m-%d%R.txt
\033[36m--rlo\033[0m configures the logrotate format; examples:
.1 = when necessary, append a dot followed by a single digit
.1! = counter is always added, even when not necessary
-3 = a hyphen followed by three-digit counter
no = disable counter; append to existing file
del = delete existing logfile and create new
over = overwrite existing logfile
append is not possible for .xz-compressed logfiles;
if logfile is *.xz and rlo is no then rlo will be .1
"""
),
],
[
"ls",
"volume inspection",
@@ -1036,17 +1281,21 @@ def get_sects():
"file/folder permissions",
dedent(
"""
global-option \033[33m--chmod-f\033[0m and volflag \033[33mchmod_f\033[0m specifies the unix-permission to use when creating a new file
global-option \033[33m--chmod-f\033[0m and volflag "\033[33mchmod_f\033[0m" specifies the unix-permission to use when creating a new file
similarly, \033[33m--chmod-d\033[0m and \033[33mchmod_d\033[0m sets the directory/folder perm
similarly, \033[33m--chmod-d\033[0m and "\033[33mchmod_d\033[0m" sets the directory/folder perm
the value is a three-digit octal number such as \033[32m755\033[0m, \033[32m750\033[0m, \033[32m644\033[0m, etc.
the value is a three- or four-digit octal number such as \033[32m755\033[0m, \033[32m0644\033[0m, \033[32m2750\033[0m, etc.
first digit = "User"; permission for the unix-user
second digit = "Group"; permission for the unix-group
third digit = "Other"; permission for all other users/groups
if 3 digits: User, Group, Other
if 4 digits: Special, User, Group, Other
for files:
"Special" digit (sum of the following):
\033[32m1\033[0m = sticky (files: n/a; dirs: files in directory can be deleted only by their owners)
\033[32m2\033[0m = setgid (files: run executable as file group; dirs: files inherit group from directory)
\033[32m4\033[0m = setuid (files: run executable as file owner; dirs: n/a)
"User", "Group", "Other" digits for files:
\033[32m0\033[0m = \033[35m---\033[0m = no access
\033[32m1\033[0m = \033[35m--x\033[0m = can execute the file as a program
\033[32m2\033[0m = \033[35m-w-\033[0m = can write
@@ -1056,7 +1305,7 @@ def get_sects():
\033[32m6\033[0m = \033[35mrw-\033[0m = can read and write
\033[32m7\033[0m = \033[35mrwx\033[0m = can read, write, execute
for directories/folders:
"User", "Group", "Other" digits for directories/folders:
\033[32m0\033[0m = \033[35m---\033[0m = no access
\033[32m1\033[0m = \033[35m--x\033[0m = can read files in folder but not list contents
\033[32m2\033[0m = \033[35m-w-\033[0m = n/a
@@ -1174,10 +1423,16 @@ def add_general(ap, nc, srvname):
ap2.add_argument("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)")
ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS)
ap2.add_argument("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\033[0m]")
ap2.add_argument("--env-expand", metavar="N", type=int, default=-1, help="expand environment-variables in config-files? [\033[32m0\033[0m]=no, [\033[32m1\033[0m]=$VAR (old scary syntax), [\033[32m2\033[0m]=${VAR} (new recommended syntax); default is new-syntax with panic if old-syntax is seen")
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
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)")
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best")
ap2.add_argument("--reload-sig", metavar="S", type=u, default=("" if ANYWIN else "USR1"), help="reload server config when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGUSR1\033[0m], [\033[32mUSR1\033[0m], [\033[32m10\033[0m]")
ap2.add_argument("--vc-url", metavar="URL", type=u, default="", help="URL to check for vulnerable versions (default-disabled)")
ap2.add_argument("--vc-age", metavar="HOURS", type=int, default=3, help="how many hours to wait between vulnerability checks")
ap2.add_argument("--vc-sev", metavar="LEVEL", type=u, default="low", help="minimum severity to care about; one of these: \033[32mlow medium high critical\033[0m")
ap2.add_argument("--vc-exit", action="store_true", help="panic and exit if current version is vulnerable")
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
ap2.add_argument("--version", action="store_true", help="show versions and exit")
ap2.add_argument("--versionb", action="store_true", help="show version and exit")
@@ -1195,8 +1450,8 @@ def add_qr(ap, tty):
ap2.add_argument("--qrz", metavar="N", type=int, default=0, help="[\033[32m1\033[0m]=1x, [\033[32m2\033[0m]=2x, [\033[32m0\033[0m]=auto (try [\033[32m2\033[0m] on broken fonts)")
ap2.add_argument("--qr-pin", metavar="N", type=int, default=0, help="sticky/pin the qr-code to always stay on-screen; [\033[32m0\033[0m]=disabled, [\033[32m1\033[0m]=with-url, [\033[32m2\033[0m]=just-qr")
ap2.add_argument("--qr-wait", metavar="SEC", type=float, default=0, help="wait \033[33mSEC\033[0m before printing the qr-code to the log")
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without --qr-pin in case of issues)")
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when --qr-pin is enabled, check for terminal size change every \033[33mSEC\033[0m")
ap2.add_argument("--qr-every", metavar="SEC", type=float, default=0, help="print the qr-code every \033[33mSEC\033[0m (try this with/without \033[33m--qr-pin\033[0m in case of issues)")
ap2.add_argument("--qr-winch", metavar="SEC", type=float, default=0, help="when \033[33m--qr-pin\033[0m is enabled, check for terminal size change every \033[33mSEC\033[0m")
ap2.add_argument("--qr-file", metavar="TXT", type=u, action="append", help="\033[34mREPEATABLE:\033[0m write qr-code to file.\n └─To create txt or svg, \033[33mTXT\033[0m is Filepath:Zoom:Pad, for example [\033[32mqr.txt:1:2\033[0m]\n └─To create png or gif, \033[33mTXT\033[0m is Filepath:Zoom:Pad:Foreground:Background, for example [\033[32mqr.png:8:2:333333:ffcc55\033[0m], or [\033[32mqr.png:8:2::ffcc55\033[0m] for transparent")
ap2.add_argument("--qr-stdout", action="store_true", help="always display the QR-code on STDOUT in the terminal, even if \033[33m-q\033[0m")
ap2.add_argument("--qr-stderr", action="store_true", help="always display the QR-code on STDERR in the terminal, even if \033[33m-q\033[0m")
@@ -1241,16 +1496,18 @@ def add_upload(ap):
ap2.add_argument("--reg-cap", metavar="N", type=int, default=38400, help="max number of uploads to keep in memory when running without \033[33m-e2d\033[0m; roughly 1 MiB RAM per 600")
ap2.add_argument("--no-fpool", action="store_true", help="disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems)")
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--chmod-f", metavar="UGO", type=u, default="", help="unix file permissions to use when creating files; default is probably 644 (OS-decided), see \033[33m--help-chmod\033[0m. Examples: [\033[32m644\033[0m] = owner-RW + all-R, [\033[32m755\033[0m] = owner-RWX + all-RX, [\033[32m777\033[0m] = full-yolo (volflag=chmod_f)")
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see \033[33m--help-chmod\033[0m. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m2750\033[0m] = setgid + owner-RW + group-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)")
ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)")
ap2.add_argument("--wram", action="store_true", help="allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication; see \033[33m--help-dedup\033[0m (volflag=dedup)")
ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
ap2.add_argument("--hardlink-only", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly)")
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)")
ap2.add_argument("--redup", metavar="A[,B]=T", help="convert dedup-type for existing files; \033[33mA,B\033[0m is dedup-types to convert from (no/ref/sym/hard), \033[33mT\033[0m is target type; converting from sym/hard is fast-ish, from no/ref is slooow, to sym/hard is jank; see \033[33m--help-redup\033[0m. Example: [\033[32msym,hard=ref\033[0m] (volflag=redup)")
ap2.add_argument("--redup-dry", action="store_true", help="dry-run; makes \033[33m--redup\033[0m not apply changes (volflag=redup_dry)")
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
ap2.add_argument("--no-dupe-m", action="store_true", help="also reject dupes when moving a file into another volume (volflag=nodupem)")
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)")
@@ -1264,11 +1521,11 @@ def add_upload(ap):
ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
ap2.add_argument("--sparse", metavar="MiB", type=int, default=1, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
ap2.add_argument("--nosubtle", metavar="N", type=int, default=0, help="when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [\033[32m0\033[0m] = only when necessary (non-https), [\033[32m1\033[0m] = always (all browsers), [\033[32m2\033[0m] = always on chrome/firefox, [\033[32m3\033[0m] = always on chrome, [\033[32mN\033[0m] = chrome-version N and newer (recommendation: 137)")
ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed")
ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
ap2.add_argument("--u2sz", metavar="N,N", type=u, default="64,96", help="web-client: default upload chunksize in MiB, either \033[33mtarget,max\033[0m or just \033[33mtarget\033[0m (sets both); this is \033[33mtarget\033[0m in the web-UI. Each HTTP POST will aim for \033[33mtarget\033[0m and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Minimum is [\033[32m1\033[0m] and will be slow. Lowest recommended is [\033[32m8\033[0m]. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32mno\033[0m]")
ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
@@ -1291,9 +1548,9 @@ def add_network(ap):
ap2.add_argument("--cachectl", metavar="TXT", default="no-cache", help="default-value of the 'Cache-Control' response-header (controls caching in webbrowsers). Default prevents repeated downloading of the same file unless necessary (browser will ask copyparty if the file has changed). Examples: [\033[32mmax-age=604869\033[0m] will cache for 7 days, [\033[32mno-store, max-age=0\033[0m] will always redownload. (volflag=cachectl)")
ap2.add_argument("--http-vary", metavar="TXT", type=u, default="Origin, PW, Cookie", help="value of the 'Vary' response-header; a hint for caching proxies")
ap2.add_argument("--http-no-tcp", action="store_true", help="do not listen on TCP/IP for http/https; only listen on unix-domain-sockets")
if ANYWIN:
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
elif not MACOS:
if FULL_HELP or ANYWIN:
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets (windows-only); allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
if FULL_HELP or (not ANYWIN and not MACOS):
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)")
ap2.add_argument("--wr-h-eps", metavar="PATH", type=u, default="", help="write list of listening-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
ap2.add_argument("--wr-h-aon", metavar="PATH", type=u, default="", help="write list of accessible-on ip:port to textfile at \033[33mPATH\033[0m when http-servers have started")
@@ -1304,13 +1561,16 @@ def add_network(ap):
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0.0, help="debug: socket write delay in seconds")
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0.0, help="debug: response delay in seconds")
ap2.add_argument("--rsp-jtr", metavar="SEC", type=float, default=0.0, help="debug: response delay, random duration 0..\033[33mSEC\033[0m")
ap2.add_argument("--list-nics", action="store_true", help="debug: list detected network adapters")
ap2.add_argument("--list-ips", action="store_true", help="debug: list detected LAN IPs")
def add_tls(ap, cert_path):
ap2 = ap.add_argument_group("SSL/TLS options")
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain (if \033[33m--certkey\033[0m is not set), or just the certificate chain (if \033[33m--certkey\033[0m is set)")
ap2.add_argument("--certkey", metavar="PATH", type=u, default="", help="path to file containing just the certificate key; if this is set, then \033[33m--cert\033[0m should only contain the certificate chain")
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")
ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
@@ -1341,7 +1601,7 @@ def add_auth(ap):
ses_db = os.path.join(E.cfg, "sessions.db")
ap2 = ap.add_argument_group("IdP / identity provider / user authentication options")
ap2.add_argument("--idp-h-usr", metavar="HN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mHN\033[0m contains a username to associate the request with (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see --help-idp")
ap2.add_argument("--idp-hm-usr", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m bypass the copyparty authentication checks if the request-header \033[33mT\033[0m is provided, and its value exists in a mapping defined by this option; see \033[33m--help-idp\033[0m")
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
@@ -1353,13 +1613,13 @@ def add_auth(ap):
ap2.add_argument("--idp-login", metavar="L", type=u, default="", help="replace all login-buttons with a link to URL \033[33mL\033[0m (unless \033[32mpw\033[0m is in \033[33m--auth-ord\033[0m then both will be shown); [\033[32m{dst}\033[0m] expands to url of current page")
ap2.add_argument("--idp-login-t", metavar="T", type=u, default="Login with SSO", help="the label/text for the idp-login button")
ap2.add_argument("--idp-logout", metavar="L", type=u, default="", help="replace all logout-buttons with a link to URL \033[33mL\033[0m")
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see --help-auth-ord")
ap2.add_argument("--auth-ord", metavar="TXT", type=u, default="idp,ipu", help="controls auth precedence; examples: [\033[32mpw,idp,ipu\033[0m], [\033[32mipu,pw,idp\033[0m], see \033[33m--help-auth-ord\033[0m")
ap2.add_argument("--pw-hdr", metavar="NAME", type=u, default="pw", help="lowercase name of password-header (NAME: foo); \033[1;31mWARNING:\033[0m Changing this will break support for many clients")
ap2.add_argument("--pw-urlp", metavar="NAME", type=u, default="pw", help="lowercase name of password url-param (?NAME=foo); \033[1;31mWARNING:\033[0m Changing this will break support for many clients")
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=20, help="session key length; default is 120 bits ((20//4)*4*6)")
ap2.add_argument("--ses-len", metavar="CHARS", type=int, default=24, help="session key length; default is 144 bits ((24//4)*4*6)")
ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
ap2.add_argument("--grp-all", metavar="NAME", type=u, default="acct", help="the name of the auto-generated group which contains every username which is known")
ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]")
@@ -1439,8 +1699,12 @@ def add_sftp(ap):
ap2.add_argument("--sftp-pw", action="store_true", help="allow password-authentication with sftp (not just ssh-keys)")
ap2.add_argument("--sftp-anon", metavar="TXT", type=u, default="", help="allow anonymous/unauthenticated connections with \033[33mTXT\033[0m as username")
ap2.add_argument("--sftp-hostk", metavar="FP", type=u, default=E.cfg, help="path to folder with hostkeys, for example 'ssh_host_rsa_key'; missing keys will be generated")
ap2.add_argument("--sftp-fastldr", action="store_true", help="speedhack for 'loading hostkeys' on old CPUs; only use with known-good hostkeys")
ap2.add_argument("--sftp-chattr", metavar="T", default="s", help="which attributes can be modified with chattr? [\033[32ms\033[0m]=size, [\033[32mt\033[0m]=mtime, [\033[32mp\033[0m]=permissions, [\033[32mu\033[0m]=uid/gid. Example: [\033[32mstpu\033[0m]")
ap2.add_argument("--sftp-banner", metavar="T", type=u, default="", help="bannertext to send when someone connects; can be @filepath")
ap2.add_argument("--sftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m / \033[33m--ipar\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--sftp-hs-t", metavar="SEC", type=int, default=15, help="connection handshake timeout in seconds")
ap2.add_argument("--sftp-hs-n", metavar="NUM", type=int, default=4, help="max num ongoing/incomplete handshakes")
def add_ftp(ap):
@@ -1455,11 +1719,12 @@ def add_ftp(ap):
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)")
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections")
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")
ap2.add_argument("--ftp-banner", metavar="T", type=u, default="welcome to the party", help="bannertext to send when someone connects; \\n is newline, can be @filepath (loaded into memory on startup); if it has a newline then banner must be longer than 75 chars")
def add_webdav(ap):
ap2 = ap.add_argument_group("WebDAV options")
ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
ap2.add_argument("--daw", action="store_true", help="enable full write support, even if client may not be webdav. Some webdav clients need this option for editing existing files; not necessary for clients that send the 'x-oc-mtime' header. Regardless, the delete-permission must always be given. \033[1;31mWARNING:\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
ap2.add_argument("--dav-inf", action="store_true", help="allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this")
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
@@ -1494,6 +1759,7 @@ def add_smb(ap):
ap2.add_argument("--smb-nwa-1", action="store_true", help="truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance")
ap2.add_argument("--smb-nwa-2", action="store_true", help="disable impacket workaround for filecopy globs")
ap2.add_argument("--smba", action="store_true", help="small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does)")
ap2.add_argument("--smb6", action="store_true", help="enable IPv6")
ap2.add_argument("--smbv", action="store_true", help="verbose")
ap2.add_argument("--smbvv", action="store_true", help="verboser")
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
@@ -1505,6 +1771,19 @@ def add_opds(ap):
ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)")
def add_wopi(ap):
ap2 = ap.add_argument_group("WOPI options")
ap2.add_argument("--wopi", action="store_true", help="enable integration with office suites using WOPI")
ap2.add_argument("--wopi-accs", metavar="TXT", type=u, default="", help="restrict wopi to this comma-separated list of usernames; default is everyone")
ap2.add_argument("--wopi-api", metavar="URL", type=u, default="", help="URL that the WOPI-client should use to communicate with copyparty; default is same as user's webbrowser. Example: [\033[32mhttps://party.example.com/\033[0m]")
ap2.add_argument("--wopi-url", metavar="URL", type=u, default="", help="URL to your WOPI client; the host of e.g. Collabora Online. Example: [\033[32mhttps://code.example.com/\033[0m]")
ap2.add_argument("--wopi-urls", metavar="H=U", type=u, action="append", help="\033[34mREPEATABLE:\033[0m maps http \033[33mH\033[0mOST copyparty being accessed by to specific WOPI client instance \033[33mU\033[0mRL; falls back to \033[33m--wopi-url\033[0m; examples: [\033[32mparty.public.com=https://office.public.com/\033[0m], [\033[32mparty.internal.net:8443=https://office.internal.net:8443/\033[0m]")
ap2.add_argument("--wopi-crt", metavar="TXT", type=u, default="", help="if \033[33m--wopi-url\033[0m is selfsigned: path to ca.pem or cert.pem to expect/verify (can be [\033[32mno\033[0m] for full-yolo)")
ap2.add_argument("--wopi-crt-icn", action="store_true", help="if \033[33m--wopi-url\033[0m is selfsigned: ignore the CN (server ip/name) in cert")
ap2.add_argument("--wopi-ttl", metavar="SEC", type=int, default=1800, help="session lifetime; allow editing for this many seconds (default is 30 min)")
ap2.add_argument("--wopi-wdel", action="store_true", help="require permissions read+write+delete for writing to file; default is just read+write")
def add_handlers(ap):
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="\033[34mREPEATABLE:\033[0m handle 404s by executing \033[33mPY\033[0m file")
@@ -1541,10 +1820,12 @@ def add_stats(ap):
def add_yolo(ap):
ap2 = ap.add_argument_group("yolo options")
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests")
ap2.add_argument("--hack-me-bro", action="store_true", help="allow enabling dangerously buggy protocols such as \033[33m--smb\033[0m / \033[33m--tftp\033[0m")
ap2.add_argument("--allow-csrf", action="store_true", help="disable csrf protections; let other domains/sites impersonate you through cross-site requests; \033[1;31mDANGEROUS\033[0m / LAN-only")
ap2.add_argument("--cookie-lax", action="store_true", help="allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF")
ap2.add_argument("--allow-svg-js", action="store_true", help="allow svg images to execute javascript; default-disabled because ~nobody wants it (volflag=allow_svg_js)")
ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET -- \033[1;31mDANGEROUS\033[0m, removes csrf protection")
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets")
@@ -1558,10 +1839,10 @@ def add_optouts(ap):
ap2.add_argument("--no-mv", action="store_true", help="disable move/rename operations")
ap2.add_argument("--no-cp", action="store_true", help="disable copy operations")
ap2.add_argument("--no-fs-abrt", action="store_true", help="disable ability to abort ongoing copy/move")
ap2.add_argument("-nth", action="store_true", help="no title hostname; don't show \033[33m--name\033[0m in <title>")
ap2.add_argument("-nih", action="store_true", help="no info hostname -- don't show in UI")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI. This is the same as --du-who no")
ap2.add_argument("-nih", action="store_true", help="no info hostname -- removes it from the UI corner, but the value of \033[33m--bname\033[0m still shows in the browsertab title")
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI. This is the same as \033[33m--du-who no\033[0m")
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
ap2.add_argument("--smsg", metavar="T,T", type=u, default="POST", help="HTTP-methods to allow ?smsg for; will execute xm hooks like urlform / message-to-serverlog; dangerous example: [\033[32mGET,POST\033[0m]. \033[1;31mWARNING:\033[0m The default (POST) is safe, but GET is dangerous; security/CSRF hazard")
ap2.add_argument("--zipmaxn", metavar="N", type=u, default="0", help="reject download-as-zip if more than \033[33mN\033[0m files in total; optionally takes a unit suffix: [\033[32m256\033[0m], [\033[32m9K\033[0m], [\033[32m4G\033[0m] (volflag=zipmaxn)")
ap2.add_argument("--zipmaxs", metavar="SZ", type=u, default="0", help="reject download-as-zip if total download size exceeds \033[33mSZ\033[0m bytes; optionally takes a unit suffix: [\033[32m256M\033[0m], [\033[32m4G\033[0m], [\033[32m2T\033[0m] (volflag=zipmaxs)")
ap2.add_argument("--zipmaxt", metavar="TXT", type=u, default="", help="custom errormessage when download size exceeds max (volflag=zipmaxt)")
@@ -1578,19 +1859,47 @@ def add_optouts(ap):
def add_safety(ap):
th_bwrap = ""
if HAVE_BWRAP:
zsl = [
HAVE_BWRAP,
"--proc /proc",
"--tmpfs /tmp",
"--tmpfs /var",
"--tmpfs /run",
"--dev-bind /dev/null /dev/null",
"--dev-bind /dev/random /dev/random",
"--dev-bind /dev/urandom /dev/urandom",
"--chdir /tmp",
"--clearenv",
"--unshare-all",
"--cap-drop ALL",
"--die-with-parent",
"--new-session",
]
for d in ("/lib", "/lib64", "/usr/lib", "/usr/lib64"):
if os.path.isdir(d):
zsl.append(" --ro-bind %s %s" % (d, d))
th_bwrap = " ".join(zsl)
ap2 = ap.add_argument_group("safety options")
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")
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
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")
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]")
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)")
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 also has access (keeps permissions from the outer/initial volume) (volflag=xvol)")
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)")
ap2.add_argument("--vol-nospawn", action="store_true", help="if a volume's folder does not exist on the HDD, then do not create it (continue with warning) (volflag=nospawn)")
ap2.add_argument("--vol-or-crash", action="store_true", help="if a volume's folder does not exist on the HDD, then burst into flames (volflag=assert_root)")
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to turn something into a dotfile")
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings")
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings (volflag=no_logues)")
ap2.add_argument("--no-readme", action="store_true", help="disable rendering readme/preadme.md into directory listings (volflag=no_readme)")
ap2.add_argument("--csp-ui", metavar="TXT", default="script-src 'unsafe-eval' 'nonce-{{ js_nonce }}'; worker-src 'self'", help="content-security-policy to apply for the web-UI; default helps prevent XSS by blocking <script> / onclick / ... (volflag=csp_ui)")
ap2.add_argument("--csp-dl", metavar="TXT", default="", help="content-security-policy to apply for static files (volflag=csp_dl)")
ap2.add_argument("--no-script", action="store_true", help="disables javascript in html files; helps prevent XSS but kills interactive websites; this will override \033[33m--csp-dl\033[0m with [\033[32mscript-src 'none'\033[0m] (volflag=noscript)")
ap2.add_argument("--no-html", action="store_true", help="show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables \033[33m--no-script\033[0m (volflag=nohtml)")
ap2.add_argument("--no-mime", action="store_true", help="disallow changing the response mimetype with url-parameter ?mime=... (volflag=nomime)")
ap2.add_argument("--vague-403", action="store_true", help="send 404 instead of 403 (security through ambiguity, very enterprise). \033[1;31mWARNING:\033[0m Not compatible with WebDAV")
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")
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
@@ -1611,10 +1920,18 @@ def add_safety(ap):
ap2.add_argument("--aclose", metavar="MIN", type=int, default=10, help="if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for \033[33mMIN\033[0m minutes (and also kill its active connections) -- disable with 0")
ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]")
ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives")
ap2.add_argument("--acao-re", metavar="PTN", type=u, default="", help="Access-Control-Allow-Origin; supplement to \033[33m--acao\033[0m; allow request if http-header 'Origin' matches either \033[33m--acao\033[0m or regex-\033[33mPTN\033[0m; ominous example: [\033[32m^moz-extension://.*\033[0m]")
ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)")
if FULL_HELP or ANYWIN:
ap2.add_argument("--unsafe-tools", action="store_true", help="windows-only: allow running programs (ffmpeg, dcraw_emu, ...) when their location is inside a folder that can be uploaded to (dangerous due to DLL-hijacking)")
if FULL_HELP or (not ANYWIN and not UNIX):
ap2.add_argument("--th-bwrap", metavar="CMD", type=u, default=th_bwrap, help="optional bwrap sandbox command for FFmpeg and dcraw (Linux-only)")
ap2.add_argument("--use-bwrap", metavar="TXT", type=u, default="n", help="a/n/f; [\033[32ma\033[0m]=auto (yes if the program 'bwrap' exists (assumes it works)), [\033[32mn\033[0m]=no (assumes bwrap is broken), [\033[32mf\033[0m]=force (disables FFmpeg if bwrap unavailable)")
else:
ap2.add_argument("--use-bwrap", metavar="TXT", type=u, default="n", help=argparse.SUPPRESS)
def add_salt(ap, fk_salt, dk_salt, ah_salt):
def add_salt(ap, fk_salt, dk_salt, ah_salt, wopi_salt):
ap2 = ap.add_argument_group("salting options")
ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: \033[32margon2 scrypt sha2 none\033[0m (each optionally followed by alg-specific comma-sep. config)")
ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if \033[33m--ah-alg\033[0m is none (default)")
@@ -1623,6 +1940,7 @@ def add_salt(ap, fk_salt, dk_salt, ah_salt):
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
ap2.add_argument("--dk-salt", metavar="SALT", type=u, default=dk_salt, help="per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission")
ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
ap2.add_argument("--wopi-salt", metavar="TXT", type=u, default=wopi_salt, help="WOPI file ID salt; used by \033[33m--wopi\033[0m for persistent file IDs")
ap2.add_argument("--show-ah-salt", action="store_true", help="on startup, print the effective value of \033[33m--ah-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
ap2.add_argument("--show-fk-salt", action="store_true", help="on startup, print the effective value of \033[33m--fk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
ap2.add_argument("--show-dk-salt", action="store_true", help="on startup, print the effective value of \033[33m--dk-salt\033[0m (the autogenerated value in $XDG_CONFIG_HOME unless otherwise specified)")
@@ -1632,7 +1950,7 @@ def add_shutdown(ap):
ap2 = ap.add_argument_group("shutdown options")
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
ap2.add_argument("--ign-ebind-all", action="store_true", help="continue running even if it's impossible to receive connections at all")
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing")
ap2.add_argument("--exit", metavar="WHEN", type=u, default="", help="shutdown after \033[33mWHEN\033[0m has finished; [\033[32mcfg\033[0m] config parsing, [\033[32midx\033[0m] volscan + multimedia indexing, [\033[32mthgen\033[0m] thumbnail-pregen")
def add_logging(ap):
@@ -1640,6 +1958,8 @@ def add_logging(ap):
ap2.add_argument("-q", action="store_true", help="quiet; disable most STDOUT messages")
ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile; use .txt for plaintext or .xz for compressed. Example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)")
ap2.add_argument("--flo", metavar="N", type=int, default=1, help="log format for \033[33m-lo\033[0m; [\033[32m1\033[0m]=classic/colors, [\033[32m2\033[0m]=no-color")
ap2.add_argument("--rlo", metavar="TXT", type=u, default="no", help="logrotate counter format; see \033[33m--help-rlo\033[0m")
ap2.add_argument("--logrot-sig", metavar="S", type=u, default="", help="immediately logrotate when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGHUP\033[0m], [\033[32mHUP\033[0m], [\033[32m1\033[0m]")
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster")
@@ -1653,7 +1973,7 @@ def add_logging(ap):
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[xwjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
ap2.add_argument("--scan-st-r", metavar="SEC", type=float, default=0.1, help="fs-indexing: wait \033[33mSEC\033[0m between each status-message")
ap2.add_argument("--scan-pr-r", metavar="SEC", type=float, default=10, help="fs-indexing: wait \033[33mSEC\033[0m between each 'progress:' message")
ap2.add_argument("--scan-pr-s", metavar="MiB", type=float, default=1, help="fs-indexing: say 'file: <name>' when a file larger than \033[33mMiB\033[0m is about to be hashed")
@@ -1687,29 +2007,46 @@ def add_thumbnail(ap):
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
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)")
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)")
ap2.add_argument("--th-qv", metavar="N", type=int, default=40, help="thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qv)")
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,ff", help="image decoders, in order of preference")
ap2.add_argument("--th-qv", metavar="N", type=int, default=40, help="webp/jpg thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qv)")
ap2.add_argument("--th-qvx", metavar="N", type=int, default=64, help="jxl thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qvx)")
ap2.add_argument("--th-dec", metavar="LIBS", default="raw,pil,ff,vips", help="image decoders, in order of preference")
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
ap2.add_argument("--th-no-jxl", action="store_true", help="disable jpeg-xl output")
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg output for video thumbs (avoids issues on some FFmpeg builds)")
ap2.add_argument("--th-ff-swr", action="store_true", help="use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds)")
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than \033[33mSEC\033[0m seconds")
ap2.add_argument("--th-vips-jxl", metavar="N", type=int, default=1, help="when to allow generating jxl thumbnails with libvips; 0=never, 1=musl-mallocng, 2=always")
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than \033[33mSEC\033[0m seconds; 0=no-keepalive")
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age -- folders which haven't been poked for longer than \033[33m--th-poke\033[0m seconds will get deleted every \033[33m--th-clean\033[0m seconds")
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern")
ap2.add_argument("--th-pregen", metavar="F,F", type=u, default="", help="pregenerate thumbnails on startup; \033[33mF,F\033[0m is comma-separated list of formats; example: [\033[32mj,jf,w,w3,wf,wf3,x,xf\033[0m] NOTE: remember to set \033[33m--th-maxage 123456789\033[0m (volflag=th_pregen)")
ap2.add_argument("--th-pre-rl", metavar="SEC", type=int, default=30, help="while pregen is running, ratelimit the thumbnailer logger to one message every \033[33mSEC\033[0m seconds (only works with \033[33m-j1\033[0m); set 0 to disable ratelimit")
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; enabling \033[33m-e2d\033[0m will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern (volflag=th_covers)")
ap2.add_argument("--th-spec-p", metavar="N", type=u, default=1, help="for music, do spectrograms or embedded coverart? [\033[32m0\033[0m]=only-art, [\033[32m1\033[0m]=prefer-art, [\033[32m2\033[0m]=only-spec")
ap2.add_argument("--th-spec-fl", action="store_true", help="generate spectrograms with logarithmic frequency scale instead of linear")
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
# https://github.com/libvips/libvips
# https://stackoverflow.com/a/47612661
# ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:'
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="avif,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,nii,pfm,pgm,png,ppm,svg,tif,tiff,webp", help="image formats to decode using pyvips")
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mrw,nef,orf,pef,raf,raw,sr2,srf,x3f", help="image formats to decode using rawpy")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,kra,ora,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow")
ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="3fr,arw,avif,cr2,cr3,crw,dcr,dng,erf,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,k25,kdc,mdc,mef,mrw,nef,nii,nrw,orf,pfm,pgm,png,ppm,raf,raw,rw2,sr2,srf,srw,svg,tif,tiff,webp,x3f", help="image formats to decode using pyvips")
ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,rw2,sr2,srf,srw,x3f", help="image formats to decode using rawpy (if available) or libraw's dcraw_emu")
ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,kra,ora,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,mdgz,mdxz,mdz,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mka,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg")
ap2.add_argument("--th-pil-add", metavar="T", type=u, default="", help="add or remove image formats to decode using pillow listed in \033[33m--th-r-pil\033[0m; example: [\033[32mfb2,pdf,-psd,-bmp\033[0m] (add fb2 and pdf, remove psd and bmp)")
ap2.add_argument("--th-vips-add", metavar="T", type=u, default="", help="add or remove formats; same as \033[33m--th-pil-add\033[0m but for pyvips")
ap2.add_argument("--th-raw-add", metavar="T", type=u, default="", help="add or remove formats; same as \033[33m--th-pil-add\033[0m but for rawpy/libraw")
ap2.add_argument("--th-ffi-add", metavar="T", type=u, default="", help="add or remove formats; same as \033[33m--th-pil-add\033[0m but for ffmpeg images")
ap2.add_argument("--th-ffv-add", metavar="T", type=u, default="", help="add or remove formats; same as \033[33m--th-pil-add\033[0m but for ffmpeg videos")
ap2.add_argument("--th-ffa-add", metavar="T", type=u, default="", help="add or remove formats; same as \033[33m--th-pil-add\033[0m but for ffmpeg audio")
ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg")
ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub, kra=png.kra, ora=png.ora", help="audio/image formats to decompress before passing to ffmpeg")
ap2.add_argument("--th-extract", metavar="T", type=u, action="append", help="\033[34mREPEATABLE:\033[0m list of file extensions to thumbnail using a custom plugin (a python script); example: [\033[32mmdf,iso,dmg=/thumbs/diskimg.py\033[0m], see \033[33m--help-thumb-ex\033[0m")
ap2.add_argument("--th-extr-sz", metavar="M", type=int, default=16, help="max num megabytes to allow \033[33m--th-extract\033[0m plugins to extract from each file")
ap2.add_argument("--hot-th-extr", action="store_true", help="recompile extractors on each thumbnail extraction -- expensive but convenient when hacking on stuff")
def add_transcoding(ap):
@@ -1756,6 +2093,7 @@ def add_db_general(ap, hcores):
ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
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)")
ap2.add_argument("--dbpath", metavar="PATH", type=u, default="", help="override where the volume databases are to be placed; default is the same as \033[33m--hist\033[0m (volflag=dbpath)")
ap2.add_argument("--fika", metavar="TXT", type=u, default="ucd", help="list of user-actions to allow while filesystem-indexer is still busy; set blank to never interrupt indexing (old default). [\033[32mu\033[0m]=uploads, [\033[32mc\033[0m]=filecopy, [\033[32mm\033[0m]=move/rename, [\033[32md\033[0m]=delete. NOTE: [\033[32mm\033[0m] is untested/scary, and blank is recommended if dedup enabled")
ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (must be specified as one big regex, not multiple times) (volflag=nohash)")
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scan (must be specified as one big regex, not multiple times) (volflag=noidx)")
ap2.add_argument("--no-dirsz", action="store_true", help="do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz)")
@@ -1765,11 +2103,13 @@ def add_db_general(ap, hcores):
ap2.add_argument("--no-forget", action="store_true", help="never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget)")
ap2.add_argument("--forget-ip", metavar="MIN", type=int, default=0, help="remove uploader-IP from database (and make unpost impossible) \033[33mMIN\033[0m minutes after upload, for GDPR reasons. Default [\033[32m0\033[0m] is never-forget. [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month. (volflag=forget_ip)")
ap2.add_argument("--dbd", metavar="PROFILE", default="wal", help="database durability profile; sets the tradeoff between robustness and speed, see \033[33m--help-dbd\033[0m (volflag=dbd)")
ap2.add_argument("--hist-cow", action="store_true", help="btrfs-only: histpaths (db/thumbs) are nocow by default; if you really need Copy-on-write then enable this (volflag=hist_cow)")
ap2.add_argument("--xlink", action="store_true", help="on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink)")
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")
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)")
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, ...)")
ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags")
ap2.add_argument("--srch-icase", action="store_true", help="case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 3x), and NOTE: only applies to filenames/paths, not tags")
ap2.add_argument("--srch-nfkc", action="store_true", help="case-insensitive and unicode-normalization-insensitive search (NFC/NFD) for filenames/paths; slightly slower than \033[33m--srch-icase\033[0m (about 15%% slower / 87%% as fast)")
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")
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")
ap2.add_argument("--srch-excl", metavar="PTN", type=u, default="", help="regex: exclude files from search results if the file-URL matches \033[33mPTN\033[0m (case-sensitive). Example: [\033[32mpassword|logs/[0-9]\033[0m] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl)")
@@ -1797,8 +2137,11 @@ def add_db_metadata(ap):
def add_txt(ap):
ap2 = ap.add_argument_group("textfile options")
ap2.add_argument("--rw-edit", metavar="T,T", type=u, default="md", help="comma-sep. list of file-extensions to allow editing with permissions read+write; all others require read+write+delete (volflag=rw_edit)")
ap2.add_argument("--md-no-br", action="store_true", help="markdown: disable newline-is-newline; will only render a newline into the html given two trailing spaces or a double-newline (volflag=md_no_br)")
ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of textfiles; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
ap2.add_argument("--md-nhist", metavar="TXT", type=int, default=99, help="how many old versions of textfiles to keep; 0=infinite (volflag=md_nhist)")
ap2.add_argument("--show-hist", action="store_true", help="allow browsing the .hist folder; enable to view old markdown files (volflag=show_hist)")
ap2.add_argument("--txt-eol", metavar="TYPE", type=u, default="", help="enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol)")
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds")
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
@@ -1832,9 +2175,11 @@ def add_ui(ap, retry: int):
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)")
ap2.add_argument("--gauto", metavar="PERCENT", type=int, default=0, help="switch to gridview if more than \033[33mPERCENT\033[0m of files are pics/vids; 0=disabled")
ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double")
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
ap2.add_argument("--glang", action="store_true", help="guess the browser's default language, otherwise fall back to \033[33m--lang\033[0m")
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
@@ -1845,6 +2190,7 @@ def add_ui(ap, retry: int):
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)")
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
ap2.add_argument("--dothidden", action="store_true", help="hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden)")
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")
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)")
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)")
@@ -1854,8 +2200,8 @@ def add_ui(ap, retry: int):
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s)")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template; \033[32m<script>\033[0m will not work, use \033[32m<script nonce=\"{{ js_nonce }}\">\033[0m (volflag=html_head)")
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) \033[33m--html-head\033[0m but only accepts static text (volflag=html_head_s)")
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)")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
@@ -1866,7 +2212,7 @@ def add_ui(ap, retry: int):
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as --ver-who all")
ap2.add_argument("--ver", action="store_true", help="show version on the control panel (incompatible with \033[33m-nb\033[0m). This is the same as \033[33m--ver-who all\033[0m")
ap2.add_argument("--ver-who", metavar="TXT", type=u, default="no", help="only show version for: [\033[32ma\033[0m]=admin-permission-anywhere, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone")
ap2.add_argument("--du-who", metavar="TXT", type=u, default="all", help="only show disk usage for: [\033[32mno\033[0m]=nobody, [\033[32ma\033[0m]=admin-permission, [\033[32mrw\033[0m]=read-write, [\033[32mw\033[0m]=write, [\033[32mauth\033[0m]=authenticated, [\033[32mall\033[0m]=anyone (volflag=du_who)")
ap2.add_argument("--ver-iwho", type=int, default=0, help=argparse.SUPPRESS)
@@ -1897,16 +2243,21 @@ def add_debug(ap):
ap2.add_argument("--vc", action="store_true", help="verbose config file parser (explain config)")
ap2.add_argument("--cgen", action="store_true", help="generate config file from current config (best-effort; probably buggy)")
ap2.add_argument("--deps", action="store_true", help="list information about detected optional dependencies")
if hasattr(select, "poll"):
if FULL_HELP or hasattr(select, "poll"):
ap2.add_argument("--no-poll", action="store_true", help="kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700)")
ap2.add_argument("--no-sendfile", action="store_true", help="kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead")
ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead")
ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests")
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
if sys.version_info > (3, 7) and not ANYWIN:
ap2.add_argument("--sig-thr", action="store_true", help="start separate thread for OS-signals (try this if CTRL-C is busted)")
else:
ap2.add_argument("--sig-thr", action="store_true", default=True, help=argparse.SUPPRESS)
ap2.add_argument("--rm-sck", action="store_true", help="when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind")
ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks")
ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc")
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")
ap2.add_argument("--stack-sig", metavar="S", type=u, default="", help="show stacktrace when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGUSR2\033[0m], [\033[32mUSR2\033[0m], [\033[32m12\033[0m]")
ap2.add_argument("--log-thrs", metavar="SEC", type=float, default=0.0, help="list active threads every \033[33mSEC\033[0m")
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")
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")
@@ -1934,6 +2285,7 @@ def run_argparse(
fk_salt = get_salt("fk", 18)
dk_salt = get_salt("dk", 30)
ah_salt = get_salt("ah", 18)
wopi_salt = get_salt("wopi", 18)
# alpine peaks at 5 threads for some reason,
# all others scale past that (but try to avoid SMT),
@@ -1968,8 +2320,9 @@ def run_argparse(
add_tftp(ap)
add_smb(ap)
add_opds(ap)
add_wopi(ap)
add_safety(ap)
add_salt(ap, fk_salt, dk_salt, ah_salt)
add_salt(ap, fk_salt, dk_salt, ah_salt, wopi_salt)
add_optouts(ap)
add_shutdown(ap)
add_yolo(ap)
@@ -2055,6 +2408,14 @@ def main(argv: Optional[list[str]] = None) -> None:
print("\n".join("%8s %s" % (k, v) for k, v in sorted(MIMES.items())))
sys.exit(0)
if "--list-ips" in argv:
print("\n".join(str(x) for x in sorted(list_ips())))
sys.exit(0)
if "--list-nics" in argv:
print("\n".join(str(x) for x in sorted(list_nics(True).items())))
sys.exit(0)
if EXE:
print("pybin: {}\n".format(pybin), end="")
@@ -2064,6 +2425,9 @@ def main(argv: Optional[list[str]] = None) -> None:
argv.pop(n)
break
if CFG_DEF:
CFG_DEF[0] = absreal(CFG_DEF[0])
ensure_locale()
ensure_webdeps()
@@ -2138,6 +2502,17 @@ def main(argv: Optional[list[str]] = None) -> None:
except:
sys.exit(1)
quotecheck(al)
if al.env_expand == 2:
al.shenvexp = expand_osenv_c
elif al.env_expand == 1:
al.shenvexp = expand_osenv_s
elif al.env_expand == 0:
al.shenvexp = expand_osenv_noop
else:
al.shenvexp = expand_osenv_cs
if al.chdir:
os.chdir(al.chdir)
@@ -2207,7 +2582,8 @@ def main(argv: Optional[list[str]] = None) -> None:
if al.ciphers:
configure_ssl_ciphers(al)
else:
warn("ssl module does not exist; cannot enable https")
if not al.http_only and not NO_TLS:
warn("ssl module does not exist; cannot enable https")
al.http_only = True
if PY2 and WINDOWS and al.e2d:
@@ -2231,7 +2607,7 @@ def main(argv: Optional[list[str]] = None) -> None:
# signal.signal(signal.SIGINT, sighandler)
SvcHub(al, dal, argv, "".join(printed)).run()
SvcHub(al, dal, argv).run()
if __name__ == "__main__":
+2 -2
View File
@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 20, 2)
VERSION = (1, 20, 26)
CODENAME = "sftp is fine too"
BUILD_DT = (2026, 1, 19)
BUILD_DT = (2026, 10, 14)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
+404 -84
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
import time
@@ -106,14 +106,14 @@ def utime(
def utime_c(
log: Union["NamedLogger", Any],
p: str,
ts: int,
ts: float,
follow_symlinks: bool = True,
throw: bool = False,
) -> Optional[int]:
) -> Optional[float]:
clamp = 0
ov = ts
bp = fsenc(p)
now = int(time.time())
now = time.time()
while True:
try:
if SYMTIME:
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
+15 -8
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import threading
import time
@@ -146,13 +146,13 @@ class BrokerMp(object):
returns a Queue object which eventually contains the response if want_retval
(not-impl here since nothing uses it yet)
"""
if dest == "httpsrv.listen":
for p in self.procs:
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
elif dest == "httpsrv.set_netdevs":
for p in self.procs:
p.q_pend.put((0, dest, list(args)))
if dest.startswith("httpsrv."):
if dest == "httpsrv.listen":
for p in self.procs:
p.q_pend.put((0, dest, [args[0], len(self.procs)]))
else:
for p in self.procs:
p.q_pend.put((0, dest, list(args)))
elif dest == "cb_httpsrv_up":
self.hub.cb_httpsrv_up()
@@ -160,6 +160,13 @@ class BrokerMp(object):
else:
raise Exception("what is " + str(dest))
def say1(self, dest: str, *args: Any) -> None:
"""
send message to one lucky recipient
"""
p = self.procs[0]
p.q_pend.put((0, dest, list(args)))
def periodic(self) -> None:
while True:
time.sleep(1)
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import os
+6 -6
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
import threading
@@ -53,12 +53,12 @@ class BrokerThr(BrokerCli):
return NotExQueue(obj(*args)) # type: ignore
def say(self, dest: str, *args: Any) -> None:
if dest == "httpsrv.listen":
self.httpsrv.listen(args[0], 1)
return
if dest.startswith("httpsrv."):
if dest == "httpsrv.listen":
self.httpsrv.listen(args[0], 1)
return
if dest == "httpsrv.set_netdevs":
self.httpsrv.set_netdevs(args[0])
getattr(self.httpsrv, dest[8:])(*args)
return
# new ipc invoking managed service in hub
+3 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
@@ -16,6 +16,7 @@ if True: # pylint: disable=using-constant-test
if TYPE_CHECKING:
from .httpsrv import HttpSrv
from .svchub import SvcHub
class ExceptionalQueue(Queue, object):
@@ -50,6 +51,7 @@ class BrokerCli(object):
for example resolving httpconn.* in httpcli -- see lines tagged #mypy404
"""
hub: "SvcHub"
log: "RootLogger"
args: argparse.Namespace
asrv: AuthSrv
+13 -5
View File
@@ -1,3 +1,6 @@
# coding: utf-8
from __future__ import division, print_function, unicode_literals
import calendar
import errno
import json
@@ -8,6 +11,7 @@ import time
from .__init__ import ANYWIN
from .util import Netdev, atomic_move, load_resource, runcmd, wunlink
NO_TLS = bool(os.environ.get("PRTY_NO_TLS"))
HAVE_CFSSL = not os.environ.get("PRTY_NO_CFSSL")
if True: # pylint: disable=using-constant-test
@@ -51,9 +55,12 @@ def ensure_cert(log: "RootLogger", args) -> None:
with open(args.cert, "wb") as f:
f.write(cert_insec)
if args.certkey and not os.path.isfile(args.certkey):
raise Exception("certificate-key file does not exist: " + args.certkey)
with open(args.cert, "rb") as f:
buf = f.read()
o1 = buf.find(b" PRIVATE KEY-")
o1 = buf.find(b" PRIVATE KEY-") if not args.certkey else 0
o2 = buf.find(b" CERTIFICATE-")
m = "unsupported certificate format: "
if o1 < 0:
@@ -67,8 +74,9 @@ def ensure_cert(log: "RootLogger", args) -> None:
with open(args.cert, "rb") as f:
active_cert = f.read()
if active_cert == cert_insec:
t = "using default TLS certificate; https will be insecure:\033[36m {}"
log("cert", t.format(args.cert), 3)
t = "using default TLS certificate; %s will be insecure:\033[36m %s"
t2 = "https" if not args.ftps else "ftps"
log("cert", t % (t2, args.cert), 3)
except:
pass
@@ -249,10 +257,10 @@ def _gen_srv(log: "RootLogger", args, netdevs: dict[str, Netdev]):
def gencert(log: "RootLogger", args, netdevs: dict[str, Netdev]):
global HAVE_CFSSL
if args.http_only:
if args.http_only and not args.ftps:
return
if args.no_crt or not HAVE_CFSSL:
if args.no_crt or args.certkey or not HAVE_CFSSL:
ensure_cert(log, args)
return
+42 -3
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
# awk -F\" '/add_argument\("-[^-]/{print(substr($2,2))}' copyparty/__main__.py | sort | tr '\n' ' '
zs = "a c e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vp e2vu ed emp i j lo mcr mte mth mtm mtp nb nc nid nih nth nw p q s ss sss v z zv"
@@ -21,8 +21,11 @@ def vf_bmap() -> dict[str, str]:
"no_dupe": "nodupe",
"no_dupe_m": "nodupem",
"no_forget": "noforget",
"no_html": "nohtml",
"no_mime": "nomime",
"no_pipe": "nopipe",
"no_robots": "norobots",
"no_script": "noscript",
"no_tail": "notail",
"no_thumb": "dthumb",
"no_vthumb": "dvthumb",
@@ -31,8 +34,10 @@ def vf_bmap() -> dict[str, str]:
"vol_or_crash": "assert_root",
}
for k in (
"allow_svg_js",
"dedup",
"dlni",
"dothidden",
"dotsrch",
"e2d",
"e2ds",
@@ -47,9 +52,12 @@ def vf_bmap() -> dict[str, str]:
"grid",
"gsel",
"hardlink",
"hist_cow",
"magic",
"md_no_br",
"no_db_ip",
"no_logues",
"no_readme",
"no_sb_md",
"no_sb_lg",
"nsort",
@@ -58,10 +66,12 @@ def vf_bmap() -> dict[str, str]:
"og_s_title",
"opds",
"rand",
"redup_dry",
"reflink",
"rm_partial",
"rmagic",
"rss",
"show_hist",
"ui_noacci",
"ui_nocpla",
"ui_nolbar",
@@ -102,6 +112,8 @@ def vf_vmap() -> dict[str, str]:
"casechk",
"chmod_d",
"chmod_f",
"csp_ui",
"csp_dl",
"dbd",
"db_xattr",
"du_who",
@@ -117,6 +129,7 @@ def vf_vmap() -> dict[str, str]:
"lg_sba",
"md_sba",
"md_hist",
"md_nhist",
"nrand",
"u2ow",
"og_desc",
@@ -134,11 +147,14 @@ def vf_vmap() -> dict[str, str]:
"put_ck",
"put_name",
"readmes",
"redup",
"rotf_tz",
"mv_retry",
"rm_retry",
"rss_sort",
"rss_fmt_t",
"rss_fmt_d",
"rw_edit",
"shr_who",
"sort",
"tail_fd",
@@ -146,7 +162,10 @@ def vf_vmap() -> dict[str, str]:
"tail_tmax",
"tail_who",
"tcolor",
"th_covers",
"th_pregen",
"th_qv",
"th_qvx",
"th_spec_p",
"txt_eol",
"unlist",
@@ -212,6 +231,8 @@ flagcats = {
"hardlinkonly": "dedup with hardlink only, never symlink;\nmake a full copy if hardlink is impossible",
"reflink": "enable reflink-based file deduplication,\nwith fallback on full copy when that is impossible",
"safededup": "verify on-disk data before using it for dedup",
"redup=A,B=T": "convert dedup-type for existing files",
"redup_dry": "dry-run mode for redup",
"noclone": "take dupe data from clients, even if available on HDD",
"nodupe": "rejects existing files (instead of linking/cloning them)",
"nodupem": "rejects existing files during moves as well",
@@ -283,6 +304,7 @@ flagcats = {
"no_db_ip": "never store uploader-IP in the db; disables unpost",
"fat32": "avoid excessive reindexing on android sdcardfs",
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
"hist_cow": "btrfs: keep copy-on-write enabled on db/thumbs",
"xlink": "cross-volume dupe detection / linking (dangerous)",
"xdev": "do not descend into other filesystems",
"xvol": "do not follow symlinks leaving the volume root",
@@ -302,15 +324,19 @@ flagcats = {
"dvthumb": "disables video thumbnails",
"dathumb": "disables audio thumbnails (spectrograms)",
"dithumb": "disables image thumbnails",
"dethumb": "disables custom thumbnails (--th-extract)",
"pngquant": "compress audio waveforms 33% better",
"thsize": "thumbnail res; WxH",
"crop": "center-cropping (y/n/fy/fn)",
"th3x": "3x resolution (y/n/fy/fn)",
"th_qv=40": "thumbnail quality (10~90)",
"th_qv=40": "webp/jpg thumbnail quality (10~90)",
"th_qvx=40": "jxl thumbnail quality (10~90)",
"th_covers=a.jpg,a.png": "filenames to prefer as folder-thumbs",
"convt": "convert-to-image timeout in seconds",
"aconvt": "convert-to-audio timeout in seconds",
"th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always",
"ext_th=s=/b.png": "use /b.png as thumbnail for file-extension s",
"th_pregen=w,wf": "pregenerate thumbs for these formats",
},
"handlers\n(better explained in --help-handlers)": {
"on404=PY": "handle 404s by executing PY file",
@@ -337,6 +363,7 @@ flagcats = {
"hsortn": "number of sort-rules to add to media URLs",
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
"unlist": "dont list files matching REGEX",
"dothidden": "enable support for .hidden files",
"dlni": "force-download (no-inline) files on click",
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
"html_head_s=TXT": "additional static text in the html <head>",
@@ -351,6 +378,10 @@ flagcats = {
"epilogues=.epilogue.html": "files to embed below/after files",
"readmes=readme.md,README.md": "files to embed as readmes",
"preadmes=preadme.md,PREADME.md": "files to embed as preadmes",
"plainreadme": "embed readme/preadme as monospace plaintext",
"plainlogues": "embed prologue/epilogue as sans-serif plaintext",
"no_readme": "disable embedding readme/preadme",
"no_logues": "disable embedding prologue/epilogue",
"no_sb_md": "disable js sandbox for markdown files",
"no_sb_lg": "disable js sandbox for prologue/epilogue",
"sb_md": "enable js sandbox for markdown files (default)",
@@ -359,7 +390,12 @@ flagcats = {
"lg_sbf": "list of *logue-sandbox safeguards to disable",
"md_sba": "value of iframe allow-prop for markdown-sandbox",
"lg_sba": "value of iframe allow-prop for *logue-sandbox",
"csp_ui": "content-security-policy for the web-UI",
"csp_dl": "content-security-policy for static files",
"nohtml": "return html and markdown as text/html",
"nomime": "disallow ?mime to change response type",
"noscript": "disable most javascript by use of CSP",
"allow_svg_js": "allow javascript in svg images",
"ui_noacci": "hide account-info in the UI",
"ui_nocpla": "hide cpanel-link in the UI",
"ui_nolbar": "hide link-bar in the UI",
@@ -389,8 +425,11 @@ flagcats = {
"opds_exts": "file formats to list in OPDS feeds; leave empty to show everything",
},
"textfiles": {
"rw_edit=md,txt": "only require read+write to edit .md and .txt",
"md_no_br": "newline only on double-newline or two tailing spaces",
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
"md_hist": "where to put text-editor backups; s=subfolder, v=volHist, n=nope",
"md_nhist=99": "how many text-editor backups to keep",
"show_hist": "allow browsing the .hist folder (old textfiles)",
"exp": "enable textfile expansion; see --help-exp",
"exp_md": "placeholders to expand in markdown files; see --help",
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import importlib
import sys
+27 -7
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import json
@@ -7,10 +7,10 @@ import os
import re
import time
from .__init__ import ANYWIN, MACOS
from .__init__ import ANYWIN, FREEBSD, MACOS, UNIX
from .authsrv import AXS, VFS, AuthSrv
from .bos import bos
from .util import chkcmd, json_hesc, min_ex, undot
from .util import chkcmd, fsenc, json_hesc, min_ex, runcmd, undot
if True: # pylint: disable=using-constant-test
from typing import Optional, Union
@@ -18,6 +18,11 @@ if True: # pylint: disable=using-constant-test
from .util import RootLogger, undot
PTN_FILEFRAG = re.compile(
r"^\s*[0-9]+:\s*[0-9]+\.\.\s*[0-9]+:\s*([0-9]+)\.\.\s*([0-9]+):\s*[0-9]+:\s*(.*)$"
)
class Fstab(object):
def __init__(self, log: "RootLogger", args: argparse.Namespace, verbose: bool):
self.log_func = log
@@ -88,7 +93,7 @@ class Fstab(object):
def _from_sp_mount(self) -> dict[str, str]:
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
if MACOS:
if MACOS or FREEBSD:
sptn = r"^.*? on (.*) \(([^ ]+), .*"
ptn = re.compile(sptn)
@@ -118,7 +123,7 @@ class Fstab(object):
def build_tab(self) -> None:
self.log("inspecting mtab for changes")
dtab = self._from_sp_mount() if MACOS else self._from_proc()
dtab = self._from_sp_mount() if UNIX else self._from_proc()
# keep empirically-correct values if mounttab unchanged
srctab = str(sorted(dtab.items()))
@@ -130,7 +135,7 @@ class Fstab(object):
try:
fuses = [mp for mp, fs in dtab.items() if fs == "fuseblk"]
if not fuses or MACOS:
if not fuses or UNIX:
raise Exception()
try:
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6
@@ -248,7 +253,7 @@ def ramdisk_chk(asrv: AuthSrv) -> None:
t2 = ["\n volume=[/%s], abspath=%r, type=%s, root=%r" % x for x in mods]
log("vfs", t + "".join(t2) + "\n", 1)
assume = "mac" if MACOS else "lin"
assume = "mac" if MACOS else "win" if ANYWIN else "lin"
for vol in asrv.vfs.all_nodes.values():
if not vol.realpath or vol.flags.get("is_file"):
continue
@@ -263,3 +268,18 @@ def ramdisk_chk(asrv: AuthSrv) -> None:
htm = json.loads(vol.js_htm)
vol.flags["fsnt"] = vol.js_ls["fsnt"] = htm["fsnt"] = fs
vol.js_htm = json_hesc(json.dumps(htm))
# md_htm is dontcare; only relevant for up2k request body
def filefrag(ap: str) -> list[str]:
rc, so, se = runcmd([b"filefrag", b"-v", fsenc(ap)], timeout=10)
ptn = PTN_FILEFRAG
ret = []
for ln in so.split("\n"):
m = ptn.match(ln)
if not m:
continue
a, b, s = m.groups()
if "shared" in s.strip().split(","):
ret.append((a, b))
return ret
+72 -31
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import errno
@@ -15,7 +15,7 @@ from pyftpdlib.handlers import FTPHandler
from pyftpdlib.ioloop import IOLoop
from pyftpdlib.servers import FTPServer
from .__init__ import PY2, TYPE_CHECKING
from .__init__ import ANYWIN, PY2, TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .util import (
@@ -24,10 +24,11 @@ from .util import (
ODict,
Pebkac,
exclude_dotfiles,
exclude_dothidden,
fsenc,
ipnorm,
pybin,
relchk,
read_utf8,
runhook,
sanitize_fn,
set_fperms,
@@ -147,6 +148,7 @@ class FtpFs(AbstractedFS):
self.hub: "SvcHub" = cmd_channel.hub
self.args = cmd_channel.args
self.uname = cmd_channel.uname
self.log_src = "%s:FTP" % (cmd_channel.cli_ip,)
self.cwd = "/" # pyftpdlib convention of leading slash
self.root = "/var/lib/empty"
@@ -154,8 +156,8 @@ class FtpFs(AbstractedFS):
self.listdirinfo = self.listdir
self.chdir(".")
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.hub.log("ftpd", msg, c)
def clog(self, msg: str, c: Union[int, str] = 0) -> None:
self.hub.log(self.log_src, msg, c)
def v2a(
self,
@@ -166,16 +168,23 @@ class FtpFs(AbstractedFS):
d: bool = False,
) -> tuple[str, VFS, str]:
try:
vpath = vpath.replace("\\", "/").strip("/")
rd, fn = os.path.split(vpath)
if relchk(rd):
logging.warning("malicious vpath: %s", vpath)
t = "Unsupported characters in [{}]"
raise FSE(t.format(vpath), 1)
# because:
# * ftp-spec doesn't define sep
# * windows filename limitations
# need to allow/translate illegal fn,
# but require filesystem-correct rd
if ANYWIN or ("\\" in vpath and "/" not in vpath):
vpath = vpath.replace("\\", "/")
vpath = vpath.strip("/\\")
if ANYWIN:
rd, fn = os.path.split(vpath)
fn = sanitize_fn(fn, "win")
vpath = vjoin(rd, fn)
# end of windows jank
fn = sanitize_fn(fn or "")
vpath = vjoin(rd, fn)
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
vpath = vjoin(vfs.vpath, rem)
rd, fn = os.path.split(vpath)
if (
w
and fn.lower() in vfs.flags["emb_all"]
@@ -190,21 +199,12 @@ class FtpFs(AbstractedFS):
t = "No filesystem mounted at [{}]"
raise FSE(t.format(vpath))
if "xdev" in vfs.flags or "xvol" in vfs.flags:
ap = vfs.canonical(rem)
avfs = vfs.chk_ap(ap)
t = "Permission denied in [{}]"
if not avfs:
raise FSE(t.format(vpath), 1)
cr, cw, cm, cd, _, _, _, _, _ = avfs.uaxs[self.h.uname]
if r and not cr or w and not cw or m and not cm or d and not cd:
raise FSE(t.format(vpath), 1)
ap = vfs.canonical(rem, False)
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
raise FSE("No such file or directory", 1)
return os.path.join(vfs.realpath, rem), vfs, rem
return ap, vfs, rem
except Pebkac as ex:
raise FSE(str(ex))
@@ -234,6 +234,8 @@ class FtpFs(AbstractedFS):
return True
def open(self, filename: str, mode: str) -> typing.IO[Any]:
self.clog("open(%r,%r) @%s" % (filename, mode, self.uname))
r = "r" in mode
w = "w" in mode or "a" in mode or "+" in mode
@@ -251,7 +253,7 @@ class FtpFs(AbstractedFS):
xbu = vfs.flags.get("xbu")
if xbu:
hr = runhook(
self.log,
self.clog,
None,
self.hub.up2k,
"xbu.ftp",
@@ -271,7 +273,7 @@ class FtpFs(AbstractedFS):
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r" % (filename,)
self.log(t, 3)
self.clog(t, 3)
raise FSE(t)
if w and need_unlink: # type: ignore # !rm
@@ -296,17 +298,22 @@ class FtpFs(AbstractedFS):
# Don't unlink file for append mode
elif "a" not in mode:
wunlink(self.log, ap, VF_CAREFUL)
wunlink(self.clog, ap, VF_CAREFUL)
ret = open(fsenc(ap), mode, self.args.iobuf)
if w and "fperms" in vfs.flags:
set_fperms(ret, vfs.flags)
self.clog(" `-- %r" % (ap,))
return ret
def chdir(self, path: str) -> None:
nwd = join(self.cwd, path)
vfs, rem = self.hub.asrv.vfs.get(nwd, self.uname, False, False)
try:
vfs, rem = self.hub.asrv.vfs.get(nwd, self.uname, False, False)
except Exception as ex:
raise FSE(str(ex))
if not vfs.realpath:
self.cwd = nwd
return
@@ -327,10 +334,12 @@ class FtpFs(AbstractedFS):
self.cwd = nwd
def mkdir(self, path: str) -> None:
self.clog("mkdir(%r) @%s" % (path, self.uname))
ap, vfs, _ = self.rv2a(path, w=True)
bos.makedirs(ap, vf=vfs.flags) # filezilla expects this
def listdir(self, path: str) -> list[str]:
self.clog("ls(%r) @%s" % (path, self.uname))
vpath = join(self.cwd, path)
try:
ap, vfs, rem = self.v2a(vpath, True, False)
@@ -348,7 +357,10 @@ class FtpFs(AbstractedFS):
vfs_ls.extend(vfs_virt.keys())
if self.uname not in vfs.axs.udot:
vfs_ls = exclude_dotfiles(vfs_ls)
if "dothidden" in vfs.flags and ".hidden" in [x[0] for x in vfs_ls]:
vfs_ls = exclude_dothidden(vfs_ls, fsroot)
else:
vfs_ls = exclude_dotfiles(vfs_ls)
vfs_ls.sort()
return vfs_ls
@@ -377,6 +389,7 @@ class FtpFs(AbstractedFS):
return ret
def rmdir(self, path: str) -> None:
self.clog("rmdir(%r) @%s" % (path, self.uname))
ap = self.rv2a(path, d=True)[0]
try:
bos.rmdir(ap)
@@ -385,6 +398,7 @@ class FtpFs(AbstractedFS):
raise
def remove(self, path: str) -> None:
self.clog("rm(%r) @%s" % (path, self.uname))
if self.args.no_del:
raise FSE("The delete feature is disabled in server config")
@@ -395,6 +409,7 @@ class FtpFs(AbstractedFS):
raise FSE(str(ex))
def rename(self, src: str, dst: str) -> None:
self.clog("mv(%r,%r) @%s" % (src, dst, self.uname))
if self.args.no_mv:
raise FSE("The rename/move feature is disabled in server config")
@@ -424,6 +439,7 @@ class FtpFs(AbstractedFS):
return st
def utime(self, path: str, timeval: float) -> None:
self.clog("utime(%r,%r) @%s" % (path, timeval, self.uname))
ap = self.rv2a(path, w=True)[0]
bos.utime_c(logging.warning, ap, int(timeval), False)
@@ -431,6 +447,9 @@ class FtpFs(AbstractedFS):
ap = self.rv2a(path)[0]
return bos.stat(ap)
def readlink(self, *a) -> None:
raise OSError(errno.EINVAL, "nope")
def isfile(self, path: str) -> bool:
try:
st = self.stat(path)
@@ -473,9 +492,13 @@ class FtpFs(AbstractedFS):
def get_user_by_uid(self, uid: int) -> str:
return "root"
def get_group_by_uid(self, gid: int) -> str:
def get_group_by_gid(self, gid: int) -> str:
return "root"
def mkstemp(self, *a, **ka):
self.clog("mkstemp%r @%s" % (a, self.uname))
raise FSE("nope")
class FtpHandler(FTPHandler):
abstracted_fs = FtpFs
@@ -504,6 +527,7 @@ class FtpHandler(FTPHandler):
return
self.cli_ip = cip
self.log_src = "%s:FTP" % (cip,)
# abspath->vpath mapping to resolve log_transfer paths
self.vfs_map: dict[str, str] = {}
@@ -511,6 +535,15 @@ class FtpHandler(FTPHandler):
# reduce non-debug logging
self.log_cmds_list = [x for x in self.log_cmds_list if x not in ("CWD", "XCWD")]
def clog(self, msg: str, c: Union[int, str] = 0) -> None:
self.hub.log(self.log_src, msg, c)
def ftp_NOOP(self, *a) -> None:
self.respond("200 pong", logging.debug)
def ftp_STOU(self, *a) -> None:
self.respond("550 nope", logging.warning)
def ftp_STOR(self, file: str, mode: str = "w") -> Any:
# Optional[str]
vp = join(self.fs.cwd, file).lstrip("/")
@@ -523,7 +556,7 @@ class FtpHandler(FTPHandler):
xbu = vfs.flags.get("xbu")
if xbu:
hr = runhook(
None,
self.clog,
None,
self.hub.up2k,
"xbu.ftpd",
@@ -610,17 +643,25 @@ class Ftpd(object):
print(t.format(pybin))
sys.exit(1)
if self.args.certkey:
h1.keyfile = self.args.certkey
h1.certfile = self.args.cert
h1.tls_control_required = True
h1.tls_data_required = True
hs.append([h1, self.args.ftps])
zs = self.args.ftp_banner.replace("\\n", "\n")
if zs.startswith("@"):
zs = read_utf8(None, zs[1:], False)
banner = zs.replace("\r", "").replace("\n", "\r\n").strip()
for h_lp in hs:
h2, lp = h_lp
FtpHandler.hub = h2.hub = hub
FtpHandler.args = h2.args = hub.args
FtpHandler.authorizer = h2.authorizer = FtpAuth(hub)
FtpHandler.banner = banner
if self.args.ftp_pr:
p1, p2 = [int(x) for x in self.args.ftp_pr.split("-")]
+861 -261
View File
File diff suppressed because it is too large. Load diff
+34 -28
View File
@@ -1,32 +1,29 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse # typechk
import os
import re
import socket
import threading # typechk
import time
try:
if os.environ.get("PRTY_NO_TLS"):
raise Exception()
HAVE_SSL = True
import ssl
except:
HAVE_SSL = False
from . import util as Util
from .__init__ import TYPE_CHECKING, EnvParams
from .authsrv import AuthSrv # typechk
from .cert import NO_TLS
from .httpcli import HttpCli
from .ico import Ico
from .mtag import HAVE_FFMPEG
from .th_cli import ThumbCli
from .th_srv import HAVE_PIL, HAVE_VIPS
from .u2idx import U2idx
from .util import HMaccas, NetMap, shut_socket
from .util import HMaccas, NetMap, min_ex, shut_socket
try:
if NO_TLS:
raise Exception()
import ssl
HAVE_SSL = True
except:
HAVE_SSL = False
if True: # pylint: disable=using-constant-test
from typing import Optional, Pattern, Union
@@ -69,21 +66,15 @@ class HttpConn(object):
self.bans: dict[str, int] = hsrv.bans
self.aclose: dict[str, int] = hsrv.aclose
enth = (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb
self.thumbcli: Optional[ThumbCli] = ThumbCli(hsrv) if enth else None # mypy404
self.ico: Ico = Ico(self.args) # mypy404
self.t0: float = time.time() # mypy404
self.freshen_pwd: float = 0.0
self.stopping = False
self.nreq: int = -1 # mypy404
self.nbyte: int = 0 # mypy404
self.u2idx: Optional[U2idx] = None
self.lf_url: Optional[Pattern[str]] = self.args.lf_url
self.log_func: "Util.RootLogger" = hsrv.log # mypy404
self.log_src: str = "httpconn" # mypy404
self.lf_url: Optional[Pattern[str]] = (
re.compile(self.args.lf_url) if self.args.lf_url else None
) # mypy404
self.set_rproxy()
def shutdown(self) -> None:
@@ -167,7 +158,7 @@ class HttpConn(object):
try:
assert ssl # type: ignore # !rm
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain(self.args.cert)
ctx.load_cert_chain(self.args.cert, self.args.certkey)
if self.args.ssl_ver:
ctx.options &= ~self.args.ssl_flags_en
ctx.options |= self.args.ssl_flags_de
@@ -204,12 +195,12 @@ class HttpConn(object):
except Exception as ex:
em = str(ex)
if "ALERT_CERTIFICATE_UNKNOWN" in em:
# android-chrome keeps doing this
pass
if "ALERT_" in em:
self.log("client refused our TLS cert or config: " + em, c=6)
else:
self.log("handshake\033[0m " + em, c=5)
t = "https-handshake failed, probably due to client:\n"
self.log(t + min_ex(), c=5)
return
@@ -222,6 +213,21 @@ class HttpConn(object):
if not self.cli.run():
return
if self.sr.te == 1:
self.log("closing socket (leftover TE)", "90")
return
if (
"content-length" in self.cli.headers
and int(self.cli.headers["content-length"]) != self.sr.nb
):
self.log("closing socket (CL mismatch)", "90")
return
# note: proxies reject PUT sans Content-Length; illegal for HTTP/1.1
self.sr.nb = self.sr.te = 0
if self.u2idx:
self.hsrv.put_u2idx(str(self.addr), self.u2idx)
self.u2idx = None
+97 -7
View File
@@ -1,8 +1,7 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import hashlib
import math
import os
import re
import socket
@@ -55,8 +54,14 @@ except SyntaxError:
)
sys.exit(1)
from .authsrv import LEELOO_DALLAS
from .httpconn import HttpConn
from .ico import Ico
from .metrics import Metrics
from .mtag import HAVE_FFMPEG
from .sutil import gfilter2
from .th_cli import ThumbCli
from .th_srv import HAVE_PIL, HAVE_VIPS
from .u2idx import U2idx
from .util import (
E_SCK,
@@ -133,16 +138,20 @@ class HttpSrv(object):
self.bans: dict[str, int] = {}
self.aclose: dict[str, int] = {}
self.thumbcli: Optional[ThumbCli] = None
self.ico: Ico = Ico(self.args)
dli: dict[str, tuple[float, int, "VFS", str, str]] = {} # info
dls: dict[str, tuple[float, int]] = {} # state
self.dli = self.tdli = dli
self.dls = self.tdls = dls
self.iiam = '<img src="%s.cpr/iiam.gif?cache=i" />' % (self.args.SRS,)
self.iiam = '<img src="%s.cpr/w/iiam.gif?cache=i" />' % (self.args.SRS,)
self.bound: set[tuple[str, int]] = set()
self.name = "hsrv" + nsuf
self.mutex = threading.Lock()
self.u2mutex = threading.Lock()
self.bad_ver = False
self.stopping = False
self.tp_nthr = 0 # actual
@@ -170,6 +179,8 @@ class HttpSrv(object):
self.u2idx_free: dict[str, U2idx] = {}
self.u2idx_n = 0
self.wopi_files: dict[str, dict[str, Any]] = {}
assert jinja2 # type: ignore # !rm
env = jinja2.Environment()
env.loader = jinja2.FunctionLoader(lambda f: load_jinja2_resource(self.E, f))
@@ -185,9 +196,11 @@ class HttpSrv(object):
"shares",
"splash",
"svcs",
"wopi",
]
self.j2 = {x: env.get_template(x + ".html") for x in jn}
self.j2["opds"] = env.get_template("opds.xml")
self.j2["opds_osd"] = env.get_template("opds_osd.xml")
self.prism = has_resource(self.E, "web/deps/prism.js.gz")
if self.args.ipu:
@@ -228,16 +241,23 @@ class HttpSrv(object):
if self.args.log_thrs:
start_log_thrs(self.log, self.args.log_thrs, nid)
self.th_cfg: dict[str, set[str]] = {}
Daemon(self.post_init, "hsrv-init2")
if (HAVE_PIL or HAVE_VIPS or HAVE_FFMPEG) and not self.args.no_thumb:
Daemon(self.post_init, "hsrv-init2")
def post_init(self) -> None:
try:
x = self.broker.ask("thumbsrv.getcfg")
self.th_cfg = x.get()
self.thumbcli = ThumbCli(self, x.get())
except:
pass
def set_th_cfg(self, c: dict[str, set[str]], opts: tuple[bool]) -> None:
self.args.th_no_jxl = opts[0]
self.thumbcli = ThumbCli(self, c)
def set_bad_ver(self) -> None:
self.bad_ver = True
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
ips = set()
for ip, _ in self.bound:
@@ -297,7 +317,7 @@ class HttpSrv(object):
self.srvs.append(sck)
self.bound.add((ip, port))
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
self.nclimax = -int(-self.args.nc // nlisteners)
Daemon(
self.thr_listen,
"httpsrv-n{}-listen-{}-{}".format(self.nid or "0", ip, port),
@@ -641,3 +661,73 @@ class HttpSrv(object):
self.tdli = dli
self.tdls = dls
def pregen_thumbs(self) -> None:
Daemon(self._pregen_thumbs, "th_pregen")
def _pregen_thumbs(self) -> None:
def log(msg, n):
self.log("thumb-pregen", msg, n)
if getattr(self, "_pregen", False):
log("already running", 1)
return
self._pregen = True
for n in range(9999999):
x = self.broker.ask("up2k.is_busy")
zb, zi = x.get()
if zi:
break
if not n:
log("waiting for up2k to finish initializing", 6)
time.sleep(1 if n < 10 else 5 if n < 300 else 15)
if not self.thumbcli:
log("no thumbcli", 1)
return
if self.args.th_pre_rl:
try:
self.broker.hub.thumbsrv.log = self.broker.hub.thumbsrv._slog
except:
pass
nfiles = 0
t0 = time.time()
scandir = not self.args.no_scandir
for vn in self.asrv.vfs.all_nodes.values():
fmts = vn.flags.get("th_pregen", "")
if not fmts:
continue
log("starting for volume /%s" % (vn.vpath,), 6)
g = vn.walk("", "/", [], LEELOO_DALLAS, [[True]], 2, scandir, False, False)
g = gfilter2(g, self, vn.vpath, fmts.split(","))
for f in g:
nfiles += 1
if not nfiles % 256:
now = time.time()
for n in range(9999999):
x = self.broker.ask("up2k.is_busy")
zb, zi = x.get()
if not zb:
if n:
t0 += time.time() - now
break
if not n:
log("waiting for up2k to finish indexing", 6)
time.sleep(5)
if self.args.th_pre_rl:
try:
self.broker.hub.thumbsrv.log = self.broker.hub.thumbsrv._log
except:
pass
t = "finished; %d files in %d seconds"
log(t % (nfiles, time.time() - t0), 6)
self._pregen = False
if self.args.exit == "thgen":
self.broker.say("sigterm")
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse # typechk
import colorsys
+3 -4
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import errno
import os
@@ -16,7 +16,7 @@ from .multicast import MC_Sck, MCast
from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_DNSLIB"):
if os.environ.get("PRTY_SYS_ALL") or os.environ.get("PRTY_SYS_DNSLIB"):
raise ImportError()
from .stolen.dnslib import (
AAAA,
@@ -51,7 +51,6 @@ except ImportError:
SRV,
TXT,
A,
Bimap,
DNSHeader,
DNSQuestion,
DNSRecord,
@@ -66,7 +65,7 @@ if TYPE_CHECKING:
if True: # pylint: disable=using-constant-test
from typing import Any, Optional, Union
if os.getenv("PRTY_MODSPEC"):
if os.environ.get("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: dnslib:", getsourcefile(A))
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import json
import time
+79 -26
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import json
@@ -15,8 +15,12 @@ from .authsrv import VFS
from .bos import bos
from .util import (
FFMPEG_URL,
LOG,
REKOBO_LKEY,
SCWD,
VF_CAREFUL,
absreal,
expand_osenv_c,
fsenc,
gzip,
min_ex,
@@ -26,6 +30,9 @@ from .util import (
sfsenc,
uncyg,
wunlink,
zip_fi,
zip_lim,
zip_readf,
)
if True: # pylint: disable=using-constant-test
@@ -45,29 +52,42 @@ except:
HAVE_MUTAGEN = False
def have_ff(scmd: str) -> bool:
if ANYWIN:
def have_ff(name: str) -> list[bytes]:
uname = name.upper()
if os.environ.get("PRTY_NO_" + uname):
return []
ebin = os.environ.get("PRTY_%s_BIN" % (uname,))
try:
scmd = (ebin or name).decode("utf-8")
except:
scmd: str = ebin or name
if ANYWIN and not ebin:
scmd += ".exe"
if PY2:
print("# checking {}".format(scmd))
acmd = (scmd + " -version").encode("ascii").split(b" ")
print("# checking %s" % (scmd,))
bcmd = scmd.encode("utf-8")
try:
sp.Popen(acmd, stdout=sp.PIPE, stderr=sp.PIPE).communicate()
return True
sp.Popen([bcmd, b"-version"], stdout=sp.PIPE, stderr=sp.PIPE).communicate()
return [bcmd]
except:
return False
return []
else:
return bool(shutil.which(scmd))
ret = shutil.which(scmd)
return [absreal(ret).encode("utf-8")] if ret else []
HAVE_FFMPEG = not os.environ.get("PRTY_NO_FFMPEG") and have_ff("ffmpeg")
HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
HAVE_FFMPEG = have_ff("ffmpeg")
HAVE_FFPROBE = have_ff("ffprobe")
TH_BWRAP = []
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif jxl".split())
CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
FMT_AU = set("mp3 ogg flac wav".split())
M4A = set("aac m4a m4b m4r".split())
class MParser(object):
@@ -85,7 +105,7 @@ class MParser(object):
while True:
try:
bp = os.path.expanduser(args)
bp = os.path.expanduser(expand_osenv_c(args))
if WINDOWS:
bp = uncyg(bp)
@@ -132,6 +152,7 @@ class MParser(object):
def au_unpk(
log: "NamedLogger", fmt_map: dict[str, str], abspath: str, vn: Optional[VFS] = None
) -> str:
fd = 0
ret = ""
maxsz = 1024 * 1024 * 64
try:
@@ -156,7 +177,7 @@ def au_unpk(
zil = [x for x in zil if x.filename.lower().split(".")[-1] == au]
if not zil:
raise Exception("no audio inside zip")
fi = zf.open(zil[0])
fi = zf.open(zip_lim(zil[0], maxsz))
elif pk == "cbz":
import zipfile
@@ -170,11 +191,18 @@ def au_unpk(
t = "cbz: %d files, %d hits" % (nf, len(znil))
if not znil:
raise Exception("no images inside cbz")
using = sorted(znil)[0][1].filename
zi = sorted(znil)[0][1]
if znil:
t += ", using " + using
t += ", using " + zi.filename
log(t)
fi = zf.open(using)
fi = zf.open(zip_lim(zi, maxsz))
elif pk == "kra" or pk == "ora":
import zipfile
zf = zipfile.ZipFile(abspath, "r")
zi = zip_fi(zf, "mergedimage.png", maxsz)
fi = zf.open(zi)
elif pk == "epub":
fi = get_cover_from_epub(log, abspath)
@@ -185,6 +213,7 @@ def au_unpk(
fsz = 0
with os.fdopen(fd, "wb") as fo:
fd = 0
while True:
buf = fi.read(32768)
if not buf:
@@ -199,6 +228,8 @@ def au_unpk(
return ret
except Exception as ex:
if fd:
os.close(fd)
if ret:
t = "failed to decompress file %r: %r"
log(t % (abspath, ex))
@@ -208,18 +239,37 @@ def au_unpk(
return abspath
def bwrap(prog: bytes, ap_in: bytes, ap_out: bytes) -> list[bytes]:
if not TH_BWRAP:
return [prog]
ret = TH_BWRAP + [b"--ro-bind", prog, prog, b"--ro-bind", ap_in, ap_in]
if ap_out:
zs = ap_out.rsplit(b"/", 1)[0]
ret += [b"--bind", zs, zs]
ret.append(prog)
return ret
def bwrap_fail(serr: str) -> None:
if "bwrap:" in serr:
LOG[0]("root", "ffmpeg failed due to --th-bwrap;\n " + serr, 3)
def ffprobe(
abspath: str, timeout: int = 60
) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]:
cmd = [
b"ffprobe",
# ffprobe -hide_banner -show_streams -show_format --
bap = fsenc(abspath)
cmd = bwrap(HAVE_FFPROBE[0], bap, b"") + [
b"-hide_banner",
b"-show_streams",
b"-show_format",
b"--",
fsenc(abspath),
bap,
]
rc, so, se = runcmd(cmd, timeout=timeout, nice=True, oom=200)
rc, so, se = runcmd(cmd, cwd=SCWD, timeout=timeout, nice=True, oom=200)
if rc and TH_BWRAP:
bwrap_fail(se)
retchk(rc, cmd, se)
return parse_ffprobe(so)
@@ -259,7 +309,7 @@ def parse_ffprobe(
md: dict[str, list[Any]] = {} # raw tags
is_audio = fmt.get("format_name") in FMT_AU
if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
if fmt.get("filename", "").split(".")[-1].lower() in M4A:
is_audio = True
# if audio file, ensure audio stream appears first
@@ -392,7 +442,8 @@ def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]
with zipfile.ZipFile(abspath, "r") as z:
# First open the container file to find the package document (.opf file)
try:
container_root = parse_xml(z.read("META-INF/container.xml").decode())
zb = zip_readf(z, "META-INF/container.xml", 2 << 20)
container_root = parse_xml(zb.decode())
except KeyError:
log("epub: no container file found in %s" % (abspath,))
return None
@@ -406,7 +457,8 @@ def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]
# Then open the first package document to find the path of the cover image
try:
package_root = parse_xml(z.read(rootfile_path).decode())
zb = zip_readf(z, rootfile_path, 2 << 20)
package_root = parse_xml(zb.decode())
except KeyError:
log("epub: no package document found in %s" % (abspath,))
return None
@@ -430,7 +482,8 @@ def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]
adjusted_cover_path = urljoin(rootfile_path, coverimage_path)
try:
return z.open(adjusted_cover_path)
zi = zip_fi(z, adjusted_cover_path, 2 << 24)
return z.open(zi)
except KeyError:
t = "epub: cover specified in package document, but doesn't exist: %s"
log(t % (adjusted_cover_path,))
@@ -666,7 +719,7 @@ class MTag(object):
zb = os.getxattr(abspath, xattr)
ret[xattr] = zb.decode("utf-8", "replace")
except:
self.log("failed to read xattrs from [%s]\n%s", abspath, min_ex(), 3)
self.log("failed to read xattrs from [%s]\n%s" % (abspath, min_ex()), 3)
elif "db_xattr_yes" in vf:
for xattr in vf["db_xattr_yes"]:
if "=" in xattr:
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import socket
import time
+3 -2
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import base64
@@ -14,8 +14,9 @@ try:
if os.environ.get("PRTY_NO_ARGON2"):
raise Exception()
HAVE_ARGON2 = True
from argon2 import exceptions as argon2ex
HAVE_ARGON2 = True
except:
HAVE_ARGON2 = False
+3 -3
View File
@@ -1,10 +1,10 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_QRCG"):
if os.environ.get("PRTY_SYS_ALL") or os.environ.get("PRTY_SYS_QRCG"):
raise ImportError()
from .stolen.qrcodegen import QrCode
@@ -14,7 +14,7 @@ except ImportError:
VENDORED = False
from qrcodegen import QrCode
if os.getenv("PRTY_MODSPEC"):
if os.environ.get("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: qrcode:", getsourcefile(QrCode))
+109 -39
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import errno
import hashlib
@@ -8,7 +8,6 @@ import os
import select
import socket
import time
from threading import ExceptHookArgs
import paramiko
import paramiko.common
@@ -21,6 +20,7 @@ from paramiko.sftp import (
SFTP_OP_UNSUPPORTED,
SFTP_PERMISSION_DENIED,
)
from queue import Queue
from .__init__ import ANYWIN, TYPE_CHECKING
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
@@ -30,10 +30,13 @@ from .util import (
Daemon,
ODict,
Pebkac,
exclude_dotfiles,
exclude_dotfiles_ls,
exclude_dothidden,
exclude_dothidden_ls,
ipnorm,
min_ex,
read_utf8,
relchk,
runhook,
sanitize_fn,
ub64enc,
@@ -51,6 +54,23 @@ if True: # pylint: disable=using-constant-test
SATTR = paramiko.sftp_attr.SFTPAttributes
try:
from paramiko.rsakey import serialization
_load_der_pk = serialization.load_der_private_key
def fastloader(*a, **ka):
ka0 = ka.copy()
try:
ka["unsafe_skip_rsa_key_validation"] = True
return _load_der_pk(*a, **ka)
except:
return _load_der_pk(*a, **ka0)
except:
pass
class SSH_Srv(paramiko.ServerInterface):
def __init__(self, hub: "SvcHub", addr: Any):
self.hub = hub
@@ -291,6 +311,17 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
if self.uname == LEELOO_DALLAS:
raise Exception("send her back")
cm = 0
for zs, zi in (
("s", SATTR.FLAG_SIZE),
("t", SATTR.FLAG_AMTIME),
("p", SATTR.FLAG_PERMISSIONS),
("u", SATTR.FLAG_UIDGID),
):
if zs in self.args.sftp_chattr:
cm |= zi
self.chattr_mask = cm
self.vols = [
vp
for vp, vn in self.asrv.vfs.all_vols.items()
@@ -316,15 +347,23 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
m: bool = False,
d: bool = False,
) -> tuple[str, VFS, str]:
vpath = vpath.replace(os.sep, "/").strip("/")
rd, fn = os.path.split(vpath)
if relchk(rd):
self.log("malicious vpath: %s", vpath)
raise Exception("Unsupported characters in [%s]" % (vpath,))
# because:
# * maybe some clients use \
# * windows filename limitations
# need to allow/translate illegal fn,
# but require filesystem-correct rd
if ANYWIN or ("\\" in vpath and "/" not in vpath):
vpath = vpath.replace("\\", "/")
vpath = vpath.strip("/\\")
if ANYWIN:
rd, fn = os.path.split(vpath)
fn = sanitize_fn(fn, "win")
vpath = vjoin(rd, fn)
# end of windows jank
fn = sanitize_fn(fn or "")
vpath = vjoin(rd, fn)
vn, rem = self.hub.asrv.vfs.get(vpath, self.uname, r, w, m, d)
vpath = vjoin(vn.vpath, rem)
rd, fn = os.path.split(vpath)
if (
w
and fn.lower() in vn.flags["emb_all"]
@@ -339,21 +378,12 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
# return "", vn, rem
raise OSError(errno.ENOENT, "no filesystem mounted at [/%s]" % (vpath,))
if "xdev" in vn.flags or "xvol" in vn.flags:
ap = vn.canonical(rem)
avn = vn.chk_ap(ap)
t = "Permission denied in [{}]"
if not avn:
raise OSError(errno.EPERM, "permission denied in [/%s]" % (vpath,))
cr, cw, cm, cd, _, _, _, _, _ = avn.uaxs[self.uname]
if r and not cr or w and not cw or m and not cm or d and not cd:
raise OSError(errno.EPERM, "permission denied in [/%s]" % (vpath,))
ap = vn.canonical(rem, False)
if "bcasechk" in vn.flags and not vn.casechk(rem, True):
raise OSError(errno.ENOENT, "file does not exist case-sensitively")
return os.path.join(vn.realpath, rem), vn, rem
return ap, vn, rem
def list_folder(self, path: str) -> list[SATTR] | int:
try:
@@ -399,21 +429,27 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
self.log("ls(%s): vfs-vols; |%d|" % (path, len(ret)))
return ret
_, vfs_ls, vfs_virt = vn.ls(
fsroot, vfs_ls, vfs_virt = vn.ls(
rem,
self.uname,
not self.args.no_scandir,
[[True, False], [False, True]],
throw=True,
)
vnames = list(vfs_virt)
if self.uname not in vn.axs.udot:
if "dothidden" in vn.flags and ".hidden" in [x[0] for x in vfs_ls]:
vfs_ls = exclude_dothidden_ls(vfs_ls, fsroot)
vnames = exclude_dothidden(vnames, fsroot)
else:
vfs_ls = exclude_dotfiles_ls(vfs_ls)
vnames = exclude_dotfiles(vnames)
ret = [SATTR.from_stat(x[1], filename=x[0]) for x in vfs_ls]
for zs, vn2 in vfs_virt.items():
if not vn2.realpath:
if not vn2.realpath or zs not in vnames:
continue
st = bos.stat(vn2.realpath)
ret.append(SATTR.from_stat(st, filename=zs))
if self.uname not in vn.axs.udot:
ret = [x for x in ret if not x.filename.split("/")[-1].startswith(".")]
ret.sort(key=lambda x: x.filename)
self.log("ls(%s): |%d|" % (path, len(ret)))
return ret
@@ -483,8 +519,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
smode = "rb"
try:
vn, rem = self.asrv.vfs.get(vp, self.uname, rd, wr)
ap = os.path.join(vn.realpath, rem)
ap, vn, rem = self.v2a(vp, rd, wr)
vf = vn.flags
except Pebkac as ex:
t = "denied open file [%s], iflag=%s, read=%s, write=%s: %s"
@@ -656,8 +691,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
def _mkdir(self, vp: str, attr: SATTR) -> int:
self.log("mkdir(%s)" % (vp,))
try:
vn, rem = self.asrv.vfs.get(vp, self.uname, False, True)
ap = os.path.join(vn.realpath, rem)
ap, vn, _ = self.v2a(vp, w=True)
bos.makedirs(ap, vf=vn.flags) # filezilla expects this
if attr is not None:
paramiko.SFTPServer.set_file_attr(ap, attr)
@@ -680,8 +714,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
def _rmdir(self, vp: str) -> int:
self.log("rmdir(%s)" % (vp,))
try:
vn, rem = self.asrv.vfs.get(vp, self.uname, False, False, will_del=True)
ap = os.path.join(vn.realpath, rem)
ap, _, _ = self.v2a(vp, d=True)
bos.rmdir(ap)
return SFTP_OK
except Pebkac as ex:
@@ -702,8 +735,8 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
def _chattr(self, vp: str, attr: SATTR) -> int:
self.log("chattr(%s, %s)" % (vp, attr))
try:
vn, rem = self.asrv.vfs.get(vp, self.uname, False, True, will_del=True)
ap = os.path.join(vn.realpath, rem)
attr._flags &= self.chattr_mask
ap, _, _ = self.v2a(vp, w=True, d=True)
paramiko.SFTPServer.set_file_attr(ap, attr)
return SFTP_OK
except Pebkac as ex:
@@ -724,6 +757,22 @@ class SFTP_Srv(paramiko.SFTPServerInterface):
return "/%s" % (undot(path),)
class Transport2(paramiko.Transport):
def __init__(self, cli, addr):
self.addr = addr
super(Transport2, self).__init__(cli)
def _check_banner(self):
try:
return super()._check_banner()
except Exception as ex:
if "Error reading SSH protocol bann" in str(ex):
self.logger.warning("%s rejected: %s" % (self.addr, ex))
self.addr = None
else:
raise
class Sftpd(object):
def __init__(self, hub: "SvcHub") -> None:
self.hub = hub
@@ -731,6 +780,7 @@ class Sftpd(object):
self.log_func = hub.log
self.srv: list[socket.socket] = []
self.bound: list[str] = []
self.hs_q = Queue()
self.sessions = {}
ips = args.sftp_i
@@ -746,6 +796,10 @@ class Sftpd(object):
self.log("cannot start sftp-server; no compatible IPs in -i", 1)
return
if args.sftp_fastldr:
serialization.load_der_private_key = fastloader # type: ignore
else:
self.log("loading hostkeys...")
self.hostkeys = []
hostkeytypes = (
("ed25519", "Ed25519Key", {}), # best
@@ -773,6 +827,8 @@ class Sftpd(object):
self.hostkeys.append(pkey)
if args.sftpv:
self.log("loaded hostkey %r" % (pkey,))
if args.sftp_fastldr:
serialization.load_der_private_key = _load_der_pk # type: ignore
ips = list(ODict.fromkeys(ips)) # dedup
@@ -781,6 +837,9 @@ class Sftpd(object):
self.log("listening @ %s port %s" % (self.bound, args.sftp))
for n in range(self.args.sftp_hs_n):
Daemon(self.acceptor, "sftp-hs-%d" % (n,))
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.hub.log("sftp", msg, c)
@@ -805,6 +864,10 @@ class Sftpd(object):
self.bound.append(ip)
except Exception as ex:
if ip == "0.0.0.0" and "::" in self.bound:
try:
srv.close() # type: ignore
except:
pass
return # dualstack
self.log("could not listen on (%s,%s): %r" % (ip, port, ex), 3)
@@ -812,12 +875,18 @@ class Sftpd(object):
cli, addr = srv.accept()
# cli.settimeout(0) # == srv.setblocking(False)
self.log("%r is connecting" % (addr,))
zs = "sftp-%s" % (addr[0],)
# Daemon(self._accept2, zs, (cli, addr))
self._accept2(cli, addr)
self.hs_q.put((cli, addr))
def acceptor(self) -> None:
while True:
try:
cli, addr = self.hs_q.get()
self._accept2(cli, addr)
except:
time.sleep(1)
def _accept2(self, cli, addr) -> None:
tra = paramiko.Transport(cli)
tra = Transport2(cli, addr)
for hkey in self.hostkeys:
tra.add_server_key(hkey)
tra.set_subsystem_handler("sftp", paramiko.SFTPServer, SFTP_Srv)
@@ -825,11 +894,12 @@ class Sftpd(object):
try:
tra.start_server(server=psrv)
except Exception as ex:
self.log("%r could not establish connection: %r" % (addr, ex), 3)
if tra.addr:
self.log("%r could not establish connection: %r" % (addr, ex), 3)
cli.close()
return
chan = tra.accept()
chan = tra.accept(timeout=self.args.sftp_hs_t)
if chan is None:
self.log("%r did not open an sftp channel" % (addr,), 3)
cli.close()
+8 -5
View File
@@ -12,7 +12,7 @@ from types import SimpleNamespace
from .__init__ import ANYWIN, EXE, TYPE_CHECKING
from .authsrv import LEELOO_DALLAS, VFS
from .bos import bos
from .util import Daemon, absreal, min_ex, pybin, runhook, vjoin
from .util import Daemon, absreal, min_ex, pybin, runhook
if True: # pylint: disable=using-constant-test
from typing import Any, Union
@@ -89,13 +89,15 @@ class SMB(object):
smbserver.isInFileJail = self._is_in_file_jail
self._disarm()
ip = next((x for x in self.args.smb_i if ":" not in x), None)
zs = " " if self.args.smb6 else ":"
ip = next((x for x in self.args.smb_i if zs not in x), None)
if not ip:
self.log("smb", "IPv6 not supported for SMB; listening on 0.0.0.0", 3)
self.log("smb", "IPv6 not enabled with --smb6; listening on 0.0.0.0", 3)
ip = "0.0.0.0"
port = int(self.args.smb_port)
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
kw = {"ipv6": True} if ":" in ip else {}
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port, **kw)
try:
if self.accs:
srv.setAuthCallback(self._auth_cb)
@@ -121,6 +123,7 @@ class SMB(object):
self.srv = srv
self.stop = srv.stop
ip = "[%s]" % (ip,) if kw else ip
self.log("smb", "listening @ {}:{}".format(ip, port))
def nlog(self, msg: str, c: Union[int, str] = 0) -> None:
@@ -191,7 +194,7 @@ class SMB(object):
vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
if not vfs.realpath:
raise Exception("unmapped vfs")
return vfs, vjoin(vfs.realpath, rem)
return vfs, vfs.canonical(rem, False)
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
vpath = vpath.replace("\\", "/").lstrip("/")
+1 -2
View File
@@ -1,11 +1,10 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import errno
import re
import select
import socket
import time
from .__init__ import TYPE_CHECKING
from .multicast import MC_Sck, MCast
+1 -1
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import re
import stat
+15 -6
View File
@@ -8,6 +8,7 @@ https://github.com/pydron/ifaddr/tree/0.2.0
"""
import os
import platform
from ._shared import IP, Adapter
@@ -16,20 +17,28 @@ def nope(include_unconfigured=False):
return []
try:
S390X = os.uname().machine == "s390x"
except:
S390X = False
host_os = platform.system()
machine = platform.machine()
py_impl = platform.python_implementation()
if os.environ.get("PRTY_NO_IFADDR") or S390X:
if os.environ.get("PRTY_NO_IFADDR"):
get_adapters = nope
elif machine in ("s390x",) or host_os in ("IRIX32",):
# s390x deadlocks at libc.getifaddrs
# irix libc does not have getifaddrs at all
print("ifaddr unavailable; can't determine LAN IP: unsupported OS")
get_adapters = nope
elif py_impl in ("GraalVM",):
print("ifaddr unavailable; can't determine LAN IP: unsupported interpreter")
get_adapters = nope
elif os.name == "nt":
from ._win32 import get_adapters
elif os.name == "posix":
from ._posix import get_adapters
else:
raise RuntimeError("Unsupported Operating System: %s" % os.name)
print("ifaddr unavailable; can't determine LAN IP: unsupported OS")
get_adapters = nope
__all__ = ["Adapter", "IP", "get_adapters"]
+8 -1
View File
@@ -2,6 +2,7 @@
from __future__ import print_function, unicode_literals
import ctypes
import sys
from ctypes import wintypes
if True: # pylint: disable=using-constant-test
@@ -9,6 +10,9 @@ if True: # pylint: disable=using-constant-test
from . import _shared as shared
WINVER = sys.getwindowsversion()
LE_XP = WINVER.major < 6
NO_ERROR = 0
ERROR_BUFFER_OVERFLOW = 111
MAX_ADAPTER_NAME_LENGTH = 256
@@ -83,7 +87,10 @@ def enumerate_interfaces_of_adapter(
t = "sockaddr_to_ip({}) returned None"
raise Exception(t.format(address.Address.lpSockaddr))
network_prefix = address.OnLinkPrefixLength
if LE_XP:
network_prefix = 16 if str(ip).startswith("169.254") else 24
else:
network_prefix = address.OnLinkPrefixLength
yield shared.IP(ip, network_prefix, nice_name)
+85 -10
View File
@@ -1,23 +1,27 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
import tempfile
from datetime import datetime
from .__init__ import CORES
from .authsrv import VFS, AuthSrv
from .__init__ import CORES, TYPE_CHECKING
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
from .bos import bos
from .th_cli import ThumbCli
from .util import UTC, vjoin, vol_san
from .th_srv import TH_CH
from .util import UTC, sigblock, vjoin, vol_san
if True: # pylint: disable=using-constant-test
from typing import Any, Generator, Optional
from .util import NamedLogger
if TYPE_CHECKING:
from httpsrv import HttpSrv
TAR_NO_OPUS = set("aac|m4a|mp3|oga|ogg|opus|wma".split("|"))
TAR_NO_OPUS = set("aac|m4a|m4b|m4r|mp3|oga|ogg|opus|wma".split("|"))
class StreamArc(object):
@@ -41,20 +45,33 @@ class StreamArc(object):
self.stopped = True
_pools = {}
def close_pools() -> None:
for p in list(_pools):
try:
p.shutdown(wait=False, cancel_futures=True)
except:
pass
def gfilter(
fgen: Generator[dict[str, Any], None, None],
thumbcli: ThumbCli,
uname: str,
vtop: str,
vname: str,
fmt: str,
) -> Generator[dict[str, Any], None, None]:
from concurrent.futures import ThreadPoolExecutor
pend = []
with ThreadPoolExecutor(max_workers=CORES) as tp:
with ThreadPoolExecutor(max_workers=CORES, initializer=sigblock) as tp:
_pools[tp] = 1
try:
for f in fgen:
task = tp.submit(enthumb, thumbcli, uname, vtop, f, fmt)
task = tp.submit(enthumb, thumbcli, uname, vtop, vname, f, fmt)
pend.append((task, f))
if pend[0][0].done() or len(pend) > CORES * 4:
task, f = pend.pop(0)
@@ -78,24 +95,82 @@ def gfilter(
except:
pass
thumbcli.log("gfilter flushed")
_pools.pop(tp, None)
def gfilter2(
fgen: Generator[
tuple[
"VFS",
str,
str,
str,
list[tuple[str, os.stat_result]],
list[tuple[str, os.stat_result]],
dict[str, "VFS"],
],
None,
None,
],
hsrv: "HttpSrv",
vtop: str,
fmts: list[str],
) -> Generator[dict[str, Any], None, None]:
from concurrent.futures import ThreadPoolExecutor
pend = []
with ThreadPoolExecutor(max_workers=CORES, initializer=sigblock) as tp:
_pools[tp] = 1
for _, _, vpath, apath, files, rd, vd in fgen:
if "/.hist/" in vpath:
continue
fnames = [n[0] for n in files]
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
for vp, fi in zip(vpaths, files):
for fmt in fmts:
try:
f = {"vp": vp, "st": fi[1]}
task = tp.submit(
enthumb, hsrv.thumbcli, LEELOO_DALLAS, vtop, "", f, fmt
)
pend.append((task, f))
if pend[0][0].done() or len(pend) > CORES * 4:
task, f = pend.pop(0)
try:
f = task.result(600)
except:
pass
yield f
except:
pass
for task, f in pend:
try:
f = task.result(600)
except:
pass
yield f
_pools.pop(tp, None)
def enthumb(
thumbcli: ThumbCli, uname: str, vtop: str, f: dict[str, Any], fmt: str
thumbcli: ThumbCli, uname: str, vtop: str, vname: str, f: dict[str, Any], fmt: str
) -> dict[str, Any]:
rem = f["vp"]
ext = rem.rsplit(".", 1)[-1].lower()
if (fmt == "mp3" and ext == "mp3") or (fmt == "opus" and ext in TAR_NO_OPUS):
raise Exception()
vp = vjoin(vtop, rem.split("/", 1)[1])
if vname:
vp = vjoin(vtop, rem.split("/", 1)[1])
else:
vp = vjoin(vtop, rem)
vn, rem = thumbcli.asrv.vfs.get(vp, uname, True, False)
dbv, vrem = vn.get_dbv(rem)
thp = thumbcli.get(dbv, vrem, f["st"].st_mtime, fmt)
if not thp:
raise Exception()
ext = "jpg" if fmt == "j" else "webp" if fmt == "w" else fmt
ext = fmt if fmt == "wav" else TH_CH.get(fmt[:1], fmt)
sz = bos.path.getsize(thp)
st: os.stat_result = f["st"]
ts = st.st_mtime
+513 -144
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import calendar
import stat
@@ -104,7 +104,7 @@ def gen_hdr(
ret += spack(b"<LL", vsz, vsz)
# windows support (the "?" replace below too)
fn = sanitize_to(fn, VPTL_WIN)
fn = sanitize_to(fn, VPTL_WIN, "win")
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
# add ntfs (0x24) and/or unix (0x10) extrafields for utc, add z64 if requested
+15 -20
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import os
import re
@@ -7,7 +7,7 @@ import socket
import sys
import time
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
from .__init__ import ANYWIN, OPENBSD, PY2, TYPE_CHECKING, UNIX, unicode
from .cert import gencert
from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
from .util import (
@@ -21,7 +21,8 @@ from .util import (
VF_CAREFUL,
Netdev,
atomic_move,
get_adapters,
chkcmd,
list_nics,
min_ex,
sunpack,
termsize,
@@ -86,6 +87,9 @@ class TcpSrv(object):
successful_binds = 0
try:
for ipa in ips:
if ipa == "no":
self.args.ign_ebind_all = True
continue
try:
self._listen(ipa, port)
ok[ipa].append(port)
@@ -460,23 +464,7 @@ class TcpSrv(object):
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
nics = get_adapters(True)
eps: dict[str, Netdev] = {}
for nic in nics:
for nip in nic.ips:
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
sip = "{}/{}".format(ipa, nip.network_prefix)
nd = Netdev(sip, nic.index or 0, nic.nice_name, "")
eps[sip] = nd
try:
idx = socket.if_nametoindex(nd.name)
if idx and idx != nd.idx:
t = "netdev idx mismatch; ifaddr={} cpython={}"
self.log("tcpsrv", t.format(nd.idx, idx), 3)
nd.idx = idx
except:
pass
eps = list_nics()
netlist = str(sorted(eps.items()))
if netlist == self.netlist and self.netdevs:
return {}
@@ -510,6 +498,13 @@ class TcpSrv(object):
return eps
def _extdevs_nix(self) -> Generator[str, None, None]:
if UNIX:
so, _ = chkcmd(["netstat", "-nrf", "inet"])
for ln in so.split("\n"):
if not ln.startswith("default"):
continue
yield ln.split()[7] if OPENBSD else ln.split()[3]
return
with open("/proc/net/route", "rb") as f:
next(f)
for ln in f:
+12 -3
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
try:
from types import SimpleNamespace
@@ -42,6 +42,7 @@ from .util import (
Daemon,
ODict,
exclude_dotfiles,
exclude_dothidden,
min_ex,
runhook,
set_fperms,
@@ -274,7 +275,11 @@ class Tftpd(object):
if not vfs.realpath:
raise Exception("unmapped vfs")
return vfs, vpath, vfs.canonical(rem)
ap = vfs.canonical(rem)
if "bcasechk" in vfs.flags and not vfs.casechk(rem, True):
raise Exception("file not found")
return vfs, vpath, ap
def _ls(self, vpath: str, raddress: str, rport: int, force=False) -> Any:
# generate file listing if vpath is dir.txt and return as file object
@@ -318,7 +323,11 @@ class Tftpd(object):
ls = virs + reals
if "*" not in vn.axs.udot:
names = set(exclude_dotfiles([x[2] for x in ls]))
zsl = [x[2] for x in ls]
if "dothidden" in vn.flags and ".hidden" in zsl:
names = set(exclude_dothidden(zsl, fsroot))
else:
names = set(exclude_dotfiles(zsl))
ls = [x for x in ls if x[2] in names]
try:
+38 -33
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import errno
import os
@@ -8,7 +8,7 @@ import stat
from .__init__ import TYPE_CHECKING
from .authsrv import VFS
from .bos import bos
from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
from .th_srv import EXTS_AC, H_PIL_JXL, H_PIL_WEBP, thumb_path
from .util import Cooldown, Pebkac
if True: # pylint: disable=using-constant-test
@@ -20,28 +20,21 @@ if TYPE_CHECKING:
IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r"
IMG_EXTS = set(["webp", "jpg", "png", "jxl"])
class ThumbCli(object):
def __init__(self, hsrv: "HttpSrv") -> None:
def __init__(self, hsrv: "HttpSrv", c: dict[str, set[str]]) -> None:
self.broker = hsrv.broker
self.log_func = hsrv.log
self.args = hsrv.args
self.asrv = hsrv.asrv
# cache on both sides for less broker spam
self.cooldown = Cooldown(self.args.th_poke)
try:
c = hsrv.th_cfg
if not c:
raise Exception()
except:
c = {
k: set()
for k in ["thumbable", "pil", "vips", "raw", "ffi", "ffv", "ffa"]
}
self.cooldown = Cooldown(self.args.th_poke) if self.args.th_poke else None
self.thumbable = c["thumbable"]
self.thumbable_native = c["thumbable_native"]
self.fmt_pil = c["pil"]
self.fmt_vips = c["vips"]
self.fmt_raw = c["raw"]
@@ -50,8 +43,9 @@ class ThumbCli(object):
self.fmt_ffa = c["ffa"]
# defer args.th_ff_jpg, can change at runtime
d = next((x for x in self.args.th_dec if x in ("vips", "pil")), None)
self.can_webp = HAVE_WEBP or d == "vips"
nonpil = next((x for x in self.args.th_dec if x in ("vips", "ff")), None)
self.can_webp = (H_PIL_WEBP or nonpil) and not self.args.th_no_webp
self.can_jxl = (H_PIL_JXL or nonpil) and not self.args.th_no_jxl
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.log_func("thumbcli", msg, c)
@@ -66,6 +60,10 @@ class ThumbCli(object):
if is_vid and "dvthumb" in dbv.flags:
return None
is_custom = ext not in self.thumbable_native
if is_custom and "dethumb" in dbv.flags:
return None
want_opus = fmt in EXTS_AC
is_au = ext in self.fmt_ffa
is_vau = want_opus and ext in self.fmt_ffv
@@ -83,29 +81,32 @@ class ThumbCli(object):
elif want_opus:
return None
is_img = not is_vid and not is_au
is_img = not is_vid and not is_au and not is_custom
if is_img and "dithumb" in dbv.flags:
return None
preferred = self.args.th_dec[0] if self.args.th_dec else ""
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
if rem.startswith(".hist/th/") and rem.split(".")[-1] in IMG_EXTS:
return os.path.join(ptop, rem)
if fmt[:1] in "jw" and fmt != "wav":
sfmt = fmt[:1]
sfmt = fmt[:1]
if sfmt in "jwx" and fmt != "wav":
if sfmt == "j" and self.args.th_no_jpg:
sfmt = "w"
if sfmt == "w":
if (
self.args.th_no_webp
or (is_img and not self.can_webp)
or (self.args.th_ff_jpg and (not is_img or preferred == "ff"))
if not self.can_webp or (
self.args.th_ff_jpg and (not is_img or preferred == "ff")
):
sfmt = "j"
if sfmt == "x" and (
not self.can_jxl or (not H_PIL_JXL and ext in self.fmt_raw)
):
sfmt = "w"
vf_crop = dbv.flags["crop"]
vf_th3x = dbv.flags["th3x"]
@@ -121,7 +122,7 @@ class ThumbCli(object):
fmt = sfmt
elif fmt[:1] == "p" and not is_au and not is_vid:
elif sfmt == "p" and not is_au and not is_vid:
t = "cannot thumbnail %r: png only allowed for waveforms"
self.log(t % (rem,), 6)
return None
@@ -133,9 +134,12 @@ class ThumbCli(object):
tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
tpaths = [tpath]
if fmt[:1] == "w" and fmt != "wav":
fmtc = fmt[:1]
if fmtc == "w" and fmt != "wav":
# also check for jpg (maybe webp is unavailable)
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
elif fmtc == "x":
tpaths.append(tpath.rsplit(".", 1)[0] + ".webp")
ret = None
abort = False
@@ -152,14 +156,15 @@ class ThumbCli(object):
pass
if ret:
tdir = os.path.dirname(tpath)
if self.cooldown.poke(tdir):
self.broker.say("thumbsrv.poke", tdir)
if self.cooldown:
tdir = os.path.dirname(tpath)
if self.cooldown.poke(tdir):
self.broker.say("thumbsrv.poke", tdir)
if want_opus:
# audio files expire individually
if self.cooldown.poke(tpath):
self.broker.say("thumbsrv.poke", tpath)
if want_opus:
# audio files expire individually
if self.cooldown.poke(tpath):
self.broker.say("thumbsrv.poke", tpath)
return ret
+408 -163
View File
File diff suppressed because it is too large. Load diff
+25 -4
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import calendar
import os
@@ -22,11 +22,20 @@ from .util import (
quotep,
s3dec,
vjoin,
vjoins,
)
if HAVE_SQLITE3:
import sqlite3
try:
if os.environ.get("PRTY_NO_UNIDATA"):
raise Exception()
import unicodedata
except:
pass
try:
from pathlib import Path
except:
@@ -42,6 +51,14 @@ if PY2:
range = xrange # type: ignore
def norm_icase(txt: str) -> str:
return txt.casefold() if txt else txt
def norm_nfkc(txt: str) -> str:
return unicodedata.normalize("NFKC", txt).casefold() if txt else txt
class U2idx(object):
def __init__(self, hsrv: "HttpSrv") -> None:
self.log_func = hsrv.log
@@ -54,6 +71,10 @@ class U2idx(object):
return
if self.args.srch_icase:
if self.args.srch_nfkc:
self.normfun = norm_nfkc
else:
self.normfun = norm_icase
self._open_db = self._open_db_icase
else:
self._open_db = self._open_db_std
@@ -81,7 +102,7 @@ class U2idx(object):
def _open_db_icase(self, *args, **kwargs):
db = self._open_db_std(*args, **kwargs)
db.create_function("casefold", 1, lambda x: x.casefold() if x else x)
db.create_function("casefold", 1, self.normfun)
return db
def shutdown(self) -> None:
@@ -322,7 +343,7 @@ class U2idx(object):
if icase and "casefold(" in q:
try:
v = unicode(v).casefold()
v = self.normfun(unicode(v))
except:
v = unicode(v).lower()
@@ -437,7 +458,7 @@ class U2idx(object):
if rd.startswith("//") or fn.startswith("//"):
rd, fn = s3dec(rd, fn)
vp = vjoin(vjoin(vtop, rd), fn)
vp = vjoins(vtop, rd, fn)
if vp in seen_rps:
continue
+397 -131
View File
File diff suppressed because it is too large. Load diff
+454 -72
View File
@@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
from __future__ import division, print_function, unicode_literals
import argparse
import base64
@@ -10,7 +10,6 @@ import hashlib
import hmac
import json
import logging
import math
import mimetypes
import os
import platform
@@ -45,6 +44,7 @@ except:
from .__init__ import (
ANYWIN,
EXE,
GRAAL,
MACOS,
PY2,
PY36,
@@ -56,6 +56,25 @@ from .__init__ import (
)
from .__version__ import S_BUILD_DT, S_VERSION
def noop(*a, **ka):
pass
def lprint(*a: "Any", **ka: "Any") -> None:
eol = ka.pop("end", "\n")
txt = " ".join(unicode(x) for x in a) + eol
lprinted.append(txt)
if not VT100 and "\033" in txt:
txt = RE_ANSI.sub("", txt)
print(txt, end="", **ka)
lprinted: list[str] = []
LOG: list["Callable[..., None]"] = [lprint]
try:
from datetime import datetime, timezone
@@ -135,25 +154,41 @@ except:
HAVE_FICLONE = False
try:
import ctypes
import termios
except:
pass
try:
if os.environ.get("PRTY_NO_CTYPES"):
raise Exception()
import ctypes
except:
ctypes = None
try:
wk32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
except:
wk32 = None
try:
if os.environ.get("PRTY_NO_IFADDR"):
raise Exception()
try:
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_IFADDR"):
if os.environ.get("PRTY_SYS_ALL") or os.environ.get("PRTY_SYS_IFADDR"):
raise ImportError()
from .stolen.ifaddr import get_adapters
IFADDR_VND = True
except ImportError:
IFADDR_VND = False
from ifaddr import get_adapters
HAVE_IFADDR = True
except:
HAVE_IFADDR = False
IFADDR_VND = False
def get_adapters(include_unconfigured=False):
return []
@@ -163,10 +198,10 @@ try:
if os.environ.get("PRTY_NO_SQLITE"):
raise Exception()
HAVE_SQLITE3 = True
import sqlite3
assert hasattr(sqlite3, "connect") # graalpy
HAVE_SQLITE3 = True
except:
HAVE_SQLITE3 = False
@@ -181,8 +216,9 @@ try:
if os.environ.get("PRTY_NO_PSUTIL"):
raise Exception()
HAVE_PSUTIL = True
import psutil
HAVE_PSUTIL = True
except:
HAVE_PSUTIL = False
@@ -193,10 +229,12 @@ try:
raise Exception()
import magic
except:
pass
if os.getenv("PRTY_MODSPEC"):
HAVE_MAGIC = True
except:
HAVE_MAGIC = False
if os.environ.get("PRTY_MODSPEC"):
from inspect import getsourcefile
print("PRTY_MODSPEC: ifaddr:", getsourcefile(get_adapters))
@@ -251,14 +289,29 @@ try:
raise Exception()
socket.inet_pton(socket.AF_INET6, "::1")
if GRAAL:
try:
# --python.PosixModuleBackend=java throws OSError: illegal IP address
socket.inet_pton(socket.AF_INET, "127.0.0.1")
except:
_inet_pton = socket.inet_pton
def inet_pton(fam, ip):
if fam == socket.AF_INET:
return socket.inet_aton(ip)
return _inet_pton(fam, ip)
socket.inet_pton = inet_pton
HAVE_IPV6 = True
except:
HAVE_IPV6 = False
def inet_pton(fam, ip):
return socket.inet_aton(ip)
socket.inet_pton = inet_pton
HAVE_IPV6 = False
try:
@@ -280,6 +333,16 @@ except:
BITNESS = struct.calcsize("P") * 8
try:
if ANYWIN or PY2 or GRAAL or not hasattr(signal, "pthread_sigmask"):
raise Exception()
BLOCK_SIGS = [signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGUSR1]
CAN_SIGMASK = True
except:
BLOCK_SIGS = []
CAN_SIGMASK = False
RE_ANSI = re.compile("\033\\[[^mK]*[mK]")
RE_HTML_SH = re.compile(r"[<>&$?`\"';]")
RE_CTYPE = re.compile(r"^content-type: *([^; ]+)", re.IGNORECASE)
@@ -296,19 +359,25 @@ RE_MEMAVAIL = re.compile("^MemAvailable:.* kB")
if PY2:
def umktrans(s1, s2):
def umktrans(s1: str, s2: str):
return {ord(c1): ord(c2) for c1, c2 in zip(s1, s2)}
else:
umktrans = str.maketrans
FNTL_WIN = umktrans('<>:|?*"\\/', "<>:|?*"\/")
VPTL_WIN = umktrans('<>:|?*"\\', "<>:|?*"\")
APTL_WIN = umktrans('<>:|?*"/', "<>:|?*"/")
FNTL_MAC = VPTL_MAC = APTL_MAC = umktrans(":", ":")
FNTL_OS = FNTL_WIN if ANYWIN else FNTL_MAC if MACOS else None
VPTL_MAC = umktrans(":", ":")
VPTL_OS = VPTL_WIN if ANYWIN else VPTL_MAC if MACOS else None
APTL_OS = APTL_WIN if ANYWIN else APTL_MAC if MACOS else None
BADNAMES = []
if ANYWIN:
BADNAMES += ["con", "prn", "aux", "nul"]
BADNAMES += ["com%s" % (n,) for n in range(1, 10)]
BADNAMES += ["lpt%s" % (n,) for n in range(1, 10)]
BADNAMES += ["com%s" % (n,) for n in ("¹", "²", "³")]
BADNAMES += ["lpt%s" % (n,) for n in ("¹", "²", "³")]
BADNAMESET = set(BADNAMES)
BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
@@ -373,17 +442,35 @@ IMPLICATIONS = [
["e2vu", "e2v"],
["e2vp", "e2v"],
["e2v", "e2d"],
["srch_nfkc", "srch_icase"],
["hardlink_only", "hardlink"],
["hardlink", "dedup"],
["tftpvv", "tftpv"],
["nodupem", "nodupe"],
["no_dupe_m", "no_dupe"],
["no_html", "no_script"],
["nohtml", "noscript"],
["no_html", "nohtml"], # user-typo
["no_script", "noscript"], # user-typo
["no_readmes", "no_readme"], # user-typo
["noreadmes", "no_readme"], # user-typo
["noreadme", "no_readme"], # user-typo
["no_logue", "no_logues"], # user-typo
["nologues", "no_logues"], # user-typo
["nologue", "no_logues"], # user-typo
["nw", "redup_dry"],
["plainlogue", "plainlogues"], # user-typo
["plainreadmes", "plainreadme"], # user-typo
["noscript", "plainlogues"],
["noscript", "plainreadme"],
["sftpvv", "sftpv"],
["smbw", "smb"],
["smb1", "smb"],
["smbvvv", "smbvv"],
["smbvv", "smbv"],
["smbv", "smb"],
["xdev", "xvd"],
["xvol", "xvd"],
["zv", "zmv"],
["zv", "zsv"],
["z", "zm"],
@@ -439,7 +526,7 @@ MIMES = {
}
def _add_mimes() -> None:
def _add_mimes() -> set[str]:
# `mimetypes` is woefully unpopulated on windows
# but will be used as fallback on linux
@@ -454,19 +541,18 @@ font woff woff2 otf ttf
for v in vs.strip().split():
MIMES[v] = "{}/{}".format(k, v)
for ln in """text md=plain txt=plain js=javascript
for ln in """text md=plain js=javascript ass=plain ssa=plain txt=plain
application 7z=x-7z-compressed tar=x-tar bz2=x-bzip2 gz=gzip rar=x-rar-compressed zst=zstd xz=x-xz lz=lzip cpio=x-cpio
application msi=x-ms-installer cab=vnd.ms-cab-compressed rpm=x-rpm crx=x-chrome-extension
application epub=epub+zip mobi=x-mobipocket-ebook lit=x-ms-reader rss=rss+xml atom=atom+xml torrent=x-bittorrent
application p7s=pkcs7-signature dcm=dicom shx=vnd.shx shp=vnd.shp dbf=x-dbf gml=gml+xml gpx=gpx+xml amf=x-amf
application swf=x-shockwave-flash m3u=vnd.apple.mpegurl db3=vnd.sqlite3 sqlite=vnd.sqlite3
text ass=plain ssa=plain
image jpg=jpeg xpm=x-xpixmap psd=vnd.adobe.photoshop jpf=jpx tif=tiff ico=x-icon djvu=vnd.djvu
image heic=heic-sequence heif=heif-sequence hdr=vnd.radiance svg=svg+xml
image heics=heic-sequence heifs=heif-sequence hdr=vnd.radiance svg=svg+xml
image arw=x-sony-arw cr2=x-canon-cr2 crw=x-canon-crw dcr=x-kodak-dcr dng=x-adobe-dng erf=x-epson-erf
image k25=x-kodak-k25 kdc=x-kodak-kdc mrw=x-minolta-mrw nef=x-nikon-nef orf=x-olympus-orf
image pef=x-pentax-pef raf=x-fuji-raf raw=x-panasonic-raw sr2=x-sony-sr2 srf=x-sony-srf x3f=x-sigma-x3f
audio caf=x-caf mp3=mpeg m4a=mp4 mid=midi mpc=musepack aif=aiff au=basic qcp=qcelp
audio caf=x-caf mp3=mpeg m4a=mp4 m4b=mp4 m4r=mp4 mid=midi mka=x-matroska mpc=musepack aif=aiff au=basic qcp=qcelp
video mkv=x-matroska mov=quicktime avi=x-msvideo m4v=x-m4v ts=mp2t
video asf=x-ms-asf flv=x-flv 3gp=3gpp 3g2=3gpp2 rmvb=vnd.rn-realmedia-vbr
font ttc=collection
@@ -476,8 +562,11 @@ font ttc=collection
ext, mime = em.split("=")
MIMES[ext] = "{}/{}".format(k, mime)
ptn = re.compile("html|script|tension|wasm|xml")
return {x for x in MIMES.values() if not ptn.search(x)}
_add_mimes()
SAFE_MIMES = _add_mimes()
EXTS: dict[str, str] = {v: k for k, v in MIMES.items()}
@@ -597,6 +686,20 @@ if EXE:
pass
try:
if PY2 or ANYWIN:
raise Exception()
HAVE_BWRAP = shutil.which("bwrap")
except:
HAVE_BWRAP = ""
if ANYWIN:
SCWD = os.environ["systemroot"]
else:
SCWD = None
def py_desc() -> str:
interp = platform.python_implementation()
py_ver = ".".join([str(x) for x in sys.version_info])
@@ -749,7 +852,7 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
if log:
log(t, 3)
else:
print(t)
LOG[0]("#", t)
return buf.decode("utf-8", "replace")
t = "ERROR: The file [%s] is not using the UTF-8 character encoding, and cannot be loaded. The first unreadable character was byte %r at offset %d. Please convert this file to UTF-8 by opening the file in your text-editor and saving it as UTF-8."
@@ -757,7 +860,7 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
if log:
log(t, 3)
else:
print(t)
LOG[0]("#", t)
raise NotUTF8(t)
@@ -779,10 +882,8 @@ class Daemon(threading.Thread):
self.start()
def run(self):
if not ANYWIN and not PY2:
signal.pthread_sigmask(
signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
)
if BLOCK_SIGS:
signal.pthread_sigmask(signal.SIG_BLOCK, BLOCK_SIGS)
self.fun(*self.a, **self.ka)
@@ -961,11 +1062,14 @@ class _Unrecv(object):
self.s = s
self.log = log
self.buf: bytes = b""
self.nb = 0
self.te = 0
def recv(self, nbytes: int, spins: int = 1) -> bytes:
if self.buf:
ret = self.buf[:nbytes]
self.buf = self.buf[nbytes:]
self.nb += len(ret)
return ret
while True:
@@ -985,6 +1089,7 @@ class _Unrecv(object):
if not ret:
raise UnrecvEOF("client stopped sending data")
self.nb += len(ret)
return ret
def recv_ex(self, nbytes: int, raise_on_trunc: bool = True) -> bytes:
@@ -1012,6 +1117,7 @@ class _Unrecv(object):
def unrecv(self, buf: bytes) -> None:
self.buf = buf + self.buf
self.nb -= len(buf)
# !rm.yes>
@@ -1024,6 +1130,7 @@ class _LUnrecv(object):
self.s = s
self.log = log
self.buf = b""
self.nb = 0
def recv(self, nbytes: int, spins: int) -> bytes:
if self.buf:
@@ -1031,6 +1138,7 @@ class _LUnrecv(object):
self.buf = self.buf[nbytes:]
t = "\033[0;7mur:pop:\033[0;1;32m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
print(t.format(ret, self.buf))
self.nb += len(ret)
return ret
ret = self.s.recv(nbytes)
@@ -1039,6 +1147,7 @@ class _LUnrecv(object):
if not ret:
raise UnrecvEOF("client stopped sending data")
self.nb += len(ret)
return ret
def recv_ex(self, nbytes: int, raise_on_trunc: bool = True) -> bytes:
@@ -1067,6 +1176,7 @@ class _LUnrecv(object):
def unrecv(self, buf: bytes) -> None:
self.buf = buf + self.buf
self.nb -= len(buf)
t = "\033[0;7mur:push\033[0;1;31m {}\n\033[0;7mur:rem:\033[0;1;35m {}\033[0m"
print(t.format(buf, self.buf))
@@ -1267,7 +1377,7 @@ class MTHash(object):
self.csz = chunksz
chunks: dict[int, tuple[str, int, int]] = {}
nchunks = int(math.ceil(fsz / chunksz))
nchunks = -int(-fsz // chunksz)
for nch in range(nchunks):
self.work_q.put(nch)
@@ -1453,8 +1563,7 @@ class Garda(object):
return 0, ip
if ":" in ip:
# assume /64 clients; drop 4 groups
ip = IPv6Address(ip).exploded[:-20]
ip = ipnorm(ip)
if prev and self.uniq:
if self.prev.get(ip) == prev:
@@ -1515,6 +1624,75 @@ def dedent(txt: str) -> str:
return "\n".join([ln[pad:] for ln in lns])
def expand_osenv_noop(txt) -> str:
return txt
def _expand_osenv_c(txt) -> str:
if "${" not in txt:
return txt
zsl = txt.split("${")
ret = zsl[0]
for v in zsl[1:]:
if "}" not in v:
t = "missing '}' after %r in config-value %r" % (v, txt)
LOG[0]("ERROR:", t)
raise Exception(t)
a, b = v.split("}", 1)
try:
ret += os.environ[a] + b
continue
except:
pass
t = "env-var %r not defined; config-value %r" % (a, txt)
LOG[0]("ERROR:", t)
raise Exception(t)
return ret
if os.environ.get("PRTY_NO_ENVEXPAND"):
expand_osenv_c = expand_osenv_noop
expand_osenv_s = expand_osenv_noop
else:
expand_osenv_c = _expand_osenv_c
expand_osenv_s = os.path.expandvars
def expand_osenv_cs(txt) -> str:
a = expand_osenv_c(txt)
b = expand_osenv_s(txt)
if a == b:
return a
t = "config-value %r is using old syntax for environment-variables; choose one of the following:\noption 1: update the config-value to the new syntax; ${VAR} instead of $VAR or %%VAR%%\noption 2: allow and expand old-syntax with global-option --env-expand 1 (risky)\noption 3: ignore/disable expansion of old-syntax with global-option --env-expand 2\noption 4: disable all env-var expansions by setting env-var PRTY_NO_ENVEXPAND=1"
t = t % (txt,)
LOG[0]("WARNING:", t)
try:
_, _ = txt.split("$")
zs = r"\$(LOGS_DIRECTORY|XDG_[A-Z]+_HOME|XDG_[A-Z]+_DIR)\b"
txt = re.sub(zs, r"${\1}", txt)
a = expand_osenv_c(txt)
b = expand_osenv_s(txt)
if a == b:
return a
except:
pass
raise Exception(t)
def signame2int(txt: str) -> int:
try:
return int(txt)
except:
txt = txt.upper()
if not txt.startswith("SIG"):
txt = "SIG" + txt
return int(getattr(signal, txt))
def rice_tid() -> str:
tid = threading.current_thread().ident
c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:])
@@ -1647,13 +1825,11 @@ def log_thrs(log: Callable[[str, str, int], None], ival: float, name: str) -> No
log(name, "\033[0m \033[33m".join(tv), 3)
def sigblock():
if ANYWIN or PY2:
return
def _sigblock():
signal.pthread_sigmask(signal.SIG_BLOCK, BLOCK_SIGS)
signal.pthread_sigmask(
signal.SIG_BLOCK, [signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]
)
sigblock = _sigblock if CAN_SIGMASK else noop
def vol_san(vols: list["VFS"], txt: bytes) -> bytes:
@@ -1807,6 +1983,11 @@ class MultipartParser(object):
self.log = log_func
self.args = args
self.headers = http_headers
try:
self.clen = int(http_headers["content-length"])
sr.nb = 0
except:
self.clen = 0
self.re_ctype = RE_CTYPE
self.re_cdisp = RE_CDISP
@@ -1962,7 +2143,10 @@ class MultipartParser(object):
if tail == b"--":
# EOF indicated by this immediately after final boundary
tail = self.sr.recv_ex(2, False)
if self.clen == self.sr.nb:
tail = b"\r\n" # dillo doesn't terminate with trailing \r\n
else:
tail = self.sr.recv_ex(2, False)
run = False
if tail != b"\r\n":
@@ -1980,7 +2164,10 @@ class MultipartParser(object):
def parse(self) -> None:
boundary = get_boundary(self.headers)
self.log("boundary=%r" % (boundary,))
if boundary.startswith('"') and boundary.endswith('"'):
boundary = boundary[1:-1] # dillo uses quotes
if len(boundary) > 72: # rfc-2046 <=70
raise Pebkac(400, "boundary 2big: %d" % (len(boundary),))
# spec says there might be junk before the first boundary,
# can't have the leading \r\n if that's not the case
@@ -2046,7 +2233,7 @@ def read_header(sr: Unrecv, t_idle: int, t_tot: int) -> list[str]:
raise Pebkac(
400,
"protocol error while reading headers",
log=ret.decode("utf-8", "replace"),
log=repr(ret.decode("utf-8", "replace")),
)
ofs = ret.find(b"\r\n\r\n")
@@ -2250,7 +2437,7 @@ def uncyg(path: str) -> str:
return "%s:\\%s" % (path[1], path[3:])
def undot(path: str) -> str:
def _undot(path: str) -> str:
ret: list[str] = []
for node in path.split("/"):
if node == "." or not node:
@@ -2266,30 +2453,56 @@ def undot(path: str) -> str:
return "/".join(ret)
def sanitize_fn(fn: str) -> str:
fn = fn.replace("\\", "/").split("/")[-1]
if APTL_OS:
fn = sanitize_to(fn, APTL_OS)
def _undot_win(path: str) -> str:
ret: list[str] = []
for node in path.replace("\\", "/").split("/"):
if node == "..":
if ret:
ret.pop()
continue
node = node.rstrip(". ")
if node:
ret.append(node)
return "/".join(ret)
if ANYWIN:
undot = _undot_win
else:
undot = _undot
def sanitize_fn(fn: str, fsnt: Optional[str] = None) -> str:
fn = fn.replace("\x00", "_").split("/")[-1]
if fsnt and fsnt != "lin":
fn = fn.translate(VPTL_WIN if fsnt == "win" or ANYWIN else VPTL_MAC)
fn2 = fn.replace("\\", "/")
if ANYWIN or fsnt == "win" or "/../" in fn2:
fn = fn2.split("/")[-1].rstrip(". ")
if VPTL_OS:
fn = sanitize_to(fn, VPTL_OS)
return fn.strip()
def sanitize_to(fn: str, tl: dict[int, int]) -> str:
def sanitize_to(fn: str, tl: dict[int, int], fsnt: Optional[str] = None) -> str:
fn = fn.translate(tl)
if ANYWIN:
bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10):
bad += ("com%s lpt%s" % (n, n)).split(" ")
if fn.lower().split(".")[0] in bad:
if ANYWIN or fsnt == "win":
fn = fn.rstrip(". ")
if fn.lower().split(".")[0].strip() in BADNAMESET:
fn = "_" + fn
return fn
def sanitize_vpath(vp: str) -> str:
if not APTL_OS:
def sanitize_vpath(vp: str, fsnt: Optional[str] = None) -> str:
vptl = VPTL_OS
if fsnt and fsnt != "lin":
vptl = VPTL_WIN if fsnt == "win" or ANYWIN else VPTL_MAC
if not vptl:
return vp
parts = vp.replace(os.sep, "/").split("/")
ret = [sanitize_to(x, APTL_OS) for x in parts]
ret = [sanitize_to(x, vptl, fsnt) for x in parts]
return "/".join(ret)
@@ -2332,6 +2545,39 @@ def exclude_dotfiles(filepaths: list[str]) -> list[str]:
return [x for x in filepaths if not x.split("/")[-1].startswith(".")]
def exclude_dotfiles_ls(
vfs_ls: list[tuple[str, os.stat_result]]
) -> list[tuple[str, os.stat_result]]:
return [x for x in vfs_ls if not x[0].split("/")[-1].startswith(".")]
def exclude_dothidden(filepaths: list[str], fsroot: Any) -> list[str]:
ret = [x for x in filepaths if not x.split("/")[-1].startswith(".")]
filt = load_dothidden(fsroot)
if filt:
ret = [x for x in ret if x.split("/")[-1] not in filt]
return ret
def exclude_dothidden_ls(
vfs_ls: list[tuple[str, os.stat_result]], fsroot: Any
) -> list[tuple[str, os.stat_result]]:
ret = [x for x in vfs_ls if not x[0].split("/")[-1].startswith(".")]
filt = load_dothidden(fsroot)
if filt:
ret = [x for x in ret if x[0].split("/")[-1] not in filt]
return ret
def load_dothidden(dpath: str) -> list[str]:
try:
with open(os.path.join(dpath, ".hidden"), "rb") as f:
zsl = f.read().decode("utf-8").splitlines()
return [x.strip() for x in zsl]
except OSError:
return []
def odfusion(
base: Union[ODict[str, bool], ODict["LiteralString", bool]], oth: str
) -> ODict[str, bool]:
@@ -2354,8 +2600,8 @@ def odfusion(
def ipnorm(ip: str) -> str:
if ":" in ip:
# assume /64 clients; drop 4 groups
return IPv6Address(ip).exploded[:-20]
# assume /56 clients; drop final 72 bits
return str(IPv6Network(ip + "/56", strict=False).network_address)
return ip
@@ -2503,6 +2749,10 @@ def vjoin(rd: str, fn: str) -> str:
return rd or fn
def vjoins(*a: str) -> str:
return "/".join([x for x in a if x])
# url-join
def ujoin(rd: str, fn: str) -> str:
if rd and fn:
@@ -2747,6 +2997,25 @@ def trystat_shutil_copy2(log: "NamedLogger", src: bytes, dst: bytes) -> bytes:
raise
def trystat_shutil_move(log: "NamedLogger", src: bytes, dst: bytes) -> bytes:
try:
return shutil.move(src, dst)
except:
# ignore failed mtime on linux+ntfs; for example:
# shutil.py:437 <copy2>: copystat(src, dst, follow_symlinks=follow_symlinks)
# shutil.py:376 <copystat>: lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns),
# [PermissionError] [Errno 1] Operation not permitted, '/windows/_videos'
_, _, tb = sys.exc_info()
for _, _, fun, _ in traceback.extract_tb(tb):
if fun == "copystat":
if log:
t = "warning: failed to retain some file attributes (timestamp and/or permissions) during move from %r to %r:\n%s"
log(t % (src, dst, min_ex()), 3)
os.unlink(src)
return dst # close enough
raise
def _fs_mvrm(
log: "NamedLogger", src: str, dst: str, atomic: bool, flags: dict[str, Any]
) -> bool:
@@ -2798,7 +3067,9 @@ def _fs_mvrm(
if not attempt and ex.errno == errno.EXDEV:
t = "using copy+delete (%s)\n %s\n %s"
log(t % (ex.strerror, src, dst))
osfun = shutil.move
if osfun in (os.replace, os.rename):
args.insert(0, log)
osfun = trystat_shutil_move
continue
if now - t0 > maxtime or attempt == 90209:
raise
@@ -2835,7 +3106,7 @@ def atomic_move(log: "NamedLogger", src: str, dst: str, flags: dict[str, Any]) -
os.unlink(bdst)
except:
pass
shutil.move(bsrc, bdst) # type: ignore
trystat_shutil_move(log, bsrc, bdst) # type: ignore
def wunlink(log: "NamedLogger", abspath: str, flags: dict[str, Any]) -> bool:
@@ -2859,7 +3130,7 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]:
bfree = ctypes.c_ulonglong(0)
btotal = ctypes.c_ulonglong(0)
bavail = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
wk32.GetDiskFreeSpaceExW( # type: ignore
ctypes.c_wchar_p(abspath),
ctypes.pointer(bavail),
ctypes.pointer(btotal),
@@ -2918,6 +3189,8 @@ def shut_socket(log: "NamedLogger", sck: socket.socket, timeout: int = 3) -> Non
sck.shutdown(socket.SHUT_RDWR)
except:
pass
except OSError as ex:
log("shut(%d): ok; client has already disconnected; %s" % (fd, ex.errno), "90")
except Exception as ex:
log("shut({}): {}".format(fd, ex), "90")
finally:
@@ -2981,6 +3254,7 @@ def read_socket_chunked(
if chunklen == 0:
x = sr.recv_ex(2, False)
if x == b"\r\n":
sr.te = 2
return
t = "protocol error after final chunk: want b'\\r\\n', got {!r}"
@@ -3010,6 +3284,31 @@ def list_ips() -> list[str]:
return list(ret)
def list_nics(alll: bool = False) -> dict[str, Netdev]:
nics = get_adapters(alll)
eps: dict[str, Netdev] = {}
for nic in nics:
name = nic.nice_name
try:
idx = socket.if_nametoindex(name)
if idx and idx != nic.index:
LOG[0]("#", "nic-idx mismatch; ifaddr=%r libc=%r" % (nic.index, idx), 3)
except:
idx = nic.index
for nip in nic.ips:
ipa = nip.ip[0] if ":" in str(nip.ip) else nip.ip
sip = "%s/%s" % (ipa, nip.network_prefix)
nd = Netdev(sip, idx or 0, name, "")
eps[sip] = nd
if alll and not nic.ips:
zs = "no-ip-%s" % (idx,)
eps[zs] = Netdev(zs, idx or 0, name, "")
return eps
def build_netmap(csv: str, defer_mutex: bool = False):
csv = csv.lower().strip()
@@ -3376,8 +3675,10 @@ def rmdirs_up(top: str, stop: str) -> tuple[list[str], list[str]]:
return [top] + ok, ng
def unescape_cookie(orig: str) -> str:
def unescape_cookie(orig: str, name: str) -> str:
# mw=idk; doot=qwe%2Crty%3Basd+fgh%2Bjkl%25zxc%26vbn # qwe,rty;asd fgh+jkl%zxc&vbn
if not name.startswith("cppw"):
orig = orig[:3]
ret = []
esc = ""
for ch in orig:
@@ -3438,6 +3739,13 @@ def guess_mime(
return ret
def safe_mime(mime: str) -> str:
if "text/" in mime or "xml" in mime:
return "text/plain; charset=utf-8"
else:
return "application/octet-stream"
def getalive(pids: list[int], pgid: int) -> list[int]:
alive = []
for pid in pids:
@@ -3745,7 +4053,7 @@ def _parsehook(
argv = cmd.split(",") if "," in cmd else [cmd]
argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
argv[0] = os.path.expanduser(expand_osenv_c(argv[0]))
return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv
@@ -3778,9 +4086,9 @@ def runihook(
}
for x, vp, ap in zip(ups, vps, aps)
]
sp_ka["sin"] = json.dumps(ja).encode("utf-8", "replace")
sp_ka["sin"] = json.dumps(ja).encode("utf-8", "replace") + b"\n"
else:
sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps)
sp_ka["sin"] = b"\n".join(fsenc(x) for x in aps) + b"\n"
if acmd[0].startswith("zmq:"):
try:
@@ -3795,7 +4103,7 @@ def runihook(
t0 = time.time()
if fork:
Daemon(runcmd, cmd, bcmd, ka=sp_ka)
Daemon(runcmd, cmd, [bcmd], ka=sp_ka)
else:
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
if chk and rc:
@@ -3958,15 +4266,22 @@ def _runhook(
"src": src,
}
if txt:
ja["txt"] = txt[0]
ja["body"] = txt[1]
if src in ("xm", "xban"):
ja["txt"] = txt[0]
ja["body"] = txt[1]
elif src == "xbr":
ja["ap_to"] = txt[0]
elif src in ("xar.ln", "xar.mv"):
ja["ap_from"] = txt[0]
else:
ja["wark"] = txt[0] # acshually the dwark but less confusing
if imp:
ja["log"] = log
mod = loadpy(acmd[0], False)
return mod.main(ja)
arg = json.dumps(ja)
else:
arg = txt[0] if txt else ap
arg = txt[0] if txt and src in ("xm", "xban") else ap
if acmd[0].startswith("zmq:"):
zi, zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
@@ -4072,7 +4387,7 @@ def runhook(
else:
ret[k] = v
except Exception as ex:
(log or print)("hook: %r, %s" % (ex, ex))
(log or print)("hook failed; %s:\n%s" % (ex, min_ex()))
if ",c," in "," + cmd:
return {"rc": 1}
break
@@ -4088,7 +4403,7 @@ def loadpy(ap: str, hot: bool) -> Any:
depending on what other inconveniently named files happen
to be in the same folder
"""
ap = os.path.expandvars(os.path.expanduser(ap))
ap = os.path.expanduser(expand_osenv_c(ap))
mdir, mfile = os.path.split(absreal(ap))
mname = mfile.rsplit(".", 1)[0]
sys.path.insert(0, mdir)
@@ -4121,6 +4436,36 @@ def gzip_file_orig_sz(f) -> int:
return sunpack(b"I", rv)[0] # type: ignore
def zip_fi(zf, fp, max_sz):
zi = zf.getinfo(fp)
if max_sz and zi.file_size > max_sz:
raise Pebkac(404, "zip bomb defused")
return zi
def zip_lim(zi, max_sz):
if zi.file_size > max_sz:
raise Pebkac(404, "zip bomb defused")
return zi
def zip_read(zf, zi, max_sz):
ret = b""
with zf.open(zi) as f:
while True:
buf = f.read(max_sz)
if not buf:
break
ret += buf
if len(ret) >= max_sz:
raise Pebkac(404, "zip bomb defused")
return ret
def zip_readf(zf, zp, max_sz):
return zip_read(zf, zip_fi(zf, zp, max_sz), max_sz)
def align_tab(lines: list[str]) -> list[str]:
rows = []
ncols = 0
@@ -4252,8 +4597,8 @@ def termsize() -> tuple[int, int]:
def hidedir(dp) -> None:
if ANYWIN:
try:
assert ctypes # type: ignore # !rm
k32 = ctypes.WinDLL("kernel32")
assert wk32 # type: ignore # !rm
k32 = wk32
attrs = k32.GetFileAttributesW(dp)
if attrs >= 0:
k32.SetFileAttributesW(dp, attrs | 2)
@@ -4261,6 +4606,18 @@ def hidedir(dp) -> None:
pass
def winsparse(f: typing.BinaryIO) -> None:
assert ctypes # !rm
assert wk32 # !rm
import msvcrt
fh = msvcrt.get_osfhandle(f.fileno())
if not wk32.DeviceIoControl(
fh, 0x900C4, None, 0, None, 0, ctypes.byref(ctypes.c_ulong()), None
):
raise ctypes.WinError(ctypes.get_last_error())
_flocks = {}
@@ -4328,6 +4685,31 @@ else:
lock_file = _lock_file_noop
def _open_nolock_windows(bap: Union[str, bytes], *a, **ka) -> typing.BinaryIO:
assert ctypes # !rm
assert wk32 # !rm
import msvcrt
try:
ap = bap.decode("utf-8", "replace") # type: ignore
except:
ap = bap
fh = wk32.CreateFileW(ap, 0x80000000, 7, None, 3, 0x80, None)
# `-ap, GENERIC_READ, FILE_SHARE_READ|WRITE|DELETE, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None
if fh == -1:
ec = ctypes.get_last_error() # type: ignore
raise ctypes.WinError(ec) # type: ignore
fd = msvcrt.open_osfhandle(fh, os.O_RDONLY) # type: ignore
return os.fdopen(fd, "rb")
if ANYWIN:
open_nolock = _open_nolock_windows
else:
open_nolock = open
try:
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
# py3.8 doesn't have .files
+6
View File
@@ -6,6 +6,12 @@ pk: $(addsuffix .gz, $(wildcard tl/*.js *.js *.css) \
a/webdav-cfg.txt )
un: $(addsuffix .un, $(wildcard tl/*.gz *.gz a/*.gz))
tl/%.js.gz: tl/%.js
sh Makefile.s1 <$< | pigz -c11 -J 34 -I 573 >$@
pigz -dc <$@ | grep -q ^Ls
touch -r $< $@
rm $<
%.gz: %
pigz -c11 -J 34 -I 573 <$< >$@
touch -r $< $@
+1
View File
@@ -0,0 +1 @@
sed -r 's/^\t+//;s/^"([a-zA-Z][a-zA-Z0-9_]*)": /\1:/; /^\/\//d; s/([^\]["'\''],) *\/\/.*/\1/; /^$/d'
+59 -13
View File
@@ -28,7 +28,7 @@ window.baguetteBox = (function () {
onChange: null,
readDirRtl: false,
},
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnVmode, btnReadDir, btnClose,
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnVmode, btnReadDir, btnGoPage, btnClose,
currentGallery = [],
currentIndex = 0,
isOverlayVisible = false,
@@ -36,15 +36,17 @@ window.baguetteBox = (function () {
touchFlag = false, // busy
scrollCSS = ['', ''],
scrollTimer = 0,
re_i = /^[^?]+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i,
re_i = APPLE ?
/^[^?]+\.(a?png|avif|bmp|gif|hei[cf]s?|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i :
/^[^?]+\.(a?png|avif|bmp|gif|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i,
re_v = /^[^?]+\.(webm|mkv|mp4|m4v|mov)(\?|$)/i,
re_cbz = /^[^?]+\.(cbz)(\?|$)/i,
cbz_pics = ["png", "jpg", "jpeg", "gif", "bmp", "tga", "tif", "tiff", "webp", "avif"],
anims = ['slideIn', 'fadeIn', 'none'],
data = {}, // all galleries
imagesElements = [],
documentLastFocus = null,
isFullscreen = false,
isCbz = false,
vmute = false,
vloop = sread('vmode') == 'L',
vnext = sread('vmode') == 'C',
@@ -136,6 +138,9 @@ window.baguetteBox = (function () {
};
var trapFocusInsideOverlay = function (e) {
if (modal.busy)
return;
if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) {
e.stopPropagation();
btnClose.focus();
@@ -195,6 +200,16 @@ window.baguetteBox = (function () {
return [selectorData.galleries, options];
}
function getCbzPage() {
try {
var ret = parseInt(/,p([0-9]+)(,|$)/.exec(location.hash)[1]);
if (isNum(ret))
return Math.max(Math.min(ret, currentGallery.length), 1) - 1;
}
catch (ex) { }
return 0;
}
function bindCbzClickListeners(tagsNodeList, userOptions) {
var cbzNodes = [].filter.call(tagsNodeList, function (element) {
return re_cbz.test(element.href);
@@ -211,10 +226,9 @@ window.baguetteBox = (function () {
e.preventDefault ? e.preventDefault() : e.returnValue = false;
fillCbzGallery(gallery, cbzElement, eventHandler).then(function () {
prepareOverlay(gallery, userOptions);
showOverlay(0);
}
).catch(function (reason) {
prepareOverlay(gallery, userOptions, true);
showOverlay(getCbzPage());
}).catch(function (reason) {
console.error("cbz-ded", reason);
var t;
try {
@@ -251,8 +265,7 @@ window.baguetteBox = (function () {
var imagesList = fileList.map(function (file) {
return file["fn"];
}).filter(function (file) {
return file.indexOf(".") !== -1
&& cbz_pics.indexOf(file.split(".").pop()) !== -1;
return re_i.test(file);
}).sort();
if (imagesList.length === 0) {
@@ -269,6 +282,7 @@ window.baguetteBox = (function () {
href: imageHref,
imageElement: cbzElement,
eventHandler: eventHandler,
page: index + 1,
};
gallery.push(galleryItem);
});
@@ -317,6 +331,7 @@ window.baguetteBox = (function () {
'<button id="bbox-full" type="button" tt="full-screen">⛶</button>' +
'<button id="bbzoom" type="button" tt="zoom/stretch">z</button>' +
'<button id="bbox-vmode" type="button" tt="a"></button>' +
'<button id="bbox-gopage" type="button" tt="go to page">pg</button>' +
'<button id="bbox-close" type="button" aria-label="Close">X</button>' +
'</div></div>'
);
@@ -336,6 +351,7 @@ window.baguetteBox = (function () {
btnFull = ebi('bbox-full');
btnZoom = ebi('bbzoom');
btnVmode = ebi('bbox-vmode');
btnGoPage = ebi('bbox-gopage');
btnClose = ebi('bbox-close');
bcfg_bind(options, 'bbzoom', 'bbzoom', false, setzoom);
@@ -357,6 +373,7 @@ window.baguetteBox = (function () {
['F', 'toggle fullscreen'],
['Z', 'toggle zoom/stretch'],
['S', 'toggle file selection'],
['G', 'cbz: go to page'],
['space, P, K', 'video: play / pause'],
['U', 'video: seek 10sec back'],
['O', 'video: seek 10sec ahead'],
@@ -420,7 +437,7 @@ window.baguetteBox = (function () {
playpause();
else if (kl == "u" || kl == "o")
relseek(kl == "u" ? -10 : 10);
else if (v && /^[0-9]$/.test(k))
else if (v && /^[0-9]$/.test(k) && isNum(v.duration))
v.currentTime = v.duration * parseInt(k) * 0.1;
else if (kl == "m" && v) {
v.muted = vmute = !vmute;
@@ -442,6 +459,8 @@ window.baguetteBox = (function () {
btnZoom.click();
else if (kl == "s")
tglsel();
else if (kl == "g" && isCbz)
gotoCbzPage(e);
else if (kl == "r")
rotn(e.shiftKey ? -1 : 1);
else if (kl == "y")
@@ -507,6 +526,24 @@ window.baguetteBox = (function () {
v.play();
}
function gotoCbzPage(e) {
ev(e);
var cur = currentIndex + 1,
max = currentGallery.length;
modal.prompt('Go to page (1-' + max + ')', '' + cur, function (v) {
if (!v)
return;
v = parseInt(v);
if (!isNum(v))
return toast.warn(4, 'invalid input');
show(Math.max(Math.min(v, max), 1) - 1);
});
}
function tglVmode() {
if (vloop) {
vnext = true;
@@ -636,6 +673,7 @@ window.baguetteBox = (function () {
bind(btnRotR, 'click', rotr);
bind(btnSel, 'click', tglsel);
bind(btnFull, 'click', tglfull);
bind(btnGoPage, 'click', gotoCbzPage);
bind(slider, 'contextmenu', contextmenuHandler);
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
@@ -660,18 +698,23 @@ window.baguetteBox = (function () {
unbind(btnRotR, 'click', rotr);
unbind(btnSel, 'click', tglsel);
unbind(btnFull, 'click', tglfull);
unbind(btnGoPage, 'click', gotoCbzPage);
unbind(slider, 'contextmenu', contextmenuHandler);
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
unbind(overlay, 'touchend', touchendHandler);
unbind(document, 'focus', trapFocusInsideOverlay, true);
timer.rm(rotn);
clearTimeout(unrot_timer);
}
function prepareOverlay(gallery, userOptions) {
function prepareOverlay(gallery, userOptions, cbz) {
if (currentGallery === gallery)
return;
isCbz = cbz;
btnGoPage.style.display = isCbz ? '' : 'none';
currentGallery = gallery;
setOptions(userOptions);
slider.innerHTML = '';
@@ -1176,8 +1219,11 @@ window.baguetteBox = (function () {
}
function relseek(sec) {
if (vid())
vid().currentTime += sec;
var v = vid(),
t = v && v.currentTime;
if (isNum(t))
v.currentTime = t + sec;
}
function vidEnd() {
+15 -2
View File
@@ -1456,7 +1456,8 @@ html.dz input {
width: calc(100% - 16.2em);
}
input.drc_v,
input.eq_gain {
input.eq_gain,
input.ssconf_v {
width: 3em;
text-align: center;
margin: 0 .6em;
@@ -1466,7 +1467,8 @@ input.eq_gain {
border-collapse: collapse;
}
#audio_drc td,
#audio_eq td {
#audio_eq td,
#audio_ss td {
text-align: center;
}
#audio_eq a.eq_step {
@@ -1474,15 +1476,22 @@ input.eq_gain {
display: block;
padding: 0;
}
#au_ss,
#au_drc,
#au_eq {
display: block;
margin-top: .5em;
padding: 1.3em .3em;
}
#au_ss,
#au_drc {
padding: .4em .3em;
}
#au_ss.on {
width: 3em;
height: 1em;
overflow: hidden;
}
#ico1 {
cursor: pointer;
}
@@ -1759,6 +1768,10 @@ html.y #tree.nowrap .ntree a+a:hover {
top: .3em;
z-index: 9;
}
.dumb_loader_thing.ns {
animation: none;
opacity: 1;
}
#dlt_t {
left: 0;
}
+13 -10
View File
@@ -7,8 +7,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
<meta name="theme-color" content="#{{ tcolor }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/browser.css?_={{ ts }}">
{{ html_head }}
{%- if css %}
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
@@ -121,6 +121,8 @@
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
<p id="js_bork">Looks like javascript is broken; see "csp nonce" section in readme</p>
<a href="#" id="repl">π</a>
</div>
@@ -131,8 +133,9 @@
<div id="rcm" tabindex="0"></div>
<script>
<script nonce="{{ js_nonce }}">
var SR = "{{ r }}",
JS_NONCE = "{{ js_nonce }}",
CGV1 = {{ cgv1 }},
CGV = {{ cgv|tojson }},
TS = "{{ ts }}",
@@ -146,17 +149,17 @@
var STG = window.localStorage;
document.documentElement.className = (STG && STG.cpp_thm) || dtheme;
</script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
{%- if lang != "eng" %}
<script src="{{ r }}/.cpr/tl/{{ lang }}.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/tl/{{ lang }}.js?_={{ ts }}"></script>
{%- endif %}
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/baguettebox.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/browser.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script>
{%- if js %}
<script src="{{ js }}_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>
{%- endif %}
<script>
<script nonce="{{ js_nonce }}">
Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
jsldp("J_UTL","util");
jsldp("J_BBX","baguettebox");
+506 -125
View File
File diff suppressed because it is too large. Load diff
+10 -2
View File
@@ -28,7 +28,11 @@
{%- endif %}
{%- if logues[0] %}
<div>{{ logues[0] }}</div><br />
{%- if sb_lg %}
<div>{{ logues[0][:2000]|e }}</div><br />
{%- else %}
<div>{{ logues[0][:2000] }}</div><br />
{%- endif %}
{%- endif %}
<table id="files">
@@ -53,7 +57,11 @@
</table>
{%- if logues[1] %}
<div>{{ logues[1] }}</div><br />
{%- if sb_lg %}
<div>{{ logues[1][:2000]|e }}</div><br />
{%- else %}
<div>{{ logues[1][:2000] }}</div><br />
{%- endif %}
{%- endif %}
<h2><a href="{{ r }}/{{ url_suf }}{{ url_suf and '&amp;' or '?' }}h">control-panel</a></h2>
+1 -1
View File
@@ -14,7 +14,7 @@
<p>sorry for the inconvenience</p>
</div>
<script>
<script nonce="{{ js_nonce }}">
setTimeout(function() {
document.getElementById('box').style.opacity = 1;
}, 500);
+18
View File
@@ -0,0 +1,18 @@
this folder *mostly* contains third-party dependencies; run `make -C scripts/deps-docker` to build the following files and have them appear here:
* `easymde.css.gz` and `easymde.js.gz` is the fancy markdown editor, [EasyMDE](https://github.com/Ionaru/easy-markdown-editor)
* `marked.js.gz` is the markdown rendering library [Marked](https://github.com/markedjs/marked)
* `mini-fa.css.gz` and `mini-fa.woff` is a small subset of [fontawesome](https://github.com/FortAwesome/Font-Awesome)
* `prism.css.gz` and `prism.js.gz` is the syntax highlighter [PrismJS](https://prismjs.com/)
* `scp.woff2` is a subset of the monospace font [Source Code Pro](https://github.com/adobe-fonts/source-code-pro)
* `sha512.hw.js.gz` is the Wasm sha512 library [hash-wasm](https://github.com/Daninet/hash-wasm)
additionally, the following files are vendored into the copyparty git repository, but do NOT originate from the copyparty project (as mentioned in `--license`):
* `sha512.ac.js.gz` is a compiled and slightly golfed/modified [asmcrypto.js](https://github.com/asmcrypto/asmcrypto.js), © 2013 Artem S Vybornov (MIT-Licensed)
* vendored because it no longer builds with modern versions of NodeJS/npm
* is only loaded by *really old* webbrowsers (ie11, firefox 51, chrome 56)
finally, there is also the following files which *does* originate from the copyparty project, yet appear here for technical reasons:
* `busy.mp3.gz` is a short mp3-file to make iphones stop glitching out
Binary file not shown.
Binary file not shown.
+7 -6
View File
@@ -8,8 +8,8 @@
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#{{ tcolor }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/shares.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
{{ html_head }}
</head>
@@ -36,9 +36,10 @@
{%- endif %}
</div>
<a href="#" id="repl">π</a>
<script>
<script nonce="{{ js_nonce }}">
var SR="{{ r }}",
JS_NONCE = "{{ js_nonce }}",
lang="{{ lang }}",
dfavico="{{ favico }}";
@@ -46,11 +47,11 @@ var STG = window.localStorage;
document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
</script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
{%- if js %}
<script src="{{ js }}_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>
{%- endif %}
<script>
<script nonce="{{ js_nonce }}">
Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
jsldp("J_UTL","util");
</script>
+12 -17
View File
@@ -7,10 +7,10 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<meta name="theme-color" content="#{{ tcolor }}">
<link rel="stylesheet" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" href="{{ r }}/.cpr/md.css?_={{ ts }}">
<link rel="stylesheet" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
<link rel="stylesheet" href="{{ r }}/.cpr/w/md.css?_={{ ts }}">
{%- if edit %}
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
<link rel="stylesheet" href="{{ r }}/.cpr/w/md2.css?_={{ ts }}">
{%- endif %}
{{ html_head }}
</head>
@@ -131,19 +131,14 @@ write markdown (most html is 🙆 too)
</div>
{%- endif %}
<script>
<script nonce="{{ js_nonce }}">
var SR = "{{ r }}",
JS_NONCE = "{{ js_nonce }}",
CGV1 = {{ cgv1 }},
last_modified = {{ lastmod }},
have_emp = {{ have_emp }},
md_no_br = {{ md_no_br }},
dfavico = "{{ favico }}";
var md_opt = {
link_md_as_html: false,
modpoll_freq: {{ md_chk_rate }}
};
(function () {
var l = window.localStorage,
drk = (l && l.light) != 1,
@@ -159,13 +154,13 @@ try { l.light = drk? 0:1; } catch (ex) { }
})();
</script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/deps/marked.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/md.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/deps/marked.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/md.js?_={{ ts }}"></script>
{%- if edit %}
<script src="{{ r }}/.cpr/md2.js?_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/md2.js?_={{ ts }}"></script>
{%- endif %}
<script>
<script nonce="{{ js_nonce }}">
Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
jsldp("J_UTL","util");
jsldp("J_MD","md");
@@ -174,7 +169,7 @@ try { l.light = drk? 0:1; } catch (ex) { }
{%- endif %}
</script>
{%- if js %}
<script src="{{ js }}_={{ ts }}"></script>
<script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>
{%- endif %}
</body>
</html>
+5 -2
View File
@@ -316,8 +316,8 @@ function Modpoll() {
};
setTimeout(r.periodic, 300);
if (md_opt.modpoll_freq > 0)
setInterval(r.periodic, 1000 * md_opt.modpoll_freq);
if (modpoll_freq > 0)
setInterval(r.periodic, 1000 * modpoll_freq);
return r;
}
@@ -325,6 +325,9 @@ var modpoll = new Modpoll();
window.onbeforeunload = function (e) {
if (window.gtfo)
return;
if ((ebi("save").className + '').indexOf('disabled') >= 0)
return; //nice (todo)
Loaded 100 of 216 files, more files were not shown because too many files have changed in this diff. Show more