Compare commits

...

42 Commits

Author SHA1 Message Date
ed
ea002ee71d v1.12.1 2024-04-09 23:34:31 +00:00
ed
ab18893cd2 update deps 2024-04-09 23:25:54 +00:00
ed
844d16b9e5 bbox: scrollwheel for prev/next pic
inspired by d385305f5e
2024-04-09 20:39:07 +00:00
ed
989cc613ef fix tree-rendering when history-popping into bbox
plus misc similar technically-incorrect addq usages;
most of these don't matter in practice since they'll
never get a url with a hash, but makes the intent clear

and make sure hashes never get passed around
like they're part of a dirkey, harmless as it is
2024-04-09 19:54:15 +00:00
ed
4f0cad5468 fix bbox destructor, closes #81 for real 2024-04-09 19:10:55 +00:00
ed
f89de6b35d preloading too aggressive, chill a bit 2024-04-09 18:44:23 +00:00
ed
e0bcb88ee7 update pkgs to 1.12.0 2024-04-06 20:56:52 +00:00
ed
a0022805d1 v1.12.0 (closes #64) 2024-04-06 20:11:49 +00:00
ed
853adb5d04 update deps 2024-04-06 19:51:38 +00:00
ed
7744226b5c apply audio equalizer to videos too 2024-04-06 18:44:08 +00:00
ed
d94b5b3fc9 fau doesn't work on iphones; compensate by preloading much earlier 2024-04-06 18:43:45 +00:00
ed
e6ba065bc2 improve cachebusters 2024-04-06 00:27:06 +00:00
ed
59a53ba9ac on phones, fix playback halting if next song didn't buffer in time 2024-04-06 00:25:28 +00:00
ed
b88cc7b5ce turns out it doesn't need to be audible... 2024-04-05 23:06:26 +00:00
ed
5ab54763c6 remove pyoxidizer (unmaintained)
partially reverts e430b2567a

the remaining stuff might be useful for other cpython alternatives
2024-04-05 17:51:26 +00:00
ed
59f815ff8c deps: add busy.mp3 2024-04-04 09:27:01 +00:00
ed
9c42cbec6f maybe fix #81 2024-04-03 00:28:15 +00:00
ed
f471b05aa4 todo: fix playback stopping on phones if slow preload 2024-04-02 23:20:58 +00:00
ed
34c32e3e89 golf:
util.js ensures `WebAssembly`, `Notification`, and `FormData`
are always declared, setting them false when not available
2024-04-02 20:25:06 +00:00
ed
a080759a03 add transcoding to mp3
because CU's car stereo can't do opus...

incidentally adds support for playing any audio format in ie11
2024-03-29 16:36:56 +00:00
ed
0ae12868e5 dirkeys: add volflag dky (skip keycheck) 2024-03-27 21:03:58 +00:00
ed
ef52e2c06c dirkeys: fix 403 in dks volumes 2024-03-27 20:34:34 +00:00
ed
32c912bb16 fix a bunch of dirkey stuff:
* breadcrumb navigation
* tree generation in `recvls`
* dirkeys in initial tree
2024-03-27 16:05:05 +00:00
ed
20870fda79 Merge branch 'dirkeys' into hovudstraum 2024-03-25 10:34:08 +00:00
ed
bdfe2c1a5f mention unproductive optimizations 2024-03-24 22:07:23 +00:00
ed
cb99fbf442 update pkgs to 1.11.2 2024-03-23 17:53:19 +00:00
ed
bccc44dc21 v1.11.2 2024-03-23 17:24:36 +00:00
ed
2f20d29edd idp: mention lack of volume persistence 2024-03-23 16:35:45 +00:00
ed
c6acd3a904 add option --s-rd-sz (socket read size):
counterpart of `--s-wr-sz` which existed already

the default (256 KiB) appears optimal in the most popular scenario
(linux host with storage on local physical disk, usually NVMe)

was previously 32 KiB, so large uploads should now use 17% less CPU

also adds sanchecks for values of `--iobuf`, `--s-rd-sz`, `--s-wr-sz`

also adds file-overwrite feature for multipart posts
2024-03-23 16:35:14 +00:00
ed
2b24c50eb7 add option --iobuf (file r/w buffersize):
the default (256 KiB) appears optimal in the most popular scenario
(linux host with storage on local physical disk, usually NVMe)

was previously a mix of 64 and 512 KiB;
now the same value is enforced everywhere

download-as-tar is now 20% faster with the default value
2024-03-23 16:17:40 +00:00
ed
d30ae8453d idp: precise expansion of ${u} (fixes #79);
it is now possible to grant access to users other than `${u}`
(the user which the volume belongs to)

previously, permissions did not apply correctly to IdP volumes due to
the way `${u}` and `${g}` was expanded, which was a funky iteration
over all known users/groups instead of... just expanding them?

also adds another sanchk that a volume's URL must contain a
`${u}` to be allowed to mention `${u}` in the accs list, and
similarly for `${g}` / `@${g}` since users can be in multiple groups
2024-03-21 20:10:27 +00:00
ed
8e5c436bef black + isort 2024-03-21 18:51:23 +00:00
ed
f500e55e68 update pkgs to 1.11.1 2024-03-18 17:41:43 +00:00
ed
9700a12366 v1.11.1 2024-03-18 17:09:56 +00:00
ed
2b6a34dc5c sfx: lexically comparable git-build versions
if building from an untagged git commit, the third value in the
VERSION tuple (in __version__.py) was a string instead of an int,
causing the version to compare and sort incorrectly
2024-03-18 17:04:49 +00:00
ed
ee80cdb9cf docs: real-ip (with or without cloudflare) 2024-03-18 16:30:51 +00:00
ed
2def4cd248 fix linter warnings + a test 2024-03-18 15:25:10 +00:00
ed
0287c7baa5 fix unpost when there is no rootfs;
the volflags of `/` were used to determine if e2d was enabled,
which is wrong in two ways:

* if there is no `/` volume, it would be globally disabled

* if `/` has e2d, but another volume doesn't, it would
   erroneously think unpost was available, which is not an
   issue unless that volume used to have e2d enabled AND
   there is stale data matching the client's IP

3f05b665 (v1.11.0) had an incomplete fix for the stale-data part of
the above, which also introduced the other issue
2024-03-18 06:15:32 +01:00
ed
51d31588e6 parse xff before deciding to reject a connection
this commit partially fixes the following issue:
if a client manages to escape real-ip detection, copyparty will
try to ban the reverse-proxy instead, effectively banning all clients

this can happen if the configuration says to obtain client real-ip
from a cloudflare header, but the server is not configured to reject
connections from non-cloudflare IPs, so a scanner will eventually
hit the server IP with malicious-looking requests and trigger a ban

copyparty will now continue to process requests from banned IPs until
the header has been parsed and the real-ip has been obtained (or not),
causing an increased server load from malicious clients

assuming the `--xff-src` and `--xff-hdr` config is correct,
this issue should no longer be hitting innocent clients

the old behavior of immediately rejecting a banned IP address
can be re-enabled with the new option `--early-ban`
2024-03-17 02:36:03 +00:00
ed
32553e4520 fix building mtp deps on python 3.12 2024-03-16 13:59:08 +00:00
ed
211a30da38 update pkgs to 1.11.0 2024-03-15 21:34:29 +00:00
ed
10bc2d9205 unsuccessful attempt at dirkeys (#64) 2023-12-17 22:30:22 +00:00
50 changed files with 1241 additions and 439 deletions

View File

@@ -75,6 +75,7 @@ turn almost any device into a file server with resumable uploads/downloads using
* [themes](#themes) * [themes](#themes)
* [complete examples](#complete-examples) * [complete examples](#complete-examples)
* [reverse-proxy](#reverse-proxy) - running copyparty next to other websites * [reverse-proxy](#reverse-proxy) - running copyparty next to other websites
* [real-ip](#real-ip) - teaching copyparty how to see client IPs
* [prometheus](#prometheus) - metrics/stats can be enabled * [prometheus](#prometheus) - metrics/stats can be enabled
* [packages](#packages) - the party might be closer than you think * [packages](#packages) - the party might be closer than you think
* [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes) * [arch package](#arch-package) - now [available on aur](https://aur.archlinux.org/packages/copyparty) maintained by [@icxes](https://github.com/icxes)
@@ -93,6 +94,7 @@ turn almost any device into a file server with resumable uploads/downloads using
* [gotchas](#gotchas) - behavior that might be unexpected * [gotchas](#gotchas) - behavior that might be unexpected
* [cors](#cors) - cross-site request config * [cors](#cors) - cross-site request config
* [filekeys](#filekeys) - prevent filename bruteforcing * [filekeys](#filekeys) - prevent filename bruteforcing
* [dirkeys](#dirkeys) - share specific folders in a volume
* [password hashing](#password-hashing) - you can hash passwords * [password hashing](#password-hashing) - you can hash passwords
* [https](#https) - both HTTP and HTTPS are accepted * [https](#https) - both HTTP and HTTPS are accepted
* [recovering from crashes](#recovering-from-crashes) * [recovering from crashes](#recovering-from-crashes)
@@ -198,7 +200,7 @@ firewall-cmd --reload
* browser * browser
* ☑ [navpane](#navpane) (directory tree sidebar) * ☑ [navpane](#navpane) (directory tree sidebar)
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename)) * ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
* ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus transcoding) * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
* ☑ image gallery with webm player * ☑ image gallery with webm player
* ☑ textfile browser with syntax hilighting * ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails) * ☑ [thumbnails](#thumbnails)
@@ -357,6 +359,9 @@ upgrade notes
* firefox refuses to connect over https, saying "Secure Connection Failed" or "SEC_ERROR_BAD_SIGNATURE", but the usual button to "Accept the Risk and Continue" is not shown * firefox refuses to connect over https, saying "Secure Connection Failed" or "SEC_ERROR_BAD_SIGNATURE", but the usual button to "Accept the Risk and Continue" is not shown
* firefox has corrupted its certstore; fix this by exiting firefox, then find and delete the file named `cert9.db` somewhere in your firefox profile folder * firefox has corrupted its certstore; fix this by exiting firefox, then find and delete the file named `cert9.db` somewhere in your firefox profile folder
* the server keeps saying `thank you for playing` when I try to access the website
* you've gotten banned for malicious traffic! if this happens by mistake, and you're running a reverse-proxy and/or something like cloudflare, see [real-ip](#real-ip) on how to fix this
* copyparty seems to think I am using http, even though the URL is https * copyparty seems to think I am using http, even though the URL is https
* your reverse-proxy is not sending the `X-Forwarded-Proto: https` header; this could be because your reverse-proxy itself is confused. Ensure that none of the intermediates (such as cloudflare) are terminating https before the traffic hits your entrypoint * your reverse-proxy is not sending the `X-Forwarded-Proto: https` header; this could be because your reverse-proxy itself is confused. Ensure that none of the intermediates (such as cloudflare) are terminating https before the traffic hits your entrypoint
@@ -583,7 +588,7 @@ you can also zip a selection of files or folders by clicking them in the browser
![copyparty-zipsel-fs8](https://user-images.githubusercontent.com/241032/129635374-e5136e01-470a-49b1-a762-848e8a4c9cdc.png) ![copyparty-zipsel-fs8](https://user-images.githubusercontent.com/241032/129635374-e5136e01-470a-49b1-a762-848e8a4c9cdc.png)
cool trick: download a folder by appending url-params `?tar&opus` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus before they're added to the archive 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 * 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-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images * and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
* can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0` * can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`
@@ -597,7 +602,7 @@ this initiates an upload using `up2k`; there are two uploaders available:
* `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0 * `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
* `[🚀] up2k`, the good / fancy one * `[🚀] up2k`, the good / fancy one
NB: you can undo/delete your own uploads with `[🧯]` [unpost](#unpost) NB: you can undo/delete your own uploads with `[🧯]` [unpost](#unpost) (and this is also where you abort unfinished uploads, but you have to refresh the page first)
up2k has several advantages: up2k has several advantages:
* you can drop folders into the browser (files are added recursively) * you can drop folders into the browser (files are added recursively)
@@ -774,9 +779,9 @@ open the `[🎺]` media-player-settings tab to configure it,
* `[loop]` keeps looping the folder * `[loop]` keeps looping the folder
* `[next]` plays into the next folder * `[next]` plays into the next folder
* "transcode": * "transcode":
* `[flac]` converts `flac` and `wav` files into opus * `[flac]` converts `flac` and `wav` files into opus (if supported by browser) or mp3
* `[aac]` converts `aac` and `m4a` files into opus * `[aac]` converts `aac` and `m4a` files into opus (if supported by browser) or mp3
* `[oth]` converts all other known formats into opus * `[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|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
* "tint" reduces the contrast of the playback bar * "tint" reduces the contrast of the playback bar
@@ -1037,6 +1042,8 @@ tweaking the ui
* to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href` * to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
* to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at` * to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
see [./docs/rice](./docs/rice) for more
## file indexing ## file indexing
@@ -1287,6 +1294,8 @@ you may experience poor upload performance this way, but that can sometimes be f
someone has also tested geesefs in combination with [gocryptfs](https://nuetzlich.net/gocryptfs/) with surprisingly good results, getting 60 MiB/s upload speeds on a gbit line, but JuiceFS won with 80 MiB/s using its built-in encryption someone has also tested geesefs in combination with [gocryptfs](https://nuetzlich.net/gocryptfs/) with surprisingly good results, getting 60 MiB/s upload speeds on a gbit line, but JuiceFS won with 80 MiB/s using its built-in encryption
you may improve performance by specifying larger values for `--iobuf` / `--s-rd-sz` / `--s-wr-sz`
## hiding from google ## hiding from google
@@ -1383,6 +1392,15 @@ example webserver configs:
* [apache2 config](contrib/apache/copyparty.conf) -- location-based * [apache2 config](contrib/apache/copyparty.conf) -- location-based
### real-ip
teaching copyparty how to see client IPs when running behind a reverse-proxy, or a WAF, or another protection service such as cloudflare
if you (and maybe everybody else) keep getting a message that says `thank you for playing`, then you've gotten banned for malicious traffic. This ban applies to the IP address that copyparty *thinks* identifies the shady client -- so, depending on your setup, you might have to tell copyparty where to find the correct IP
for most common setups, there should be a helpful message in the server-log explaining what to do, but see [docs/xff.md](docs/xff.md) if you want to learn more, including a quick hack to **just make it work** (which is **not** recommended, but hey...)
## prometheus ## prometheus
metrics/stats can be enabled at URL `/.cpr/metrics` for grafana / prometheus / etc (openmetrics 1.0.0) metrics/stats can be enabled at URL `/.cpr/metrics` for grafana / prometheus / etc (openmetrics 1.0.0)
@@ -1727,6 +1745,7 @@ below are some tweaks roughly ordered by usefulness:
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set * `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
* and also makes thumbnails load faster, regardless of e2d/e2t * and also makes thumbnails load faster, regardless of e2d/e2t
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable * `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
* if your volumes are on a network-disk such as NFS / SMB / s3, specifying larger values for `--iobuf` and/or `--s-rd-sz` and/or `--s-wr-sz` may help; try setting all of them to `524288` or `1048576` or `4194304`
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger) * `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
* `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example: * `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example:
* lots of connections (many users or heavy clients) * lots of connections (many users or heavy clients)
@@ -1823,12 +1842,29 @@ cors can be configured with `--acao` and `--acam`, or the protections entirely d
prevent filename bruteforcing prevent filename bruteforcing
volflag `c,fk` generates filekeys (per-file accesskeys) for all files; users which have full read-access (permission `r`) will then see URLs with the correct filekey `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404 volflag `fk` generates filekeys (per-file accesskeys) for all files; users which have full read-access (permission `r`) will then see URLs with the correct filekey `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
by default, filekeys are generated based on salt (`--fk-salt`) + filesystem-path + file-size + inode (if not windows); add volflag `fka` to generate slightly weaker filekeys which will not be invalidated if the file is edited (only salt + path) by default, filekeys are generated based on salt (`--fk-salt`) + filesystem-path + file-size + inode (if not windows); add volflag `fka` to generate slightly weaker filekeys which will not be invalidated if the file is edited (only salt + path)
permissions `wG` (write + upget) lets users upload files and receive their own filekeys, still without being able to see other uploads permissions `wG` (write + upget) lets users upload files and receive their own filekeys, still without being able to see other uploads
### dirkeys
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
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
* `dk` + `dky` gives the same behavior as if all users with `g` access have full read-access, but subfolders are hidden files (their names start with a dot), so `dky` is an alternative to renaming all the folders for that purpose, maybe just for some users
volflag `dks` lets people enter subfolders as well, and also enables download-as-zip/tar
dirkeys are generated based on another salt (`--dk-salt`) + filesystem-path and have a few limitations:
* the key does not change if the contents of the folder is modified
* if you need a new dirkey, either change the salt or rename the folder
* linking to a textfile (so it opens in the textfile viewer) is not possible if recipient doesn't have read-access
## password hashing ## password hashing

View File

@@ -223,7 +223,10 @@ install_vamp() {
# use msys2 in mingw-w64 mode # use msys2 in mingw-w64 mode
# pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk} # pacman -S --needed mingw-w64-x86_64-{ffmpeg,python,python-pip,vamp-plugin-sdk}
$pybin -m pip install --user vamp $pybin -m pip install --user vamp || {
printf '\n\033[7malright, trying something else...\033[0m\n'
$pybin -m pip install --user --no-build-isolation vamp
}
cd "$td" cd "$td"
echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || { echo '#include <vamp-sdk/Plugin.h>' | g++ -x c++ -c -o /dev/null - || [ -e ~/pe/vamp-sdk ] || {

View File

@@ -11,6 +11,14 @@
# (5'000 requests per second, or 20gbps upload/download in parallel) # (5'000 requests per second, or 20gbps upload/download in parallel)
# #
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1 # on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
#
# if you are behind cloudflare (or another protection service),
# remember to reject all connections which are not coming from your
# protection service -- for cloudflare in particular, you can
# generate the list of permitted IP ranges like so:
# (curl -s https://www.cloudflare.com/ips-v{4,6} | sed 's/^/allow /; s/$/;/'; echo; echo "deny all;") > /etc/nginx/cloudflare-only.conf
#
# and then enable it below by uncomenting the cloudflare-only.conf line
upstream cpp { upstream cpp {
server 127.0.0.1:3923 fail_timeout=1s; server 127.0.0.1:3923 fail_timeout=1s;
@@ -21,7 +29,10 @@ server {
listen [::]:443 ssl; listen [::]:443 ssl;
server_name fs.example.com; server_name fs.example.com;
# uncomment the following line to reject non-cloudflare connections, ensuring client IPs cannot be spoofed:
#include /etc/nginx/cloudflare-only.conf;
location / { location / {
proxy_pass http://cpp; proxy_pass http://cpp;
proxy_redirect off; proxy_redirect off;

View File

@@ -1,6 +1,6 @@
# Maintainer: icxes <dev.null@need.moe> # Maintainer: icxes <dev.null@need.moe>
pkgname=copyparty pkgname=copyparty
pkgver="1.10.2" pkgver="1.12.0"
pkgrel=1 pkgrel=1
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++" pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
arch=("any") arch=("any")
@@ -21,7 +21,7 @@ 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") source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
backup=("etc/${pkgname}.d/init" ) backup=("etc/${pkgname}.d/init" )
sha256sums=("001be979a0fdd8ace7d48cab79a137c13b87b78be35fc9633430f45a2831c3ed") sha256sums=("20d06469924b77f80f104d75eaebbb6ffa8aa4e5701b78f7264c4ce435768e5e")
build() { build() {
cd "${srcdir}/${pkgname}-${pkgver}" cd "${srcdir}/${pkgname}-${pkgver}"

View File

@@ -1,5 +1,5 @@
{ {
"url": "https://github.com/9001/copyparty/releases/download/v1.10.2/copyparty-sfx.py", "url": "https://github.com/9001/copyparty/releases/download/v1.12.0/copyparty-sfx.py",
"version": "1.10.2", "version": "1.12.0",
"hash": "sha256-O9lkN30gy3kwIH+39O4dN7agZPkuH36BDTk8mEsQCVg=" "hash": "sha256-Y18pCX/U0POgEH+wcs8Sf7c4lSUYu6bCtKGzIXa/X0M="
} }

View File

@@ -56,7 +56,6 @@ class EnvParams(object):
self.t0 = time.time() self.t0 = time.time()
self.mod = "" self.mod = ""
self.cfg = "" self.cfg = ""
self.ox = getattr(sys, "oxidized", None)
E = EnvParams() E = EnvParams()

View File

@@ -157,7 +157,8 @@ def warn(msg: str) -> None:
def init_E(EE: EnvParams) -> None: def init_E(EE: EnvParams) -> None:
# __init__ runs 18 times when oxidized; do expensive stuff here # some cpython alternatives (such as pyoxidizer) can
# __init__ several times, so do expensive stuff here
E = EE # pylint: disable=redefined-outer-name E = EE # pylint: disable=redefined-outer-name
@@ -190,34 +191,9 @@ def init_E(EE: EnvParams) -> None:
raise Exception("could not find a writable path for config") raise Exception("could not find a writable path for config")
def _unpack() -> str: E.mod = os.path.dirname(os.path.realpath(__file__))
import atexit if E.mod.endswith("__init__"):
import tarfile E.mod = os.path.dirname(E.mod)
import tempfile
from importlib.resources import open_binary
td = tempfile.TemporaryDirectory(prefix="")
atexit.register(td.cleanup)
tdn = td.name
with open_binary("copyparty", "z.tar") as tgz:
with tarfile.open(fileobj=tgz) as tf:
try:
tf.extractall(tdn, filter="tar")
except TypeError:
tf.extractall(tdn) # nosec (archive is safe)
return tdn
try:
E.mod = os.path.dirname(os.path.realpath(__file__))
if E.mod.endswith("__init__"):
E.mod = os.path.dirname(E.mod)
except:
if not E.ox:
raise
E.mod = _unpack()
if sys.platform == "win32": if sys.platform == "win32":
bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "." bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
@@ -274,6 +250,19 @@ def get_fk_salt() -> str:
return ret.decode("utf-8") return ret.decode("utf-8")
def get_dk_salt() -> str:
fp = os.path.join(E.cfg, "dk-salt.txt")
try:
with open(fp, "rb") as f:
ret = f.read().strip()
except:
ret = base64.b64encode(os.urandom(30))
with open(fp, "wb") as f:
f.write(ret + b"\n")
return ret.decode("utf-8")
def get_ah_salt() -> str: def get_ah_salt() -> str:
fp = os.path.join(E.cfg, "ah-salt.txt") fp = os.path.join(E.cfg, "ah-salt.txt")
try: try:
@@ -869,6 +858,7 @@ def add_fs(ap):
ap2 = ap.add_argument_group("filesystem options") ap2 = ap.add_argument_group("filesystem options")
rm_re_def = "5/0.1" if ANYWIN else "0/0" rm_re_def = "5/0.1" if ANYWIN else "0/0"
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)") ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
def add_upload(ap): def add_upload(ap):
@@ -916,6 +906,7 @@ def add_network(ap):
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("--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("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)") ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=186, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost") ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=186, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes") ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds") ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0, help="debug: socket write delay in seconds")
ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds") ap2.add_argument("--rsp-slp", metavar="SEC", type=float, default=0, help="debug: response delay in seconds")
@@ -1122,19 +1113,21 @@ def add_safety(ap):
ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to permissions g/G/h (decent replacement for \033[33m--ban-404\033[0m if that can't be used)") ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to permissions g/G/h (decent replacement for \033[33m--ban-404\033[0m if that can't be used)")
ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]") ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]") ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
ap2.add_argument("--early-ban", action="store_true", help="if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy")
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("--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("--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", 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("--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)") 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)")
def add_salt(ap, fk_salt, ah_salt): def add_salt(ap, fk_salt, dk_salt, ah_salt):
ap2 = ap.add_argument_group('salting options') 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-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)") 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)")
ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]") ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]")
ap2.add_argument("--ah-cli", action="store_true", help="launch an interactive shell which hashes passwords without ever storing or displaying the original passwords") ap2.add_argument("--ah-cli", action="store_true", help="launch an interactive shell which hashes passwords without ever storing or displaying the original passwords")
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("--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("--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)")
@@ -1200,6 +1193,8 @@ def add_thumbnail(ap):
def add_transcoding(ap): def add_transcoding(ap):
ap2 = ap.add_argument_group('transcoding options') ap2 = ap.add_argument_group('transcoding options')
ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding") ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)") ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)")
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds") ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
@@ -1316,6 +1311,7 @@ def run_argparse(
cert_path = os.path.join(E.cfg, "cert.pem") cert_path = os.path.join(E.cfg, "cert.pem")
fk_salt = get_fk_salt() fk_salt = get_fk_salt()
dk_salt = get_dk_salt()
ah_salt = get_ah_salt() ah_salt = get_ah_salt()
# alpine peaks at 5 threads for some reason, # alpine peaks at 5 threads for some reason,
@@ -1347,7 +1343,7 @@ def run_argparse(
add_tftp(ap) add_tftp(ap)
add_smb(ap) add_smb(ap)
add_safety(ap) add_safety(ap)
add_salt(ap, fk_salt, ah_salt) add_salt(ap, fk_salt, dk_salt, ah_salt)
add_optouts(ap) add_optouts(ap)
add_shutdown(ap) add_shutdown(ap)
add_yolo(ap) add_yolo(ap)

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 11, 0) VERSION = (1, 12, 1)
CODENAME = "You Can (Not) Proceed" CODENAME = "locksmith"
BUILD_DT = (2024, 3, 15) BUILD_DT = (2024, 4, 9)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -555,7 +555,12 @@ class VFS(object):
# no vfs nodes in the list of real inodes # no vfs nodes in the list of real inodes
real = [x for x in real if x[0] not in self.nodes] real = [x for x in real if x[0] not in self.nodes]
dbv = self.dbv or self
for name, vn2 in sorted(self.nodes.items()): for name, vn2 in sorted(self.nodes.items()):
if vn2.dbv == dbv and self.flags.get("dk"):
virt_vis[name] = vn2
continue
ok = False ok = False
zx = vn2.axs zx = vn2.axs
axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget] axs = [zx.uread, zx.uwrite, zx.umove, zx.udel, zx.uget]
@@ -1224,7 +1229,9 @@ class AuthSrv(object):
if un.startswith("@"): if un.startswith("@"):
grp = un[1:] grp = un[1:]
uns = [x[0] for x in un_gns.items() if grp in x[1]] uns = [x[0] for x in un_gns.items() if grp in x[1]]
if not uns and grp != "${g}" and not self.args.idp_h_grp: if grp == "${g}":
unames.append(un)
elif not uns and not self.args.idp_h_grp:
t = "group [%s] must be defined with --grp argument (or in a [groups] config section)" t = "group [%s] must be defined with --grp argument (or in a [groups] config section)"
raise CfgEx(t % (grp,)) raise CfgEx(t % (grp,))
@@ -1234,31 +1241,28 @@ class AuthSrv(object):
# unames may still contain ${u} and ${g} so now expand those; # unames may still contain ${u} and ${g} so now expand those;
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns] un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
if "*" not in un_gns:
# need ("*","") to match "*" in unames
un_gn.append(("*", ""))
for _, dst, vu, vg in vols: for src, dst, vu, vg in vols:
unames2 = set() unames2 = set(unames)
for un, gn in un_gn:
# if vu/vg (volume user/group) is non-null,
# then each non-null value corresponds to
# ${u}/${g}; consider this a filter to
# apply to unames, as well as un_gn
if (vu and vu != un) or (vg and vg != gn):
continue
for uname in unames + ([un] if vu or vg else []): if "${u}" in unames:
if uname == "${u}": if not vu:
uname = vu or un t = "cannot use ${u} in accs of volume [%s] because the volume url does not contain ${u}"
elif uname in ("${g}", "@${g}"): raise CfgEx(t % (src,))
uname = vg or gn unames2.add(vu)
if vu and vu != uname: if "@${g}" in unames:
continue if not vg:
t = "cannot use @${g} in accs of volume [%s] because the volume url does not contain @${g}"
raise CfgEx(t % (src,))
unames2.update([un for un, gn in un_gn if gn == vg])
if uname: if "${g}" in unames:
unames2.add(uname) t = 'the accs of volume [%s] contains "${g}" but the only supported way of specifying that is "@${g}"'
raise CfgEx(t % (src,))
unames2.discard("${u}")
unames2.discard("@${g}")
self._read_vol_str(lvl, list(unames2), axs[dst]) self._read_vol_str(lvl, list(unames2), axs[dst])
@@ -1682,6 +1686,20 @@ class AuthSrv(object):
vol.flags["fk"] = int(fk) if fk is not True else 8 vol.flags["fk"] = int(fk) if fk is not True else 8
have_fk = True have_fk = True
dk = vol.flags.get("dk")
dks = vol.flags.get("dks")
dky = vol.flags.get("dky")
if dks is not None and dky is not None:
t = "WARNING: volume /%s has both dks and dky enabled; this is too yolo and not permitted"
raise Exception(t % (vol.vpath,))
if dks and not dk:
dk = dks
if dky and not dk:
dk = dky
if dk:
vol.flags["dk"] = int(dk) if dk is not True else 8
if have_fk and re.match(r"^[0-9\.]+$", self.args.fk_salt): if have_fk and re.match(r"^[0-9\.]+$", self.args.fk_salt):
self.log("filekey salt: {}".format(self.args.fk_salt)) self.log("filekey salt: {}".format(self.args.fk_salt))

View File

@@ -218,7 +218,7 @@ class FtpFs(AbstractedFS):
raise FSE("Cannot open existing file for writing") raise FSE("Cannot open existing file for writing")
self.validpath(ap) self.validpath(ap)
return open(fsenc(ap), mode) return open(fsenc(ap), mode, self.args.iobuf)
def chdir(self, path: str) -> None: def chdir(self, path: str) -> None:
nwd = join(self.cwd, path) nwd = join(self.cwd, path)

View File

@@ -36,6 +36,7 @@ from .bos import bos
from .star import StreamTar from .star import StreamTar
from .sutil import StreamArc, gfilter from .sutil import StreamArc, gfilter
from .szip import StreamZip from .szip import StreamZip
from .util import unquote # type: ignore
from .util import ( from .util import (
APPLESAN_RE, APPLESAN_RE,
BITNESS, BITNESS,
@@ -84,7 +85,6 @@ from .util import (
sendfile_py, sendfile_py,
undot, undot,
unescape_cookie, unescape_cookie,
unquote, # type: ignore
unquotep, unquotep,
vjoin, vjoin,
vol_san, vol_san,
@@ -174,7 +174,6 @@ class HttpCli(object):
self.parser: Optional[MultipartParser] = None self.parser: Optional[MultipartParser] = None
# end placeholders # end placeholders
self.bufsz = 1024 * 32
self.html_head = "" self.html_head = ""
def log(self, msg: str, c: Union[int, str] = 0) -> None: def log(self, msg: str, c: Union[int, str] = 0) -> None:
@@ -228,7 +227,7 @@ class HttpCli(object):
"Cache-Control": "no-store, max-age=0", "Cache-Control": "no-store, max-age=0",
} }
if self.is_banned(): if self.args.early_ban and self.is_banned():
return False return False
if self.conn.ipa_nm and not self.conn.ipa_nm.map(self.conn.addr[0]): if self.conn.ipa_nm and not self.conn.ipa_nm.map(self.conn.addr[0]):
@@ -323,9 +322,7 @@ class HttpCli(object):
if "." in pip if "." in pip
else ":".join(pip.split(":")[:4]) + ":" else ":".join(pip.split(":")[:4]) + ":"
) + "0.0/16" ) + "0.0/16"
zs2 = ( zs2 = ' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
' or "--xff-src=lan"' if self.conn.hsrv.xff_lan.map(pip) else ""
)
self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs, zs2), 3) self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs, zs2), 3)
else: else:
self.ip = cli_ip self.ip = cli_ip
@@ -496,9 +493,7 @@ class HttpCli(object):
else ":".join(pip.split(":")[:4]) + ":" else ":".join(pip.split(":")[:4]) + ":"
) + "0.0/16" ) + "0.0/16"
zs2 = ( zs2 = (
' or "--xff-src=lan"' ' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
if self.conn.hsrv.xff_lan.map(pip)
else ""
) )
self.log(t % (pip, idp_usr, idp_grp, zs, zs2), 3) self.log(t % (pip, idp_usr, idp_grp, zs, zs2), 3)
@@ -1615,15 +1610,16 @@ class HttpCli(object):
return enc or "utf-8" return enc or "utf-8"
def get_body_reader(self) -> tuple[Generator[bytes, None, None], int]: def get_body_reader(self) -> tuple[Generator[bytes, None, None], int]:
bufsz = self.args.s_rd_sz
if "chunked" in self.headers.get("transfer-encoding", "").lower(): if "chunked" in self.headers.get("transfer-encoding", "").lower():
return read_socket_chunked(self.sr), -1 return read_socket_chunked(self.sr, bufsz), -1
remains = int(self.headers.get("content-length", -1)) remains = int(self.headers.get("content-length", -1))
if remains == -1: if remains == -1:
self.keepalive = False self.keepalive = False
return read_socket_unbounded(self.sr), remains return read_socket_unbounded(self.sr, bufsz), remains
else: else:
return read_socket(self.sr, remains), remains return read_socket(self.sr, bufsz, remains), remains
def dump_to_file(self, is_put: bool) -> tuple[int, str, str, int, str, str]: def dump_to_file(self, is_put: bool) -> tuple[int, str, str, int, str, str]:
# post_sz, sha_hex, sha_b64, remains, path, url # post_sz, sha_hex, sha_b64, remains, path, url
@@ -1645,7 +1641,7 @@ class HttpCli(object):
bos.makedirs(fdir) bos.makedirs(fdir)
open_ka: dict[str, Any] = {"fun": open} open_ka: dict[str, Any] = {"fun": open}
open_a = ["wb", 512 * 1024] open_a = ["wb", self.args.iobuf]
# user-request || config-force # user-request || config-force
if ("gz" in vfs.flags or "xz" in vfs.flags) and ( if ("gz" in vfs.flags or "xz" in vfs.flags) and (
@@ -1904,7 +1900,7 @@ class HttpCli(object):
f.seek(ofs) f.seek(ofs)
with open(fp, "wb") as fo: with open(fp, "wb") as fo:
while nrem: while nrem:
buf = f.read(min(nrem, 512 * 1024)) buf = f.read(min(nrem, self.args.iobuf))
if not buf: if not buf:
break break
@@ -1926,7 +1922,7 @@ class HttpCli(object):
return "%s %s n%s" % (spd1, spd2, self.conn.nreq) return "%s %s n%s" % (spd1, spd2, self.conn.nreq)
def handle_post_multipart(self) -> bool: def handle_post_multipart(self) -> bool:
self.parser = MultipartParser(self.log, self.sr, self.headers) self.parser = MultipartParser(self.log, self.args, self.sr, self.headers)
self.parser.parse() self.parser.parse()
file0: list[tuple[str, Optional[str], Generator[bytes, None, None]]] = [] file0: list[tuple[str, Optional[str], Generator[bytes, None, None]]] = []
@@ -1970,7 +1966,12 @@ class HttpCli(object):
v = self.uparam[k] v = self.uparam[k]
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False) if self._use_dirkey():
vn = self.vn
rem = self.rem
else:
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
zs = self.parser.require("files", 1024 * 1024) zs = self.parser.require("files", 1024 * 1024)
if not zs: if not zs:
raise Pebkac(422, "need files list") raise Pebkac(422, "need files list")
@@ -2155,7 +2156,7 @@ class HttpCli(object):
self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains)) self.log("writing {} #{} @{} len {}".format(path, chash, cstart, remains))
reader = read_socket(self.sr, remains) reader = read_socket(self.sr, self.args.s_rd_sz, remains)
f = None f = None
fpool = not self.args.no_fpool and sprs fpool = not self.args.no_fpool and sprs
@@ -2166,7 +2167,7 @@ class HttpCli(object):
except: except:
pass pass
f = f or open(fsenc(path), "rb+", 512 * 1024) f = f or open(fsenc(path), "rb+", self.args.iobuf)
try: try:
f.seek(cstart[0]) f.seek(cstart[0])
@@ -2189,7 +2190,8 @@ class HttpCli(object):
) )
ofs = 0 ofs = 0
while ofs < chunksize: while ofs < chunksize:
bufsz = min(chunksize - ofs, 4 * 1024 * 1024) bufsz = max(4 * 1024 * 1024, self.args.iobuf)
bufsz = min(chunksize - ofs, bufsz)
f.seek(cstart[0] + ofs) f.seek(cstart[0] + ofs)
buf = f.read(bufsz) buf = f.read(bufsz)
for wofs in cstart[1:]: for wofs in cstart[1:]:
@@ -2442,6 +2444,18 @@ class HttpCli(object):
suffix = "-{:.6f}-{}".format(time.time(), dip) suffix = "-{:.6f}-{}".format(time.time(), dip)
open_args = {"fdir": fdir, "suffix": suffix} open_args = {"fdir": fdir, "suffix": suffix}
if "replace" in self.uparam:
abspath = os.path.join(fdir, fname)
if not self.can_delete:
self.log("user not allowed to overwrite with ?replace")
elif bos.path.exists(abspath):
try:
wunlink(self.log, abspath, vfs.flags)
t = "overwriting file with new upload: %s"
except:
t = "toctou while deleting for ?replace: %s"
self.log(t % (abspath,))
# reserve destination filename # reserve destination filename
with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as zfw: with ren_open(fname, "wb", fdir=fdir, suffix=suffix) as zfw:
fname = zfw["orz"][1] fname = zfw["orz"][1]
@@ -2486,7 +2500,7 @@ class HttpCli(object):
v2 = lim.dfv - lim.dfl v2 = lim.dfv - lim.dfl
max_sz = min(v1, v2) if v1 and v2 else v1 or v2 max_sz = min(v1, v2) if v1 and v2 else v1 or v2
with ren_open(tnam, "wb", 512 * 1024, **open_args) as zfw: with ren_open(tnam, "wb", self.args.iobuf, **open_args) as zfw:
f, tnam = zfw["orz"] f, tnam = zfw["orz"]
tabspath = os.path.join(fdir, tnam) tabspath = os.path.join(fdir, tnam)
self.log("writing to {}".format(tabspath)) self.log("writing to {}".format(tabspath))
@@ -2782,7 +2796,7 @@ class HttpCli(object):
if bos.path.exists(fp): if bos.path.exists(fp):
wunlink(self.log, fp, vfs.flags) wunlink(self.log, fp, vfs.flags)
with open(fsenc(fp), "wb", 512 * 1024) as f: with open(fsenc(fp), "wb", self.args.iobuf) as f:
sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp) sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
if lim: if lim:
@@ -2861,6 +2875,30 @@ class HttpCli(object):
return file_lastmod, True return file_lastmod, True
def _use_dirkey(self, ap: str = "") -> bool:
if self.can_read or not self.can_get:
return False
if self.vn.flags.get("dky"):
return True
req = self.uparam.get("k") or ""
if not req:
return False
dk_len = self.vn.flags.get("dk")
if not dk_len:
return False
ap = ap or self.vn.canonical(self.rem)
zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_len]
if req == zs:
return True
t = "wrong dirkey, want %s, got %s\n vp: %s\n ap: %s"
self.log(t % (zs, req, self.req, ap), 6)
return False
def _expand(self, txt: str, phs: list[str]) -> str: def _expand(self, txt: str, phs: list[str]) -> str:
for ph in phs: for ph in phs:
if ph.startswith("hdr."): if ph.startswith("hdr."):
@@ -3014,8 +3052,7 @@ class HttpCli(object):
upper = gzip_orig_sz(fs_path) upper = gzip_orig_sz(fs_path)
else: else:
open_func = open open_func = open
# 512 kB is optimal for huge files, use 64k open_args = [fsenc(fs_path), "rb", self.args.iobuf]
open_args = [fsenc(fs_path), "rb", 64 * 1024]
use_sendfile = ( use_sendfile = (
# fmt: off # fmt: off
not self.tls not self.tls
@@ -3140,7 +3177,7 @@ class HttpCli(object):
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]})) # for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
cfmt = "" cfmt = ""
if self.thumbcli and not self.args.no_bacode: if self.thumbcli and not self.args.no_bacode:
for zs in ("opus", "w", "j"): for zs in ("opus", "mp3", "w", "j"):
if zs in self.ouparam or uarg == zs: if zs in self.ouparam or uarg == zs:
cfmt = zs cfmt = zs
@@ -3150,6 +3187,7 @@ class HttpCli(object):
bgen = packer( bgen = packer(
self.log, self.log,
self.args,
fgen, fgen,
utf8="utf" in uarg, utf8="utf" in uarg,
pre_crc="crc" in uarg, pre_crc="crc" in uarg,
@@ -3227,7 +3265,7 @@ class HttpCli(object):
sz_md = 0 sz_md = 0
lead = b"" lead = b""
fullfile = b"" fullfile = b""
for buf in yieldfile(fs_path): for buf in yieldfile(fs_path, self.args.iobuf):
if sz_md < max_sz: if sz_md < max_sz:
fullfile += buf fullfile += buf
else: else:
@@ -3300,7 +3338,7 @@ class HttpCli(object):
if fullfile: if fullfile:
self.s.sendall(fullfile) self.s.sendall(fullfile)
else: else:
for buf in yieldfile(fs_path): for buf in yieldfile(fs_path, self.args.iobuf):
self.s.sendall(html_bescape(buf)) self.s.sendall(html_bescape(buf))
self.s.sendall(html[1]) self.s.sendall(html[1])
@@ -3548,7 +3586,7 @@ class HttpCli(object):
dst = dst[len(top) + 1 :] dst = dst[len(top) + 1 :]
ret = self.gen_tree(top, dst) ret = self.gen_tree(top, dst, self.uparam.get("k", ""))
if self.is_vproxied: if self.is_vproxied:
parents = self.args.R.split("/") parents = self.args.R.split("/")
for parent in reversed(parents): for parent in reversed(parents):
@@ -3558,18 +3596,25 @@ class HttpCli(object):
self.reply(zs.encode("utf-8"), mime="application/json") self.reply(zs.encode("utf-8"), mime="application/json")
return True return True
def gen_tree(self, top: str, target: str) -> dict[str, Any]: def gen_tree(self, top: str, target: str, dk: str) -> dict[str, Any]:
ret: dict[str, Any] = {} ret: dict[str, Any] = {}
excl = None excl = None
if target: if target:
excl, target = (target.split("/", 1) + [""])[:2] excl, target = (target.split("/", 1) + [""])[:2]
sub = self.gen_tree("/".join([top, excl]).strip("/"), target) sub = self.gen_tree("/".join([top, excl]).strip("/"), target, dk)
ret["k" + quotep(excl)] = sub ret["k" + quotep(excl)] = sub
vfs = self.asrv.vfs vfs = self.asrv.vfs
dk_sz = False
if dk:
vn, rem = vfs.get(top, self.uname, False, False)
if vn.flags.get("dks") and self._use_dirkey(vn.canonical(rem)):
dk_sz = vn.flags.get("dk")
dots = False dots = False
fsroot = ""
try: try:
vn, rem = vfs.get(top, self.uname, True, False) vn, rem = vfs.get(top, self.uname, not dk_sz, False)
fsroot, vfs_ls, vfs_virt = vn.ls( fsroot, vfs_ls, vfs_virt = vn.ls(
rem, rem,
self.uname, self.uname,
@@ -3577,7 +3622,9 @@ class HttpCli(object):
[[True, False], [False, True]], [[True, False], [False, True]],
) )
dots = self.uname in vn.axs.udot dots = self.uname in vn.axs.udot
dk_sz = vn.flags.get("dk")
except: except:
dk_sz = None
vfs_ls = [] vfs_ls = []
vfs_virt = {} vfs_virt = {}
for v in self.rvol: for v in self.rvol:
@@ -3592,6 +3639,14 @@ class HttpCli(object):
dirs = [quotep(x) for x in dirs if x != excl] dirs = [quotep(x) for x in dirs if x != excl]
if dk_sz and fsroot:
kdirs = []
for dn in dirs:
ap = os.path.join(fsroot, dn)
zs = self.gen_fk(2, self.args.dk_salt, ap, 0, 0)[:dk_sz]
kdirs.append(dn + "?k=" + zs)
dirs = kdirs
for x in vfs_virt: for x in vfs_virt:
if x != excl: if x != excl:
try: try:
@@ -3605,8 +3660,6 @@ class HttpCli(object):
return ret return ret
def tx_ups(self) -> bool: def tx_ups(self) -> bool:
have_unpost = self.args.unpost and "e2d" in self.vn.flags
idx = self.conn.get_u2idx() idx = self.conn.get_u2idx()
if not idx or not hasattr(idx, "p_end"): if not idx or not hasattr(idx, "p_end"):
raise Pebkac(500, "sqlite3 is not available on the server; cannot unpost") raise Pebkac(500, "sqlite3 is not available on the server; cannot unpost")
@@ -3630,8 +3683,14 @@ class HttpCli(object):
) )
uret = x.get() uret = x.get()
allvols = self.asrv.vfs.all_vols if have_unpost else {} if not self.args.unpost:
for vol in allvols.values(): allvols = []
else:
allvols = list(self.asrv.vfs.all_vols.values())
allvols = [x for x in allvols if "e2d" in x.flags]
for vol in allvols:
cur = idx.get_cur(vol.realpath) cur = idx.get_cur(vol.realpath)
if not cur: if not cur:
continue continue
@@ -3683,7 +3742,7 @@ class HttpCli(object):
for v in ret: for v in ret:
v["vp"] = self.args.SR + v["vp"] v["vp"] = self.args.SR + v["vp"]
if not have_unpost: if not allvols:
ret = [{"kinshi": 1}] ret = [{"kinshi": 1}]
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, indent=0)) jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, indent=0))
@@ -3852,6 +3911,7 @@ class HttpCli(object):
self.out_headers["X-Robots-Tag"] = "noindex, nofollow" self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
is_dir = stat.S_ISDIR(st.st_mode) is_dir = stat.S_ISDIR(st.st_mode)
is_dk = False
fk_pass = False fk_pass = False
icur = None icur = None
if is_dir and (e2t or e2d): if is_dir and (e2t or e2d):
@@ -3860,7 +3920,7 @@ class HttpCli(object):
icur = idx.get_cur(dbv.realpath) icur = idx.get_cur(dbv.realpath)
th_fmt = self.uparam.get("th") th_fmt = self.uparam.get("th")
if self.can_read: if self.can_read or (self.can_get and vn.flags.get("dk")):
if th_fmt is not None: if th_fmt is not None:
nothumb = "dthumb" in dbv.flags nothumb = "dthumb" in dbv.flags
if is_dir: if is_dir:
@@ -3966,8 +4026,11 @@ class HttpCli(object):
return self.tx_file(abspath) return self.tx_file(abspath)
elif is_dir and not self.can_read and not self.can_write: elif is_dir and not self.can_read:
return self.tx_404(True) if self._use_dirkey(abspath):
is_dk = True
elif not self.can_write:
return self.tx_404(True)
srv_info = [] srv_info = []
@@ -3989,7 +4052,7 @@ class HttpCli(object):
srv_infot = "</span> // <span>".join(srv_info) srv_infot = "</span> // <span>".join(srv_info)
perms = [] perms = []
if self.can_read: if self.can_read or is_dk:
perms.append("read") perms.append("read")
if self.can_write: if self.can_write:
perms.append("write") perms.append("write")
@@ -4117,7 +4180,7 @@ class HttpCli(object):
if not self.conn.hsrv.prism: if not self.conn.hsrv.prism:
j2a["no_prism"] = True j2a["no_prism"] = True
if not self.can_read: if not self.can_read and not is_dk:
if is_ls: if is_ls:
return self.tx_ls(ls_ret) return self.tx_ls(ls_ret)
@@ -4170,8 +4233,15 @@ class HttpCli(object):
): ):
ls_names = exclude_dotfiles(ls_names) ls_names = exclude_dotfiles(ls_names)
add_dk = vf.get("dk")
add_fk = vf.get("fk") add_fk = vf.get("fk")
fk_alg = 2 if "fka" in vf else 1 fk_alg = 2 if "fka" in vf else 1
if add_dk:
if vf.get("dky"):
add_dk = False
else:
zs = self.gen_fk(2, self.args.dk_salt, abspath, 0, 0)[:add_dk]
ls_ret["dk"] = cgv["dk"] = zs
dirs = [] dirs = []
files = [] files = []
@@ -4199,6 +4269,12 @@ class HttpCli(object):
href += "/" href += "/"
if self.args.no_zip: if self.args.no_zip:
margin = "DIR" margin = "DIR"
elif add_dk:
zs = absreal(fspath)
margin = '<a href="%s?k=%s&zip" rel="nofollow">zip</a>' % (
quotep(href),
self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
)
else: else:
margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (quotep(href),) margin = '<a href="%s?zip" rel="nofollow">zip</a>' % (quotep(href),)
elif fn in hist: elif fn in hist:
@@ -4239,6 +4315,11 @@ class HttpCli(object):
0 if ANYWIN else inf.st_ino, 0 if ANYWIN else inf.st_ino,
)[:add_fk], )[:add_fk],
) )
elif add_dk and is_dir:
href = "%s?k=%s" % (
quotep(href),
self.gen_fk(2, self.args.dk_salt, fspath, 0, 0)[:add_dk],
)
else: else:
href = quotep(href) href = quotep(href)
@@ -4257,6 +4338,9 @@ class HttpCli(object):
files.append(item) files.append(item)
item["rd"] = rem item["rd"] = rem
if is_dk and not vf.get("dks"):
dirs = []
if ( if (
self.cookies.get("idxh") == "y" self.cookies.get("idxh") == "y"
and "ls" not in self.uparam and "ls" not in self.uparam

View File

@@ -55,8 +55,9 @@ class HttpConn(object):
self.E: EnvParams = self.args.E self.E: EnvParams = self.args.E
self.asrv: AuthSrv = hsrv.asrv # mypy404 self.asrv: AuthSrv = hsrv.asrv # mypy404
self.u2fh: Util.FHC = hsrv.u2fh # mypy404 self.u2fh: Util.FHC = hsrv.u2fh # mypy404
self.ipa_nm: NetMap = hsrv.ipa_nm self.ipa_nm: Optional[NetMap] = hsrv.ipa_nm
self.xff_nm: NetMap = hsrv.xff_nm self.xff_nm: Optional[NetMap] = hsrv.xff_nm
self.xff_lan: NetMap = hsrv.xff_lan # type: ignore
self.iphash: HMaccas = hsrv.broker.iphash self.iphash: HMaccas = hsrv.broker.iphash
self.bans: dict[str, int] = hsrv.bans self.bans: dict[str, int] = hsrv.bans
self.aclose: dict[str, int] = hsrv.aclose self.aclose: dict[str, int] = hsrv.aclose

View File

@@ -104,7 +104,7 @@ class HttpSrv(object):
self.t0 = time.time() self.t0 = time.time()
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else "" nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
self.magician = Magician() self.magician = Magician()
self.nm = NetMap([], {}) self.nm = NetMap([], [])
self.ssdp: Optional["SSDPr"] = None self.ssdp: Optional["SSDPr"] = None
self.gpwd = Garda(self.args.ban_pw) self.gpwd = Garda(self.args.ban_pw)
self.g404 = Garda(self.args.ban_404) self.g404 = Garda(self.args.ban_404)

View File

@@ -551,8 +551,7 @@ class MTag(object):
pypath = str(os.pathsep.join(zsl)) pypath = str(os.pathsep.join(zsl))
env["PYTHONPATH"] = pypath env["PYTHONPATH"] = pypath
except: except:
if not E.ox and not EXE: raise # might be expected outside cpython
raise
ret: dict[str, Any] = {} ret: dict[str, Any] = {}
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])): for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):

View File

@@ -206,6 +206,7 @@ class MCast(object):
except: except:
t = "announce failed on {} [{}]:\n{}" t = "announce failed on {} [{}]:\n{}"
self.log(t.format(netdev, ip, min_ex()), 3) self.log(t.format(netdev, ip, min_ex()), 3)
sck.close()
if self.args.zm_msub: if self.args.zm_msub:
for s1 in self.srv.values(): for s1 in self.srv.values():

View File

@@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import argparse
import re import re
import stat import stat
import tarfile import tarfile
@@ -44,11 +45,12 @@ class StreamTar(StreamArc):
def __init__( def __init__(
self, self,
log: "NamedLogger", log: "NamedLogger",
args: argparse.Namespace,
fgen: Generator[dict[str, Any], None, None], fgen: Generator[dict[str, Any], None, None],
cmp: str = "", cmp: str = "",
**kwargs: Any **kwargs: Any
): ):
super(StreamTar, self).__init__(log, fgen) super(StreamTar, self).__init__(log, args, fgen)
self.ci = 0 self.ci = 0
self.co = 0 self.co = 0
@@ -126,7 +128,7 @@ class StreamTar(StreamArc):
inf.gid = 0 inf.gid = 0
self.ci += inf.size self.ci += inf.size
with open(fsenc(src), "rb", 512 * 1024) as fo: with open(fsenc(src), "rb", self.args.iobuf) as fo:
self.tar.addfile(inf, fo) self.tar.addfile(inf, fo)
def _gen(self) -> None: def _gen(self) -> None:

View File

@@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import argparse
import os import os
import tempfile import tempfile
from datetime import datetime from datetime import datetime
@@ -20,10 +21,12 @@ class StreamArc(object):
def __init__( def __init__(
self, self,
log: "NamedLogger", log: "NamedLogger",
args: argparse.Namespace,
fgen: Generator[dict[str, Any], None, None], fgen: Generator[dict[str, Any], None, None],
**kwargs: Any **kwargs: Any
): ):
self.log = log self.log = log
self.args = args
self.fgen = fgen self.fgen = fgen
self.stopped = False self.stopped = False
@@ -78,7 +81,9 @@ def enthumb(
) -> dict[str, Any]: ) -> dict[str, Any]:
rem = f["vp"] rem = f["vp"]
ext = rem.rsplit(".", 1)[-1].lower() ext = rem.rsplit(".", 1)[-1].lower()
if fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|"): if (fmt == "mp3" and ext == "mp3") or (
fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|")
):
raise Exception() raise Exception()
vp = vjoin(vtop, rem.split("/", 1)[1]) vp = vjoin(vtop, rem.split("/", 1)[1])

View File

@@ -28,7 +28,7 @@ if True: # pylint: disable=using-constant-test
import typing import typing
from typing import Any, Optional, Union from typing import Any, Optional, Union
from .__init__ import ANYWIN, E, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, E, EnvParams, unicode
from .authsrv import BAD_CFG, AuthSrv from .authsrv import BAD_CFG, AuthSrv
from .cert import ensure_cert from .cert import ensure_cert
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
@@ -173,6 +173,26 @@ class SvcHub(object):
self.log("root", t.format(args.j), c=3) self.log("root", t.format(args.j), c=3)
args.no_fpool = True args.no_fpool = True
for name, arg in (
("iobuf", "iobuf"),
("s-rd-sz", "s_rd_sz"),
("s-wr-sz", "s_wr_sz"),
):
zi = getattr(args, arg)
if zi < 32768:
t = "WARNING: expect very poor performance because you specified a very low value (%d) for --%s"
self.log("root", t % (zi, name), 3)
zi = 2
zi2 = 2 ** (zi - 1).bit_length()
if zi != zi2:
zi3 = 2 ** ((zi - 1).bit_length() - 1)
t = "WARNING: expect poor performance because --%s is not a power-of-two; consider using %d or %d instead of %d"
self.log("root", t % (name, zi2, zi3, zi), 3)
if args.s_rd_sz > args.iobuf:
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
bri = "zy"[args.theme % 2 :][:1] bri = "zy"[args.theme % 2 :][:1]
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)] ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
args.theme = "{0}{1} {0} {1}".format(ch, bri) args.theme = "{0}{1} {0} {1}".format(ch, bri)
@@ -256,6 +276,11 @@ class SvcHub(object):
if want_ff and ANYWIN: if want_ff and ANYWIN:
self.log("thumb", "download FFmpeg to fix it:\033[0m " + FFMPEG_URL, 3) self.log("thumb", "download FFmpeg to fix it:\033[0m " + FFMPEG_URL, 3)
if not args.no_acode:
if not re.match("^(0|[qv][0-9]|[0-9]{2,3}k)$", args.q_mp3.lower()):
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
raise Exception(t % (args.q_mp3,))
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage) args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
zms = "" zms = ""

View File

@@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import argparse
import calendar import calendar
import stat import stat
import time import time
@@ -218,12 +219,13 @@ class StreamZip(StreamArc):
def __init__( def __init__(
self, self,
log: "NamedLogger", log: "NamedLogger",
args: argparse.Namespace,
fgen: Generator[dict[str, Any], None, None], fgen: Generator[dict[str, Any], None, None],
utf8: bool = False, utf8: bool = False,
pre_crc: bool = False, pre_crc: bool = False,
**kwargs: Any **kwargs: Any
) -> None: ) -> None:
super(StreamZip, self).__init__(log, fgen) super(StreamZip, self).__init__(log, args, fgen)
self.utf8 = utf8 self.utf8 = utf8
self.pre_crc = pre_crc self.pre_crc = pre_crc
@@ -248,7 +250,7 @@ class StreamZip(StreamArc):
crc = 0 crc = 0
if self.pre_crc: if self.pre_crc:
for buf in yieldfile(src): for buf in yieldfile(src, self.args.iobuf):
crc = zlib.crc32(buf, crc) crc = zlib.crc32(buf, crc)
crc &= 0xFFFFFFFF crc &= 0xFFFFFFFF
@@ -257,7 +259,7 @@ class StreamZip(StreamArc):
buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc) buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc)
yield self._ct(buf) yield self._ct(buf)
for buf in yieldfile(src): for buf in yieldfile(src, self.args.iobuf):
if not self.pre_crc: if not self.pre_crc:
crc = zlib.crc32(buf, crc) crc = zlib.crc32(buf, crc)

View File

@@ -97,8 +97,6 @@ class Tftpd(object):
cbak = [] cbak = []
if not self.args.tftp_no_fast and not EXE: if not self.args.tftp_no_fast and not EXE:
try: try:
import inspect
ptn = re.compile(r"(^\s*)log\.debug\(.*\)$") ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
for C in Cs: for C in Cs:
cbak.append(C.__dict__) cbak.append(C.__dict__)
@@ -342,6 +340,9 @@ class Tftpd(object):
if not self.args.tftp_nols and bos.path.isdir(ap): if not self.args.tftp_nols and bos.path.isdir(ap):
return self._ls(vpath, "", 0, True) return self._ls(vpath, "", 0, True)
if not a:
a = [self.args.iobuf]
return open(ap, mode, *a, **ka) return open(ap, mode, *a, **ka)
def _mkdir(self, vpath: str, *a) -> None: def _mkdir(self, vpath: str, *a) -> None:

View File

@@ -57,7 +57,7 @@ class ThumbCli(object):
if is_vid and "dvthumb" in dbv.flags: if is_vid and "dvthumb" in dbv.flags:
return None return None
want_opus = fmt in ("opus", "caf") want_opus = fmt in ("opus", "caf", "mp3")
is_au = ext in self.fmt_ffa is_au = ext in self.fmt_ffa
if is_au: if is_au:
if want_opus: if want_opus:

View File

@@ -16,9 +16,9 @@ from .__init__ import ANYWIN, TYPE_CHECKING
from .authsrv import VFS from .authsrv import VFS
from .bos import bos from .bos import bos
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
from .util import BytesIO # type: ignore
from .util import ( from .util import (
FFMPEG_URL, FFMPEG_URL,
BytesIO, # type: ignore
Cooldown, Cooldown,
Daemon, Daemon,
Pebkac, Pebkac,
@@ -109,7 +109,7 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str, ffa: set[str]) -
h = hashlib.sha512(afsenc(fn)).digest() h = hashlib.sha512(afsenc(fn)).digest()
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24] fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
if fmt in ("opus", "caf"): if fmt in ("opus", "caf", "mp3"):
cat = "ac" cat = "ac"
else: else:
fc = fmt[:1] fc = fmt[:1]
@@ -307,6 +307,8 @@ class ThumbSrv(object):
elif lib == "ff" and ext in self.fmt_ffa: elif lib == "ff" and ext in self.fmt_ffa:
if tpath.endswith(".opus") or tpath.endswith(".caf"): if tpath.endswith(".opus") or tpath.endswith(".caf"):
funs.append(self.conv_opus) funs.append(self.conv_opus)
elif tpath.endswith(".mp3"):
funs.append(self.conv_mp3)
elif tpath.endswith(".png"): elif tpath.endswith(".png"):
funs.append(self.conv_waves) funs.append(self.conv_waves)
png_ok = True png_ok = True
@@ -637,8 +639,47 @@ class ThumbSrv(object):
cmd += [fsenc(tpath)] cmd += [fsenc(tpath)]
self._run_ff(cmd, vn) self._run_ff(cmd, vn)
def conv_mp3(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
quality = self.args.q_mp3.lower()
if self.args.no_acode or not quality:
raise Exception("disabled in server config")
self.wait4ram(0.2, tpath)
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
if "ac" not in ret:
raise Exception("not audio")
if quality.endswith("k"):
qk = b"-b:a"
qv = quality.encode("ascii")
else:
qk = b"-q:a"
qv = quality[1:].encode("ascii")
# extremely conservative choices for output format
# (always 2ch 44k1) because if a device is old enough
# to not support opus then it's probably also super picky
# fmt: off
cmd = [
b"ffmpeg",
b"-nostdin",
b"-v", b"error",
b"-hide_banner",
b"-i", fsenc(abspath),
b"-map_metadata", b"-1",
b"-map", b"0:a:0",
b"-ar", b"44100",
b"-ac", b"2",
b"-c:a", b"libmp3lame",
qk, qv,
fsenc(tpath)
]
# fmt: on
self._run_ff(cmd, vn, oom=300)
def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None: def conv_opus(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
if self.args.no_acode: if self.args.no_acode or not self.args.q_opus:
raise Exception("disabled in server config") raise Exception("disabled in server config")
self.wait4ram(0.2, tpath) self.wait4ram(0.2, tpath)
@@ -662,6 +703,7 @@ class ThumbSrv(object):
pass pass
caf_src = abspath if src_opus else tmp_opus caf_src = abspath if src_opus else tmp_opus
bq = ("%dk" % (self.args.q_opus,)).encode("ascii")
if not want_caf or not src_opus: if not want_caf or not src_opus:
# fmt: off # fmt: off
@@ -674,7 +716,7 @@ class ThumbSrv(object):
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-map", b"0:a:0", b"-map", b"0:a:0",
b"-c:a", b"libopus", b"-c:a", b"libopus",
b"-b:a", b"128k", b"-b:a", bq,
fsenc(tmp_opus) fsenc(tmp_opus)
] ]
# fmt: on # fmt: on
@@ -697,7 +739,7 @@ class ThumbSrv(object):
b"-map_metadata", b"-1", b"-map_metadata", b"-1",
b"-ac", b"2", b"-ac", b"2",
b"-c:a", b"libopus", b"-c:a", b"libopus",
b"-b:a", b"128k", b"-b:a", bq,
b"-f", b"caf", b"-f", b"caf",
fsenc(tpath) fsenc(tpath)
] ]
@@ -771,7 +813,7 @@ class ThumbSrv(object):
def _clean(self, cat: str, thumbpath: str) -> int: def _clean(self, cat: str, thumbpath: str) -> int:
# self.log("cln {}".format(thumbpath)) # self.log("cln {}".format(thumbpath))
exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf"] exts = ["jpg", "webp", "png"] if cat == "th" else ["opus", "caf", "mp3"]
maxage = getattr(self.args, cat + "_maxage") maxage = getattr(self.args, cat + "_maxage")
now = time.time() now = time.time()
prev_b64 = None prev_b64 = None

View File

@@ -3920,7 +3920,7 @@ class Up2k(object):
csz = up2k_chunksize(fsz) csz = up2k_chunksize(fsz)
ret = [] ret = []
suffix = " MB, {}".format(path) suffix = " MB, {}".format(path)
with open(fsenc(path), "rb", 512 * 1024) as f: with open(fsenc(path), "rb", self.args.iobuf) as f:
if self.mth and fsz >= 1024 * 512: if self.mth and fsz >= 1024 * 512:
tlt = self.mth.hash(f, fsz, csz, self.pp, prefix, suffix) tlt = self.mth.hash(f, fsz, csz, self.pp, prefix, suffix)
ret = [x[0] for x in tlt] ret = [x[0] for x in tlt]

View File

@@ -1400,10 +1400,15 @@ def ren_open(
class MultipartParser(object): class MultipartParser(object):
def __init__( def __init__(
self, log_func: "NamedLogger", sr: Unrecv, http_headers: dict[str, str] self,
log_func: "NamedLogger",
args: argparse.Namespace,
sr: Unrecv,
http_headers: dict[str, str],
): ):
self.sr = sr self.sr = sr
self.log = log_func self.log = log_func
self.args = args
self.headers = http_headers self.headers = http_headers
self.re_ctype = re.compile(r"^content-type: *([^; ]+)", re.IGNORECASE) self.re_ctype = re.compile(r"^content-type: *([^; ]+)", re.IGNORECASE)
@@ -1502,7 +1507,7 @@ class MultipartParser(object):
def _read_data(self) -> Generator[bytes, None, None]: def _read_data(self) -> Generator[bytes, None, None]:
blen = len(self.boundary) blen = len(self.boundary)
bufsz = 32 * 1024 bufsz = self.args.s_rd_sz
while True: while True:
try: try:
buf = self.sr.recv(bufsz) buf = self.sr.recv(bufsz)
@@ -2243,10 +2248,11 @@ def shut_socket(log: "NamedLogger", sck: socket.socket, timeout: int = 3) -> Non
sck.close() sck.close()
def read_socket(sr: Unrecv, total_size: int) -> Generator[bytes, None, None]: def read_socket(
sr: Unrecv, bufsz: int, total_size: int
) -> Generator[bytes, None, None]:
remains = total_size remains = total_size
while remains > 0: while remains > 0:
bufsz = 32 * 1024
if bufsz > remains: if bufsz > remains:
bufsz = remains bufsz = remains
@@ -2260,16 +2266,16 @@ def read_socket(sr: Unrecv, total_size: int) -> Generator[bytes, None, None]:
yield buf yield buf
def read_socket_unbounded(sr: Unrecv) -> Generator[bytes, None, None]: def read_socket_unbounded(sr: Unrecv, bufsz: int) -> Generator[bytes, None, None]:
try: try:
while True: while True:
yield sr.recv(32 * 1024) yield sr.recv(bufsz)
except: except:
return return
def read_socket_chunked( def read_socket_chunked(
sr: Unrecv, log: Optional["NamedLogger"] = None sr: Unrecv, bufsz: int, log: Optional["NamedLogger"] = None
) -> Generator[bytes, None, None]: ) -> Generator[bytes, None, None]:
err = "upload aborted: expected chunk length, got [{}] |{}| instead" err = "upload aborted: expected chunk length, got [{}] |{}| instead"
while True: while True:
@@ -2303,7 +2309,7 @@ def read_socket_chunked(
if log: if log:
log("receiving %d byte chunk" % (chunklen,)) log("receiving %d byte chunk" % (chunklen,))
for chunk in read_socket(sr, chunklen): for chunk in read_socket(sr, bufsz, chunklen):
yield chunk yield chunk
x = sr.recv_ex(2, False) x = sr.recv_ex(2, False)
@@ -2361,10 +2367,11 @@ def build_netmap(csv: str):
return NetMap(ips, cidrs, True) return NetMap(ips, cidrs, True)
def yieldfile(fn: str) -> Generator[bytes, None, None]: def yieldfile(fn: str, bufsz: int) -> Generator[bytes, None, None]:
with open(fsenc(fn), "rb", 512 * 1024) as f: readsz = min(bufsz, 128 * 1024)
with open(fsenc(fn), "rb", bufsz) as f:
while True: while True:
buf = f.read(128 * 1024) buf = f.read(readsz)
if not buf: if not buf:
break break

View File

@@ -29,6 +29,7 @@ window.baguetteBox = (function () {
isOverlayVisible = false, isOverlayVisible = false,
touch = {}, // start-pos touch = {}, // start-pos
touchFlag = false, // busy touchFlag = false, // busy
scrollTimer = 0,
re_i = /^[^?]+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i, re_i = /^[^?]+\.(a?png|avif|bmp|gif|heif|jpe?g|jfif|svg|webp)(\?|$)/i,
re_v = /^[^?]+\.(webm|mkv|mp4)(\?|$)/i, re_v = /^[^?]+\.(webm|mkv|mp4)(\?|$)/i,
anims = ['slideIn', 'fadeIn', 'none'], anims = ['slideIn', 'fadeIn', 'none'],
@@ -91,6 +92,30 @@ window.baguetteBox = (function () {
touchendHandler(); touchendHandler();
}; };
var overlayWheelHandler = function (e) {
if (!options.noScrollbars || anymod(e))
return;
ev(e);
var x = e.deltaX,
y = e.deltaY,
d = Math.abs(x) > Math.abs(y) ? x : y;
if (e.deltaMode)
d *= 10;
if (Date.now() - scrollTimer < (Math.abs(d) > 20 ? 100 : 300))
return;
scrollTimer = Date.now();
if (d > 0)
showNextImage();
else
showPreviousImage();
};
var trapFocusInsideOverlay = function (e) { var trapFocusInsideOverlay = function (e) {
if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) { if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(e.target))) {
e.stopPropagation(); e.stopPropagation();
@@ -394,8 +419,7 @@ window.baguetteBox = (function () {
} }
function dlpic() { function dlpic() {
var url = findfile()[3].href; var url = addq(findfile()[3].href, 'cache');
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache';
dl_file(url); dl_file(url);
} }
@@ -452,6 +476,7 @@ window.baguetteBox = (function () {
bind(document, 'keyup', keyUpHandler); bind(document, 'keyup', keyUpHandler);
bind(document, 'fullscreenchange', onFSC); bind(document, 'fullscreenchange', onFSC);
bind(overlay, 'click', overlayClickHandler); bind(overlay, 'click', overlayClickHandler);
bind(overlay, 'wheel', overlayWheelHandler);
bind(btnPrev, 'click', showPreviousImage); bind(btnPrev, 'click', showPreviousImage);
bind(btnNext, 'click', showNextImage); bind(btnNext, 'click', showNextImage);
bind(btnClose, 'click', hideOverlay); bind(btnClose, 'click', hideOverlay);
@@ -474,6 +499,7 @@ window.baguetteBox = (function () {
unbind(document, 'keyup', keyUpHandler); unbind(document, 'keyup', keyUpHandler);
unbind(document, 'fullscreenchange', onFSC); unbind(document, 'fullscreenchange', onFSC);
unbind(overlay, 'click', overlayClickHandler); unbind(overlay, 'click', overlayClickHandler);
unbind(overlay, 'wheel', overlayWheelHandler);
unbind(btnPrev, 'click', showPreviousImage); unbind(btnPrev, 'click', showPreviousImage);
unbind(btnNext, 'click', showNextImage); unbind(btnNext, 'click', showNextImage);
unbind(btnClose, 'click', hideOverlay); unbind(btnClose, 'click', hideOverlay);
@@ -584,7 +610,7 @@ window.baguetteBox = (function () {
isOverlayVisible = true; isOverlayVisible = true;
} }
function hideOverlay(e) { function hideOverlay(e, dtor) {
ev(e); ev(e);
playvid(false); playvid(false);
removeFromCache('#files'); removeFromCache('#files');
@@ -592,7 +618,15 @@ window.baguetteBox = (function () {
document.documentElement.style.overflowY = 'auto'; document.documentElement.style.overflowY = 'auto';
document.body.style.overflowY = 'auto'; document.body.style.overflowY = 'auto';
} }
if (overlay.style.display === 'none')
try {
if (document.fullscreenElement)
document.exitFullscreen();
}
catch (ex) { }
isFullscreen = false;
if (dtor || overlay.style.display === 'none')
return; return;
if (options.duringHide) if (options.duringHide)
@@ -600,11 +634,6 @@ window.baguetteBox = (function () {
sethash(''); sethash('');
unbindEvents(); unbindEvents();
try {
document.exitFullscreen();
isFullscreen = false;
}
catch (ex) { }
// Fade out and hide the overlay // Fade out and hide the overlay
overlay.className = ''; overlay.className = '';
@@ -682,7 +711,7 @@ window.baguetteBox = (function () {
options.captions.call(currentGallery, imageElement) : options.captions.call(currentGallery, imageElement) :
imageElement.getAttribute('data-caption') || imageElement.title; imageElement.getAttribute('data-caption') || imageElement.title;
imageSrc += imageSrc.indexOf('?') < 0 ? '?cache' : '&cache'; imageSrc = addq(imageSrc, 'cache');
if (is_vid && index != currentIndex) if (is_vid && index != currentIndex)
return; // no preload return; // no preload
@@ -720,6 +749,9 @@ window.baguetteBox = (function () {
figure.appendChild(image); figure.appendChild(image);
if (is_vid && window.afilt)
afilt.apply(undefined, image);
if (options.async && callback) if (options.async && callback)
callback(); callback();
} }
@@ -1062,6 +1094,7 @@ window.baguetteBox = (function () {
} }
function destroyPlugin() { function destroyPlugin() {
hideOverlay(undefined, true);
unbindEvents(); unbindEvents();
clearCachedData(); clearCachedData();
document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay')); document.getElementsByTagName('body')[0].removeChild(ebi('bbox-overlay'));

View File

@@ -244,6 +244,7 @@ var Ls = {
"mt_preload": "start loading the next song near the end for gapless playback\">preload", "mt_preload": "start loading the next song near the end for gapless playback\">preload",
"mt_prescan": "go to the next folder before the last song$Nends, keeping the webbrowser happy$Nso it doesn't stop the playback\">nav", "mt_prescan": "go to the next folder before the last song$Nends, keeping the webbrowser happy$Nso it doesn't stop the playback\">nav",
"mt_fullpre": "try to preload the entire song;$N✅ enable on <b>unreliable</b> connections,$N❌ <b>disable</b> on slow connections probably\">full", "mt_fullpre": "try to preload the entire song;$N✅ enable on <b>unreliable</b> connections,$N❌ <b>disable</b> on slow connections probably\">full",
"mt_fau": "on phones, prevent music from stopping if the next song doesn't preload fast enough (can make tags display glitchy)\">☕️",
"mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s", "mt_waves": "waveform seekbar:$Nshow audio amplitude in the scrubber\">~s",
"mt_npclip": "show buttons for clipboarding the currently playing song\">/np", "mt_npclip": "show buttons for clipboarding the currently playing song\">/np",
"mt_octl": "os integration (media hotkeys / osd)\">os-ctl", "mt_octl": "os integration (media hotkeys / osd)\">os-ctl",
@@ -745,6 +746,7 @@ var Ls = {
"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles", "mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
"mt_prescan": "ved behov, bla til neste mappe$Nslik at nettleseren lar oss$Nfortsette å spille musikk\">bla", "mt_prescan": "ved behov, bla til neste mappe$Nslik at nettleseren lar oss$Nfortsette å spille musikk\">bla",
"mt_fullpre": "hent ned hele neste sang, ikke bare litt:$N✅ skru på hvis nettet ditt er <b>ustabilt</b>,$N❌ skru av hvis nettet ditt er <b>tregt</b>\">full", "mt_fullpre": "hent ned hele neste sang, ikke bare litt:$N✅ skru på hvis nettet ditt er <b>ustabilt</b>,$N❌ skru av hvis nettet ditt er <b>tregt</b>\">full",
"mt_fau": "for telefoner: forhindre at avspilling stopper hvis nettet er for tregt til å laste neste sang i tide. Hvis påskrudd, kan forårsake at sang-info ikke vises korrekt i OS'et\">☕️",
"mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s", "mt_waves": "waveform seekbar:$Nvis volumkurve i avspillingsfeltet\">~s",
"mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np", "mt_npclip": "vis knapper for å kopiere info om sangen du hører på\">/np",
"mt_octl": "integrering med operativsystemet (fjernkontroll, info-skjerm)\">os-ctl", "mt_octl": "integrering med operativsystemet (fjernkontroll, info-skjerm)\">os-ctl",
@@ -1400,7 +1402,9 @@ var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext),
ACB = sread('au_cbv') || 1, ACB = sread('au_cbv') || 1,
noih = /[?&]v\b/.exec('' + location), noih = /[?&]v\b/.exec('' + location),
hash0 = location.hash, hash0 = location.hash,
mp; ldks = [],
dks = {},
dk, mp;
var mpl = (function () { var mpl = (function () {
@@ -1413,6 +1417,7 @@ var mpl = (function () {
'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' + '<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
'<a href="#" class="tgl btn" id="au_prescan" tt="' + L.mt_prescan + '</a>' + '<a href="#" class="tgl btn" id="au_prescan" tt="' + L.mt_prescan + '</a>' +
'<a href="#" class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' + '<a href="#" class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' +
'<a href="#" class="tgl btn" id="au_fau" tt="' + L.mt_fau + '</a>' +
'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' + '<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' + '<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
'<a href="#" class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' + '<a href="#" class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' +
@@ -1459,6 +1464,15 @@ var mpl = (function () {
bcfg_bind(r, 'preload', 'au_preload', true); bcfg_bind(r, 'preload', 'au_preload', true);
bcfg_bind(r, 'prescan', 'au_prescan', true); bcfg_bind(r, 'prescan', 'au_prescan', true);
bcfg_bind(r, 'fullpre', 'au_fullpre', false); bcfg_bind(r, 'fullpre', 'au_fullpre', false);
bcfg_bind(r, 'fau', 'au_fau', MOBILE && !IPHONE, function (v) {
mp.nopause();
if (mp.fau) {
mp.fau.pause();
mp.fau = mpo.fau = null;
console.log('stop fau');
}
mp.init_fau();
});
bcfg_bind(r, 'waves', 'au_waves', true, function (v) { bcfg_bind(r, 'waves', 'au_waves', true, function (v) {
if (!v) pbar.unwave(); if (!v) pbar.unwave();
}); });
@@ -1532,7 +1546,7 @@ var mpl = (function () {
c = r.ac_flac; c = r.ac_flac;
else if (/\.(aac|m4a)$/i.exec(url)) else if (/\.(aac|m4a)$/i.exec(url))
c = r.ac_aac; c = r.ac_aac;
else if (/\.opus$/i.exec(url) && !can_ogg) else if (/\.(ogg|opus)$/i.exec(url) && !can_ogg)
c = true; c = true;
else if (re_au_native.exec(url)) else if (re_au_native.exec(url))
c = false; c = false;
@@ -1540,7 +1554,7 @@ var mpl = (function () {
if (!c) if (!c)
return url; return url;
return url + (url.indexOf('?') < 0 ? '?' : '&') + 'th=' + (can_ogg ? 'opus' : 'caf'); return addq(url, 'th=' + (can_ogg ? 'opus' : (IPHONE || MACOS) ? 'caf' : 'mp3'));
}; };
r.pp = function () { r.pp = function () {
@@ -1591,7 +1605,7 @@ var mpl = (function () {
} }
if (cover) { if (cover) {
cover += (cover.indexOf('?') === -1 ? '?' : '&') + 'th=j'; cover = addq(cover, 'th=j');
tags.artwork = [{ "src": cover, type: "image/jpeg" }]; tags.artwork = [{ "src": cover, type: "image/jpeg" }];
} }
} }
@@ -1650,26 +1664,23 @@ var mpl = (function () {
var can_ogg = true; var can_ogg = true;
try { try {
can_ogg = new Audio().canPlayType('audio/ogg; codecs=opus') === 'probably'; can_ogg = new Audio().canPlayType('audio/ogg; codecs=opus') === 'probably';
if (document.documentMode)
can_ogg = true; // ie8-11
} }
catch (ex) { } catch (ex) { }
var re_au_native = can_ogg ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i,
have_acode ? /\.(aac|flac|m4a|mp3|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i,
re_au_all = /\.(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)$/i; re_au_all = /\.(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)$/i;
// extract songs + add play column // extract songs + add play column
var mpo = { "au": null, "au2": null, "acs": null }; var mpo = { "au": null, "au2": null, "acs": null, "fau": null };
function MPlayer() { function MPlayer() {
var r = this; var r = this;
r.id = Date.now(); r.id = Date.now();
r.au = mpo.au; r.au = mpo.au;
r.au2 = mpo.au2; r.au2 = mpo.au2;
r.acs = mpo.acs; r.acs = mpo.acs;
r.fau = mpo.fau;
r.tracks = {}; r.tracks = {};
r.order = []; r.order = [];
r.cd_pause = 0; r.cd_pause = 0;
@@ -1682,8 +1693,8 @@ function MPlayer() {
link = tds[1].getElementsByTagName('a'); link = tds[1].getElementsByTagName('a');
link = link[link.length - 1]; link = link[link.length - 1];
var url = noq_href(link), var url = link.getAttribute('href'),
m = re_audio.exec(url); m = re_audio.exec(url.split('?')[0]);
if (m) { if (m) {
var tid = link.getAttribute('id'); var tid = link.getAttribute('id');
@@ -1795,12 +1806,11 @@ function MPlayer() {
var t0 = Date.now(), var t0 = Date.now(),
fname = uricom_dec(url.split('/').pop()); fname = uricom_dec(url.split('/').pop());
url = mpl.acode(url); url = addq(mpl.acode(url), 'cache=987&_=' + ACB);
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987&_=' + ACB;
mpl.preload_url = full ? url : null; mpl.preload_url = full ? url : null;
if (mpl.waves) if (mpl.waves)
fetch(url.replace(/\bth=opus&/, '') + '&th=p').then(function (x) { fetch(url.replace(/\bth=(opus|mp3)&/, '') + '&th=p').then(function (x) {
x.body.getReader().read(); x.body.getReader().read();
}); });
@@ -1842,6 +1852,17 @@ function MPlayer() {
r.nopause = function () { r.nopause = function () {
r.cd_pause = Date.now(); r.cd_pause = Date.now();
}; };
r.init_fau = function () {
if (r.fau || !mpl.fau)
return;
// breaks touchbar-macs
console.log('init fau');
r.fau = new Audio(SR + '/.cpr/deps/busy.mp3?_=' + TS);
r.fau.loop = true;
r.fau.play();
};
} }
@@ -2379,8 +2400,7 @@ function dl_song() {
return toast.inf(10, L.f_dls); return toast.inf(10, L.f_dls);
} }
var url = mp.tracks[mp.au.tid]; var url = addq(mp.tracks[mp.au.tid], 'cache=987&_=' + ACB);
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987&_=' + ACB;
dl_file(url); dl_file(url);
} }
@@ -2514,11 +2534,11 @@ var mpui = (function () {
rem = pos > 1 ? len - pos : 999, rem = pos > 1 ? len - pos : 999,
full = null; full = null;
if (rem < (mpl.fullpre ? 7 : 20)) { if (rem < 7 || (!mpl.fullpre && (rem < 40 || (rem < 90 && pos > 10)))) {
preloaded = fpreloaded = mp.au.rsrc; preloaded = fpreloaded = mp.au.rsrc;
full = false; full = false;
} }
else if (rem < 40 && mpl.fullpre && fpreloaded != mp.au.rsrc) { else if (rem < 60 && mpl.fullpre && fpreloaded != mp.au.rsrc) {
fpreloaded = mp.au.rsrc; fpreloaded = mp.au.rsrc;
full = true; full = true;
} }
@@ -2714,7 +2734,7 @@ var afilt = (function () {
mp.acs = mpo.acs = null; mp.acs = mpo.acs = null;
}; };
r.apply = function (v) { r.apply = function (v, au) {
r.init(); r.init();
r.draw(); r.draw();
@@ -2734,12 +2754,13 @@ var afilt = (function () {
if (r.plugs[a].en) if (r.plugs[a].en)
plug = true; plug = true;
if (!actx || !mp.au || (!r.eqen && !plug && !mp.acs)) au = au || (mp && mp.au);
if (!actx || !au || (!r.eqen && !plug && !mp.acs))
return; return;
r.stop(); r.stop();
mp.au.id = mp.au.id || Date.now(); au.id = au.id || Date.now();
mp.acs = r.acst[mp.au.id] = r.acst[mp.au.id] || actx.createMediaElementSource(mp.au); mp.acs = r.acst[au.id] = r.acst[au.id] || actx.createMediaElementSource(au);
if (r.eqen) if (r.eqen)
add_eq(); add_eq();
@@ -2995,6 +3016,7 @@ function play(tid, is_ev, seek) {
return; return;
mpl.preload_url = null; mpl.preload_url = null;
mp.nopause();
mp.stopfade(true); mp.stopfade(true);
var tn = tid; var tn = tid;
@@ -3041,9 +3063,9 @@ function play(tid, is_ev, seek) {
mp.au.onended = next_song; mp.au.onended = next_song;
widget.open(); widget.open();
} }
mp.init_fau();
var url = mpl.acode(mp.tracks[tid]); var url = addq(mpl.acode(mp.tracks[tid]), 'cache=987&_=' + ACB);
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=987&_=' + ACB;
if (mp.au.rsrc == url) if (mp.au.rsrc == url)
mp.au.currentTime = 0; mp.au.currentTime = 0;
@@ -3107,7 +3129,7 @@ function play(tid, is_ev, seek) {
pbar.unwave(); pbar.unwave();
if (mpl.waves) if (mpl.waves)
pbar.loadwaves(url.replace(/\bth=opus&/, '') + '&th=p'); pbar.loadwaves(url.replace(/\bth=(opus|mp3)&/, '') + '&th=p');
mpui.progress_updater(); mpui.progress_updater();
pbar.onresize(); pbar.onresize();
@@ -3194,7 +3216,10 @@ function evau_error(e) {
toast.warn(15, esc(basenames(err + mfile))); toast.warn(15, esc(basenames(err + mfile)));
}; };
xhr.send(); xhr.send();
return;
} }
setTimeout(next_song, 15000);
} }
@@ -4214,12 +4239,12 @@ var showfile = (function () {
qsr('#prism_css'); qsr('#prism_css');
var el = mknod('link', 'prism_css'); var el = mknod('link', 'prism_css');
el.rel = 'stylesheet'; el.rel = 'stylesheet';
el.href = SR + '/.cpr/deps/prism' + (light ? '' : 'd') + '.css'; el.href = SR + '/.cpr/deps/prism' + (light ? '' : 'd') + '.css?_=' + TS;
document.head.appendChild(el); document.head.appendChild(el);
}; };
r.active = function () { r.active = function () {
return location.search.indexOf('doc=') + 1; return !!/[?&]doc=/.exec(location.search);
}; };
r.getlang = function (fn) { r.getlang = function (fn) {
@@ -4260,12 +4285,15 @@ var showfile = (function () {
}; };
r.show = function (url, no_push) { r.show = function (url, no_push) {
var xhr = new XHR(); var xhr = new XHR(),
m = /[?&](k=[^&#]+)/.exec(url);
url = url.split('?')[0] + (m ? '?' + m[1] : '');
xhr.url = url; xhr.url = url;
xhr.fname = uricom_dec(url.split('/').pop()); xhr.fname = uricom_dec(url.split('/').pop());
xhr.no_push = no_push; xhr.no_push = no_push;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.open('GET', url.split('?')[0], true); xhr.open('GET', url, true);
xhr.onprogress = loading; xhr.onprogress = loading;
xhr.onload = xhr.onerror = load_cb; xhr.onload = xhr.onerror = load_cb;
xhr.send(); xhr.send();
@@ -4303,14 +4331,14 @@ var showfile = (function () {
var url = doc[0], var url = doc[0],
lnh = doc[1], lnh = doc[1],
txt = doc[2], txt = doc[2],
name = url.split('/').pop(), name = url.split('?')[0].split('/').pop(),
tname = uricom_dec(name), tname = uricom_dec(name),
lang = r.getlang(name), lang = r.getlang(name),
is_md = lang == 'md'; is_md = lang == 'md';
ebi('files').style.display = ebi('gfiles').style.display = ebi('lazy').style.display = ebi('pro').style.display = ebi('epi').style.display = 'none'; ebi('files').style.display = ebi('gfiles').style.display = ebi('lazy').style.display = ebi('pro').style.display = ebi('epi').style.display = 'none';
ebi('dldoc').setAttribute('href', url); ebi('dldoc').setAttribute('href', url);
ebi('editdoc').setAttribute('href', url + (url.indexOf('?') > 0 ? '&' : '?') + 'edit'); ebi('editdoc').setAttribute('href', addq(url, 'edit'));
ebi('editdoc').style.display = (has(perms, 'write') && (is_md || has(perms, 'delete'))) ? '' : 'none'; ebi('editdoc').style.display = (has(perms, 'write') && (is_md || has(perms, 'delete'))) ? '' : 'none';
var wr = ebi('bdoc'), var wr = ebi('bdoc'),
@@ -4361,7 +4389,7 @@ var showfile = (function () {
wintitle(tname + ' \u2014 '); wintitle(tname + ' \u2014 ');
document.documentElement.scrollTop = 0; document.documentElement.scrollTop = 0;
var hfun = no_push ? hist_replace : hist_push; var hfun = no_push ? hist_replace : hist_push;
hfun(get_evpath() + '?doc=' + url.split('/').pop()); hfun(get_evpath() + '?doc=' + name); // can't dk: server wants dk and js needs fk
qsr('#docname'); qsr('#docname');
el = mknod('span', 'docname'); el = mknod('span', 'docname');
@@ -4563,7 +4591,7 @@ var thegrid = (function () {
if (!force) if (!force)
return; return;
hist_push(get_evpath()); hist_push(get_evpath() + (dk ? '?k=' + dk : ''));
wintitle(); wintitle();
} }
@@ -4795,10 +4823,10 @@ var thegrid = (function () {
ref = ao.getAttribute('id'), ref = ao.getAttribute('id'),
isdir = href.endsWith('/'), isdir = href.endsWith('/'),
ac = isdir ? ' class="dir"' : '', ac = isdir ? ' class="dir"' : '',
ihref = href; ihref = ohref;
if (r.thumbs) { if (r.thumbs) {
ihref += '?th=' + (have_webp ? 'w' : 'j'); ihref = addq(ihref, 'th=' + (have_webp ? 'w' : 'j'));
if (!r.crop) if (!r.crop)
ihref += 'f'; ihref += 'f';
if (r.x3) if (r.x3)
@@ -4834,7 +4862,7 @@ var thegrid = (function () {
} }
ihref = SR + '/.cpr/ico/' + ext; ihref = SR + '/.cpr/ico/' + ext;
} }
ihref += (ihref.indexOf('?') > 0 ? '&' : '?') + 'cache=i&_=' + ACB; ihref = addq(ihref, 'cache=i&_=' + ACB + TS);
if (CHROME) if (CHROME)
ihref += "&raster"; ihref += "&raster";
@@ -4875,10 +4903,10 @@ var thegrid = (function () {
baguetteBox.destroy(); baguetteBox.destroy();
var br = baguetteBox.run(isrc, { var br = baguetteBox.run(isrc, {
noScrollbars: true,
duringHide: r.onhide, duringHide: r.onhide,
afterShow: function () { afterShow: function () {
r.bbox_opts.refocus = true; r.bbox_opts.refocus = true;
document.body.style.overflow = 'hidden';
}, },
captions: function (g) { captions: function (g) {
var idx = -1, var idx = -1,
@@ -4901,7 +4929,8 @@ var thegrid = (function () {
}; };
r.onhide = function () { r.onhide = function () {
document.body.style.overflow = ''; afilt.apply();
if (!thegrid.ihop) if (!thegrid.ihop)
return; return;
@@ -5723,6 +5752,14 @@ var treectl = (function () {
}; };
r.nvis = r.lim; r.nvis = r.lim;
ldks = jread('dks', []);
for (var a = ldks.length - 1; a >= 0; a--) {
var s = ldks[a],
o = s.lastIndexOf('?');
dks[s.slice(0, o)] = s.slice(o + 1);
}
function setwrap(v) { function setwrap(v) {
clmod(ebi('tree'), 'nowrap', !v); clmod(ebi('tree'), 'nowrap', !v);
reload_tree(); reload_tree();
@@ -5937,12 +5974,15 @@ var treectl = (function () {
}; };
function get_tree(top, dst, rst) { function get_tree(top, dst, rst) {
var xhr = new XHR(); var xhr = new XHR(),
m = /[?&](k=[^&#]+)/.exec(dst),
k = m ? '&' + m[1] : dk ? '&k=' + dk : '';
xhr.top = top; xhr.top = top;
xhr.dst = dst; xhr.dst = dst;
xhr.rst = rst; xhr.rst = rst;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.open('GET', dst + '?tree=' + top + (r.dots ? '&dots' : ''), true); xhr.open('GET', addq(dst, 'tree=' + top + (r.dots ? '&dots' : '') + k), true);
xhr.onload = xhr.onerror = recvtree; xhr.onload = xhr.onerror = recvtree;
xhr.send(); xhr.send();
enspin('#tree'); enspin('#tree');
@@ -5969,7 +6009,7 @@ var treectl = (function () {
} }
ebi('treeul').setAttribute('ts', ts); ebi('treeul').setAttribute('ts', ts);
var top = top0 == '.' ? dst : top0, var top = (top0 == '.' ? dst : top0).split('?')[0],
name = uricom_dec(top.split('/').slice(-2)[0]), name = uricom_dec(top.split('/').slice(-2)[0]),
rtop = top.replace(/^\/+/, ""), rtop = top.replace(/^\/+/, ""),
html = parsetree(res, rtop); html = parsetree(res, rtop);
@@ -5986,7 +6026,7 @@ var treectl = (function () {
var links = QSA('#treeul a+a'); var links = QSA('#treeul a+a');
for (var a = 0, aa = links.length; a < aa; a++) { for (var a = 0, aa = links.length; a < aa; a++) {
if (links[a].getAttribute('href') == top) { if (links[a].getAttribute('href').split('?')[0] == top) {
var o = links[a].parentNode; var o = links[a].parentNode;
if (!o.getElementsByTagName('li').length) if (!o.getElementsByTagName('li').length)
o.innerHTML = html; o.innerHTML = html;
@@ -6019,14 +6059,20 @@ var treectl = (function () {
function reload_tree() { function reload_tree() {
var cdir = r.nextdir || get_vpath(), var cdir = r.nextdir || get_vpath(),
cevp = get_evpath(),
links = QSA('#treeul a+a'), links = QSA('#treeul a+a'),
nowrap = QS('#tree.nowrap') && QS('#hovertree.on'), nowrap = QS('#tree.nowrap') && QS('#hovertree.on'),
act = null; act = null;
for (var a = 0, aa = links.length; a < aa; a++) { for (var a = 0, aa = links.length; a < aa; a++) {
var href = uricom_dec(links[a].getAttribute('href')), var qhref = links[a].getAttribute('href'),
ehref = qhref.split('?')[0],
href = uricom_dec(ehref),
cl = ''; cl = '';
if (dk && ehref == cevp && !/[?&]k=/.exec(qhref))
links[a].setAttribute('href', addq(qhref, 'k=' + dk));
if (href == cdir) { if (href == cdir) {
act = links[a]; act = links[a];
cl = 'hl'; cl = 'hl';
@@ -6125,13 +6171,16 @@ var treectl = (function () {
if (IE && !history.pushState) if (IE && !history.pushState)
return window.location = url; return window.location = url;
var xhr = new XHR(); var xhr = new XHR(),
m = /[?&](k=[^&#]+)/.exec(url),
k = m ? '&' + m[1] : dk ? '&k=' + dk : '';
xhr.top = url.split('?')[0]; xhr.top = url.split('?')[0];
xhr.back = back xhr.back = back
xhr.hpush = hpush; xhr.hpush = hpush;
xhr.hydrate = hydrate; xhr.hydrate = hydrate;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.open('GET', xhr.top + '?ls' + (r.dots ? '&dots' : ''), true); xhr.open('GET', xhr.top + '?ls' + (r.dots ? '&dots' : '') + k, true);
xhr.onload = xhr.onerror = recvls; xhr.onload = xhr.onerror = recvls;
xhr.send(); xhr.send();
@@ -6193,6 +6242,7 @@ var treectl = (function () {
read_dsort(res.dsort); read_dsort(res.dsort);
dcrop = res.dcrop; dcrop = res.dcrop;
dth3x = res.dth3x; dth3x = res.dth3x;
dk = res.dk;
srvinf = res.srvinf; srvinf = res.srvinf;
try { try {
@@ -6201,14 +6251,22 @@ var treectl = (function () {
catch (ex) { } catch (ex) { }
if (this.hpush && !showfile.active()) if (this.hpush && !showfile.active())
hist_push(this.top); hist_push(this.top + (dk ? '?k=' + dk : ''));
if (!this.back) { if (!this.back) {
var dirs = []; var dirs = [];
for (var a = 0; a < res.dirs.length; a++) for (var a = 0; a < res.dirs.length; a++) {
dirs.push(res.dirs[a].href.split('/')[0].split('?')[0]); var dh = res.dirs[a].href,
dn = dh.split('/')[0].split('?')[0],
m = /[?&](k=[^&#]+)/.exec(dh);
rendertree({ "a": dirs }, this.ts, ".", get_evpath()); if (m)
dn += '?' + m[1];
dirs.push(dn);
}
rendertree({ "a": dirs }, this.ts, ".", get_evpath() + (dk ? '?k=' + dk : ''));
} }
r.gentab(this.top, res); r.gentab(this.top, res);
@@ -6268,6 +6326,10 @@ var treectl = (function () {
if (ae = ae.querySelector('a[id]')) if (ae = ae.querySelector('a[id]'))
cid = ae.getAttribute('id'); cid = ae.getAttribute('id');
var m = /[?&]k=([^&]+)/.exec(location.search);
if (m)
memo_dk(top, m[1]);
r.lsc = res; r.lsc = res;
if (res.unlist) { if (res.unlist) {
var ptn = new RegExp(res.unlist); var ptn = new RegExp(res.unlist);
@@ -6309,7 +6371,7 @@ var treectl = (function () {
if (lang) { if (lang) {
showfile.files.push({ 'id': id, 'name': fname }); showfile.files.push({ 'id': id, 'name': fname });
if (lang == 'md') if (lang == 'md')
tn.href += tn.href.indexOf('?') < 0 ? '?v' : '&v'; tn.href = addq(tn.href, 'v');
} }
if (tn.lead == '-') if (tn.lead == '-')
@@ -6385,7 +6447,7 @@ var treectl = (function () {
url = url.href; url = url.href;
var mt = m[0] == 'a' ? 'audio' : /\.(webm|mkv)($|\?)/i.exec(url) ? 'video' : 'image' var mt = m[0] == 'a' ? 'audio' : /\.(webm|mkv)($|\?)/i.exec(url) ? 'video' : 'image'
if (mt == 'image') { if (mt == 'image') {
url += url.indexOf('?') < 0 ? '?cache' : '&cache'; url = addq(url, 'cache');
console.log(url); console.log(url);
new Image().src = url; new Image().src = url;
} }
@@ -6417,6 +6479,30 @@ var treectl = (function () {
setTimeout(eval_hash, 1); setTimeout(eval_hash, 1);
}; };
function memo_dk(vp, k) {
dks[vp] = k;
var lv = vp + "?" + k;
if (has(ldks, lv))
return;
ldks.unshift(lv);
if (ldks.length > 32) {
var keep = [], evp = get_evpath();
for (var a = 0; a < ldks.length; a++) {
var s = ldks[a];
if (evp.startsWith(s.replace(/\?[^?]+$/, '')))
keep.push(s);
}
var lim = 32 - keep.length;
for (var a = 0; a < lim; a++) {
if (!has(keep, ldks[a]))
keep.push(ldks[a])
}
ldks = keep;
}
jwrite('dks', ldks);
}
r.setlazy = function (plain) { r.setlazy = function (plain) {
var html = ['<div id="plazy">', esc(plain.join(' ')), '</div>'], var html = ['<div id="plazy">', esc(plain.join(' ')), '</div>'],
all = r.lsc.files.length + r.lsc.dirs.length, all = r.lsc.files.length + r.lsc.dirs.length,
@@ -6488,7 +6574,9 @@ var treectl = (function () {
keys.sort(function (a, b) { return a.localeCompare(b); }); keys.sort(function (a, b) { return a.localeCompare(b); });
for (var a = 0; a < keys.length; a++) { for (var a = 0; a < keys.length; a++) {
var kk = keys[a], var kk = keys[a],
ks = kk.slice(1), m = /(\?k=[^\n]+)/.exec(kk),
kdk = m ? m[1] : '',
ks = kk.replace(kdk, '').slice(1),
ded = ks.endsWith('\n'), ded = ks.endsWith('\n'),
k = uricom_sdec(ded ? ks.replace(/\n$/, '') : ks), k = uricom_sdec(ded ? ks.replace(/\n$/, '') : ks),
hek = esc(k[0]), hek = esc(k[0]),
@@ -6496,7 +6584,7 @@ var treectl = (function () {
url = '/' + (top ? top + uek : uek) + '/', url = '/' + (top ? top + uek : uek) + '/',
sym = res[kk] ? '-' : '+', sym = res[kk] ? '-' : '+',
link = '<a href="#">' + sym + '</a><a href="' + link = '<a href="#">' + sym + '</a><a href="' +
url + '">' + hek + '</a>'; url + kdk + '">' + hek + '</a>';
if (res[kk]) { if (res[kk]) {
var subtree = parsetree(res[kk], url.slice(1)); var subtree = parsetree(res[kk], url.slice(1));
@@ -6537,16 +6625,24 @@ var treectl = (function () {
if (!e.state) if (!e.state)
return; return;
var url = new URL(e.state, "https://" + document.location.host); var url = new URL(e.state, "https://" + location.host),
var hbase = url.pathname; req = url.pathname,
var cbase = document.location.pathname; hbase = req,
if (url.search.indexOf('doc=') + 1 && hbase == cbase) cbase = location.pathname,
mdoc = /[?&]doc=/.exec('' + url),
mdk = /[?&](k=[^&#]+)/.exec('' + url);
if (mdoc && hbase == cbase)
return showfile.show(hbase + showfile.sname(url.search), true); return showfile.show(hbase + showfile.sname(url.search), true);
r.goto(url.pathname, false, true); if (mdk)
req += '?' + mdk[1];
r.goto(req, false, true);
}; };
hist_replace(get_evpath() + location.hash); var evp = get_evpath() + (dk ? '?k=' + dk : '');
hist_replace(evp + location.hash);
r.onscroll = onscroll; r.onscroll = onscroll;
return r; return r;
})(); })();
@@ -7171,11 +7267,11 @@ var arcfmt = (function () {
if (!/^(zip|tar|pax|tgz|txz)$/.exec(txt)) if (!/^(zip|tar|pax|tgz|txz)$/.exec(txt))
continue; continue;
var ofs = href.lastIndexOf('?'); var m = /(.*[?&])(tar|zip)([^&#]*)(.*)$/.exec(href);
if (ofs < 0) if (!m)
throw new Error('missing arg in url'); throw new Error('missing arg in url');
o.setAttribute("href", href.slice(0, ofs + 1) + arg); o.setAttribute("href", m[1] + arg + m[4]);
o.textContent = fmt.split('_')[0]; o.textContent = fmt.split('_')[0];
} }
ebi('selzip').textContent = fmt.split('_')[0]; ebi('selzip').textContent = fmt.split('_')[0];
@@ -7238,13 +7334,20 @@ var msel = (function () {
vbase = get_evpath(); vbase = get_evpath();
for (var a = 0, aa = links.length; a < aa; a++) { for (var a = 0, aa = links.length; a < aa; a++) {
var href = noq_href(links[a]).replace(/\/$/, ""), var qhref = links[a].getAttribute('href'),
href = qhref.split('?')[0].replace(/\/$/, ""),
item = {}; item = {};
item.id = links[a].getAttribute('id'); item.id = links[a].getAttribute('id');
item.sel = clgot(links[a].closest('tr'), 'sel'); item.sel = clgot(links[a].closest('tr'), 'sel');
item.vp = href.indexOf('/') !== -1 ? href : vbase + href; item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
if (dk) {
var m = /[?&](k=[^&#]+)/.exec(qhref);
item.q = m ? '?' + m[1] : '';
}
else item.q = '';
r.all.push(item); r.all.push(item);
if (item.sel) if (item.sel)
r.sel.push(item); r.sel.push(item);
@@ -7361,6 +7464,9 @@ var msel = (function () {
frm = mknod('form'), frm = mknod('form'),
txt = []; txt = [];
if (dk)
arg += '&k=' + dk;
for (var a = 0; a < sel.length; a++) for (var a = 0; a < sel.length; a++)
txt.push(vsplit(sel[a].vp)[1]); txt.push(vsplit(sel[a].vp)[1]);
@@ -7385,7 +7491,7 @@ var msel = (function () {
ev(e); ev(e);
var sel = r.getsel(); var sel = r.getsel();
for (var a = 0; a < sel.length; a++) for (var a = 0; a < sel.length; a++)
dl_file(sel[a].vp); dl_file(sel[a].vp + sel[a].q);
}; };
r.render = function () { r.render = function () {
var tds = QSA('#files tbody td+td+td'), var tds = QSA('#files tbody td+td+td'),
@@ -7405,7 +7511,7 @@ var msel = (function () {
(function () { (function () {
if (!window.FormData) if (!FormData)
return; return;
var form = QS('#op_new_md>form'), var form = QS('#op_new_md>form'),
@@ -7427,7 +7533,7 @@ var msel = (function () {
(function () { (function () {
if (!window.FormData) if (!FormData)
return; return;
var form = QS('#op_mkdir>form'), var form = QS('#op_mkdir>form'),
@@ -7621,7 +7727,7 @@ function show_md(md, name, div, url, depth) {
if (depth) { if (depth) {
clmod(div, 'raw', 1); clmod(div, 'raw', 1);
div.textContent = "--[ " + name + " ]---------\r\n" + md; div.textContent = "--[ " + name + " ]---------\r\n" + md;
return toast.warn(10, errmsg + (window.WebAssembly ? 'failed to load marked.js' : 'your browser is too old')); return toast.warn(10, errmsg + (WebAssembly ? 'failed to load marked.js' : 'your browser is too old'));
} }
wfp_debounce.n--; wfp_debounce.n--;
@@ -7934,7 +8040,7 @@ var unpost = (function () {
function linklist() { function linklist() {
var ret = [], var ret = [],
base = document.location.origin.replace(/\/$/, ''); base = location.origin.replace(/\/$/, '');
for (var a = 0; a < r.files.length; a++) for (var a = 0; a < r.files.length; a++)
ret.push(base + r.files[a].vp); ret.push(base + r.files[a].vp);
@@ -8111,8 +8217,9 @@ ebi('files').onclick = ebi('docul').onclick = function (e) {
tgt = e.target.closest('a[hl]'); tgt = e.target.closest('a[hl]');
if (tgt) { if (tgt) {
var a = ebi(tgt.getAttribute('hl')), var a = ebi(tgt.getAttribute('hl')),
href = a.getAttribute('href'),
fun = function () { fun = function () {
showfile.show(noq_href(a), tgt.getAttribute('lang')); showfile.show(href, tgt.getAttribute('lang'));
}, },
szs = ft2dict(a.closest('tr'))[0].sz, szs = ft2dict(a.closest('tr'))[0].sz,
sz = parseInt(szs.replace(/[, ]/g, '')); sz = parseInt(szs.replace(/[, ]/g, ''));
@@ -8139,6 +8246,7 @@ function reload_mp() {
mpo.au = mp.au; mpo.au = mp.au;
mpo.au2 = mp.au2; mpo.au2 = mp.au2;
mpo.acs = mp.acs; mpo.acs = mp.acs;
mpo.fau = mp.fau;
mpl.unbuffer(); mpl.unbuffer();
} }
var plays = QSA('tr>td:first-child>a.play'); var plays = QSA('tr>td:first-child>a.play');
@@ -8173,8 +8281,10 @@ function reload_browser() {
for (var a = 0; a < parts.length - 1; a++) { for (var a = 0; a < parts.length - 1; a++) {
link += parts[a] + '/'; link += parts[a] + '/';
var link2 = dks[link] ? addq(link, 'k=' + dks[link]) : link;
o = mknod('a'); o = mknod('a');
o.setAttribute('href', link); o.setAttribute('href', link2);
o.textContent = uricom_dec(parts[a]) || '/'; o.textContent = uricom_dec(parts[a]) || '/';
ebi('path').appendChild(mknod('i')); ebi('path').appendChild(mknod('i'));
ebi('path').appendChild(o); ebi('path').appendChild(o);

View File

@@ -17,7 +17,7 @@ function goto_up2k() {
var up2k = null, var up2k = null,
up2k_hooks = [], up2k_hooks = [],
hws = [], hws = [],
sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11 sha_js = WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
m = 'will use ' + sha_js + ' instead of native sha512 due to'; m = 'will use ' + sha_js + ' instead of native sha512 due to';
try { try {
@@ -717,7 +717,7 @@ function Donut(uc, st) {
sfx(); sfx();
// firefox may forget that filedrops are user-gestures so it can skip this: // firefox may forget that filedrops are user-gestures so it can skip this:
if (uc.upnag && window.Notification && Notification.permission == 'granted') if (uc.upnag && Notification && Notification.permission == 'granted')
new Notification(uc.nagtxt); new Notification(uc.nagtxt);
} }
@@ -779,8 +779,8 @@ function up2k_init(subtle) {
}; };
setTimeout(function () { setTimeout(function () {
if (window.WebAssembly && !hws.length) if (WebAssembly && !hws.length)
fetch(SR + '/.cpr/w.hash.js' + CB); fetch(SR + '/.cpr/w.hash.js?_=' + TS);
}, 1000); }, 1000);
function showmodal(msg) { function showmodal(msg) {
@@ -869,7 +869,7 @@ function up2k_init(subtle) {
bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo); bcfg_bind(uc, 'turbo', 'u2turbo', turbolvl > 1, draw_turbo);
bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null); bcfg_bind(uc, 'datechk', 'u2tdate', turbolvl < 3, null);
bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort); bcfg_bind(uc, 'az', 'u2sort', u2sort.indexOf('n') + 1, set_u2sort);
bcfg_bind(uc, 'hashw', 'hashw', !!window.WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw); bcfg_bind(uc, 'hashw', 'hashw', !!WebAssembly && (!subtle || !CHROME || MOBILE || VCHROME >= 107), set_hashw);
bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag); bcfg_bind(uc, 'upnag', 'upnag', false, set_upnag);
bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx); bcfg_bind(uc, 'upsfx', 'upsfx', false, set_upsfx);
@@ -1347,9 +1347,9 @@ function up2k_init(subtle) {
var evpath = get_evpath(), var evpath = get_evpath(),
draw_each = good_files.length < 50; draw_each = good_files.length < 50;
if (window.WebAssembly && !hws.length) { if (WebAssembly && !hws.length) {
for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++) for (var a = 0; a < Math.min(navigator.hardwareConcurrency || 4, 16); a++)
hws.push(new Worker(SR + '/.cpr/w.hash.js' + CB)); hws.push(new Worker(SR + '/.cpr/w.hash.js?_=' + TS));
console.log(hws.length + " hashers"); console.log(hws.length + " hashers");
} }
@@ -2950,7 +2950,7 @@ function up2k_init(subtle) {
} }
function set_hashw() { function set_hashw() {
if (!window.WebAssembly) { if (!WebAssembly) {
bcfg_set('hashw', uc.hashw = false); bcfg_set('hashw', uc.hashw = false);
toast.err(10, L.u_nowork); toast.err(10, L.u_nowork);
} }
@@ -2967,7 +2967,7 @@ function up2k_init(subtle) {
nopenag(); nopenag();
} }
if (!window.Notification || !HTTPS) if (!Notification || !HTTPS)
return nopenag(); return nopenag();
if (en && Notification.permission == 'default') if (en && Notification.permission == 'default')
@@ -2989,7 +2989,7 @@ function up2k_init(subtle) {
}; };
} }
if (uc.upnag && (!window.Notification || Notification.permission != 'granted')) if (uc.upnag && (!Notification || Notification.permission != 'granted'))
bcfg_set('upnag', uc.upnag = false); bcfg_set('upnag', uc.upnag = false);
ebi('nthread_add').onclick = function (e) { ebi('nthread_add').onclick = function (e) {

View File

@@ -16,7 +16,6 @@ var wah = '',
NOAC = 'autocorrect="off" autocapitalize="off"', NOAC = 'autocorrect="off" autocapitalize="off"',
L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked, L, tt, treectl, thegrid, up2k, asmCrypto, hashwasm, vbar, marked,
T0 = Date.now(), T0 = Date.now(),
CB = '?_=' + Math.floor(T0 / 1000).toString(36),
R = SR.slice(1), R = SR.slice(1),
RS = R ? "/" + R : "", RS = R ? "/" + R : "",
HALFMAX = 8192 * 8192 * 8192 * 8192, HALFMAX = 8192 * 8192 * 8192 * 8192,
@@ -52,8 +51,6 @@ catch (ex) {
} }
try { try {
CB = '?' + document.currentScript.src.split('?').pop();
if (navigator.userAgentData.mobile) if (navigator.userAgentData.mobile)
MOBILE = true; MOBILE = true;
@@ -130,7 +127,7 @@ if ((document.location + '').indexOf(',rej,') + 1)
try { try {
console.hist = []; console.hist = [];
var CMAXHIST = 100; var CMAXHIST = 1000;
var hook = function (t) { var hook = function (t) {
var orig = console[t].bind(console), var orig = console[t].bind(console),
cfun = function () { cfun = function () {
@@ -151,8 +148,6 @@ try {
hook('error'); hook('error');
} }
catch (ex) { catch (ex) {
if (console.stdlog)
console.log = console.stdlog;
console.log('console capture failed', ex); console.log('console capture failed', ex);
} }
var crashed = false, ignexd = {}, evalex_fatal = false; var crashed = false, ignexd = {}, evalex_fatal = false;
@@ -182,7 +177,7 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
if (url.indexOf('easymde.js') + 1) if (url.indexOf('easymde.js') + 1)
return; // clicking the preview pane return; // clicking the preview pane
if (url.indexOf('deps/marked.js') + 1 && !window.WebAssembly) if (url.indexOf('deps/marked.js') + 1 && !WebAssembly)
return; // ff<52 return; // ff<52
crashed = true; crashed = true;
@@ -740,6 +735,15 @@ function vjoin(p1, p2) {
} }
function addq(url, q) {
var uh = url.split('#', 1),
u = uh[0],
h = uh.length == 1 ? '' : '#' + uh[1];
return u + (u.indexOf('?') < 0 ? '?' : '&') + (q === undefined ? '' : q) + h;
}
function uricom_enc(txt, do_fb_enc) { function uricom_enc(txt, do_fb_enc) {
try { try {
return encodeURIComponent(txt); return encodeURIComponent(txt);
@@ -1469,7 +1473,7 @@ var toast = (function () {
clmod(obj, 'vis'); clmod(obj, 'vis');
r.visible = false; r.visible = false;
r.tag = obj; r.tag = obj;
if (!window.WebAssembly) if (!WebAssembly)
te = setTimeout(function () { te = setTimeout(function () {
obj.className = 'hide'; obj.className = 'hide';
}, 500); }, 500);
@@ -1885,7 +1889,7 @@ function md_thumbs(md) {
float = has(flags, 'l') ? 'left' : has(flags, 'r') ? 'right' : ''; float = has(flags, 'l') ? 'left' : has(flags, 'r') ? 'right' : '';
if (!/[?&]cache/.exec(url)) if (!/[?&]cache/.exec(url))
url += (url.indexOf('?') < 0 ? '?' : '&') + 'cache=i'; url = addq(url, 'cache=i');
md[a] = '<a href="' + url + '" class="mdth mdth' + float.slice(0, 1) + '"><img src="' + url + '&th=w" alt="' + alt + '" /></a>' + md[a].slice(o2 + 1); md[a] = '<a href="' + url + '" class="mdth mdth' + float.slice(0, 1) + '"><img src="' + url + '&th=w" alt="' + alt + '" /></a>' + md[a].slice(o2 + 1);
} }

View File

@@ -5,9 +5,6 @@ a living list of upcoming features / fixes / changes, very roughly in order of p
* maybe resumable downloads (chrome-only, jank api) * maybe resumable downloads (chrome-only, jank api)
* maybe checksum validation (return sha512 of requested range in responses, and probably also warks) * maybe checksum validation (return sha512 of requested range in responses, and probably also warks)
* [github issue #64](https://github.com/9001/copyparty/issues/64) - dirkeys 2nd season
* popular feature request, finally time to refactor browser.js i suppose...
* [github issue #37](https://github.com/9001/copyparty/issues/37) - upload PWA * [github issue #37](https://github.com/9001/copyparty/issues/37) - upload PWA
* or [maybe not](https://arstechnica.com/tech-policy/2024/02/apple-under-fire-for-disabling-iphone-web-apps-eu-asks-developers-to-weigh-in/), or [maybe](https://arstechnica.com/gadgets/2024/03/apple-changes-course-will-keep-iphone-eu-web-apps-how-they-are-in-ios-17-4/) * or [maybe not](https://arstechnica.com/tech-policy/2024/02/apple-under-fire-for-disabling-iphone-web-apps-eu-asks-developers-to-weigh-in/), or [maybe](https://arstechnica.com/gadgets/2024/03/apple-changes-course-will-keep-iphone-eu-web-apps-how-they-are-in-ios-17-4/)

96
docs/bufsize.txt Normal file
View File

@@ -0,0 +1,96 @@
notes from testing various buffer sizes of files and sockets
summary:
download-folder-as-tar: would be 7% faster with --iobuf 65536 (but got 20% faster in v1.11.2)
download-folder-as-zip: optimal with default --iobuf 262144
download-file-over-https: optimal with default --iobuf 262144
put-large-file: optimal with default --iobuf 262144, --s-rd-sz 262144 (and got 14% faster in v1.11.2)
post-large-file: optimal with default --iobuf 262144, --s-rd-sz 262144 (and got 18% faster in v1.11.2)
----
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/?tar
3.3 req/s 1.11.1
4.3 4.0 3.3 req/s 1.12.2
64 256 512 --iobuf 256 (prefer smaller)
32 32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/?zip
2.9 req/s 1.11.1
2.5 2.9 2.9 req/s 1.12.2
64 256 512 --iobuf 256 (prefer bigger)
32 32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/?tar
8.3 req/s 1.11.1
8.4 8.4 8.5 req/s 1.12.2
64 256 512 --iobuf 256 (prefer bigger)
32 32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/?zip
13.9 req/s 1.11.1
14.1 14.0 13.8 req/s 1.12.2
64 256 512 --iobuf 256 (prefer smaller)
32 32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/pairdupes/987a
5260 req/s 1.11.1
5246 5246 5280 5268 req/s 1.12.2
64 256 512 256 --iobuf dontcare
32 32 32 512 --s-rd-sz dontcare
oha -z10s -c1 --ipv4 --insecure https://127.0.0.1:3923/pairdupes/987a
4445 req/s 1.11.1
4462 4494 4444 req/s 1.12.2
64 256 512 --iobuf dontcare
32 32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure http://127.0.0.1:3923/bigs/gssc-02-cannonball-skydrift/track10.cdda.flac
95 req/s 1.11.1
95 97 req/s 1.12.2
64 512 --iobuf dontcare
32 32 --s-rd-sz
oha -z10s -c1 --ipv4 --insecure https://127.0.0.1:3923/bigs/gssc-02-cannonball-skydrift/track10.cdda.flac
15.4 req/s 1.11.1
15.4 15.3 14.9 15.4 req/s 1.12.2
64 256 512 512 --iobuf 256 (prefer smaller, and smaller than s-wr-sz)
32 32 32 32 --s-rd-sz
256 256 256 512 --s-wr-sz
----
python3 ~/dev/old/copyparty\ v1.11.1\ dont\ ban\ the\ pipes.py -q -i 127.0.0.1 -v .::A --daw
python3 ~/dev/copyparty/dist/copyparty-sfx.py -q -i 127.0.0.1 -v .::A --daw --iobuf $((1024*512))
oha -z10s -c1 --ipv4 --insecure -mPUT -r0 -D ~/Music/gssc-02-cannonball-skydrift/track10.cdda.flac http://127.0.0.1:3923/a.bin
10.8 req/s 1.11.1
10.8 11.5 11.8 12.1 12.2 12.3 req/s new
512 512 512 512 512 256 --iobuf 256
32 64 128 256 512 256 --s-rd-sz 256 (prefer bigger)
----
buildpost() {
b=--jeg-er-grensestaven;
printf -- "$b\r\nContent-Disposition: form-data; name=\"act\"\r\n\r\nbput\r\n$b\r\nContent-Disposition: form-data; name=\"f\"; filename=\"a.bin\"\r\nContent-Type: audio/mpeg\r\n\r\n"
cat "$1"
printf -- "\r\n${b}--\r\n"
}
buildpost ~/Music/gssc-02-cannonball-skydrift/track10.cdda.flac >big.post
buildpost ~/Music/bottomtext.txt >smol.post
oha -z10s -c1 --ipv4 --insecure -mPOST -r0 -T 'multipart/form-data; boundary=jeg-er-grensestaven' -D big.post http://127.0.0.1:3923/?replace
9.6 11.2 11.3 11.1 10.9 req/s v1.11.2
512 512 256 128 256 --iobuf 256
32 512 256 128 128 --s-rd-sz 256
oha -z10s -c1 --ipv4 --insecure -mPOST -r0 -T 'multipart/form-data; boundary=jeg-er-grensestaven' -D smol.post http://127.0.0.1:3923/?replace
2445 2414 2401 2437
256 128 256 256 --iobuf 256
128 128 256 64 --s-rd-sz 128 (but use 256 since big posts are more important)

View File

@@ -1,3 +1,170 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0406-2011 `v1.12.0` locksmith
## new features
* #64 dirkeys; option to auto-generate passwords for folders, so you can give someone a link to a specific folder inside a volume without sharing the rest of the volume 10bc2d92 32c912bb ef52e2c0 0ae12868
* enabled by volflag `dk` (exact folder only) and/or volflag `dks` (also subfolders); see [readme](https://github.com/9001/copyparty#dirkeys)
* audio transcoding to mp3 if browser doesn't support opus a080759a
* recursively transcode and download a folder using `?tar&mp3`
* accidentally adds support for playing just about any audio format in ie11
* audio equalizer also applies to videos 7744226b
## bugfixes
* #81 scrolling could break after viewing an image in the lightbox 9c42cbec
* on phones, audio playback could stop if network is slow/unreliable 59f815ff b88cc7b5 59a53ba9
* fixes the issue on android, but ios/safari appears to be [impossible](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#music-playback-halting-on-phones) d94b5b3f
## other changes
* updated dompurify to 3.0.11
* copyparty.exe: updated to python 3.11.9
* support for building with pyoxidizer was removed 5ab54763
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0323-1724 `v1.11.2` public idp volumes
* read-only demo server at https://a.ocv.me/pub/demo/
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) [client testbed](https://cd.ocv.me/b/)
there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` in case of future important updates, such as [vulnerabilities](https://github.com/9001/copyparty/security) (most recently 2023-07-23)
## new features
* global-option `--iobuf` to set a custom I/O buffersize 2b24c50e
* changes the default buffersize to 256 KiB everywhere (was a mix of 64 and 512)
* may improve performance of networked volumes (s3 etc.) if increased
* on gbit networks: download-as-tar is now up to 20% faster
* slightly faster FTP and TFTP too
* global-option `--s-rd-sz` to set a custom read-size for sockets c6acd3a9
* changes the default from 32 to 256 KiB
* may improve performance of networked volumes (s3 etc.) if increased
* on 10gbit networks: uploading large files is now up to 17% faster
* add url parameter `?replace` to overwrite any existing files with a multipart-post c6acd3a9
## bugfixes
* #79 idp volumes (introduced in [v1.11.0](https://github.com/9001/copyparty/releases/tag/v1.11.0)) would only accept permissions for the user that owned the volume; was impossible to grant read/write-access to other users d30ae845
## other changes
* mention the [lack of persistence for idp volumes](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#important-notes) in the IdP docs 2f20d29e
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0318-1709 `v1.11.1` dont ban the pipes
the [previous release](https://github.com/9001/copyparty/releases/tag/v1.11.0) had all the fun new features... this one's just bugfixes
* read-only demo server at https://a.ocv.me/pub/demo/
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) [client testbed](https://cd.ocv.me/b/)
### no vulnerabilities since 2023-07-23
* there is a [discord server](https://discord.gg/25J8CdTT6G) with an `@everyone` in case of future important updates
* [v1.8.7](https://github.com/9001/copyparty/releases/tag/v1.8.7) (2023-07-23) - [CVE-2023-38501](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-38501) - reflected XSS
* [v1.8.2](https://github.com/9001/copyparty/releases/tag/v1.8.2) (2023-07-14) - [CVE-2023-37474](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-37474) - path traversal (first CVE)
## bugfixes
* less aggressive rejection of requests from banned IPs 51d31588
* clients would get kicked before the header was parsed (which contains the xff header), meaning the server could become inaccessible to everyone if the reverse-proxy itself were to "somehow" get banned
* ...which can happen if a server behind cloudflare also accepts non-cloudflare connections, meaning the client IP would not be resolved, and it'll ban the LAN IP instead heh
* that part still happens, but now it won't affect legit clients through the intended route
* the old behavior can be restored with `--early-ban` to save some cycles, and/or avoid slowloris somewhat
* the unpost feature could appear to be disabled on servers where no volume was mapped to `/` 0287c7ba
* python 3.12 support for [compiling the dependencies](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag#dependencies) necessary to detect bpm/key in audio files 32553e45
## other changes
* mention [real-ip configuration](https://github.com/9001/copyparty?tab=readme-ov-file#real-ip) in the readme ee80cdb9
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0315-2047 `v1.11.0` You Can (Not) Proceed
this release was made possible by [stoltzekleiven, kvikklunsj, and tako](https://a.ocv.me/pub/g/nerd-stuff/2024-0310-stoltzekleiven.jpg)
## new features
* #62 support for [identity providers](https://github.com/9001/copyparty#identity-providers) and automatically creating volumes for each user/group ("home folders")
* login with passkeys / fido2 / webauthn / yubikey / ldap / active directory / oauth / many other single-sign-on contraptions
* [documentation](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md) and [examples](https://github.com/9001/copyparty/tree/hovudstraum/docs/examples/docker/idp-authelia-traefik) could still use some help (I did my best)
* #77 UI to cancel unfinished uploads (available in the 🧯 unpost tab) 3f05b665
* the user's IP and username must match the upload by default; can be changed with global-option / volflag `u2abort`
* new volflag `sparse` to pretend sparse files are supported even if the filesystem doesn't 8785d2f9
* gives drastically better performance when writing to s3 buckets through juicefs/geesefs
* only for when you know the filesystem can deal with it (so juicefs/geesefs is OK, but **definitely not** fat32)
* `--xff-src` and `--ipa` now support CIDR notation (but the old syntax still works) b377791b
* ux:
* #74 option to use [custom fonts](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice) 263adec7 6cc7101d 8016e671
* option to disable autoplay when page url contains a song hash 8413ed6d
* good if you're using copyparty to listen to music at the office and the office policy is to have the webbrowser automatically restart to install updates, meaning your coworkers are suddenly and involuntarily enjoying some loud af jcore while you're asleep at home
## bugfixes
* don't panic if cloudflare (or another reverse-proxy) decides to hijack json responses and replace them with html 7741870d
* #73 the fancy markdown editor was incompatible with caddy (a reverse-proxy) ac96fd9c
* media player could get confused if neighboring folders had songs with the same filenames 206af8f1
* benign race condition in the config reloader (could only be triggered by admins and/or SIGUSR1) 096de508
* running tftp with optimizations enabled would cause issues for `--ipa` b377791b
* cosmetic tftp bugs 115020ba
* ux:
* up2k rendering glitch if the last couple uploads were dupes 547a4863
* up2k rendering glitch when switching between readonly/writeonly folders 51a83b04
* markdown editor preview was glitchy on tiny screens e5582605
## other changes
* add a [sharex v12.1](https://github.com/9001/copyparty/tree/hovudstraum/contrib#sharexsxcu) config example 2527e903
* make it easier to discover/diagnose issues with docker and/or reverse-proxy config d744f3ff
* stop recommending the use of `--xff-src=any` in the log messages 7f08f10c
* ux:
* remove the `k304` togglebutton in the controlpanel by default 1c011ff0
* mention that a full restart is required for `[global]` config changes to take effect 0c039219
* docs e78af022
* [how to use copyparty with amazon aws s3](https://github.com/9001/copyparty#using-the-cloud-as-storage)
* faq: http/https confusion caused by incorrectly configured cloudflare
* #76 docker: ftp-server howto
* copyparty.exe: updated pyinstaller to 6.5.0 bdbcbbb0
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0221-2132 `v1.10.2` tall thumbs
## new features
* thumbnails can be way taller when centercrop is disabled in the browser UI 5026b212
* good for folders with lots of portrait pics (no more letterboxing)
* more thumbnail stuff:
* zoom levels are twice as granular 5026b212
* write-only folders get an "upload-only" icon 89c6c2e0
* inaccessible files/folders get a 403/404 icon 8a38101e
## bugfixes
* tftp fixes d07859e8
* server could crash if a nic disappeared / got restarted mid-transfer
* tiny resource leak if dualstack causes ipv4 bind to fail
* thumbnails:
* when behind a caching proxy (cloudflare), icons in folders would be a random mix of png and svg 43ee6b9f
* produce valid folder icons when thumbnails are disabled 14af136f
* trailing newline in html responses d39a99c9
## other changes
* webdeps: update dompurify 13e77777
* copyparty.exe: update jinja2, markupsafe, pyinstaller, upx 13e77777
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2024-0218-1554 `v1.10.1` big thumbs # 2024-0218-1554 `v1.10.1` big thumbs

View File

@@ -20,6 +20,8 @@
* [just the sfx](#just-the-sfx) * [just the sfx](#just-the-sfx)
* [build from release tarball](#build-from-release-tarball) - uses the included prebuilt webdeps * [build from release tarball](#build-from-release-tarball) - uses the included prebuilt webdeps
* [complete release](#complete-release) * [complete release](#complete-release)
* [debugging](#debugging)
* [music playback halting on phones](#music-playback-halting-on-phones) - mostly fine on android
* [todo](#todo) - roughly sorted by priority * [todo](#todo) - roughly sorted by priority
* [discarded ideas](#discarded-ideas) * [discarded ideas](#discarded-ideas)
@@ -164,6 +166,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
| PUT | `?xz` | (binary data) | compress with xz and write into file at URL | | PUT | `?xz` | (binary data) | compress with xz and write into file at URL |
| mPOST | | `f=FILE` | upload `FILE` into the folder at URL | | mPOST | | `f=FILE` | upload `FILE` into the folder at URL |
| mPOST | `?j` | `f=FILE` | ...and reply with json | | mPOST | `?j` | `f=FILE` | ...and reply with json |
| mPOST | `?replace` | `f=FILE` | ...and overwrite existing files |
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL | | mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
| POST | `?delete` | | delete URL recursively | | POST | `?delete` | | delete URL recursively |
| jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively | | jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |
@@ -300,6 +303,19 @@ in the `scripts` folder:
* run `./rls.sh 1.2.3` which uploads to pypi + creates github release + sfx * run `./rls.sh 1.2.3` which uploads to pypi + creates github release + sfx
# debugging
## music playback halting on phones
mostly fine on android, but still haven't find a way to massage iphones into behaving well
* conditionally starting/stopping mp.fau according to mp.au.readyState <3 or <4 doesn't help
* loop=true doesn't work, and manually looping mp.fau from an onended also doesn't work (it does nothing)
* assigning fau.currentTime in a timer doesn't work, as safari merely pretends to assign it
can be reproduced with `--no-sendfile --s-wr-sz 8192 --s-wr-slp 0.3 --rsp-slp 6` and then play a collection of small audio files with the screen off, `ffmpeg -i track01.cdda.flac -c:a libopus -b:a 128k -segment_time 12 -f segment smol-%02d.opus`
# todo # todo
roughly sorted by priority roughly sorted by priority
@@ -309,6 +325,10 @@ roughly sorted by priority
## discarded ideas ## discarded ideas
* optimization attempts which didn't improve performance
* remove brokers / multiprocessing stuff; https://github.com/9001/copyparty/tree/no-broker
* reduce the nesting / indirections in `HttpCli` / `httpcli.py`
* nearly zero benefit from stuff like replacing all the `self.conn.hsrv` with a local `hsrv` variable
* reduce up2k roundtrips * reduce up2k roundtrips
* start from a chunk index and just go * start from a chunk index and just go
* terminate client on bad data * terminate client on bad data

View File

@@ -5,3 +5,18 @@ to configure IdP from scratch, you must place copyparty behind a reverse-proxy w
in the copyparty `[global]` config, specify which headers to read client info from; username is required (`idp-h-usr: X-Authooley-User`), group(s) are optional (`idp-h-grp: X-Authooley-Groups`) in the copyparty `[global]` config, specify which headers to read client info from; username is required (`idp-h-usr: X-Authooley-User`), group(s) are optional (`idp-h-grp: X-Authooley-Groups`)
* it is also required to specify the subnet that legit requests will be coming from, for example `--xff-src=10.88.0.0/24` to allow 10.88.x.x (or `--xff-src=lan` for all private IPs), and it is recommended to configure the reverseproxy to include a secret header as proof that the other headers are also legit (and not smuggled in by a malicious client), telling copyparty the headername to expect with `idp-h-key: shangala-bangala` * it is also required to specify the subnet that legit requests will be coming from, for example `--xff-src=10.88.0.0/24` to allow 10.88.x.x (or `--xff-src=lan` for all private IPs), and it is recommended to configure the reverseproxy to include a secret header as proof that the other headers are also legit (and not smuggled in by a malicious client), telling copyparty the headername to expect with `idp-h-key: shangala-bangala`
# important notes
## IdP volumes are forgotten on shutdown
IdP volumes, meaning dynamically-created volumes, meaning volumes that contain `${u}` or `${g}` in their URL, will be forgotten during a server restart and then "revived" when the volume's owner sends their first request after the restart
until each IdP volume is revived, it will inherit the permissions of its parent volume (if any)
this means that, if an IdP volume is located inside a folder that is readable by anyone, then each of those IdP volumes will **also become readable by anyone** until the volume is revived
and likewise -- if the IdP volume is inside a folder that is only accessible by certain users, but the IdP volume is configured to allow access from unauthenticated users, then the contents of the volume will NOT be accessible until it is revived
until this limitation is fixed (if ever), it is recommended to place IdP volumes inside an appropriate parent volume, so they can inherit acceptable permissions until their revival; see the "strategic volumes" at the bottom of [./examples/docker/idp/copyparty.conf](./examples/docker/idp/copyparty.conf)

View File

@@ -1,52 +0,0 @@
pyoxidizer doesn't crosscompile yet so need to build in a windows vm,
luckily possible to do mostly airgapped (https-proxy for crates)
none of this is version-specific but doing absolute links just in case
(only exception is py3.8 which is the final win7 ver)
# deps (download on linux host):
https://www.python.org/ftp/python/3.10.7/python-3.10.7-amd64.exe
https://github.com/indygreg/PyOxidizer/releases/download/pyoxidizer%2F0.22.0/pyoxidizer-0.22.0-x86_64-pc-windows-msvc.zip
https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip
https://static.rust-lang.org/dist/rust-1.61.0-x86_64-pc-windows-msvc.msi
https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.8.13%2B20220528-i686-pc-windows-msvc-static-noopt-full.tar.zst
# need cl.exe, prefer 2017 -- download on linux host:
https://visualstudio.microsoft.com/downloads/?q=build+tools
https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-history#release-dates-and-build-numbers
https://aka.ms/vs/15/release/vs_buildtools.exe # 2017
https://aka.ms/vs/16/release/vs_buildtools.exe # 2019
https://aka.ms/vs/17/release/vs_buildtools.exe # 2022
https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2017
# use disposable w10 vm to prep offline installer; xfer to linux host with firefox to copyparty
vs_buildtools-2017.exe --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --layout c:\msbt2017 --lang en-us
# need two proxies on host; s5s or ssh for msys2(socks5), and tinyproxy for rust(http)
UP=- python3 socks5server.py 192.168.123.1 4321
ssh -vND 192.168.123.1:4321 localhost
git clone https://github.com/tinyproxy/tinyproxy.git
./autogen.sh
./configure --prefix=/home/ed/pe/tinyproxy
make -j24 install
printf '%s\n' >cfg "Port 4380" "Listen 192.168.123.1"
./tinyproxy -dccfg
https://github.com/msys2/msys2-installer/releases/download/2022-09-04/msys2-x86_64-20220904.exe
export all_proxy=socks5h://192.168.123.1:4321
# if chat dies after auth (2 messages) it probably failed dns, note the h in socks5h to tunnel dns
pacman -Syuu
pacman -S git patch mingw64/mingw-w64-x86_64-zopfli
cd /c && curl -k https://192.168.123.1:3923/ro/ox/msbt2017/?tar | tar -xv
first install certs from msbt/certificates then admin-cmd `vs_buildtools.exe --noweb`,
default selection (vc++2017-v15.9-v14.16, vc++redist, vc++bt-core) += win10sdk (for io.h)
install rust without documentation, python 3.10, put upx and pyoxidizer into ~/bin,
[cmd.exe] python -m pip install --user -U wheel-0.37.1.tar.gz strip-hints-0.1.10.tar.gz
p=192.168.123.1:4380; export https_proxy=$p; export http_proxy=$p
# and with all of the one-time-setup out of the way,
mkdir /c/d; cd /c/d && curl -k https://192.168.123.1:3923/cpp/gb?pw=wark > gb && git clone gb copyparty
cd /c/d/copyparty/ && curl -k https://192.168.123.1:3923/cpp/patch?pw=wark | patch -p1
cd /c/d/copyparty/scripts && CARGO_HTTP_CHECK_REVOKE=false PATH=/c/Users/$USER/AppData/Local/Programs/Python/Python310:/c/Users/$USER/bin:"$(cygpath "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Tools\MSVC\14.16.27023\bin\Hostx86\x86"):$PATH" ./make-sfx.sh ox ultra

45
docs/xff.md Normal file
View File

@@ -0,0 +1,45 @@
when running behind a reverse-proxy, or a WAF, or another protection service such as cloudflare:
if you (and maybe everybody else) keep getting a message that says `thank you for playing`, then you've gotten banned for malicious traffic. This ban applies to the IP-address that copyparty *thinks* identifies the shady client -- so, depending on your setup, you might have to tell copyparty where to find the correct IP
knowing the correct IP is also crucial for some other features, such as the unpost feature which lets you delete your own recent uploads -- but if everybody has the same IP, well...
----
for most common setups, there should be a helpful message in the server-log explaining what to do, something like `--xff-src=10.88.0.0/16` or `--xff-src=lan` to accept the `X-Forwarded-For` header from your reverse-proxy with a LAN IP of `10.88.x.y`
if you are behind cloudflare, it is recommended to also set `--xff-hdr=cf-connecting-ip` to use a more trustworthy source of info, but then it's also very important to ensure your reverse-proxy does not accept connections from anything BUT cloudflare; you can do this by generating an ip-address allowlist and reject all other connections
* if you are using nginx as your reverse-proxy, see the [example nginx config](https://github.com/9001/copyparty/blob/hovudstraum/contrib/nginx/copyparty.conf) on how the cloudflare allowlist can be done
----
the server-log will give recommendations in the form of commandline arguments;
to do the same thing using config files, take the options that are suggested in the serverlog and put them into the `[global]` section in your `copyparty.conf` like so:
```yaml
[global]
xff-src: lan
xff-hdr: cf-connecting-ip
```
----
# but if you just want to get it working:
...and don't care about security, you can optionally disable the bot-detectors, either by specifying commandline-args `--ban-404=no --ban-403=no --ban-422=no --ban-url=no --ban-pw=no`
or by adding these lines inside the `[global]` section in your `copyparty.conf`:
```yaml
[global]
ban-404: no
ban-403: no
ban-422: no
ban-url: no
ban-pw: no
```
but remember that this will make other features insecure as well, such as unpost

View File

@@ -1,48 +0,0 @@
# builds win7-i386 exe on win10-ltsc-1809(17763.316)
# see docs/pyoxidizer.txt
def make_exe():
dist = default_python_distribution(flavor="standalone_static", python_version="3.8")
policy = dist.make_python_packaging_policy()
policy.allow_files = True
policy.allow_in_memory_shared_library_loading = True
#policy.bytecode_optimize_level_zero = True
#policy.include_distribution_sources = False # error instantiating embedded Python interpreter: during initializing Python main: init_fs_encoding: failed to get the Python codec of the filesystem encoding
policy.include_distribution_resources = False
policy.include_non_distribution_sources = False
policy.include_test = False
python_config = dist.make_python_interpreter_config()
#python_config.module_search_paths = ["$ORIGIN/lib"]
python_config.run_module = "copyparty"
exe = dist.to_python_executable(
name="copyparty",
config=python_config,
packaging_policy=policy,
)
exe.windows_runtime_dlls_mode = "never"
exe.windows_subsystem = "console"
exe.add_python_resources(exe.read_package_root(
path="sfx",
packages=[
"copyparty",
"jinja2",
"markupsafe",
"pyftpdlib",
"python-magic",
]
))
return exe
def make_embedded_resources(exe):
return exe.to_embedded_resources()
def make_install(exe):
files = FileManifest()
files.add_python_resource("copyparty", exe)
return files
register_target("exe", make_exe)
register_target("resources", make_embedded_resources, depends=["exe"], default_build_script=True)
register_target("install", make_install, depends=["exe"], default=True)
resolve_targets()

View File

@@ -3,7 +3,7 @@ WORKDIR /z
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \ ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
ver_hashwasm=4.10.0 \ ver_hashwasm=4.10.0 \
ver_marked=4.3.0 \ ver_marked=4.3.0 \
ver_dompf=3.0.9 \ ver_dompf=3.0.11 \
ver_mde=2.18.0 \ ver_mde=2.18.0 \
ver_codemirror=5.65.16 \ ver_codemirror=5.65.16 \
ver_fontawesome=5.13.0 \ ver_fontawesome=5.13.0 \
@@ -24,7 +24,9 @@ ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
# the scp url is regular latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap # the scp url is regular latin from https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap
RUN mkdir -p /z/dist/no-pk \ RUN mkdir -p /z/dist/no-pk \
&& wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \ && wget https://fonts.gstatic.com/s/sourcecodepro/v11/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2 -O scp.woff2 \
&& apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev py3-brotli \ && apk add \
bash brotli cmake make g++ git gzip lame npm patch pigz \
python3 python3-dev py3-brotli sox tar unzip wget \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& wget https://github.com/openpgpjs/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \ && wget https://github.com/openpgpjs/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \
&& wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \ && wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \
@@ -58,6 +60,11 @@ RUN mkdir -p /z/dist/no-pk \
&& tar -xf zopfli.tgz && tar -xf zopfli.tgz
COPY busy-mp3.sh /z/
RUN /z/busy-mp3.sh \
&& mv -v /dev/shm/busy.mp3.gz /z/dist
# build fonttools (which needs zopfli) # build fonttools (which needs zopfli)
RUN tar -xf zopfli.tgz \ RUN tar -xf zopfli.tgz \
&& cd zopfli* \ && cd zopfli* \

61
scripts/deps-docker/busy-mp3.sh Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
set -e
cat >/dev/null <<EOF
a frame is 1152 samples
1sec @ 48000 = 41.66 frames
11 frames = 12672 samples = 0.264 sec
22 frames = 25344 samples = 0.528 sec
EOF
fast=1
fast=
echo
mkdir -p /dev/shm/$1
cd /dev/shm/$1
find -maxdepth 1 -type f -iname 'a.*.mp3*' -delete
min=99999999
for freq in 425; do # {400..500}
for vol in 0; do # {10..30}
for kbps in 32; do
for fdur in 1124; do # {800..1200}
for fdu2 in 1042; do # {800..1200}
for ftyp in h; do # q h t l p
for ofs1 in 9214; do # {0..32768}
for ofs2 in 0; do # {0..4096}
for ofs3 in 0; do # {0..4096}
for nores in --nores; do # '' --nores
f=a.b$kbps$nores-f$freq-v$vol-$ftyp$fdur-$fdu2-o$ofs1-$ofs2-$ofs3
sox -r48000 -Dn -r48000 -b16 -c2 -t raw s1.pcm synth 25344s sin $freq vol 0.$vol fade $ftyp ${fdur}s 25344s ${fdu2}s
sox -r48000 -Dn -r48000 -b16 -c2 -t raw s0.pcm synth 12672s sin $freq vol 0
tail -c +$ofs1 s0.pcm >s0a.pcm
tail -c +$ofs2 s0.pcm >s0b.pcm
tail -c +$ofs3 s0.pcm >s0c.pcm
cat s{0a,1,0,0b,1,0c}.pcm > a.pcm
lame --silent -r -s 48 --bitwidth 16 --signed a.pcm -m j --resample 48 -b $kbps -q 0 $nores $f.mp3
if [ $fast ]
then gzip -c9 <$f.mp3 >$f.mp3.gz
else pigz -c11 -I1 <$f.mp3 >$f.mp3.gz
fi
sz=$(wc -c <$f.mp3.gz)
printf '\033[A%d %s\033[K\n' $sz $f
[ $sz -le $((min+10)) ] && echo
[ $sz -le $min ] && echo && min=$sz
done;done;done;done;done;done;done;done;done;done
true
f=a.b32--nores-f425-v0-h1124-1042-o9214-0-0.mp3
[ $fast ] &&
pigz -c11 -I1 <$f >busy.mp3.gz ||
mv $f.gz busy.mp3.gz
sz=$(wc -c <busy.mp3.gz)
[ "$sz" -eq 106 ] &&
echo busy.mp3 built successfully ||
echo WARNING: unexpected size of busy.mp3
find -maxdepth 1 -type f -iname 'a.*.mp3*' -delete

View File

@@ -1,4 +1,4 @@
FROM fedora:38 FROM fedora:39
WORKDIR /z WORKDIR /z
LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \ LABEL org.opencontainers.image.url="https://github.com/9001/copyparty" \
org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \ org.opencontainers.image.source="https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker" \
@@ -21,7 +21,7 @@ RUN dnf install -y \
vips vips-jxl vips-poppler vips-magick \ vips vips-jxl vips-poppler vips-magick \
python3-numpy fftw libsndfile \ python3-numpy fftw libsndfile \
gcc gcc-c++ make cmake patchelf jq \ gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools \ python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools python3-wheel \
vamp-plugin-sdk qm-vamp-plugins \ vamp-plugin-sdk qm-vamp-plugins \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \ vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \ && rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
@@ -29,7 +29,7 @@ RUN dnf install -y \
&& bash install-deps.sh \ && bash install-deps.sh \
&& dnf erase -y \ && dnf erase -y \
gcc gcc-c++ make cmake patchelf jq \ gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools \ python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools python3-wheel \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \ vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& dnf clean all \ && dnf clean all \
&& find /usr/ -name __pycache__ | xargs rm -rf \ && find /usr/ -name __pycache__ | xargs rm -rf \

View File

@@ -16,8 +16,6 @@ help() { exec cat <<'EOF'
# `re` does a repack of an sfx which you already executed once # `re` does a repack of an sfx which you already executed once
# (grabs files from the sfx-created tempdir), overrides `clean` # (grabs files from the sfx-created tempdir), overrides `clean`
# #
# `ox` builds a pyoxidizer exe instead of py
#
# `gz` creates a gzip-compressed python sfx instead of bzip2 # `gz` creates a gzip-compressed python sfx instead of bzip2
# #
# `lang` limits which languages/translations to include, # `lang` limits which languages/translations to include,
@@ -111,7 +109,6 @@ while [ ! -z "$1" ]; do
case $1 in case $1 in
clean) clean=1 ; ;; clean) clean=1 ; ;;
re) repack=1 ; ;; re) repack=1 ; ;;
ox) use_ox=1 ; ;;
gz) use_gz=1 ; ;; gz) use_gz=1 ; ;;
gzz) shift;use_gzz=$1;use_gz=1; ;; gzz) shift;use_gzz=$1;use_gz=1; ;;
no-ftp) no_ftp=1 ; ;; no-ftp) no_ftp=1 ; ;;
@@ -368,7 +365,7 @@ git describe --tags >/dev/null 2>/dev/null && {
printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+-[0-9]+-g[0-9a-f]+$' && { printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+-[0-9]+-g[0-9a-f]+$' && {
# long format (unreleased commit) # long format (unreleased commit)
t_ver="$(printf '%s\n' "$ver" | sed -r 's/\./, /g; s/(.*) (.*)/\1 "\2"/')" t_ver="$(printf '%s\n' "$ver" | sed -r 's/[-.]/, /g; s/(.*) (.*)/\1 "\2"/')"
} }
[ -z "$t_ver" ] && { [ -z "$t_ver" ] && {
@@ -461,8 +458,8 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py
iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
done done
[ ! $repack ] && [ ! $use_ox ] && { [ ! $repack ] && {
# uncomment; oxidized drops 45 KiB but becomes undebuggable # uncomment
find | grep -E '\.py$' | find | grep -E '\.py$' |
grep -vE '__version__' | grep -vE '__version__' |
tr '\n' '\0' | tr '\n' '\0' |
@@ -570,33 +567,6 @@ nf=$(ls -1 "$zdir"/arc.* 2>/dev/null | wc -l)
} }
[ $use_ox ] && {
tgt=x86_64-pc-windows-msvc
tgt=i686-pc-windows-msvc # 2M smaller (770k after upx)
bdir=build/$tgt/release/install/copyparty
t="res web"
(printf "\n\n\nBUT WAIT! THERE'S MORE!!\n\n";
cat ../$bdir/COPYING.txt) >> copyparty/res/COPYING.txt ||
echo "copying.txt 404 pls rebuild"
mv ftp/* j2/* .
rm -rf ftp j2 py2 py37
(cd copyparty; tar -cvf z.tar $t; rm -rf $t)
cd ..
pyoxidizer build --release --target-triple $tgt
mv $bdir/copyparty.exe dist/
cp -pv "$(for d in '/c/Program Files (x86)/Microsoft Visual Studio/'*'/BuildTools/VC/Redist/MSVC'; do
find "$d" -name vcruntime140.dll; done | sort | grep -vE '/x64/|/onecore/' | head -n 1)" dist/
dist/copyparty.exe --version
cp -pv dist/copyparty{,.orig}.exe
[ $ultra ] && a="--best --lzma" || a=-1
/bin/time -f %es upx $a dist/copyparty.exe >/dev/null
ls -al dist/copyparty{,.orig}.exe
exit 0
}
echo gen tarlist echo gen tarlist
for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints for d in copyparty partftpy magic j2 py2 py37 ftp; do find $d -type f || true; done | # strip_hints
sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort | sed -r 's/(.*)\.(.*)/\2 \1/' | LC_ALL=C sort |

View File

@@ -118,4 +118,12 @@ base64 | head -c12 >> dist/copyparty.exe
dist/copyparty.exe --version dist/copyparty.exe --version
curl -fkT dist/copyparty.exe -b cppwd=wark https://192.168.123.1:3923/copyparty$esuf.exe csum=$(sha512sum <dist/copyparty.exe | cut -c-56)
curl -fkT dist/copyparty.exe -b cppwd=wark https://192.168.123.1:3923/copyparty$esuf.exe >uplod.log
cat uplod.log
grep -q $csum uplod.log && echo upload OK || {
echo UPLOAD FAILED
exit 1
}

View File

@@ -1,33 +1,33 @@
f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl f117016b1e6a7d7e745db30d3e67f1acf7957c443a0dd301b6c5e10b8368f2aa4db6be9782d2d3f84beadd139bfeef4982e40f21ca5d9065cb794eeb0e473e82 altgraph-0.17.4-py2.py3-none-any.whl
eda6c38fc4d813fee897e969ff9ecc5acc613df755ae63df0392217bbd67408b5c1f6c676f2bf5497b772a3eb4e1a360e1245e1c16ee83f0af555f1ab82c3977 Git-2.39.1-32-bit.exe e0d2e6183437af321a36944f04a501e85181243e5fa2da3254254305dd8119161f62048bc56bff8849b49f546ff175b02b4c999401f1c404f6b88e6f46a9c96e Git-2.44.0-32-bit.exe
9d2c31701a4d3fef553928c00528a48f9e1854ab5333528b50e358a214eba90029d687f039bcda5760b6fdf9f2de3bcf3784ae21a6374cf2a97a845d33b636c6 packaging-24.0-py3-none-any.whl
17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl 17ce52ba50692a9d964f57a23ac163fb74c77fdeb2ca988a6d439ae1fe91955ff43730c073af97a7b3223093ffea3479a996b9b50ee7fba0869247a56f74baa6 pefile-2023.2.7-py3-none-any.whl
126ca016c00256f4ff13c88707ead21b3b98f3c665ae57a5bcbb80c8be3004bff36d9c7f9a1cc9d20551019708f2b195154f302d80a1e5a2026d6d0fe9f3d5f4 pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl 126ca016c00256f4ff13c88707ead21b3b98f3c665ae57a5bcbb80c8be3004bff36d9c7f9a1cc9d20551019708f2b195154f302d80a1e5a2026d6d0fe9f3d5f4 pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl
749a473646c6d4c7939989649733d4c7699fd1c359c27046bf5bc9c070d1a4b8b986bbc65f60d7da725baf16dbfdd75a4c2f5bb8335f2cb5685073f5fee5c2d1 pywin32_ctypes-0.2.2-py3-none-any.whl 749a473646c6d4c7939989649733d4c7699fd1c359c27046bf5bc9c070d1a4b8b986bbc65f60d7da725baf16dbfdd75a4c2f5bb8335f2cb5685073f5fee5c2d1 pywin32_ctypes-0.2.2-py3-none-any.whl
6e0d854040baff861e1647d2bece7d090bc793b2bd9819c56105b94090df54881a6a9b43ebd82578cd7c76d47181571b671e60672afd9def389d03c9dae84fcf setuptools-68.2.2-py3-none-any.whl 6e0d854040baff861e1647d2bece7d090bc793b2bd9819c56105b94090df54881a6a9b43ebd82578cd7c76d47181571b671e60672afd9def389d03c9dae84fcf setuptools-68.2.2-py3-none-any.whl
3c5adf0a36516d284a2ede363051edc1bcc9df925c5a8a9fa2e03cab579dd8d847fdad42f7fd5ba35992e08234c97d2dbfec40a9d12eec61c8dc03758f2bd88e typing_extensions-4.4.0-py3-none-any.whl
# u2c (win7) # u2c (win7)
f3390290b896019b2fa169932390e4930d1c03c014e1f6db2405ca2eb1f51f5f5213f725885853805b742997b0edb369787e5c0069d217bc4e8b957f847f58b6 certifi-2023.11.17-py3-none-any.whl 7a3bd4849f95e1715fe2e99613df70a0fedd944a9bfde71a0fadb837fe62c3431c30da4f0b75c74de6f1a459f1fdf7cb62eaf404fdbe45e2d121e0b1021f1580 certifi-2024.2.2-py3-none-any.whl
904eb57b13bea80aea861de86987e618665d37fa9ea0856e0125a9ba767a53e5064de0b9c4735435a2ddf4f16f7f7d2c75a682e1de83d9f57922bdca8e29988c charset_normalizer-3.3.0-cp37-cp37m-win32.whl 9cc8acc5e269e6421bc32bb89261101da29d6ca337d39d60b9106de9ed7904e188716e4a48d78a2c4329026443fcab7acab013d2fe43778e30d6c4e4506a1b91 charset_normalizer-3.3.2-cp37-cp37m-win32.whl
ffdd45326f4e91c02714f7a944cbcc2fdd09299f709cfa8aec0892053eef0134fb80d9ba3790afd319538a86feb619037cbf533e2f5939cb56b35bb17f56c858 idna-3.4-py3-none-any.whl 0ec1ae5c928b4a0001a254c8598b746049406e1eed720bfafa94d4474078eff76bf6e032124e2d4df4619052836523af36162443c6d746487b387d2e3476e691 idna-3.6-py3-none-any.whl
b795abb26ba2f04f1afcfb196f21f638014b26c8186f8f488f1c2d91e8e0220962fbd259dbc9c3875222eb47fc95c73fc0606aaa6602b9ebc524809c9ba3501f requests-2.31.0-py3-none-any.whl b795abb26ba2f04f1afcfb196f21f638014b26c8186f8f488f1c2d91e8e0220962fbd259dbc9c3875222eb47fc95c73fc0606aaa6602b9ebc524809c9ba3501f requests-2.31.0-py3-none-any.whl
5a25cb9b79bb6107f9055dc3e9f62ebc6d4d9ca2c730d824985c93cd82406b723c200d6300c5064e42ee9fc7a2853d6ec6661394f3ed7bac03750e1f2a6840d1 urllib3-1.26.17-py2.py3-none-any.whl 61ed4500b6361632030f05229705c5c5a52cb47e31c0e6b55151c8f3beed631cd752ca6c3d6393d56a2acf6a453cfcf801e877116123c550922249c3a976e0f4 urllib3-1.26.18-py2.py3-none-any.whl
# win7 # win7
91c025f7d94bcdf93df838fab67053165a414fc84e8496f92ecbb910dd55f6b6af5e360bbd051444066880c5a6877e75157bd95e150ead46e5c605930dfc50f2 future-0.18.2.tar.gz d130bfa136bd171b9972b5c281c578545f2a84a909fdf18a6d2d71dd12fb3d512a7a1fa5cf7300433adece1d306eb2f22d7278f4c90e744e04dc67ba627a82c0 future-1.0.0-py3-none-any.whl
c06b3295d1d0b0f0a6f9a6cd0be861b9b643b4a5ea37857f0bd41c45deaf27bb927b71922dab74e633e43d75d04a9bd0d1c4ad875569740b0f2a98dd2bfa5113 importlib_metadata-5.0.0-py3-none-any.whl 0b4d07434bf8d314f42893d90bce005545b44a509e7353a73cad26dc9360b44e2824218a1a74f8174d02eba87fba91baffa82c8901279a32ebc6b8386b1b4275 importlib_metadata-6.7.0-py3-none-any.whl
016a8cbd09384f1a9a44cb0e8274df75a8bcb2f3966bb5d708c62145289efaa5db98f75256c97e4f8046735ce2e529fbb076f284a46cdb716e89a75660200ad9 pip-23.2.1-py3-none-any.whl 5d7462a584105bccaa9cf376f5a8c5827ead099c813c8af7392d478a4398f373d9e8cac7bbad2db51b335411ab966b21e119b1b1234c9a7ab70c6ddfc9306da6 pip-24.0-py3-none-any.whl
f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl f298e34356b5590dde7477d7b3a88ad39c622a2bcf3fcd7c53870ce8384dd510f690af81b8f42e121a22d3968a767d2e07595036b2ed7049c8ef4d112bcf3a61 pyinstaller-5.13.2-py3-none-win32.whl
6bb73cc2db795c59c92f2115727f5c173cacc9465af7710db9ff2f2aec2d73130d0992d0f16dcb3fac222dc15c0916562d0813b2337401022020673a4461df3d python-3.7.9-amd64.exe 6bb73cc2db795c59c92f2115727f5c173cacc9465af7710db9ff2f2aec2d73130d0992d0f16dcb3fac222dc15c0916562d0813b2337401022020673a4461df3d python-3.7.9-amd64.exe
500747651c87f59f2436c5ab91207b5b657856e43d10083f3ce27efb196a2580fadd199a4209519b409920c562aaaa7dcbdfb83ed2072a43eaccae6e2d056f31 python-3.7.9.exe 500747651c87f59f2436c5ab91207b5b657856e43d10083f3ce27efb196a2580fadd199a4209519b409920c562aaaa7dcbdfb83ed2072a43eaccae6e2d056f31 python-3.7.9.exe
03e50aecc85914567c114e38a1777e32628ee098756f37177bc23220eab33ac7d3ff591fd162db3b4d4e34d55cee93ef0dc67af68a69c38bb1435e0768dee57e typing_extensions-4.7.1-py3-none-any.whl
2e04acff170ca3bbceeeb18489c687126c951ec0bfd53cccfb389ba8d29a4576c1a9e8f2e5ea26c84dd21bfa2912f4e71fa72c1e2653b71e34afc0e65f1722d4 upx-4.2.2-win32.zip 2e04acff170ca3bbceeeb18489c687126c951ec0bfd53cccfb389ba8d29a4576c1a9e8f2e5ea26c84dd21bfa2912f4e71fa72c1e2653b71e34afc0e65f1722d4 upx-4.2.2-win32.zip
68e1b618d988be56aaae4e2eb92bc0093627a00441c1074ebe680c41aa98a6161e52733ad0c59888c643a33fe56884e4f935178b2557fbbdd105e92e0d993df6 windows6.1-kb2533623-x64.msu 68e1b618d988be56aaae4e2eb92bc0093627a00441c1074ebe680c41aa98a6161e52733ad0c59888c643a33fe56884e4f935178b2557fbbdd105e92e0d993df6 windows6.1-kb2533623-x64.msu
479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu 479a63e14586ab2f2228208116fc149ed8ee7b1e4ff360754f5bda4bf765c61af2e04b5ef123976623d04df4976b7886e0445647269da81436bd0a7b5671d361 windows6.1-kb2533623-x86.msu
ba91ab0518c61eff13e5612d9e6b532940813f6b56e6ed81ea6c7c4d45acee4d98136a383a25067512b8f75538c67c987cf3944bfa0229e3cb677e2fb81e763e zipp-3.10.0-py3-none-any.whl ac96786e5d35882e0c5b724794329c9125c2b86ae7847f17acfc49f0d294312c6afc1c3f248655de3f0ccb4ca426d7957d02ba702f4a15e9fcd7e2c314e72c19 zipp-3.15.0-py3-none-any.whl
# win10 # win10
e3e2e6bd511dec484dd0292f4c46c55c88a885eabf15413d53edea2dd4a4dbae1571735b9424f78c0cd7f1082476a8259f31fd3f63990f726175470f636df2b3 Jinja2-3.1.3-py3-none-any.whl e3e2e6bd511dec484dd0292f4c46c55c88a885eabf15413d53edea2dd4a4dbae1571735b9424f78c0cd7f1082476a8259f31fd3f63990f726175470f636df2b3 Jinja2-3.1.3-py3-none-any.whl
e21495f1d473d855103fb4a243095b498ec90eb68776b0f9b48e994990534f7286c0292448e129c507e5d70409f8a05cca58b98d59ce2a815993d0a873dfc480 MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl e21495f1d473d855103fb4a243095b498ec90eb68776b0f9b48e994990534f7286c0292448e129c507e5d70409f8a05cca58b98d59ce2a815993d0a873dfc480 MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl 8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
656015f5cc2c04aa0653ee5609c39a7e5f0b6a58c84fe26b20bd070c52d20b4effb810132f7fb771168483e9fd975cc3302837dd7a1a687ee058b0460c857cc4 packaging-23.2-py3-none-any.whl 1dfe6f66bef5c9d62c9028a964196b902772ec9e19db215f3f41acb8d2d563586988d81b455fa6b895b434e9e1e9d57e4d271d1b1214483bdb3eadffcbba6a33 pillow-10.3.0-cp311-cp311-win_amd64.whl
424e20dc7263a31d524307bc39ed755a9dd82f538086fff68d98dd97e236c9b00777a8ac2e3853081b532b0e93cef44983e74d0ab274877440e8b7341b19358a pillow-10.2.0-cp311-cp311-win_amd64.whl
8760eab271e79256ae3bfb4af8ccc59010cb5d2eccdd74b325d1a533ae25eb127d51c2ec28ff90d449afed32dd7d6af62934fe9caaf1ae1f4d4831e948e912da pyinstaller-6.5.0-py3-none-win_amd64.whl 8760eab271e79256ae3bfb4af8ccc59010cb5d2eccdd74b325d1a533ae25eb127d51c2ec28ff90d449afed32dd7d6af62934fe9caaf1ae1f4d4831e948e912da pyinstaller-6.5.0-py3-none-win_amd64.whl
e6bdbae1affd161e62fc87407c912462dfe875f535ba9f344d0c4ade13715c947cd3ae832eff60f1bad4161938311d06ac8bc9b52ef203f7b0d9de1409f052a5 python-3.11.8-amd64.exe 897a14d5ee5cbc6781a0f48beffc27807a4f789d58c4329d899233f615d168a5dcceddf7f8f2d5bb52212ddcf3eba4664590d9f1fdb25bb5201f44899e03b2f7 python-3.11.9-amd64.exe
729dc52f1a02bc6274d012ce33f534102975a828cba11f6029600ea40e2d23aefeb07bf4ae19f9621d0565dd03eb2635bbb97d45fb692c1f756315e8c86c5255 upx-4.2.2-win64.zip 729dc52f1a02bc6274d012ce33f534102975a828cba11f6029600ea40e2d23aefeb07bf4ae19f9621d0565dd03eb2635bbb97d45fb692c1f756315e8c86c5255 upx-4.2.2-win64.zip

View File

@@ -8,7 +8,7 @@ run ./build.sh in git-bash to build + upload the exe
to obtain the files referenced below, see ./deps.txt to obtain the files referenced below, see ./deps.txt
download + install git (32bit OK on 64): download + install git (32bit OK on 64):
http://192.168.123.1:3923/ro/pyi/Git-2.39.1-32-bit.exe http://192.168.123.1:3923/ro/pyi/Git-2.44.0-32-bit.exe
===[ copy-paste into git-bash ]================================ ===[ copy-paste into git-bash ]================================
uname -s | grep NT-10 && w10=1 || { uname -s | grep NT-10 && w10=1 || {
@@ -16,6 +16,7 @@ uname -s | grep NT-10 && w10=1 || {
} }
fns=( fns=(
altgraph-0.17.4-py2.py3-none-any.whl altgraph-0.17.4-py2.py3-none-any.whl
packaging-24.0-py3-none-any.whl
pefile-2023.2.7-py3-none-any.whl pefile-2023.2.7-py3-none-any.whl
pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl pyinstaller_hooks_contrib-2024.3-py2.py3-none-any.whl
pywin32_ctypes-0.2.2-py3-none-any.whl pywin32_ctypes-0.2.2-py3-none-any.whl
@@ -26,26 +27,25 @@ fns=(
Jinja2-3.1.3-py3-none-any.whl Jinja2-3.1.3-py3-none-any.whl
MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl
mutagen-1.47.0-py3-none-any.whl mutagen-1.47.0-py3-none-any.whl
packaging-23.2-py3-none-any.whl pillow-10.3.0-cp311-cp311-win_amd64.whl
pillow-10.2.0-cp311-cp311-win_amd64.whl python-3.11.9-amd64.exe
python-3.11.8-amd64.exe
upx-4.2.2-win64.zip upx-4.2.2-win64.zip
) )
[ $w7 ] && fns+=( [ $w7 ] && fns+=(
pyinstaller-5.13.2-py3-none-win32.whl pyinstaller-5.13.2-py3-none-win32.whl
certifi-2023.11.17-py3-none-any.whl certifi-2024.2.2-py3-none-any.whl
chardet-5.1.0-py3-none-any.whl charset_normalizer-3.3.2-cp37-cp37m-win32.whl
idna-3.4-py3-none-any.whl idna-3.6-py3-none-any.whl
requests-2.28.2-py3-none-any.whl requests-2.31.0-py3-none-any.whl
urllib3-1.26.14-py2.py3-none-any.whl urllib3-1.26.18-py2.py3-none-any.whl
upx-4.2.2-win32.zip upx-4.2.2-win32.zip
) )
[ $w7 ] && fns+=( [ $w7 ] && fns+=(
future-0.18.2.tar.gz future-1.0.0-py3-none-any.whl
importlib_metadata-5.0.0-py3-none-any.whl importlib_metadata-6.7.0-py3-none-any.whl
pip-23.2.1-py3-none-any.whl pip-24.0-py3-none-any.whl
typing_extensions-4.4.0-py3-none-any.whl typing_extensions-4.7.1-py3-none-any.whl
zipp-3.10.0-py3-none-any.whl zipp-3.15.0-py3-none-any.whl
) )
[ $w7x64 ] && fns+=( [ $w7x64 ] && fns+=(
windows6.1-kb2533623-x64.msu windows6.1-kb2533623-x64.msu
@@ -77,9 +77,10 @@ yes | unzip upx-*-win32.zip &&
mv upx-*/upx.exe . && mv upx-*/upx.exe . &&
python -m ensurepip && python -m ensurepip &&
{ [ $w10 ] || python -m pip install --user -U pip-*.whl; } && { [ $w10 ] || python -m pip install --user -U pip-*.whl; } &&
{ [ $w7 ] || python -m pip install --user -U {packaging,setuptools,mutagen,Pillow,Jinja2,MarkupSafe}-*.whl; } && python -m pip install --user -U packaging-*.whl &&
{ [ $w7 ] || python -m pip install --user -U {setuptools,mutagen,Pillow,Jinja2,MarkupSafe}-*.whl; } &&
{ [ $w10 ] || python -m pip install --user -U {requests,urllib3,charset_normalizer,certifi,idna}-*.whl; } && { [ $w10 ] || python -m pip install --user -U {requests,urllib3,charset_normalizer,certifi,idna}-*.whl; } &&
{ [ $w10 ] || python -m pip install --user -U future-*.tar.gz importlib_metadata-*.whl typing_extensions-*.whl zipp-*.whl; } && { [ $w10 ] || python -m pip install --user -U future-*.whl importlib_metadata-*.whl typing_extensions-*.whl zipp-*.whl; } &&
python -m pip install --user -U pyinstaller-*.whl pefile-*.whl pywin32_ctypes-*.whl pyinstaller_hooks_contrib-*.whl altgraph-*.whl && python -m pip install --user -U pyinstaller-*.whl pefile-*.whl pywin32_ctypes-*.whl pyinstaller_hooks_contrib-*.whl altgraph-*.whl &&
sed -ri 's/--lzma/--best/' $appd/Python/Python$pyv/site-packages/pyinstaller/building/utils.py && sed -ri 's/--lzma/--best/' $appd/Python/Python$pyv/site-packages/pyinstaller/building/utils.py &&
curl -fkLO https://192.168.123.1:3923/cpp/scripts/uncomment.py && curl -fkLO https://192.168.123.1:3923/cpp/scripts/uncomment.py &&

View File

@@ -45,4 +45,12 @@ $APPDATA/python/python37/scripts/pyinstaller -y --clean --upx-dir=. up2k.spec
./dist/u2c.exe --version ./dist/u2c.exe --version
curl -fkT dist/u2c.exe -HPW:wark https://192.168.123.1:3923/ csum=$(sha512sum <dist/u2c.exe | cut -c-56)
curl -fkT dist/u2c.exe -HPW:wark https://192.168.123.1:3923/ >uplod.log
cat uplod.log
grep -q $csum uplod.log && echo upload OK || {
echo UPLOAD FAILED
exit 1
}

View File

@@ -81,6 +81,7 @@ copyparty/web/dd/5.png,
copyparty/web/dd/__init__.py, copyparty/web/dd/__init__.py,
copyparty/web/deps, copyparty/web/deps,
copyparty/web/deps/__init__.py, copyparty/web/deps/__init__.py,
copyparty/web/deps/busy.mp3,
copyparty/web/deps/easymde.css, copyparty/web/deps/easymde.css,
copyparty/web/deps/easymde.js, copyparty/web/deps/easymde.js,
copyparty/web/deps/marked.js, copyparty/web/deps/marked.js,

View File

@@ -234,8 +234,9 @@ def u8(gen):
def yieldfile(fn): def yieldfile(fn):
with open(fn, "rb") as f: s = 64 * 1024
for block in iter(lambda: f.read(64 * 1024), b""): with open(fn, "rb", s * 4) as f:
for block in iter(lambda: f.read(s), b""):
yield block yield block

24
tests/res/idp/6.conf Normal file
View File

@@ -0,0 +1,24 @@
# -*- mode: yaml -*-
# vim: ft=yaml:
[global]
idp-h-usr: x-idp-user
idp-h-grp: x-idp-group
[/get/${u}]
/get/${u}
accs:
g: *
r: ${u}, @su
m: @su
[/priv/${u}]
/priv/${u}
accs:
r: ${u}, @su
m: @su
[/team/${g}/${u}]
/team/${g}/${u}
accs:
r: @${g}

View File

@@ -4,6 +4,9 @@ from __future__ import print_function, unicode_literals
import io import io
import os import os
import time
import json
import pprint
import shutil import shutil
import tarfile import tarfile
import tempfile import tempfile
@@ -49,11 +52,7 @@ class TestHttpCli(unittest.TestCase):
with open(filepath, "wb") as f: with open(filepath, "wb") as f:
f.write(filepath.encode("utf-8")) f.write(filepath.encode("utf-8"))
vcfg = [ vcfg = [".::r,u1:r.,u2", "a:a:r,u1:r,u2", ".b:.b:r.,u1:r,u2"]
".::r,u1:r.,u2",
"a:a:r,u1:r,u2",
".b:.b:r.,u1:r,u2"
]
self.args = Cfg(v=vcfg, a=["u1:u1", "u2:u2"], e2dsa=True) self.args = Cfg(v=vcfg, a=["u1:u1", "u2:u2"], e2dsa=True)
self.asrv = AuthSrv(self.args, self.log) self.asrv = AuthSrv(self.args, self.log)
@@ -66,7 +65,9 @@ class TestHttpCli(unittest.TestCase):
self.assertEqual(self.curl("?tar", "x")[1][:17], "\nJ2EOT") self.assertEqual(self.curl("?tar", "x")[1][:17], "\nJ2EOT")
# search ##
## search
up2k = Up2k(self) up2k = Up2k(self)
u2idx = U2idx(self) u2idx = U2idx(self)
allvols = list(self.asrv.vfs.all_vols.values()) allvols = list(self.asrv.vfs.all_vols.values())
@@ -91,15 +92,55 @@ class TestHttpCli(unittest.TestCase):
xe = "a/da/f4 a/f3 f0 t/f1" xe = "a/da/f4 a/f3 f0 t/f1"
self.assertEqual(x, xe) self.assertEqual(x, xe)
def tardir(self, url, uname): ##
h, b = self.curl("/" + url + "?tar", uname, True) ## dirkeys
tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
top = ("top" if not url else url.lstrip(".").split("/")[0]) + "/"
assert len(tar) == len([x for x in tar if x.startswith(top)])
return " ".join([x[len(top):] for x in tar])
def curl(self, url, uname, binary=False): os.mkdir("v")
conn = tu.VHttpConn(self.args, self.asrv, self.log, hdr(url, uname)) with open("v/f1.txt", "wb") as f:
f.write(b"a")
os.rename("a", "v/a")
os.rename(".b", "v/.b")
vcfg = [
".::r.,u1:g,u2:c,dk",
"v/a:v/a:r.,u1:g,u2:c,dk",
"v/.b:v/.b:r.,u1:g,u2:c,dk",
]
self.args = Cfg(v=vcfg, a=["u1:u1", "u2:u2"])
self.asrv = AuthSrv(self.args, self.log)
zj = json.loads(self.curl("?ls", "u1")[1])
url = "?k=" + zj["dk"]
# should descend into folders, but not other volumes:
self.assertEqual(self.tardir(url, "u2"), "f0 t/f1 v/f1.txt")
zj = json.loads(self.curl("v?ls", "u1")[1])
url = "v?k=" + zj["dk"]
self.assertEqual(self.tarsel(url, "u2", ["f1.txt", "a", ".b"]), "f1.txt")
def tardir(self, url, uname):
top = url.split("?")[0]
top = ("top" if not top else top.lstrip(".").split("/")[0]) + "/"
url += ("&" if "?" in url else "?") + "tar"
h, b = self.curl(url, uname, True)
tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
if len(tar) != len([x for x in tar if x.startswith(top)]):
raise Exception("bad-prefix:", tar)
return " ".join([x[len(top) :] for x in tar])
def tarsel(self, url, uname, sel):
url += ("&" if "?" in url else "?") + "tar"
zs = '--XD\r\nContent-Disposition: form-data; name="act"\r\n\r\nzip\r\n--XD\r\nContent-Disposition: form-data; name="files"\r\n\r\n'
zs += "\r\n".join(sel) + "\r\n--XD--\r\n"
zb = zs.encode("utf-8")
hdr = "POST /%s HTTP/1.1\r\nPW: %s\r\nConnection: close\r\nContent-Type: multipart/form-data; boundary=XD\r\nContent-Length: %d\r\n\r\n"
req = (hdr % (url, uname, len(zb))).encode("utf-8") + zb
h, b = self.curl("/" + url, uname, True, req)
tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
return " ".join(tar)
def curl(self, url, uname, binary=False, req=b""):
req = req or hdr(url, uname)
conn = tu.VHttpConn(self.args, self.asrv, self.log, req)
HttpCli(conn).run() HttpCli(conn).run()
if binary: if binary:
h, b = conn.s._reply.split(b"\r\n\r\n", 1) h, b = conn.s._reply.split(b"\r\n\r\n", 1)

View File

@@ -15,6 +15,16 @@ class TestVFS(unittest.TestCase):
print(json.dumps(vfs, indent=4, sort_keys=True, default=lambda o: o.__dict__)) print(json.dumps(vfs, indent=4, sort_keys=True, default=lambda o: o.__dict__))
def log(self, src, msg, c=0): def log(self, src, msg, c=0):
m = "%s" % (msg,)
if (
"warning: filesystem-path does not exist:" in m
or "you are sharing a system directory:" in m
or "reinitializing due to new user from IdP:" in m
or m.startswith("hint: argument")
or (m.startswith("loaded ") and " config files:" in m)
):
return
print(("[%s] %s" % (src, msg)).encode("ascii", "replace").decode("ascii")) print(("[%s] %s" % (src, msg)).encode("ascii", "replace").decode("ascii"))
def nav(self, au, vp): def nav(self, au, vp):
@@ -30,21 +40,23 @@ class TestVFS(unittest.TestCase):
self.assertEqual(unpacked, expected + [[]] * pad) self.assertEqual(unpacked, expected + [[]] * pad)
def assertAxsAt(self, au, vp, expected): def assertAxsAt(self, au, vp, expected):
self.assertAxs(self.nav(au, vp).axs, expected) vn = self.nav(au, vp)
self.assertAxs(vn.axs, expected)
def assertNodes(self, vfs, expected): def assertNodes(self, vfs, expected):
got = list(sorted(vfs.nodes.keys())) got = list(sorted(vfs.nodes.keys()))
self.assertEqual(got, expected) self.assertEqual(got, expected)
def assertNodesAt(self, au, vp, expected): def assertNodesAt(self, au, vp, expected):
self.assertNodes(self.nav(au, vp), expected) vn = self.nav(au, vp)
self.assertNodes(vn, expected)
def prep(self): def prep(self):
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(os.path.dirname(__file__))
cfgdir = os.path.join(here, "res", "idp") cfgdir = os.path.join(here, "res", "idp")
# globals are applied by main so need to cheat a little # globals are applied by main so need to cheat a little
xcfg = { "idp_h_usr": "x-idp-user", "idp_h_grp": "x-idp-group" } xcfg = {"idp_h_usr": "x-idp-user", "idp_h_grp": "x-idp-group"}
return here, cfgdir, xcfg return here, cfgdir, xcfg
@@ -140,6 +152,11 @@ class TestVFS(unittest.TestCase):
self.assertEqual(self.nav(au, "vg/iga1").realpath, "/g1-iga") self.assertEqual(self.nav(au, "vg/iga1").realpath, "/g1-iga")
self.assertEqual(self.nav(au, "vg/iga2").realpath, "/g2-iga") self.assertEqual(self.nav(au, "vg/iga2").realpath, "/g2-iga")
au.idp_checkin(None, "iub", "iga")
self.assertAxsAt(au, "vu/iua", [["iua"]])
self.assertAxsAt(au, "vg/iga1", [["iua", "iub"]])
self.assertAxsAt(au, "vg/iga2", [["iua", "iub", "ua"]])
def test_5(self): def test_5(self):
""" """
one IdP user in multiple groups one IdP user in multiple groups
@@ -169,3 +186,44 @@ class TestVFS(unittest.TestCase):
self.assertAxsAt(au, "g", [["iua"]]) self.assertAxsAt(au, "g", [["iua"]])
self.assertAxsAt(au, "ga", [["iua"]]) self.assertAxsAt(au, "ga", [["iua"]])
self.assertAxsAt(au, "gb", [["iua"]]) self.assertAxsAt(au, "gb", [["iua"]])
def test_6(self):
"""
IdP volumes with anon-get and other users/groups (github#79)
"""
_, cfgdir, xcfg = self.prep()
au = AuthSrv(Cfg(c=[cfgdir + "/6.conf"], **xcfg), self.log)
self.assertAxs(au.vfs.axs, [])
self.assertEqual(au.vfs.vpath, "")
self.assertEqual(au.vfs.realpath, "")
self.assertNodes(au.vfs, [])
au.idp_checkin(None, "iua", "")
star = ["*", "iua"]
self.assertNodes(au.vfs, ["get", "priv"])
self.assertAxsAt(au, "get/iua", [["iua"], [], [], [], star])
self.assertAxsAt(au, "priv/iua", [["iua"], [], []])
au.idp_checkin(None, "iub", "")
star = ["*", "iua", "iub"]
self.assertNodes(au.vfs, ["get", "priv"])
self.assertAxsAt(au, "get/iua", [["iua"], [], [], [], star])
self.assertAxsAt(au, "get/iub", [["iub"], [], [], [], star])
self.assertAxsAt(au, "priv/iua", [["iua"], [], []])
self.assertAxsAt(au, "priv/iub", [["iub"], [], []])
au.idp_checkin(None, "iuc", "su")
star = ["*", "iua", "iub", "iuc"]
self.assertNodes(au.vfs, ["get", "priv", "team"])
self.assertAxsAt(au, "get/iua", [["iua", "iuc"], [], ["iuc"], [], star])
self.assertAxsAt(au, "get/iub", [["iub", "iuc"], [], ["iuc"], [], star])
self.assertAxsAt(au, "get/iuc", [["iuc"], [], ["iuc"], [], star])
self.assertAxsAt(au, "priv/iua", [["iua", "iuc"], [], ["iuc"]])
self.assertAxsAt(au, "priv/iub", [["iub", "iuc"], [], ["iuc"]])
self.assertAxsAt(au, "priv/iuc", [["iuc"], [], ["iuc"]])
self.assertAxsAt(au, "team/su/iuc", [["iuc"]])
au.idp_checkin(None, "iud", "su")
self.assertAxsAt(au, "team/su/iuc", [["iuc", "iud"]])
self.assertAxsAt(au, "team/su/iud", [["iuc", "iud"]])

View File

@@ -110,7 +110,7 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None, **ka0): def __init__(self, a=None, v=None, c=None, **ka0):
ka = {} ka = {}
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw q rand smb srch_dbg stats vague_403 vc ver xdev xlink xvol" ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw q rand smb srch_dbg stats vague_403 vc ver xdev xlink xvol"
ka.update(**{k: False for k in ex.split()}) ka.update(**{k: False for k in ex.split()})
ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_voldump re_dhash plain_ip" ex = "dotpart dotsrch no_dhash no_fastboot no_rescan no_sendfile no_voldump re_dhash plain_ip"
@@ -145,8 +145,10 @@ class Cfg(Namespace):
c=c, c=c,
E=E, E=E,
dbd="wal", dbd="wal",
dk_salt="b" * 16,
fk_salt="a" * 16, fk_salt="a" * 16,
idp_gsep=re.compile("[|:;+,]"), idp_gsep=re.compile("[|:;+,]"),
iobuf=256 * 1024,
lang="eng", lang="eng",
log_badpwd=1, log_badpwd=1,
logout=573, logout=573,
@@ -154,7 +156,8 @@ class Cfg(Namespace):
mth={}, mth={},
mtp=[], mtp=[],
rm_retry="0/0", rm_retry="0/0",
s_wr_sz=512 * 1024, s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024,
sort="href", sort="href",
srch_hits=99999, srch_hits=99999,
th_crop="y", th_crop="y",
@@ -254,4 +257,4 @@ class VHttpConn(object):
self.thumbcli = None self.thumbcli = None
self.u2fh = FHC() self.u2fh = FHC()
self.get_u2idx = self.hsrv.get_u2idx self.get_u2idx = self.hsrv.get_u2idx