Compare commits

..

53 Commits

Author SHA1 Message Date
ed
e4001550c1 v1.9.11 2023-10-09 00:36:54 +00:00
ed
e9f65be86a add cachebuster for dynamically loaded js files 2023-10-09 00:22:16 +00:00
ed
3b9919a486 update pkgs to 1.9.10 2023-10-08 21:16:12 +00:00
ed
acc363133f v1.9.10 2023-10-08 20:51:49 +00:00
ed
8f2d502d4d configurable printing of failed login attempts 2023-10-08 20:41:02 +00:00
ed
2ae93ad715 clear response headers for each request 2023-10-08 20:38:51 +00:00
ed
bb590e364a update pkgs to 1.9.9 2023-10-07 22:49:12 +00:00
ed
e7fff77735 v1.9.9 2023-10-07 22:29:37 +00:00
ed
753e3cfbaf revert 68c6794d (v1.6.2) and fix it better:
moving deduplicated files between volumes could drop some links
2023-10-07 22:25:44 +00:00
ed
99e9cba1f7 update pkgs to 1.9.8 2023-10-06 18:22:01 +00:00
ed
fcc3336760 v1.9.8 2023-10-06 17:50:35 +00:00
ed
0dc3c23b42 add alternative filekey generator; closes #52 2023-10-06 13:41:22 +00:00
ed
6aa10ecedc mention streaming unzip with bsdtar 2023-10-02 07:40:40 +02:00
ed
93125bba4d update pkgs to 1.9.7 2023-09-30 23:56:35 +00:00
ed
fae5a36e6f v1.9.7 2023-09-30 23:32:51 +00:00
ed
fc9b729fc2 fix #51:
* handle unexpected localstorage values
* handle unsupported --lang values
2023-09-30 22:54:21 +00:00
ed
8620ae5bb7 fix column-hiding ux on phones:
table header click-handler didn't cover the entire cell so it was
easy to sort the table by accident; also do not exit hiding mode
automatically since you usually want to hide several columns
(so also adjust css to make it obvious you're in hiding mode)
2023-09-28 09:28:26 +02:00
ed
01a851da28 mtp-deps: fix building on archlinux 2023-09-24 23:17:26 +00:00
ed
309895d39d docker: exploring alternative base images for performance 2023-09-24 22:26:51 +00:00
ed
7ac0803ded update pkgs to 1.9.6 2023-09-23 12:56:47 +00:00
ed
cae5ccea62 v1.9.6 2023-09-23 12:15:24 +00:00
ed
3768cb4723 add chat 2023-09-23 11:34:32 +00:00
ed
0815dce4c1 ensure indexing runs with --ign-ebind-all 2023-09-22 23:20:57 +00:00
ed
a62f744a18 prevent losing an out-of-volume index
if the server is started while an external drive is not mounted,
it would drop the database because all the files are missing
2023-09-22 23:05:07 +00:00
ed
163e3fce46 improve reverse-proxy support when containerized:
the x-forwarded-for header would get rejected since the reverse-proxy
is not asking from 127.0.0.1 or ::1, so make this allowlist configurable
2023-09-22 22:39:20 +00:00
ed
e76a50cb9d add indexer benchmark + bump default num cores from 4 to 5
and make the mtag deps build better on fedora
2023-09-22 20:40:52 +00:00
ed
72fc76ef48 golf / normalize window.location 2023-09-20 22:07:40 +00:00
ed
c47047c30d configurable real-ip header from reverse proxy 2023-09-20 21:56:39 +00:00
ed
3b8f66c0d5 fix a client crash when uploading from glitchy net
prevent reattempting chunks / handshakes after an upload has completed
since that is both pointless and crashy

bugreport ocr'ed from deepfried pic (thx kipu):
stack: exec_handshake -> xhr.onload -> tasked -> exec_upload -> do_send

529226 crash: t.fobj is null; firefox 117, win64
529083 zombie handshake onerror, some.flac
529081 chunkpit onerror,, 1, another.flac
528933 retrying stuck handshake
498842 ^
464213 zombie handshake onload, some.flac
464208 ^
462858 ignoring dupe-segment error, some.flac
462766 ^
462751 ^
462667 ^
462403 ^
462316 ^
461321 zombie handshake onload, some.flac
461302 ^
461152 ^
461114 ^
461110 ^
460769 ^
459954 ^
459492 ignoring dupe-segment error, some.flac
2023-09-20 21:25:59 +00:00
ed
aa96a1acdc misc optimizations / cleanup:
* slightly faster startup / shutdown
* forgot a jinja2 golf
* waste 4KiB changing prismjs back to gz since brotli is https-gated ;_;
* broke support for firefox<52 (non-var functions must be toplevel
   or immediately within another function), now even firefox 10 /
   centos 6 is somewhat supported again
2023-09-17 13:02:18 +00:00
ed
91cafc2511 faster startup on windows by asking for ffmpeg.exe explicitly
rather than just "ffmpeg" which makes windows try to open each of
ffmpeg.BAT,CMD,COM,EXE,JS,JSE,MSC,VBE,VBS,WSF,WSH one by one
(ffmpeg.js? hello??)
2023-09-13 23:32:19 +00:00
ed
23ca00bba8 support jython and graalpy 2023-09-13 23:24:56 +00:00
ed
a75a992951 golf the sfx-gz by ~27.6 kB;
* 11 kB webdeps: brotli easymde+prism instead of zopfli
* 8 kB jinja2
* 5 kB ftp
* 3 kB improve uncommenter
2023-09-13 23:21:22 +00:00
ed
4fbd6853f4 add msg-log.py initially by @clach04, closes #35 2023-09-12 19:56:05 +00:00
ed
71c3ad63b3 fix tests 2023-09-11 01:46:25 +00:00
ed
e1324e37a5 update pkgs to 1.9.5 2023-09-09 14:15:46 +00:00
ed
a996a09bba v1.9.5 2023-09-09 13:36:56 +00:00
ed
18c763ac08 smb: upgrade to impacket 0.11, full user account support,
permissions are now per-account instead of coalescing

also stops windows from freaking out if there's an offline volume
2023-09-09 12:46:37 +00:00
ed
3d9fb753ba stuff 2023-09-08 21:42:05 +00:00
ed
714fd1811a add option to generate pax-format tar archives
and forgot to commit the nix module
2023-09-08 21:13:23 +00:00
ed
4364581705 fix accidental 422-ban when uploading lots of dupes 2023-09-08 19:49:29 +00:00
ed
ba02c9cc12 readme fix + make hacker theme more hacker 2023-09-08 19:35:12 +00:00
ed
11eefaf968 create / edit non-markdown textfiles (if user has delete-access)
also enables the ansi escape code parser if the text looks like ansi
2023-09-08 18:47:31 +00:00
ed
5a968f9e47 add permission 'h': folders redirect to index.html;
safest way to make copyparty like a general-purpose webserver where
index.html is returned as expected yet directory listing is entirely
disabled / unavailable
2023-09-07 23:30:01 +00:00
ed
6420c4bd03 up to 2.6x faster download-as-zip
when there's lots of files, and especially small ones
and also reduces cpu load by at least 15%
2023-09-05 22:57:03 +00:00
ed
0f9877201b support cache directives in --css-browser, --js-browser;
for example --css-browser=/the.css?cache=600 (seconds)
or --js-browser=/.res/the.js?cache=i (7 days)
2023-09-03 19:50:31 +00:00
ed
9ba2dec9b2 lightbox: fix ccw rotation hotkey 2023-09-03 19:23:29 +00:00
ed
ae9cfea939 update pkgs to 1.9.4 2023-09-02 00:45:57 +00:00
ed
cadaeeeace v1.9.4 2023-09-02 00:18:53 +00:00
ed
767696185b add ?tar=gz, ?tar=bz2, ?tar=xz with optional level;
defaults are ?tar=gz:3, ?tar=bz2:9, ?tar=xz:1
2023-09-01 23:44:10 +00:00
ed
c1efd227b7 fix inconsistent use of symlink mtimes in database;
on upload, dupes are by default handled by symlinking to the existing
copy on disk, writing the uploader's local mtime into the symlink mtime,
which is also what gets indexed in the db

this worked as intended, however during an -e2dsa rescan on startup the
symlink destination timestamps would be used instead, causing a reindex
and the resulting loss of uploader metadata (ip, timestamp)

will now always use the symlink's mtime;
worst-case 1% slower startup (no dhash)

this change will cause a reindex of incorrectly indexed files, however
as this has already happened at least once due to the bug being fixed,
there will be no additional loss of metadata
2023-09-01 20:29:55 +00:00
ed
a50d0563c3 instantly perform search when URL contains a raw query 2023-09-01 20:16:19 +00:00
ed
e5641ddd16 update pkgs to 1.9.3 2023-08-31 23:08:32 +00:00
62 changed files with 1628 additions and 403 deletions

View File

@@ -88,6 +88,7 @@ turn almost any device into a file server with resumable uploads/downloads using
* [security](#security) - there is a [discord server](https://discord.gg/25J8CdTT6G) * [security](#security) - there is a [discord server](https://discord.gg/25J8CdTT6G)
* [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
* [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)
@@ -287,6 +288,10 @@ server notes:
* iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11) * iPhones: the volume control doesn't work because [apple doesn't want it to](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW11)
* `AudioContext` will probably never be a viable workaround as apple introduces new issues faster than they fix current ones * `AudioContext` will probably never be a viable workaround as apple introduces new issues faster than they fix current ones
* iPhones: the preload feature (in the media-player-options tab) can cause a tiny audio glitch 20sec before the end of each song, but disabling it may cause worse iOS bugs to appear instead
* just a hunch, but disabling preloading may cause playback to stop entirely, or possibly mess with bluetooth speakers
* tried to add a tooltip regarding this but looks like apple broke my tooltips
* Windows: folders cannot be accessed if the name ends with `.` * Windows: folders cannot be accessed if the name ends with `.`
* python or windows bug * python or windows bug
@@ -324,6 +329,12 @@ upgrade notes
* can I make copyparty download a file to my server if I give it a URL? * can I make copyparty download a file to my server if I give it a URL?
* yes, using [hooks](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/wget.py) * yes, using [hooks](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/wget.py)
* i want to learn python and/or programming and am considering looking at the copyparty source code in that occasion
```bash
_| _ __ _ _|_
(_| (_) | | (_) |_
```
# accounts and volumes # accounts and volumes
@@ -346,7 +357,8 @@ permissions:
* `m` (move): move files/folders *from* this folder * `m` (move): move files/folders *from* this folder
* `d` (delete): delete files/folders * `d` (delete): delete files/folders
* `g` (get): only download files, cannot see folder contents or zip/tar * `g` (get): only download files, cannot see folder contents or zip/tar
* `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below) * `G` (upget): same as `g` except uploaders get to see their own [filekeys](#filekeys) (see `fk` in examples below)
* `h` (html): same as `g` except folders return their index.html, and filekeys are not necessary for index.html
* `a` (admin): can see uploader IPs, config-reload * `a` (admin): can see uploader IPs, config-reload
examples: examples:
@@ -359,7 +371,7 @@ examples:
* `u1` can open the `inc` folder, but cannot see the contents, only upload new files to it * `u1` can open the `inc` folder, but cannot see the contents, only upload new files to it
* `u2` can browse it and move files *from* `/inc` into any folder where `u2` has write-access * `u2` can browse it and move files *from* `/inc` into any folder where `u2` has write-access
* make folder `/mnt/ss` available at `/i`, read-write for u1, get-only for everyone else, and enable filekeys: `-v /mnt/ss:i:rw,u1:g:c,fk=4` * make folder `/mnt/ss` available at `/i`, read-write for u1, get-only for everyone else, and enable filekeys: `-v /mnt/ss:i:rw,u1:g:c,fk=4`
* `c,fk=4` sets the `fk` (filekey) volflag to 4, meaning each file gets a 4-character accesskey * `c,fk=4` sets the `fk` ([filekey](#filekeys)) volflag to 4, meaning each file gets a 4-character accesskey
* `u1` can upload files, browse the folder, and see the generated filekeys * `u1` can upload files, browse the folder, and see the generated filekeys
* other users cannot browse the folder, but can access the files if they have the full file URL with the filekey * other users cannot browse the folder, but can access the files if they have the full file URL with the filekey
* replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it * replacing the `g` permission with `wg` would let anonymous users upload files, but not see the required filekey to access it
@@ -505,12 +517,20 @@ select which type of archive you want in the `[⚙️] config` tab:
| name | url-suffix | description | | name | url-suffix | description |
|--|--|--| |--|--|--|
| `tar` | `?tar` | plain gnutar, works great with `curl \| tar -xv` | | `tar` | `?tar` | plain gnutar, works great with `curl \| tar -xv` |
| `pax` | `?tar=pax` | pax-format tar, futureproof, not as fast |
| `tgz` | `?tar=gz` | gzip compressed gnu-tar (slow), for `curl \| tar -xvz` |
| `txz` | `?tar=xz` | gnu-tar with xz / lzma compression (v.slow) |
| `zip` | `?zip=utf8` | works everywhere, glitchy filenames on win7 and older | | `zip` | `?zip=utf8` | works everywhere, glitchy filenames on win7 and older |
| `zip_dos` | `?zip` | traditional cp437 (no unicode) to fix glitchy filenames | | `zip_dos` | `?zip` | traditional cp437 (no unicode) to fix glitchy filenames |
| `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software | | `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software |
* gzip default level is `3` (0=fast, 9=best), change with `?tar=gz:9`
* xz default level is `1` (0=fast, 9=best), change with `?tar=xz:9`
* bz2 default level is `2` (1=fast, 9=best), change with `?tar=bz2:9`
* hidden files (dotfiles) are excluded unless `-ed` * hidden files (dotfiles) are excluded unless `-ed`
* `up2k.db` and `dir.txt` is always excluded * `up2k.db` and `dir.txt` is always excluded
* bsdtar supports streaming unzipping: `curl foo?zip=utf8 | bsdtar -xv`
* good, because copyparty's zip is faster than tar on small files
* `zip_crc` will take longer to download since the server has to read each file twice * `zip_crc` will take longer to download since the server has to read each file twice
* this is only to support MS-DOS PKZIP v2.04g (october 1993) and older * this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
* how are you accessing copyparty actually * how are you accessing copyparty actually
@@ -522,6 +542,7 @@ you can also zip a selection of files or folders by clicking them in the browser
cool trick: download a folder by appending url-params `?tar&opus` 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` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus 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`
## uploading ## uploading
@@ -893,15 +914,13 @@ unsafe, slow, not recommended for wan, enable with `--smb` for read-only or `--
click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to see connection instructions for windows, linux, macos click the [connect](http://127.0.0.1:3923/?hc) button in the control-panel to see connection instructions for windows, linux, macos
dependencies: `python3 -m pip install --user -U impacket==0.10.0` dependencies: `python3 -m pip install --user -U impacket==0.11.0`
* newer versions of impacket will hopefully work just fine but there is monkeypatching so maybe not * newer versions of impacket will hopefully work just fine but there is monkeypatching so maybe not
some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance: some **BIG WARNINGS** specific to SMB/CIFS, in decreasing importance:
* not entirely confident that read-only is read-only * not entirely confident that read-only is read-only
* the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh) * the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [prisonparty](./bin/prisonparty.sh)
* account passwords work per-volume as expected, but account permissions are coalesced; all accounts have read-access to all volumes, and if a single account has write-access to some volume then all other accounts also do * account passwords work per-volume as expected, and so does account permissions (read/write/move/delete), but `--smbw` must be given to allow write-access from smb
* if no accounts have write-access to a specific volume, or if `--smbw` is not set, then writing to that volume from smb *should* be impossible
* will be fixed once [impacket v0.11.0](https://github.com/SecureAuthCorp/impacket/commit/d923c00f75d54b972bca573a211a82f09b55261a) is released
* [shadowing](#shadowing) probably works as expected but no guarantees * [shadowing](#shadowing) probably works as expected but no guarantees
and some minor issues, and some minor issues,
@@ -912,7 +931,7 @@ and some minor issues,
* win10 onwards does not allow connecting anonymously / without accounts * win10 onwards does not allow connecting anonymously / without accounts
* on windows, creating a new file through rightclick --> new --> textfile throws an error due to impacket limitations -- hit OK and F5 to get your file * on windows, creating a new file through rightclick --> new --> textfile throws an error due to impacket limitations -- hit OK and F5 to get your file
* python3 only * python3 only
* slow * slow (the builtin webdav support in windows is 5x faster, and rclone-webdav is 30x faster)
known client bugs: known client bugs:
* on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2 * on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2
@@ -1569,6 +1588,7 @@ below are some tweaks roughly ordered by usefulness:
* `-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)
* simultaneous downloads and uploads saturating a 20gbps connection * simultaneous downloads and uploads saturating a 20gbps connection
* if `-e2d` is enabled, `-j2` gives 4x performance for directory listings; `-j4` gives 16x
...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u ...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u
* using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others * using [pypy](https://www.pypy.org/) instead of [cpython](https://www.python.org/) *can* be 70% faster for some workloads, but slower for many others
@@ -1628,9 +1648,9 @@ safety profiles:
other misc notes: other misc notes:
* you can disable directory listings by giving permission `g` instead of `r`, only accepting direct URLs to files * you can disable directory listings by giving permission `g` instead of `r`, only accepting direct URLs to files
* combine this with volflag `c,fk` to generate filekeys (per-file accesskeys); users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404 * you may want [filekeys](#filekeys) to prevent filename bruteforcing
* the default filekey entropy is fairly small so give `--fk-salt` around 30 characters if you want filekeys longer than 16 chars * permission `h` instead of `r` makes copyparty behave like a traditional webserver with directory listing/index disabled, returning index.html instead
* permissions `wG` lets users upload files and receive their own filekeys, still without being able to see other uploads * compatibility with filekeys: index.html itself can be retrieved without the correct filekey, but all other files are protected
## gotchas ## gotchas
@@ -1658,6 +1678,17 @@ by default, except for `GET` and `HEAD` operations, all requests must either:
cors can be configured with `--acao` and `--acam`, or the protections entirely disabled with `--allow-csrf` cors can be configured with `--acao` and `--acam`, or the protections entirely disabled with `--allow-csrf`
## filekeys
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
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
## password hashing ## password hashing
you can hash passwords before putting them into config files / providing them as arguments; see `--help-pwhash` for all the details you can hash passwords before putting them into config files / providing them as arguments; see `--help-pwhash` for all the details
@@ -1734,7 +1765,7 @@ enable [thumbnails](#thumbnails) of...
* **JPEG XL pictures:** `pyvips` or `ffmpeg` * **JPEG XL pictures:** `pyvips` or `ffmpeg`
enable [smb](#smb-server) support (**not** recommended): enable [smb](#smb-server) support (**not** recommended):
* `impacket==0.10.0` * `impacket==0.11.0`
`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips` `pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`
@@ -1768,7 +1799,7 @@ can be convenient on machines where installing python is problematic, however is
* dangerous: [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) is compatible with [windows7](https://user-images.githubusercontent.com/241032/221445944-ae85d1f4-d351-4837-b130-82cab57d6cca.png), which means it uses an ancient copy of python (3.7.9) which cannot be upgraded and should never be exposed to the internet (LAN is fine) * dangerous: [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) is compatible with [windows7](https://user-images.githubusercontent.com/241032/221445944-ae85d1f4-d351-4837-b130-82cab57d6cca.png), which means it uses an ancient copy of python (3.7.9) which cannot be upgraded and should never be exposed to the internet (LAN is fine)
* dangerous and deprecated: [copyparty-winpe64.exe](https://github.com/9001/copyparty/releases/download/v1.6.8/copyparty-winpe64.exe) lets you [run copyparty in WinPE](https://user-images.githubusercontent.com/241032/205454984-e6b550df-3c49-486d-9267-1614078dd0dd.png) and is otherwise completely useless * dangerous and deprecated: [copyparty-winpe64.exe](https://github.com/9001/copyparty/releases/download/v1.8.7/copyparty-winpe64.exe) lets you [run copyparty in WinPE](https://user-images.githubusercontent.com/241032/205454984-e6b550df-3c49-486d-9267-1614078dd0dd.png) and is otherwise completely useless
meanwhile [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) instead relies on your system python which gives better performance and will stay safe as long as you keep your python install up-to-date meanwhile [copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py) instead relies on your system python which gives better performance and will stay safe as long as you keep your python install up-to-date

115
bin/hooks/msg-log.py Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python
# coding: utf-8
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
from __future__ import print_function, unicode_literals
import json
import os
import sys
import time
from datetime import datetime
"""
use copyparty as a dumb messaging server / guestbook thing;
initially contributed by @clach04 in https://github.com/9001/copyparty/issues/35 (thanks!)
Sample usage:
python copyparty-sfx.py --xm j,bin/hooks/msg-log.py
Where:
xm = execute on message-to-server-log
j = provide message information as json; not just the text - this script REQUIRES json
t10 = timeout and kill download after 10 secs
"""
# output filename
FILENAME = os.environ.get("COPYPARTY_MESSAGE_FILENAME", "") or "README.md"
# set True to write in descending order (newest message at top of file);
# note that this becomes very slow/expensive as the file gets bigger
DESCENDING = True
# the message template; the following parameters are provided by copyparty and can be referenced below:
# 'ap' = absolute filesystem path where the message was posted
# 'vp' = virtual path (URL 'path') where the message was posted
# 'mt' = 'at' = unix-timestamp when the message was posted
# 'datetime' = ISO-8601 time when the message was posted
# 'sz' = message size in bytes
# 'host' = the server hostname which the user was accessing (URL 'host')
# 'user' = username (if logged in), otherwise '*'
# 'txt' = the message text itself
# (uncomment the print(msg_info) to see if additional information has been introduced by copyparty since this was written)
TEMPLATE = """
🕒 %(datetime)s, 👤 %(user)s @ %(ip)s
%(txt)s
"""
def write_ascending(filepath, msg_text):
with open(filepath, "a", encoding="utf-8", errors="replace") as outfile:
outfile.write(msg_text)
def write_descending(filepath, msg_text):
lockpath = filepath + ".lock"
got_it = False
for _ in range(16):
try:
os.mkdir(lockpath)
got_it = True
break
except:
time.sleep(0.1)
continue
if not got_it:
return sys.exit(1)
try:
oldpath = filepath + ".old"
os.rename(filepath, oldpath)
with open(oldpath, "r", encoding="utf-8", errors="replace") as infile, open(
filepath, "w", encoding="utf-8", errors="replace"
) as outfile:
outfile.write(msg_text)
while True:
buf = infile.read(4096)
if not buf:
break
outfile.write(buf)
finally:
try:
os.unlink(oldpath)
except:
pass
os.rmdir(lockpath)
def main(argv=None):
if argv is None:
argv = sys.argv
msg_info = json.loads(sys.argv[1])
# print(msg_info)
dt = datetime.utcfromtimestamp(msg_info["at"])
msg_info["datetime"] = dt.strftime("%Y-%m-%d, %H:%M:%S")
msg_text = TEMPLATE % msg_info
filepath = os.path.join(msg_info["ap"], FILENAME)
if DESCENDING and os.path.exists(filepath):
write_descending(filepath, msg_text)
else:
write_ascending(filepath, msg_text)
print(msg_text)
if __name__ == "__main__":
main()

View File

@@ -7,6 +7,7 @@ set -e
# linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev} # linux/alpine: requires gcc g++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-dev py3-{wheel,pip} py3-numpy{,-dev}
# linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake # linux/debian: requires libav{codec,device,filter,format,resample,util}-dev {libfftw3,python3,libsndfile1}-dev python3-{numpy,pip} vamp-{plugin-sdk,examples} patchelf cmake
# linux/fedora: requires gcc gcc-c++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-devel python3-numpy vamp-plugin-sdk qm-vamp-plugins # linux/fedora: requires gcc gcc-c++ make cmake patchelf {python3,ffmpeg,fftw,libsndfile}-devel python3-numpy vamp-plugin-sdk qm-vamp-plugins
# linux/arch: requires gcc make cmake patchelf python3 ffmpeg fftw libsndfile python-{numpy,wheel,pip,setuptools}
# win64: requires msys2-mingw64 environment # win64: requires msys2-mingw64 environment
# macos: requires macports # macos: requires macports
# #
@@ -227,15 +228,16 @@ install_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 ] || {
printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n' printf '\033[33mcould not find the vamp-sdk, building from source\033[0m\n'
(dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2588/vamp-plugin-sdk-2.9.0.tar.gz) (dl_files yolo https://code.soundsoftware.ac.uk/attachments/download/2691/vamp-plugin-sdk-2.10.0.tar.gz)
sha512sum -c <( sha512sum -c <(
echo "7ef7f837d19a08048b059e0da408373a7964ced452b290fae40b85d6d70ca9000bcfb3302cd0b4dc76cf2a848528456f78c1ce1ee0c402228d812bd347b6983b -" echo "153b7f2fa01b77c65ad393ca0689742d66421017fd5931d216caa0fcf6909355fff74706fabbc062a3a04588a619c9b515a1dae00f21a57afd97902a355c48ed -"
) <vamp-plugin-sdk-2.9.0.tar.gz ) <vamp-plugin-sdk-2.10.0.tar.gz
tar -xf vamp-plugin-sdk-2.9.0.tar.gz tar -xf vamp-plugin-sdk-2.10.0.tar.gz
rm -- *.tar.gz rm -- *.tar.gz
ls -al ls -al
cd vamp-plugin-sdk-* cd vamp-plugin-sdk-*
./configure --prefix=$HOME/pe/vamp-sdk printf '%s\n' "int main(int argc, char **argv) { return 0; }" > host/vamp-simple-host.cpp
./configure --disable-programs --prefix=$HOME/pe/vamp-sdk
make -j1 install make -j1 install
} }
@@ -250,8 +252,9 @@ install_vamp() {
rm -- *.tar.gz rm -- *.tar.gz
cd beatroot-vamp-v1.0 cd beatroot-vamp-v1.0
[ -e ~/pe/vamp-sdk ] && [ -e ~/pe/vamp-sdk ] &&
sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux sed -ri 's`^(CFLAGS :=.*)`\1 -I'$HOME'/pe/vamp-sdk/include`' Makefile.linux ||
make -f Makefile.linux -j4 LDFLAGS=-L$HOME/pe/vamp-sdk/lib sed -ri 's`^(CFLAGS :=.*)`\1 -I/usr/include/vamp-sdk`' Makefile.linux
make -f Makefile.linux -j4 LDFLAGS="-L$HOME/pe/vamp-sdk/lib -L/usr/lib64"
# /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp # /home/ed/vamp /home/ed/.vamp /usr/local/lib/vamp
mkdir ~/vamp mkdir ~/vamp
cp -pv beatroot-vamp.* ~/vamp/ cp -pv beatroot-vamp.* ~/vamp/

View File

@@ -1177,7 +1177,7 @@ source file/folder selection uses rsync syntax, meaning that:
raise raise
if ar.cls: if ar.cls:
eprint("\x1b\x5b\x48\x1b\x5b\x32\x4a\x1b\x5b\x33\x4a", end="") eprint("\033[H\033[2J\033[3J", end="")
ctl = Ctl(ar) ctl = Ctl(ar)

View File

@@ -26,8 +26,8 @@ a {
<script> <script>
var a = document.getElementById('redir'), var a = document.getElementById('redir'),
proto = window.location.protocol.indexOf('https') === 0 ? 'https' : 'http', proto = location.protocol.indexOf('https') === 0 ? 'https' : 'http',
loc = window.location.hostname || '127.0.0.1', loc = location.hostname || '127.0.0.1',
port = a.getAttribute('href').split(':').pop().split('/')[0], port = a.getAttribute('href').split(':').pop().split('/')[0],
url = proto + '://' + loc + ':' + port + '/'; url = proto + '://' + loc + ':' + port + '/';
@@ -35,7 +35,7 @@ a.setAttribute('href', url);
document.getElementById('desc').innerHTML = 'redirecting to'; document.getElementById('desc').innerHTML = 'redirecting to';
setTimeout(function() { setTimeout(function() {
window.location.href = url; location.href = url;
}, 500); }, 500);
</script> </script>

View File

@@ -138,7 +138,8 @@ in {
"d" (delete): permanently delete files and folders "d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents "g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads "G" (upget): "get", but can see filekeys of their own uploads
"a" (upget): can see uploader IPs, config-reload "h" (html): "get", but folders return their index.html
"a" (admin): can see uploader IPs, config-reload
For example: "rwmd" For example: "rwmd"

View File

@@ -1,6 +1,6 @@
# Maintainer: icxes <dev.null@need.moe> # Maintainer: icxes <dev.null@need.moe>
pkgname=copyparty pkgname=copyparty
pkgver="1.9.2" pkgver="1.9.10"
pkgrel=1 pkgrel=1
pkgdesc="Portable file sharing hub" pkgdesc="Portable file sharing hub"
arch=("any") arch=("any")
@@ -20,7 +20,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=("d9509c0cf7d325124ce40637258574f62e628f06f74a61b07168fe4afa4ef489") sha256sums=("76e544b0d019ea35284abfd336b9bcd145dbe323509b1d7adba1902db8d8ad85")
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.9.2/copyparty-sfx.py", "url": "https://github.com/9001/copyparty/releases/download/v1.9.10/copyparty-sfx.py",
"version": "1.9.2", "version": "1.9.10",
"hash": "sha256-Y5sreREKvuHmKZy6kGk/oBHfKTq8b3SZGGWm9u2Iw9E=" "hash": "sha256-MDanRrJTmjYb95E81JG5NZXTPSq+2NQNYrUwNvR6Tk8="
} }

View File

@@ -0,0 +1,2 @@
rem run copyparty.exe on machines with busted environment variables
cmd /v /c "set TMP=\tmp && copyparty.exe"

View File

@@ -246,12 +246,7 @@ def get_fk_salt(cert_path) -> str:
with open(fp, "rb") as f: with open(fp, "rb") as f:
ret = f.read().strip() ret = f.read().strip()
except: except:
if os.path.exists(cert_path): ret = base64.b64encode(os.urandom(18))
zi = os.path.getmtime(cert_path)
ret = "{}".format(zi).encode("utf-8")
else:
ret = base64.b64encode(os.urandom(18))
with open(fp, "wb") as f: with open(fp, "wb") as f:
f.write(ret + b"\n") f.write(ret + b"\n")
@@ -492,6 +487,7 @@ def get_sects():
"d" (delete): permanently delete files and folders "d" (delete): permanently delete files and folders
"g" (get): download files, but cannot see folder contents "g" (get): download files, but cannot see folder contents
"G" (upget): "get", but can see filekeys of their own uploads "G" (upget): "get", but can see filekeys of their own uploads
"h" (html): "get", but folders return their index.html
"a" (admin): can see uploader IPs, config-reload "a" (admin): can see uploader IPs, config-reload
too many volflags to list here, see --help-flags too many volflags to list here, see --help-flags
@@ -807,7 +803,7 @@ def add_upload(ap):
ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)") ap2.add_argument("--use-fpool", action="store_true", help="force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...)")
ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem) (volflag=hardlink)") ap2.add_argument("--hardlink", action="store_true", help="prefer hardlinks instead of symlinks when possible (within same filesystem) (volflag=hardlink)")
ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=neversymlink)") ap2.add_argument("--never-symlink", action="store_true", help="do not fallback to symlinks when a hardlink cannot be made (volflag=neversymlink)")
ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes") ap2.add_argument("--no-dedup", action="store_true", help="disable symlink/hardlink creation; copy file contents instead (volflag=copydupes)")
ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)") ap2.add_argument("--no-dupe", action="store_true", help="reject duplicate files during upload; only matches within the same volume (volflag=nodupe)")
ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually") ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually")
ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)") ap2.add_argument("--rand", action="store_true", help="force randomized filenames, --nrand chars long (volflag=rand)")
@@ -825,7 +821,9 @@ def add_network(ap):
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6") ap2.add_argument("-i", metavar="IP", type=u, default="::", help="ip to bind (comma-sep.), default: all IPv4 and IPv6")
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)") ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 even if the NIC has routable IPs (breaks some mdns clients)") ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 even if the NIC has routable IPs (breaks some mdns clients)")
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd), [\033[32m2\033[0m]=cloudflare, [\033[32m3\033[0m]=nginx, [\033[32m-1\033[0m]=closest proxy") ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy")
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from (argument must be lowercase, but not the actual header)")
ap2.add_argument("--xff-src", metavar="IP", type=u, default="127., ::1", help="comma-separated list of trusted reverse-proxy IPs; only accept the real-ip header (--xff-hdr) if the incoming connection is from an IP starting with either of these. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using --xff-hdr=cf-connecting-ip (or similar)")
ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here (eg. /foo/bar)") ap2.add_argument("--rp-loc", metavar="PATH", type=u, default="", help="if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here (eg. /foo/bar)")
if ANYWIN: if ANYWIN:
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances") ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
@@ -931,12 +929,13 @@ def add_webdav(ap):
def add_smb(ap): def add_smb(ap):
ap2 = ap.add_argument_group('SMB/CIFS options') ap2 = ap.add_argument_group('SMB/CIFS options')
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is dangerous! Never expose to the internet. Account permissions are coalesced; if one account has write-access to a volume, then all accounts do.") ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.\n\033[1;31mWARNING:\033[0m this protocol is dangerous! Never expose to the internet!")
ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)") ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)") ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)")
ap2.add_argument("--smb-port", metavar="PORT", type=int, default=445, help="port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar") ap2.add_argument("--smb-port", metavar="PORT", type=int, default=445, help="port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar")
ap2.add_argument("--smb-nwa-1", action="store_true", help="disable impacket#1433 workaround (truncate directory listings to 64kB)") ap2.add_argument("--smb-nwa-1", action="store_true", help="disable impacket#1433 workaround (truncate directory listings to 64kB)")
ap2.add_argument("--smb-nwa-2", action="store_true", help="disable impacket workaround for filecopy globs") ap2.add_argument("--smb-nwa-2", action="store_true", help="disable impacket workaround for filecopy globs")
ap2.add_argument("--smba", action="store_true", help="small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does)")
ap2.add_argument("--smbv", action="store_true", help="verbose") ap2.add_argument("--smbv", action="store_true", help="verbose")
ap2.add_argument("--smbvv", action="store_true", help="verboser") ap2.add_argument("--smbvv", action="store_true", help="verboser")
ap2.add_argument("--smbvvv", action="store_true", help="verbosest") ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
@@ -989,6 +988,7 @@ def add_optouts(ap):
ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI") ap2.add_argument("-nid", action="store_true", help="no info disk-usage -- don't show in UI")
ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI") ap2.add_argument("-nb", action="store_true", help="no powered-by-copyparty branding in UI")
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar") ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)") ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)")
@@ -1013,8 +1013,8 @@ def add_safety(ap):
ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month") ap2.add_argument("--ban-403", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 403's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week, [\033[32m43200\033[0m]=month")
ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (422 is server fuzzing, invalid POSTs and so)") ap2.add_argument("--ban-422", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m 422's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes (422 is server fuzzing, invalid POSTs and so)")
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 (decent replacement for --ban-404 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 (decent replacement for --ban-404 if that can't be used)")
ap2.add_argument("--sus-urls", metavar="REGEX", 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="REGEX", 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("--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 MIN 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 MIN 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 B 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 B 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")
@@ -1045,7 +1045,8 @@ def add_logging(ap):
ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR") ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR")
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR") ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup") ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
ap2.add_argument("--log-tdec", type=int, default=3, help="timestamp resolution / number of timestamp decimals") ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log passphrase of failed login attempts: 0=terse, 1=plaintext, 2=hashed")
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs") ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling") ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header") ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
@@ -1140,7 +1141,7 @@ def add_db_metadata(ap):
def add_ui(ap, retry): def add_ui(ap, retry):
ap2 = ap.add_argument_group('ui options') ap2 = ap.add_argument_group('ui options')
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)") ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language") ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: eng nor")
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use") ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed") ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching REGEX in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)") ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching REGEX in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
@@ -1149,7 +1150,7 @@ def add_ui(ap, retry):
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include") ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include") ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages") ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages")
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI)") ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext") ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)") ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents") ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
@@ -1196,7 +1197,10 @@ def run_argparse(
fk_salt = get_fk_salt(cert_path) fk_salt = get_fk_salt(cert_path)
ah_salt = get_ah_salt() ah_salt = get_ah_salt()
hcores = min(CORES, 4) # optimal on py3.11 @ r5-4500U # alpine peaks at 5 threads for some reason,
# all others scale past that (but try to avoid SMT),
# 5 should be plenty anyways (3 GiB/s on most machines)
hcores = min(CORES, 5 if CORES > 8 else 4)
tty = os.environ.get("TERM", "").lower() == "linux" tty = os.environ.get("TERM", "").lower() == "linux"
@@ -1397,7 +1401,7 @@ def main(argv: Optional[list[str]] = None) -> None:
if re.match("c[^,]", opt): if re.match("c[^,]", opt):
mod = True mod = True
na.append("c," + opt[1:]) na.append("c," + opt[1:])
elif re.sub("^[rwmdgGa]*", "", opt) and "," not in opt: elif re.sub("^[rwmdgGha]*", "", opt) and "," not in opt:
mod = True mod = True
perm = opt[0] perm = opt[0]
na.append(perm + "," + opt[1:]) na.append(perm + "," + opt[1:])

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (1, 9, 3) VERSION = (1, 9, 11)
CODENAME = "prometheable" CODENAME = "prometheable"
BUILD_DT = (2023, 8, 31) BUILD_DT = (2023, 10, 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

@@ -12,7 +12,7 @@ import threading
import time import time
from datetime import datetime from datetime import datetime
from .__init__ import ANYWIN, TYPE_CHECKING, WINDOWS from .__init__ import ANYWIN, E, TYPE_CHECKING, WINDOWS
from .bos import bos from .bos import bos
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
from .pwhash import PWHash from .pwhash import PWHash
@@ -67,6 +67,7 @@ class AXS(object):
udel: Optional[Union[list[str], set[str]]] = None, udel: Optional[Union[list[str], set[str]]] = None,
uget: Optional[Union[list[str], set[str]]] = None, uget: Optional[Union[list[str], set[str]]] = None,
upget: Optional[Union[list[str], set[str]]] = None, upget: Optional[Union[list[str], set[str]]] = None,
uhtml: Optional[Union[list[str], set[str]]] = None,
uadmin: Optional[Union[list[str], set[str]]] = None, uadmin: Optional[Union[list[str], set[str]]] = None,
) -> None: ) -> None:
self.uread: set[str] = set(uread or []) self.uread: set[str] = set(uread or [])
@@ -75,10 +76,11 @@ class AXS(object):
self.udel: set[str] = set(udel or []) self.udel: set[str] = set(udel or [])
self.uget: set[str] = set(uget or []) self.uget: set[str] = set(uget or [])
self.upget: set[str] = set(upget or []) self.upget: set[str] = set(upget or [])
self.uhtml: set[str] = set(uhtml or [])
self.uadmin: set[str] = set(uadmin or []) self.uadmin: set[str] = set(uadmin or [])
def __repr__(self) -> str: def __repr__(self) -> str:
ks = "uread uwrite umove udel uget upget uadmin".split() ks = "uread uwrite umove udel uget upget uhtml uadmin".split()
return "AXS(%s)" % (", ".join("%s=%r" % (k, self.__dict__[k]) for k in ks),) return "AXS(%s)" % (", ".join("%s=%r" % (k, self.__dict__[k]) for k in ks),)
@@ -329,6 +331,7 @@ class VFS(object):
self.adel: dict[str, list[str]] = {} self.adel: dict[str, list[str]] = {}
self.aget: dict[str, list[str]] = {} self.aget: dict[str, list[str]] = {}
self.apget: dict[str, list[str]] = {} self.apget: dict[str, list[str]] = {}
self.ahtml: dict[str, list[str]] = {}
self.aadmin: dict[str, list[str]] = {} self.aadmin: dict[str, list[str]] = {}
if realpath: if realpath:
@@ -456,6 +459,7 @@ class VFS(object):
uname in c.upget or "*" in c.upget, uname in c.upget or "*" in c.upget,
uname in c.uadmin or "*" in c.uadmin, uname in c.uadmin or "*" in c.uadmin,
) )
# skip uhtml because it's rarely needed
def get( def get(
self, self,
@@ -488,7 +492,7 @@ class VFS(object):
(will_get, c.uget, "get"), (will_get, c.uget, "get"),
]: ]:
if req and (uname not in d and "*" not in d) and uname != LEELOO_DALLAS: if req and (uname not in d and "*" not in d) and uname != LEELOO_DALLAS:
if self.log and err != 999: if vpath != cvpath and vpath != "." and self.log:
ap = vn.canonical(rem) ap = vn.canonical(rem)
t = "{} has no {} in [{}] => [{}] => [{}]" t = "{} has no {} in [{}] => [{}] => [{}]"
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6) self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)
@@ -955,7 +959,7 @@ class AuthSrv(object):
try: try:
self._l(ln, 5, "volume access config:") self._l(ln, 5, "volume access config:")
sk, sv = ln.split(":") sk, sv = ln.split(":")
if re.sub("[rwmdgGa]", "", sk) or not sk: if re.sub("[rwmdgGha]", "", sk) or not sk:
err = "invalid accs permissions list; " err = "invalid accs permissions list; "
raise Exception(err) raise Exception(err)
if " " in re.sub(", *", "", sv).strip(): if " " in re.sub(", *", "", sv).strip():
@@ -964,7 +968,7 @@ class AuthSrv(object):
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp]) self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
continue continue
except: except:
err += "accs entries must be 'rwmdgGa: user1, user2, ...'" err += "accs entries must be 'rwmdgGha: user1, user2, ...'"
raise Exception(err + SBADCFG) raise Exception(err + SBADCFG)
if cat == catf: if cat == catf:
@@ -1000,7 +1004,7 @@ class AuthSrv(object):
def _read_vol_str( def _read_vol_str(
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any] self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
) -> None: ) -> None:
if lvl.strip("crwmdgGa"): if lvl.strip("crwmdgGha"):
raise Exception("invalid volflag: {},{}".format(lvl, uname)) raise Exception("invalid volflag: {},{}".format(lvl, uname))
if lvl == "c": if lvl == "c":
@@ -1029,10 +1033,12 @@ class AuthSrv(object):
("w", axs.uwrite), ("w", axs.uwrite),
("m", axs.umove), ("m", axs.umove),
("d", axs.udel), ("d", axs.udel),
("a", axs.uadmin),
("h", axs.uhtml),
("h", axs.uget),
("g", axs.uget), ("g", axs.uget),
("G", axs.uget), ("G", axs.uget),
("G", axs.upget), ("G", axs.upget),
("a", axs.uadmin),
]: # b bb bbb ]: # b bb bbb
if ch in lvl: if ch in lvl:
if un == "*": if un == "*":
@@ -1105,7 +1111,7 @@ class AuthSrv(object):
if self.args.v: if self.args.v:
# list of src:dst:permset:permset:... # list of src:dst:permset:permset:...
# permset is <rwmdgGa>[,username][,username] or <c>,<flag>[=args] # permset is <rwmdgGha>[,username][,username] or <c>,<flag>[=args]
for v_str in self.args.v: for v_str in self.args.v:
m = re_vol.match(v_str) m = re_vol.match(v_str)
if not m: if not m:
@@ -1194,7 +1200,7 @@ class AuthSrv(object):
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True) vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
vol.root = vfs vol.root = vfs
for perm in "read write move del get pget admin".split(): for perm in "read write move del get pget html admin".split():
axs_key = "u" + perm axs_key = "u" + perm
unames = ["*"] + list(acct.keys()) unames = ["*"] + list(acct.keys())
umap: dict[str, list[str]] = {x: [] for x in unames} umap: dict[str, list[str]] = {x: [] for x in unames}
@@ -1216,6 +1222,7 @@ class AuthSrv(object):
axs.udel, axs.udel,
axs.uget, axs.uget,
axs.upget, axs.upget,
axs.uhtml,
axs.uadmin, axs.uadmin,
]: ]:
for usr in d: for usr in d:
@@ -1383,6 +1390,9 @@ class AuthSrv(object):
have_fk = False have_fk = False
for vol in vfs.all_vols.values(): for vol in vfs.all_vols.values():
fk = vol.flags.get("fk") fk = vol.flags.get("fk")
fka = vol.flags.get("fka")
if fka and not fk:
fk = fka
if fk: if fk:
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
@@ -1390,6 +1400,12 @@ class AuthSrv(object):
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))
fk_len = len(self.args.fk_salt)
if have_fk and fk_len < 14:
t = "WARNING: filekeys are enabled, but the salt is only %d chars long; %d or longer is recommended. Either specify a stronger salt using --fk-salt or delete this file and restart copyparty: %s"
zs = os.path.join(E.cfg, "fk-salt.txt")
self.log(t % (fk_len, 16, zs), 3)
for vol in vfs.all_vols.values(): for vol in vfs.all_vols.values():
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags: if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
vol.flags["gz"] = False # def.pk vol.flags["gz"] = False # def.pk
@@ -1637,6 +1653,7 @@ class AuthSrv(object):
["delete", "udel"], ["delete", "udel"],
[" get", "uget"], [" get", "uget"],
[" upget", "upget"], [" upget", "upget"],
[" html", "uhtml"],
["uadmin", "uadmin"], ["uadmin", "uadmin"],
]: ]:
u = list(sorted(getattr(zv.axs, attr))) u = list(sorted(getattr(zv.axs, attr)))
@@ -1675,7 +1692,7 @@ class AuthSrv(object):
self.log(t.format(zv.realpath), c=1) self.log(t.format(zv.realpath), c=1)
try: try:
zv, _ = vfs.get("/", "*", False, True, err=999) zv, _ = vfs.get("", "*", False, True, err=999)
if self.warn_anonwrite and os.getcwd() == zv.realpath: if self.warn_anonwrite and os.getcwd() == zv.realpath:
t = "anyone can write to the current directory: {}\n" t = "anyone can write to the current directory: {}\n"
self.log(t.format(zv.realpath), c=1) self.log(t.format(zv.realpath), c=1)
@@ -1804,6 +1821,7 @@ class AuthSrv(object):
vc.udel, vc.udel,
vc.uget, vc.uget,
vc.upget, vc.upget,
vc.uhtml,
vc.uadmin, vc.uadmin,
] ]
self.log(t.format(*vs)) self.log(t.format(*vs))
@@ -1945,6 +1963,7 @@ class AuthSrv(object):
"d": "udel", "d": "udel",
"g": "uget", "g": "uget",
"G": "upget", "G": "upget",
"h": "uhtml",
"a": "uadmin", "a": "uadmin",
} }
users = {} users = {}
@@ -2148,7 +2167,7 @@ def upgrade_cfg_fmt(
else: else:
sn = sn.replace(",", ", ") sn = sn.replace(",", ", ")
ret.append(" " + sn) ret.append(" " + sn)
elif sn[:1] in "rwmdgGa": elif sn[:1] in "rwmdgGha":
if cat != catx: if cat != catx:
cat = catx cat = catx
ret.append(cat) ret.append(cat)

View File

@@ -69,7 +69,7 @@ class BrokerMp(object):
while procs: while procs:
if procs[-1].is_alive(): if procs[-1].is_alive():
time.sleep(0.1) time.sleep(0.05)
continue continue
procs.pop() procs.pop()

View File

@@ -62,6 +62,8 @@ permdescs = {
"d": "delete; permanently delete files and folders", "d": "delete; permanently delete files and folders",
"g": "get; download files, but cannot see folder contents", "g": "get; download files, but cannot see folder contents",
"G": 'upget; same as "g" but can see filekeys of their own uploads', "G": 'upget; same as "g" but can see filekeys of their own uploads',
"h": 'html; same as "g" but folders return their index.html',
"a": "admin; can see uploader IPs, config-reload",
} }
@@ -160,7 +162,8 @@ flagcats = {
"nohtml": "return html and markdown as text/html", "nohtml": "return html and markdown as text/html",
}, },
"others": { "others": {
"fk=8": 'generates per-file accesskeys,\nwhich will then be required at the "g" permission', "fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
"fka=8": 'generates slightly weaker per-file accesskeys,\nwhich are then required at the "g" permission;\nnot affected by filesize or inode numbers',
"davauth": "ask webdav clients to login for all folders", "davauth": "ask webdav clients to login for all folders",
"davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)", "davrt": "show lastmod time of symlink destination, not the link itself\n(note: this option is always enabled for recursive listings)",
}, },

View File

@@ -7,6 +7,7 @@ import calendar
import copy import copy
import errno import errno
import gzip import gzip
import hashlib
import itertools import itertools
import json import json
import os import os
@@ -131,6 +132,7 @@ class HttpCli(object):
self.mode = " " self.mode = " "
self.req = " " self.req = " "
self.http_ver = " " self.http_ver = " "
self.hint = ""
self.host = " " self.host = " "
self.ua = " " self.ua = " "
self.is_rclone = False self.is_rclone = False
@@ -142,6 +144,7 @@ class HttpCli(object):
self.rem = " " self.rem = " "
self.vpath = " " self.vpath = " "
self.vpaths = " " self.vpaths = " "
self.trailing_slash = True
self.uname = " " self.uname = " "
self.pw = " " self.pw = " "
self.rvol = [" "] self.rvol = [" "]
@@ -159,22 +162,17 @@ class HttpCli(object):
self.can_get = False self.can_get = False
self.can_upget = False self.can_upget = False
self.can_admin = False self.can_admin = False
self.out_headerlist: list[tuple[str, str]] = []
self.out_headers: dict[str, str] = {}
self.html_head = " "
# post # post
self.parser: Optional[MultipartParser] = None self.parser: Optional[MultipartParser] = None
# end placeholders # end placeholders
self.bufsz = 1024 * 32 self.bufsz = 1024 * 32
self.hint = ""
self.trailing_slash = True
self.out_headerlist: list[tuple[str, str]] = []
self.out_headers = {
"Vary": "Origin, PW, Cookie",
"Cache-Control": "no-store, max-age=0",
}
h = self.args.html_head h = self.args.html_head
if self.args.no_robots: if self.args.no_robots:
h = META_NOBOTS + (("\n" + h) if h else "") h = META_NOBOTS + (("\n" + h) if h else "")
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
self.html_head = h self.html_head = h
def log(self, msg: str, c: Union[int, str] = 0) -> None: def log(self, msg: str, c: Union[int, str] = 0) -> None:
@@ -202,8 +200,10 @@ class HttpCli(object):
if rem.startswith("/") or rem.startswith("../") or "/../" in rem: if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
raise Exception("that was close") raise Exception("that was close")
def _gen_fk(self, salt: str, fspath: str, fsize: int, inode: int) -> str: def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str:
return gen_filekey_dbg(salt, fspath, fsize, inode, self.log, self.args.log_fk) return gen_filekey_dbg(
alg, salt, fspath, fsize, inode, self.log, self.args.log_fk
)
def j2s(self, name: str, **ka: Any) -> str: def j2s(self, name: str, **ka: Any) -> str:
tpl = self.conn.hsrv.j2[name] tpl = self.conn.hsrv.j2[name]
@@ -221,10 +221,12 @@ class HttpCli(object):
def run(self) -> bool: def run(self) -> bool:
"""returns true if connection can be reused""" """returns true if connection can be reused"""
self.keepalive = False self.out_headers = {
self.is_https = False "Vary": "Origin, PW, Cookie",
self.headers = {} "Cache-Control": "no-store, max-age=0",
self.hint = "" }
if self.args.no_robots:
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
if self.is_banned(): if self.is_banned():
return False return False
@@ -262,9 +264,9 @@ class HttpCli(object):
h = {"WWW-Authenticate": 'Basic realm="a"'} if ex.code == 401 else {} h = {"WWW-Authenticate": 'Basic realm="a"'} if ex.code == 401 else {}
try: try:
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True) self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
return self.keepalive
except: except:
return False pass
return False
self.ua = self.headers.get("user-agent", "") self.ua = self.headers.get("user-agent", "")
self.is_rclone = self.ua.startswith("rclone/") self.is_rclone = self.ua.startswith("rclone/")
@@ -283,22 +285,35 @@ class HttpCli(object):
n = self.args.rproxy n = self.args.rproxy
if n: if n:
zso = self.headers.get("x-forwarded-for") zso = self.headers.get(self.args.xff_hdr)
if zso and self.conn.addr[0] in ["127.0.0.1", "::1"]: if zso:
if n > 0: if n > 0:
n -= 1 n -= 1
zsl = zso.split(",") zsl = zso.split(",")
try: try:
self.ip = zsl[n].strip() cli_ip = zsl[n].strip()
except: except:
self.ip = zsl[0].strip() cli_ip = zsl[0].strip()
t = "rproxy={} oob x-fwd {}" t = "rproxy={} oob x-fwd {}"
self.log(t.format(self.args.rproxy, zso), c=3) self.log(t.format(self.args.rproxy, zso), c=3)
self.log_src = self.conn.set_rproxy(self.ip) pip = self.conn.addr[0]
self.is_vproxied = bool(self.args.R) if self.args.xff_re and not self.args.xff_re.match(pip):
self.host = self.headers.get("x-forwarded-host") or self.host t = 'got header "%s" from untrusted source "%s" claiming the true client ip is "%s" (raw value: "%s"); if you trust this, you must allowlist this proxy with "--xff-src=%s"'
if self.headers.get("cf-connecting-ip"):
t += " Alternatively, if you are behind cloudflare, it is better to specify these two instead: --xff-hdr=cf-connecting-ip --xff-src=any"
zs = (
".".join(pip.split(".")[:2]) + "."
if "." in pip
else ":".join(pip.split(":")[:4]) + ":"
)
self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs), 3)
else:
self.ip = cli_ip
self.is_vproxied = bool(self.args.R)
self.log_src = self.conn.set_rproxy(self.ip)
self.host = self.headers.get("x-forwarded-host") or self.host
if self.is_banned(): if self.is_banned():
return False return False
@@ -629,7 +644,17 @@ class HttpCli(object):
headers: Optional[dict[str, str]] = None, headers: Optional[dict[str, str]] = None,
volsan: bool = False, volsan: bool = False,
) -> bytes: ) -> bytes:
if status > 400 and status in (403, 404, 422): if (
status > 400
and status in (403, 404, 422)
and (
status != 422
or (
not body.startswith(b"<pre>partial upload exists")
and not body.startswith(b"<pre>source file busy")
)
)
):
if status == 404: if status == 404:
g = self.conn.hsrv.g404 g = self.conn.hsrv.g404
elif status == 403: elif status == 403:
@@ -1689,7 +1714,9 @@ class HttpCli(object):
vsuf = "" vsuf = ""
if (self.can_read or self.can_upget) and "fk" in vfs.flags: if (self.can_read or self.can_upget) and "fk" in vfs.flags:
alg = 2 if "fka" in vfs.flags else 1
vsuf = "?k=" + self.gen_fk( vsuf = "?k=" + self.gen_fk(
alg,
self.args.fk_salt, self.args.fk_salt,
path, path,
post_sz, post_sz,
@@ -2096,7 +2123,15 @@ class HttpCli(object):
msg = "login ok" msg = "login ok"
dur = int(60 * 60 * self.args.logout) dur = int(60 * 60 * self.args.logout)
else: else:
self.log("invalid password: {}".format(pwd), 3) logpwd = pwd
if self.args.log_badpwd == 0:
logpwd = ""
elif self.args.log_badpwd == 2:
zb = hashlib.sha512(pwd.encode("utf-8", "replace")).digest()
logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
self.log("invalid password: {}".format(logpwd), 3)
g = self.conn.hsrv.gpwd g = self.conn.hsrv.gpwd
if g.lim: if g.lim:
bonk, ip = g.bonk(self.ip, pwd) bonk, ip = g.bonk(self.ip, pwd)
@@ -2186,7 +2221,8 @@ class HttpCli(object):
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True) vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
self._assert_safe_rem(rem) self._assert_safe_rem(rem)
if not new_file.endswith(".md"): ext = "" if "." not in new_file else new_file.split(".")[-1]
if not ext or len(ext) > 5 or not self.can_delete:
new_file += ".md" new_file += ".md"
sanitized = sanitize_fn(new_file, "", []) sanitized = sanitize_fn(new_file, "", [])
@@ -2421,7 +2457,9 @@ class HttpCli(object):
for sz, sha_hex, sha_b64, ofn, lfn, ap in files: for sz, sha_hex, sha_b64, ofn, lfn, ap in files:
vsuf = "" vsuf = ""
if (self.can_read or self.can_upget) and "fk" in vfs.flags: if (self.can_read or self.can_upget) and "fk" in vfs.flags:
alg = 2 if "fka" in vfs.flags else 1
vsuf = "?k=" + self.gen_fk( vsuf = "?k=" + self.gen_fk(
alg,
self.args.fk_salt, self.args.fk_salt,
ap, ap,
sz, sz,
@@ -2519,7 +2557,7 @@ class HttpCli(object):
fp = os.path.join(fp, fn) fp = os.path.join(fp, fn)
rem = "{}/{}".format(rp, fn).strip("/") rem = "{}/{}".format(rp, fn).strip("/")
if not rem.endswith(".md"): if not rem.endswith(".md") and not self.can_delete:
raise Pebkac(400, "only markdown pls") raise Pebkac(400, "only markdown pls")
if nullwrite: if nullwrite:
@@ -2570,7 +2608,8 @@ class HttpCli(object):
return True return True
mdir, mfile = os.path.split(fp) mdir, mfile = os.path.split(fp)
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod) fname, fext = mfile.rsplit(".", 1) if "." in mfile else (mfile, "md")
mfile2 = "{}.{:.3f}.{}".format(fname, srv_lastmod, fext)
try: try:
dp = os.path.join(mdir, ".hist") dp = os.path.join(mdir, ".hist")
bos.mkdir(dp) bos.mkdir(dp)
@@ -2895,12 +2934,26 @@ class HttpCli(object):
logmsg = "{:4} {} ".format("", self.req) logmsg = "{:4} {} ".format("", self.req)
self.keepalive = False self.keepalive = False
cancmp = not self.args.no_tarcmp
if fmt == "tar": if fmt == "tar":
mime = "application/x-tar"
packer: Type[StreamArc] = StreamTar packer: Type[StreamArc] = StreamTar
if cancmp and "gz" in uarg:
mime = "application/gzip"
ext = "tar.gz"
elif cancmp and "bz2" in uarg:
mime = "application/x-bzip"
ext = "tar.bz2"
elif cancmp and "xz" in uarg:
mime = "application/x-xz"
ext = "tar.xz"
else:
mime = "application/x-tar"
ext = "tar"
else: else:
mime = "application/zip" mime = "application/zip"
packer = StreamZip packer = StreamZip
ext = "zip"
fn = items[0] if items and items[0] else self.vpath fn = items[0] if items and items[0] else self.vpath
if fn: if fn:
@@ -2925,7 +2978,7 @@ class HttpCli(object):
ufn = b"".join(zbl).decode("ascii") ufn = b"".join(zbl).decode("ascii")
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}" cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
cdis = cdis.format(afn, fmt, ufn, fmt) cdis = cdis.format(afn, ext, ufn, ext)
self.log(cdis) self.log(cdis)
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis}) self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
@@ -2943,7 +2996,13 @@ class HttpCli(object):
self.log("transcoding to [{}]".format(cfmt)) self.log("transcoding to [{}]".format(cfmt))
fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt) fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
bgen = packer(self.log, fgen, utf8="utf" in uarg, pre_crc="crc" in uarg) bgen = packer(
self.log,
fgen,
utf8="utf" in uarg,
pre_crc="crc" in uarg,
cmp=uarg if cancmp or uarg == "pax" else "",
)
bsent = 0 bsent = 0
for buf in bgen.gen(): for buf in bgen.gen():
if not buf: if not buf:
@@ -3367,7 +3426,7 @@ class HttpCli(object):
t0 = time.time() t0 = time.time()
lim = time.time() - self.args.unpost lim = time.time() - self.args.unpost
fk_vols = { fk_vols = {
vol: vol.flags["fk"] vol: (vol.flags["fk"], 2 if "fka" in vol.flags else 1)
for vp, vol in self.asrv.vfs.all_vols.items() for vp, vol in self.asrv.vfs.all_vols.items()
if "fk" in vol.flags and (vp in self.rvol or vp in self.upvol) if "fk" in vol.flags and (vp in self.rvol or vp in self.upvol)
} }
@@ -3376,7 +3435,7 @@ class HttpCli(object):
if not cur: if not cur:
continue continue
nfk = fk_vols.get(vol, 0) nfk, fk_alg = fk_vols.get(vol) or (0, 0)
q = "select sz, rd, fn, at from up where ip=? and at>?" q = "select sz, rd, fn, at from up where ip=? and at>?"
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)): for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
@@ -3387,6 +3446,7 @@ class HttpCli(object):
rv = {"vp": quotep(vp), "sz": sz, "at": at, "nfk": nfk} rv = {"vp": quotep(vp), "sz": sz, "at": at, "nfk": nfk}
if nfk: if nfk:
rv["ap"] = vol.canonical(vjoin(rd, fn)) rv["ap"] = vol.canonical(vjoin(rd, fn))
rv["fk_alg"] = fk_alg
ret.append(rv) ret.append(rv)
if len(ret) > 3000: if len(ret) > 3000:
@@ -3400,6 +3460,7 @@ class HttpCli(object):
if not nfk: if not nfk:
continue continue
alg = rv.pop("fk_alg")
ap = rv.pop("ap") ap = rv.pop("ap")
try: try:
st = bos.stat(ap) st = bos.stat(ap)
@@ -3407,7 +3468,7 @@ class HttpCli(object):
continue continue
fk = self.gen_fk( fk = self.gen_fk(
self.args.fk_salt, ap, st.st_size, 0 if ANYWIN else st.st_ino alg, self.args.fk_salt, ap, st.st_size, 0 if ANYWIN else st.st_ino
) )
rv["vp"] += "?k=" + fk[:nfk] rv["vp"] += "?k=" + fk[:nfk]
@@ -3587,6 +3648,7 @@ class HttpCli(object):
self.out_headers.pop("X-Robots-Tag", None) self.out_headers.pop("X-Robots-Tag", None)
is_dir = stat.S_ISDIR(st.st_mode) is_dir = stat.S_ISDIR(st.st_mode)
fk_pass = False
icur = None icur = None
if is_dir and (e2t or e2d): if is_dir and (e2t or e2d):
idx = self.conn.get_u2idx() idx = self.conn.get_u2idx()
@@ -3635,10 +3697,45 @@ class HttpCli(object):
return self.tx_ico(rem) return self.tx_ico(rem)
elif self.can_get and self.avn:
axs = self.avn.axs
if self.uname not in axs.uhtml and "*" not in axs.uhtml:
pass
elif is_dir:
for fn in ("index.htm", "index.html"):
ap2 = os.path.join(abspath, fn)
try:
st2 = bos.stat(ap2)
except:
continue
# might as well be extra careful
if not stat.S_ISREG(st2.st_mode):
continue
if not self.trailing_slash:
return self.redirect(
self.vpath + "/", flavor="redirecting to", use302=True
)
fk_pass = True
is_dir = False
rem = vjoin(rem, fn)
vrem = vjoin(vrem, fn)
abspath = ap2
break
elif self.vpath.rsplit("/", 1)[1] in ("index.htm", "index.html"):
fk_pass = True
if not is_dir and (self.can_read or self.can_get): if not is_dir and (self.can_read or self.can_get):
if not self.can_read and "fk" in vn.flags: if not self.can_read and not fk_pass and "fk" in vn.flags:
alg = 2 if "fka" in vn.flags else 1
correct = self.gen_fk( correct = self.gen_fk(
self.args.fk_salt, abspath, st.st_size, 0 if ANYWIN else st.st_ino alg,
self.args.fk_salt,
abspath,
st.st_size,
0 if ANYWIN else st.st_ino,
)[: vn.flags["fk"]] )[: vn.flags["fk"]]
got = self.uparam.get("k") got = self.uparam.get("k")
if got != correct: if got != correct:
@@ -3646,7 +3743,7 @@ class HttpCli(object):
return self.tx_404() return self.tx_404()
if ( if (
abspath.endswith(".md") (abspath.endswith(".md") or self.can_delete)
and "nohtml" not in vn.flags and "nohtml" not in vn.flags
and ( and (
"v" in self.uparam "v" in self.uparam
@@ -3780,10 +3877,14 @@ class HttpCli(object):
} }
if self.args.js_browser: if self.args.js_browser:
j2a["js"] = self.args.js_browser zs = self.args.js_browser
zs += "&" if "?" in zs else "?"
j2a["js"] = zs
if self.args.css_browser: if self.args.css_browser:
j2a["css"] = self.args.css_browser zs = self.args.css_browser
zs += "&" if "?" in zs else "?"
j2a["css"] = zs
if not self.conn.hsrv.prism: if not self.conn.hsrv.prism:
j2a["no_prism"] = True j2a["no_prism"] = True
@@ -3842,6 +3943,7 @@ class HttpCli(object):
ls_names = exclude_dotfiles(ls_names) ls_names = exclude_dotfiles(ls_names)
add_fk = vn.flags.get("fk") add_fk = vn.flags.get("fk")
fk_alg = 2 if "fka" in vn.flags else 1
dirs = [] dirs = []
files = [] files = []
@@ -3898,11 +4000,15 @@ class HttpCli(object):
except: except:
ext = "%" ext = "%"
if add_fk: if add_fk and not is_dir:
href = "%s?k=%s" % ( href = "%s?k=%s" % (
quotep(href), quotep(href),
self.gen_fk( self.gen_fk(
self.args.fk_salt, fspath, sz, 0 if ANYWIN else inf.st_ino fk_alg,
self.args.fk_salt,
fspath,
sz,
0 if ANYWIN else inf.st_ino,
)[:add_fk], )[:add_fk],
) )
else: else:

View File

@@ -8,7 +8,7 @@ import shutil
import subprocess as sp import subprocess as sp
import sys import sys
from .__init__ import EXE, PY2, WINDOWS, E, unicode from .__init__ import ANYWIN, EXE, PY2, WINDOWS, E, unicode
from .bos import bos from .bos import bos
from .util import ( from .util import (
FFMPEG_URL, FFMPEG_URL,
@@ -29,6 +29,9 @@ if True: # pylint: disable=using-constant-test
def have_ff(scmd: str) -> bool: def have_ff(scmd: str) -> bool:
if ANYWIN:
scmd += ".exe"
if PY2: if PY2:
print("# checking {}".format(scmd)) print("# checking {}".format(scmd))
acmd = (scmd + " -version").encode("ascii").split(b" ") acmd = (scmd + " -version").encode("ascii").split(b" ")

View File

@@ -32,6 +32,8 @@ class SMB(object):
self.asrv = hub.asrv self.asrv = hub.asrv
self.log = hub.log self.log = hub.log
self.files: dict[int, tuple[float, str]] = {} self.files: dict[int, tuple[float, str]] = {}
self.noacc = self.args.smba
self.accs = not self.args.smba
lg.setLevel(logging.DEBUG if self.args.smbvvv else logging.INFO) lg.setLevel(logging.DEBUG if self.args.smbvvv else logging.INFO)
for x in ["impacket", "impacket.smbserver"]: for x in ["impacket", "impacket.smbserver"]:
@@ -94,6 +96,14 @@ class SMB(object):
port = int(self.args.smb_port) port = int(self.args.smb_port)
srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port) srv = smbserver.SimpleSMBServer(listenAddress=ip, listenPort=port)
try:
if self.accs:
srv.setAuthCallback(self._auth_cb)
except:
self.accs = False
self.noacc = True
t = "impacket too old; access permissions will not work! all accounts are admin!"
self.log("smb", t, 1)
ro = "no" if self.args.smbw else "yes" # (does nothing) ro = "no" if self.args.smbw else "yes" # (does nothing)
srv.addShare("A", "/", readOnly=ro) srv.addShare("A", "/", readOnly=ro)
@@ -119,24 +129,73 @@ class SMB(object):
def start(self) -> None: def start(self) -> None:
Daemon(self.srv.start) Daemon(self.srv.start)
def _v2a(self, caller: str, vpath: str, *a: Any) -> tuple[VFS, str]: def _auth_cb(self, *a, **ka):
debug("auth-result: %s %s", a, ka)
conndata = ka["connData"]
auth_ok = conndata["Authenticated"]
uname = ka["user_name"] if auth_ok else "*"
uname = self.asrv.iacct.get(uname, uname) or "*"
oldname = conndata.get("partygoer", "*") or "*"
cli_ip = conndata["ClientIP"]
cli_hn = ka["host_name"]
if uname != "*":
conndata["partygoer"] = uname
info("client %s [%s] authed as %s", cli_ip, cli_hn, uname)
elif oldname != "*":
info("client %s [%s] keeping old auth as %s", cli_ip, cli_hn, oldname)
elif auth_ok:
info("client %s [%s] authed as [*] (anon)", cli_ip, cli_hn)
else:
info("client %s [%s] rejected", cli_ip, cli_hn)
def _uname(self) -> str:
if self.noacc:
return LEELOO_DALLAS
try:
# you found it! my single worst bit of code so far
# (if you can think of a better way to track users through impacket i'm all ears)
cf0 = inspect.currentframe().f_back.f_back
cf = cf0.f_back
for n in range(3):
cl = cf.f_locals
if "connData" in cl:
return cl["connData"]["partygoer"]
cf = cf.f_back
except:
warning(
"nyoron... %s <<-- %s <<-- %s <<-- %s",
cf0.f_code.co_name,
cf0.f_back.f_code.co_name,
cf0.f_back.f_back.f_code.co_name,
cf0.f_back.f_back.f_back.f_code.co_name,
)
return "*"
def _v2a(
self, caller: str, vpath: str, *a: Any, uname="", perms=None
) -> tuple[VFS, str]:
vpath = vpath.replace("\\", "/").lstrip("/") vpath = vpath.replace("\\", "/").lstrip("/")
# cf = inspect.currentframe().f_back # cf = inspect.currentframe().f_back
# c1 = cf.f_back.f_code.co_name # c1 = cf.f_back.f_code.co_name
# c2 = cf.f_code.co_name # c2 = cf.f_code.co_name
debug('%s("%s", %s)\033[K\033[0m', caller, vpath, str(a)) if not uname:
uname = self._uname()
if not perms:
perms = [True, True]
# TODO find a way to grab `identity` in smbComSessionSetupAndX and smb2SessionSetup debug('%s("%s", %s) %s @%s\033[K\033[0m', caller, vpath, str(a), perms, uname)
vfs, rem = self.asrv.vfs.get(vpath, LEELOO_DALLAS, True, True) vfs, rem = self.asrv.vfs.get(vpath, uname, *perms)
return vfs, vfs.canonical(rem) return vfs, vfs.canonical(rem)
def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]: def _listdir(self, vpath: str, *a: Any, **ka: Any) -> list[str]:
vpath = vpath.replace("\\", "/").lstrip("/") vpath = vpath.replace("\\", "/").lstrip("/")
# caller = inspect.currentframe().f_back.f_code.co_name # caller = inspect.currentframe().f_back.f_code.co_name
debug('listdir("%s", %s)\033[K\033[0m', vpath, str(a)) uname = self._uname()
vfs, rem = self.asrv.vfs.get(vpath, LEELOO_DALLAS, False, False) # debug('listdir("%s", %s) @%s\033[K\033[0m', vpath, str(a), uname)
vfs, rem = self.asrv.vfs.get(vpath, uname, False, False)
_, vfs_ls, vfs_virt = vfs.ls( _, vfs_ls, vfs_virt = vfs.ls(
rem, LEELOO_DALLAS, not self.args.no_scandir, [[False, False]] rem, uname, not self.args.no_scandir, [[False, False]]
) )
dirs = [x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)] dirs = [x[0] for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)]
fils = [x[0] for x in vfs_ls if x[0] not in dirs] fils = [x[0] for x in vfs_ls if x[0] not in dirs]
@@ -149,8 +208,8 @@ class SMB(object):
sz = 112 * 2 # ['.', '..'] sz = 112 * 2 # ['.', '..']
for n, fn in enumerate(ls): for n, fn in enumerate(ls):
if sz >= 64000: if sz >= 64000:
t = "listing only %d of %d files (%d byte); see impacket#1433" t = "listing only %d of %d files (%d byte) in /%s; see impacket#1433"
warning(t, n, len(ls), sz) warning(t, n, len(ls), sz, vpath)
break break
nsz = len(fn.encode("utf-16", "replace")) nsz = len(fn.encode("utf-16", "replace"))
@@ -171,10 +230,12 @@ class SMB(object):
if wr and not self.args.smbw: if wr and not self.args.smbw:
yeet("blocked write (no --smbw): " + vpath) yeet("blocked write (no --smbw): " + vpath)
vfs, ap = self._v2a("open", vpath, *a) uname = self._uname()
vfs, ap = self._v2a("open", vpath, *a, uname=uname, perms=[True, wr])
if wr: if wr:
if not vfs.axs.uwrite: if not vfs.axs.uwrite:
yeet("blocked write (no-write-acc): " + vpath) t = "blocked write (no-write-acc %s): /%s @%s"
yeet(t % (vfs.axs.uwrite, vpath, uname))
xbu = vfs.flags.get("xbu") xbu = vfs.flags.get("xbu")
if xbu and not runhook( if xbu and not runhook(
@@ -204,7 +265,7 @@ class SMB(object):
_, vp = self.files.pop(fd) _, vp = self.files.pop(fd)
vp, fn = os.path.split(vp) vp, fn = os.path.split(vp)
vfs, rem = self.hub.asrv.vfs.get(vp, LEELOO_DALLAS, False, True) vfs, rem = self.hub.asrv.vfs.get(vp, self._uname(), False, True)
vfs, rem = vfs.get_dbv(rem) vfs, rem = vfs.get_dbv(rem)
self.hub.up2k.hash_file( self.hub.up2k.hash_file(
vfs.realpath, vfs.realpath,
@@ -224,15 +285,18 @@ class SMB(object):
vp1 = vp1.lstrip("/") vp1 = vp1.lstrip("/")
vp2 = vp2.lstrip("/") vp2 = vp2.lstrip("/")
vfs2, ap2 = self._v2a("rename", vp2, vp1) uname = self._uname()
vfs2, ap2 = self._v2a("rename", vp2, vp1, uname=uname)
if not vfs2.axs.uwrite: if not vfs2.axs.uwrite:
yeet("blocked rename (no-write-acc): " + vp2) t = "blocked write (no-write-acc %s): /%s @%s"
yeet(t % (vfs2.axs.uwrite, vp2, uname))
vfs1, _ = self.asrv.vfs.get(vp1, LEELOO_DALLAS, True, True) vfs1, _ = self.asrv.vfs.get(vp1, uname, True, True, True)
if not vfs1.axs.umove: if not vfs1.axs.umove:
yeet("blocked rename (no-move-acc): " + vp1) t = "blocked rename (no-move-acc %s): /%s @%s"
yeet(t % (vfs1.axs.umove, vp1, uname))
self.hub.up2k.handle_mv(LEELOO_DALLAS, vp1, vp2) self.hub.up2k.handle_mv(uname, vp1, vp2)
try: try:
bos.makedirs(ap2) bos.makedirs(ap2)
except: except:
@@ -242,52 +306,74 @@ class SMB(object):
if not self.args.smbw: if not self.args.smbw:
yeet("blocked mkdir (no --smbw): " + vpath) yeet("blocked mkdir (no --smbw): " + vpath)
vfs, ap = self._v2a("mkdir", vpath) uname = self._uname()
vfs, ap = self._v2a("mkdir", vpath, uname=uname)
if not vfs.axs.uwrite: if not vfs.axs.uwrite:
yeet("blocked mkdir (no-write-acc): " + vpath) t = "blocked mkdir (no-write-acc %s): /%s @%s"
yeet(t % (vfs.axs.uwrite, vpath, uname))
return bos.mkdir(ap) return bos.mkdir(ap)
def _stat(self, vpath: str, *a: Any, **ka: Any) -> os.stat_result: def _stat(self, vpath: str, *a: Any, **ka: Any) -> os.stat_result:
return bos.stat(self._v2a("stat", vpath, *a)[1], *a, **ka) try:
ap = self._v2a("stat", vpath, *a, perms=[True, False])[1]
ret = bos.stat(ap, *a, **ka)
# debug(" `-stat:ok")
return ret
except:
# white lie: windows freaks out if we raise due to an offline volume
# debug(" `-stat:NOPE (faking a directory)")
ts = int(time.time())
return os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, ts, ts, ts))
def _unlink(self, vpath: str) -> None: def _unlink(self, vpath: str) -> None:
if not self.args.smbw: if not self.args.smbw:
yeet("blocked delete (no --smbw): " + vpath) yeet("blocked delete (no --smbw): " + vpath)
# return bos.unlink(self._v2a("stat", vpath, *a)[1]) # return bos.unlink(self._v2a("stat", vpath, *a)[1])
vfs, ap = self._v2a("delete", vpath) uname = self._uname()
vfs, ap = self._v2a(
"delete", vpath, uname=uname, perms=[True, False, False, True]
)
if not vfs.axs.udel: if not vfs.axs.udel:
yeet("blocked delete (no-del-acc): " + vpath) yeet("blocked delete (no-del-acc): " + vpath)
vpath = vpath.replace("\\", "/").lstrip("/") vpath = vpath.replace("\\", "/").lstrip("/")
self.hub.up2k.handle_rm(LEELOO_DALLAS, "1.7.6.2", [vpath], [], False) self.hub.up2k.handle_rm(uname, "1.7.6.2", [vpath], [], False)
def _utime(self, vpath: str, times: tuple[float, float]) -> None: def _utime(self, vpath: str, times: tuple[float, float]) -> None:
if not self.args.smbw: if not self.args.smbw:
yeet("blocked utime (no --smbw): " + vpath) yeet("blocked utime (no --smbw): " + vpath)
vfs, ap = self._v2a("utime", vpath) uname = self._uname()
vfs, ap = self._v2a("utime", vpath, uname=uname)
if not vfs.axs.uwrite: if not vfs.axs.uwrite:
yeet("blocked utime (no-write-acc): " + vpath) t = "blocked utime (no-write-acc %s): /%s @%s"
yeet(t % (vfs.axs.uwrite, vpath, uname))
return bos.utime(ap, times) return bos.utime(ap, times)
def _p_exists(self, vpath: str) -> bool: def _p_exists(self, vpath: str) -> bool:
# ap = "?"
try: try:
bos.stat(self._v2a("p.exists", vpath)[1]) ap = self._v2a("p.exists", vpath, perms=[True, False])[1]
bos.stat(ap)
# debug(" `-exists((%s)->(%s)):ok", vpath, ap)
return True return True
except: except:
# debug(" `-exists((%s)->(%s)):NOPE", vpath, ap)
return False return False
def _p_getsize(self, vpath: str) -> int: def _p_getsize(self, vpath: str) -> int:
st = bos.stat(self._v2a("p.getsize", vpath)[1]) st = bos.stat(self._v2a("p.getsize", vpath, perms=[True, False])[1])
return st.st_size return st.st_size
def _p_isdir(self, vpath: str) -> bool: def _p_isdir(self, vpath: str) -> bool:
try: try:
st = bos.stat(self._v2a("p.isdir", vpath)[1]) st = bos.stat(self._v2a("p.isdir", vpath, perms=[True, False])[1])
return stat.S_ISDIR(st.st_mode) ret = stat.S_ISDIR(st.st_mode)
# debug(" `-isdir:%s:%s", st.st_mode, ret)
return ret
except: except:
return False return False

View File

@@ -214,7 +214,7 @@ CONFIGID.UPNP.ORG: 1
srv.sck.sendto(zb, addr[:2]) srv.sck.sendto(zb, addr[:2])
if cip not in self.txc.c: if cip not in self.txc.c:
self.log("{} [{}] --> {}".format(srv.name, srv.ip, cip), "6") self.log("{} [{}] --> {}".format(srv.name, srv.ip, cip), 6)
self.txc.add(cip) self.txc.add(cip)
self.txc.cln() self.txc.cln()

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 re
import stat import stat
import tarfile import tarfile
@@ -44,6 +45,7 @@ class StreamTar(StreamArc):
self, self,
log: "NamedLogger", log: "NamedLogger",
fgen: Generator[dict[str, Any], None, None], fgen: Generator[dict[str, Any], None, None],
cmp: str = "",
**kwargs: Any **kwargs: Any
): ):
super(StreamTar, self).__init__(log, fgen) super(StreamTar, self).__init__(log, fgen)
@@ -53,10 +55,36 @@ class StreamTar(StreamArc):
self.qfile = QFile() self.qfile = QFile()
self.errf: dict[str, Any] = {} self.errf: dict[str, Any] = {}
# python 3.8 changed to PAX_FORMAT as default, # python 3.8 changed to PAX_FORMAT as default;
# waste of space and don't care about the new features # slower, bigger, and no particular advantage
fmt = tarfile.GNU_FORMAT fmt = tarfile.GNU_FORMAT
self.tar = tarfile.open(fileobj=self.qfile, mode="w|", format=fmt) # type: ignore if "pax" in cmp:
# unless a client asks for it (currently
# gnu-tar has wider support than pax-tar)
fmt = tarfile.PAX_FORMAT
cmp = re.sub(r"[^a-z0-9]*pax[^a-z0-9]*", "", cmp)
try:
cmp, lv = cmp.replace(":", ",").split(",")
lv = int(lv)
except:
lv = None
arg = {"name": None, "fileobj": self.qfile, "mode": "w", "format": fmt}
if cmp == "gz":
fun = tarfile.TarFile.gzopen
arg["compresslevel"] = lv if lv is not None else 3
elif cmp == "bz2":
fun = tarfile.TarFile.bz2open
arg["compresslevel"] = lv if lv is not None else 2
elif cmp == "xz":
fun = tarfile.TarFile.xzopen
arg["preset"] = lv if lv is not None else 1
else:
fun = tarfile.open
arg["mode"] = "w|"
self.tar = fun(**arg)
Daemon(self._gen, "star-gen") Daemon(self._gen, "star-gen")

View File

@@ -198,6 +198,10 @@ class SvcHub(object):
self.log("root", "max clients: {}".format(self.args.nc)) self.log("root", "max clients: {}".format(self.args.nc))
self.tcpsrv = TcpSrv(self) self.tcpsrv = TcpSrv(self)
if not self.tcpsrv.srv and self.args.ign_ebind_all:
self.args.no_fastboot = True
self.up2k = Up2k(self) self.up2k = Up2k(self)
decs = {k: 1 for k in self.args.th_dec.split(",")} decs = {k: 1 for k in self.args.th_dec.split(",")}
@@ -339,12 +343,20 @@ class SvcHub(object):
if self.httpsrv_up != self.broker.num_workers: if self.httpsrv_up != self.broker.num_workers:
return return
time.sleep(0.1) # purely cosmetic dw ar = self.args
for _ in range(10 if ar.ftp or ar.ftps else 0):
time.sleep(0.03)
if self.ftpd:
break
if self.tcpsrv.qr: if self.tcpsrv.qr:
self.log("qr-code", self.tcpsrv.qr) self.log("qr-code", self.tcpsrv.qr)
else: else:
self.log("root", "workers OK\n") self.log("root", "workers OK\n")
self.after_httpsrv_up()
def after_httpsrv_up(self) -> None:
self.up2k.init_vols() self.up2k.init_vols()
Daemon(self.sd_notify, "sd-notify") Daemon(self.sd_notify, "sd-notify")
@@ -415,6 +427,12 @@ class SvcHub(object):
elif al.ban_url == "no": elif al.ban_url == "no":
al.sus_urls = None al.sus_urls = None
if al.xff_src in ("any", "0", ""):
al.xff_re = None
else:
zs = al.xff_src.replace(" ", "").replace(".", "\\.").replace(",", "|")
al.xff_re = re.compile("^(?:" + zs + ")")
return True return True
def _setlimits(self) -> None: def _setlimits(self) -> None:
@@ -645,19 +663,25 @@ class SvcHub(object):
ret = 1 ret = 1
try: try:
self.pr("OPYTHAT") self.pr("OPYTHAT")
tasks = []
slp = 0.0 slp = 0.0
if self.mdns: if self.mdns:
Daemon(self.mdns.stop) tasks.append(Daemon(self.mdns.stop, "mdns"))
slp = time.time() + 0.5 slp = time.time() + 0.5
if self.ssdp: if self.ssdp:
Daemon(self.ssdp.stop) tasks.append(Daemon(self.ssdp.stop, "ssdp"))
slp = time.time() + 0.5 slp = time.time() + 0.5
self.broker.shutdown() self.broker.shutdown()
self.tcpsrv.shutdown() self.tcpsrv.shutdown()
self.up2k.shutdown() self.up2k.shutdown()
if hasattr(self, "smbd"):
slp = max(slp, time.time() + 0.5)
tasks.append(Daemon(self.smbd.stop, "smbd"))
if self.thumbsrv: if self.thumbsrv:
self.thumbsrv.shutdown() self.thumbsrv.shutdown()
@@ -667,17 +691,19 @@ class SvcHub(object):
break break
if n == 3: if n == 3:
self.pr("waiting for thumbsrv (10sec)...") self.log("root", "waiting for thumbsrv (10sec)...")
if hasattr(self, "smbd"): if hasattr(self, "smbd"):
slp = max(slp, time.time() + 0.5) zf = max(time.time() - slp, 0)
Daemon(self.kill9, a=(1,)) Daemon(self.kill9, a=(zf + 0.5,))
Daemon(self.smbd.stop)
while time.time() < slp: while time.time() < slp:
time.sleep(0.1) if not next((x for x in tasks if x.is_alive), None):
break
self.pr("nailed it", end="") time.sleep(0.05)
self.log("root", "nailed it")
ret = self.retcode ret = self.retcode
except: except:
self.pr("\033[31m[ error during shutdown ]\n{}\033[0m".format(min_ex())) self.pr("\033[31m[ error during shutdown ]\n{}\033[0m".format(min_ex()))
@@ -687,7 +713,7 @@ class SvcHub(object):
print("\033]0;\033\\", file=sys.stderr, end="") print("\033]0;\033\\", file=sys.stderr, end="")
sys.stderr.flush() sys.stderr.flush()
self.pr("\033[0m") self.pr("\033[0m", end="")
if self.logf: if self.logf:
self.logf.close() self.logf.close()

View File

@@ -221,6 +221,7 @@ class StreamZip(StreamArc):
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
) -> None: ) -> None:
super(StreamZip, self).__init__(log, fgen) super(StreamZip, self).__init__(log, fgen)
@@ -275,6 +276,7 @@ class StreamZip(StreamArc):
def gen(self) -> Generator[bytes, None, None]: def gen(self) -> Generator[bytes, None, None]:
errf: dict[str, Any] = {} errf: dict[str, Any] = {}
errors = [] errors = []
mbuf = b""
try: try:
for f in self.fgen: for f in self.fgen:
if "err" in f: if "err" in f:
@@ -283,13 +285,20 @@ class StreamZip(StreamArc):
try: try:
for x in self.ser(f): for x in self.ser(f):
yield x mbuf += x
if len(mbuf) >= 16384:
yield mbuf
mbuf = b""
except GeneratorExit: except GeneratorExit:
raise raise
except: except:
ex = min_ex(5, True).replace("\n", "\n-- ") ex = min_ex(5, True).replace("\n", "\n-- ")
errors.append((f["vp"], ex)) errors.append((f["vp"], ex))
if mbuf:
yield mbuf
mbuf = b""
if errors: if errors:
errf, txt = errdesc(errors) errf, txt = errdesc(errors)
self.log("\n".join(([repr(errf)] + txt[1:]))) self.log("\n".join(([repr(errf)] + txt[1:])))
@@ -299,20 +308,23 @@ class StreamZip(StreamArc):
cdir_pos = self.pos cdir_pos = self.pos
for name, sz, ts, crc, h_pos in self.items: for name, sz, ts, crc, h_pos in self.items:
buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc) buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc)
yield self._ct(buf) mbuf += self._ct(buf)
if len(mbuf) >= 16384:
yield mbuf
mbuf = b""
cdir_end = self.pos cdir_end = self.pos
_, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end) _, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end)
if need_64: if need_64:
ecdir64_pos = self.pos ecdir64_pos = self.pos
buf = gen_ecdr64(self.items, cdir_pos, cdir_end) buf = gen_ecdr64(self.items, cdir_pos, cdir_end)
yield self._ct(buf) mbuf += self._ct(buf)
buf = gen_ecdr64_loc(ecdir64_pos) buf = gen_ecdr64_loc(ecdir64_pos)
yield self._ct(buf) mbuf += self._ct(buf)
ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end) ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end)
yield self._ct(ecdr) yield mbuf + self._ct(ecdr)
finally: finally:
if errf: if errf:
bos.unlink(errf["ap"]) bos.unlink(errf["ap"])

View File

@@ -15,6 +15,7 @@ from .util import (
E_ADDR_IN_USE, E_ADDR_IN_USE,
E_ADDR_NOT_AVAIL, E_ADDR_NOT_AVAIL,
E_UNREACH, E_UNREACH,
IP6ALL,
Netdev, Netdev,
min_ex, min_ex,
sunpack, sunpack,
@@ -254,6 +255,9 @@ class TcpSrv(object):
srvs: list[socket.socket] = [] srvs: list[socket.socket] = []
for srv in self.srv: for srv in self.srv:
ip, port = srv.getsockname()[:2] ip, port = srv.getsockname()[:2]
if ip == IP6ALL:
ip = "::" # jython
try: try:
srv.listen(self.args.nc) srv.listen(self.args.nc)
try: try:
@@ -275,6 +279,8 @@ class TcpSrv(object):
srv.close() srv.close()
continue continue
t = "\n\nERROR: could not open listening socket, probably because one of the server ports ({}) is busy on one of the requested interfaces ({}); avoid this issue by specifying a different port (-p 3939) and/or a specific interface to listen on (-i 192.168.56.1)\n"
self.log("tcpsrv", t.format(port, ip), 1)
raise raise
bound.append((ip, port)) bound.append((ip, port))

View File

@@ -314,6 +314,7 @@ class U2idx(object):
sret = [] sret = []
fk = flags.get("fk") fk = flags.get("fk")
dots = flags.get("dotsrch") dots = flags.get("dotsrch")
fk_alg = 2 if "fka" in flags else 1
c = cur.execute(uq, tuple(vuv)) c = cur.execute(uq, tuple(vuv))
for hit in c: for hit in c:
w, ts, sz, rd, fn, ip, at = hit[:7] w, ts, sz, rd, fn, ip, at = hit[:7]
@@ -333,16 +334,17 @@ class U2idx(object):
else: else:
try: try:
ap = absreal(os.path.join(ptop, rd, fn)) ap = absreal(os.path.join(ptop, rd, fn))
inf = bos.stat(ap) ino = 0 if ANYWIN or fk_alg == 2 else bos.stat(ap).st_ino
except: except:
continue continue
suf = ( suf = "?k=" + gen_filekey(
"?k=" fk_alg,
+ gen_filekey( self.args.fk_salt,
self.args.fk_salt, ap, sz, 0 if ANYWIN else inf.st_ino ap,
)[:fk] sz,
) ino,
)[:fk]
lim -= 1 lim -= 1
if lim < 0: if lim < 0:

View File

@@ -228,8 +228,10 @@ class Up2k(object):
self.log_func("up2k", msg, c) self.log_func("up2k", msg, c)
def _gen_fk(self, salt: str, fspath: str, fsize: int, inode: int) -> str: def _gen_fk(self, alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str:
return gen_filekey_dbg(salt, fspath, fsize, inode, self.log, self.args.log_fk) return gen_filekey_dbg(
alg, salt, fspath, fsize, inode, self.log, self.args.log_fk
)
def _block(self, why: str) -> None: def _block(self, why: str) -> None:
self.blocked = why self.blocked = why
@@ -952,6 +954,11 @@ class Up2k(object):
rtop = absreal(top) rtop = absreal(top)
n_add = n_rm = 0 n_add = n_rm = 0
try: try:
if not bos.listdir(rtop):
t = "volume /%s at [%s] is empty; will not be indexed as this could be due to an offline filesystem"
self.log(t % (vol.vpath, rtop), 6)
return True, False
n_add = self._build_dir( n_add = self._build_dir(
db, db,
top, top,
@@ -1050,7 +1057,7 @@ class Up2k(object):
if WINDOWS: if WINDOWS:
rd = rd.replace("\\", "/").strip("/") rd = rd.replace("\\", "/").strip("/")
g = statdir(self.log_func, not self.args.no_scandir, False, cdir) g = statdir(self.log_func, not self.args.no_scandir, True, cdir)
gl = sorted(g) gl = sorted(g)
partials = set([x[0] for x in gl if "PARTIAL" in x[0]]) partials = set([x[0] for x in gl if "PARTIAL" in x[0]])
for iname, inf in gl: for iname, inf in gl:
@@ -1065,6 +1072,12 @@ class Up2k(object):
continue continue
lmod = int(inf.st_mtime) lmod = int(inf.st_mtime)
if stat.S_ISLNK(inf.st_mode):
try:
inf = bos.stat(abspath)
except:
continue
sz = inf.st_size sz = inf.st_size
if fat32 and not ffat and inf.st_mtime % 2: if fat32 and not ffat and inf.st_mtime % 2:
fat32 = False fat32 = False
@@ -1445,9 +1458,11 @@ class Up2k(object):
pf = "v{}, {:.0f}+".format(n_left, b_left / 1024 / 1024) pf = "v{}, {:.0f}+".format(n_left, b_left / 1024 / 1024)
self.pp.msg = pf + abspath self.pp.msg = pf + abspath
st = bos.stat(abspath) # throws on broken symlinks (always did)
stl = bos.lstat(abspath)
st = bos.stat(abspath) if stat.S_ISLNK(stl.st_mode) else stl
mt2 = int(stl.st_mtime)
sz2 = st.st_size sz2 = st.st_size
mt2 = int(st.st_mtime)
if nohash or not sz2: if nohash or not sz2:
w2 = up2k_wark_from_metadata(self.salt, sz2, mt2, rd, fn) w2 = up2k_wark_from_metadata(self.salt, sz2, mt2, rd, fn)
@@ -1469,6 +1484,13 @@ class Up2k(object):
if w == w2: if w == w2:
continue continue
# symlink mtime was inconsistent before v1.9.4; check if that's it
if st != stl and (nohash or not sz2):
mt2b = int(st.st_mtime)
w2b = up2k_wark_from_metadata(self.salt, sz2, mt2b, rd, fn)
if w == w2b:
continue
rewark.append((drd, dfn, w2, sz2, mt2)) rewark.append((drd, dfn, w2, sz2, mt2))
t = "hash mismatch: {}\n db: {} ({} byte, {})\n fs: {} ({} byte, {})" t = "hash mismatch: {}\n db: {} ({} byte, {})\n fs: {} ({} byte, {})"
@@ -2603,9 +2625,10 @@ class Up2k(object):
and not self.args.nw and not self.args.nw
and (cj["user"] in vfs.axs.uread or cj["user"] in vfs.axs.upget) and (cj["user"] in vfs.axs.uread or cj["user"] in vfs.axs.upget)
): ):
alg = 2 if "fka" in vfs.flags else 1
ap = absreal(djoin(job["ptop"], job["prel"], job["name"])) ap = absreal(djoin(job["ptop"], job["prel"], job["name"]))
ino = 0 if ANYWIN else bos.stat(ap).st_ino ino = 0 if ANYWIN else bos.stat(ap).st_ino
fk = self.gen_fk(self.args.fk_salt, ap, job["size"], ino) fk = self.gen_fk(alg, self.args.fk_salt, ap, job["size"], ino)
ret["fk"] = fk[: vfs.flags["fk"]] ret["fk"] = fk[: vfs.flags["fk"]]
return ret return ret
@@ -2657,7 +2680,7 @@ class Up2k(object):
fs2 = bos.stat(os.path.dirname(dst)).st_dev fs2 = bos.stat(os.path.dirname(dst)).st_dev
if fs1 == 0 or fs2 == 0: if fs1 == 0 or fs2 == 0:
# py2 on winxp or other unsupported combination # py2 on winxp or other unsupported combination
raise OSError(38, "filesystem does not have st_dev") raise OSError(errno.ENOSYS, "filesystem does not have st_dev")
elif fs1 == fs2: elif fs1 == fs2:
# same fs; make symlink as relative as possible # same fs; make symlink as relative as possible
spl = r"[\\/]" if WINDOWS else "/" spl = r"[\\/]" if WINDOWS else "/"
@@ -3277,10 +3300,15 @@ class Up2k(object):
if bos.path.exists(dabs): if bos.path.exists(dabs):
raise Pebkac(400, "mv2: target file exists") raise Pebkac(400, "mv2: target file exists")
stl = bos.lstat(sabs)
try:
st = bos.stat(sabs)
except:
st = stl
xbr = svn.flags.get("xbr") xbr = svn.flags.get("xbr")
xar = dvn.flags.get("xar") xar = dvn.flags.get("xar")
if xbr: if xbr:
st = bos.stat(sabs)
if not runhook( if not runhook(
self.log, xbr, sabs, svp, "", uname, st.st_mtime, st.st_size, "", 0, "" self.log, xbr, sabs, svp, "", uname, st.st_mtime, st.st_size, "", 0, ""
): ):
@@ -3288,9 +3316,16 @@ class Up2k(object):
self.log(t, 1) self.log(t, 1)
raise Pebkac(405, t) raise Pebkac(405, t)
is_xvol = svn.realpath != dvn.realpath
if stat.S_ISLNK(stl.st_mode):
is_dirlink = stat.S_ISDIR(st.st_mode)
is_link = True
else:
is_link = is_dirlink = False
bos.makedirs(os.path.dirname(dabs)) bos.makedirs(os.path.dirname(dabs))
if bos.path.islink(sabs): if is_dirlink:
dlabs = absreal(sabs) dlabs = absreal(sabs)
t = "moving symlink from [{}] to [{}], target [{}]" t = "moving symlink from [{}] to [{}], target [{}]"
self.log(t.format(sabs, dabs, dlabs)) self.log(t.format(sabs, dabs, dlabs))
@@ -3313,36 +3348,22 @@ class Up2k(object):
c2 = self.cur.get(dvn.realpath) c2 = self.cur.get(dvn.realpath)
if ftime_ is None: if ftime_ is None:
st = bos.stat(sabs)
ftime = st.st_mtime ftime = st.st_mtime
fsize = st.st_size fsize = st.st_size
else: else:
ftime = ftime_ ftime = ftime_
fsize = fsize_ or 0 fsize = fsize_ or 0
try: has_dupes = False
atomic_move(sabs, dabs)
except OSError as ex:
if ex.errno != errno.EXDEV:
raise
self.log("cross-device move:\n {}\n {}".format(sabs, dabs))
b1, b2 = fsenc(sabs), fsenc(dabs)
try:
shutil.copy2(b1, b2)
except:
os.unlink(b2)
raise
os.unlink(b1)
if w: if w:
assert c1 assert c1
if c2 and c2 != c1: if c2 and c2 != c1:
self._copy_tags(c1, c2, w) self._copy_tags(c1, c2, w)
self._forget_file(svn.realpath, srem, c1, w, c1 != c2, fsize) has_dupes = self._forget_file(svn.realpath, srem, c1, w, is_xvol, fsize)
self._relink(w, svn.realpath, srem, dabs) if not is_xvol:
has_dupes = self._relink(w, svn.realpath, srem, dabs)
curs.add(c1) curs.add(c1)
if c2: if c2:
@@ -3365,6 +3386,47 @@ class Up2k(object):
else: else:
self.log("not found in src db: [{}]".format(svp)) self.log("not found in src db: [{}]".format(svp))
try:
if is_xvol and has_dupes:
raise OSError(errno.EXDEV, "src is symlink")
atomic_move(sabs, dabs)
except OSError as ex:
if ex.errno != errno.EXDEV:
raise
self.log("using copy+delete (%s):\n %s\n %s" % (ex.strerror, sabs, dabs))
b1, b2 = fsenc(sabs), fsenc(dabs)
is_link = os.path.islink(b1) # due to _relink
try:
shutil.copy2(b1, b2)
except:
try:
os.unlink(b2)
except:
pass
if not is_link:
raise
# broken symlink? keep it as-is
try:
zb = os.readlink(b1)
os.symlink(zb, b2)
except:
os.unlink(b2)
raise
if is_link:
try:
times = (int(time.time()), int(stl.st_mtime))
bos.utime(dabs, times, False)
except:
pass
os.unlink(b1)
if xar: if xar:
runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "") runhook(self.log, xar, dabs, dvp, "", uname, 0, 0, "", 0, "")
@@ -3418,14 +3480,16 @@ class Up2k(object):
wark: Optional[str], wark: Optional[str],
drop_tags: bool, drop_tags: bool,
sz: int, sz: int,
) -> None: ) -> bool:
"""forgets file in db, fixes symlinks, does not delete""" """forgets file in db, fixes symlinks, does not delete"""
srd, sfn = vsplit(vrem) srd, sfn = vsplit(vrem)
has_dupes = False
self.log("forgetting {}".format(vrem)) self.log("forgetting {}".format(vrem))
if wark and cur: if wark and cur:
self.log("found {} in db".format(wark)) self.log("found {} in db".format(wark))
if drop_tags: if drop_tags:
if self._relink(wark, ptop, vrem, ""): if self._relink(wark, ptop, vrem, ""):
has_dupes = True
drop_tags = False drop_tags = False
if drop_tags: if drop_tags:
@@ -3453,6 +3517,8 @@ class Up2k(object):
assert wark assert wark
del reg[wark] del reg[wark]
return has_dupes
def _relink(self, wark: str, sptop: str, srem: str, dabs: str) -> int: def _relink(self, wark: str, sptop: str, srem: str, dabs: str) -> int:
""" """
update symlinks from file at svn/srem to dabs (rename), update symlinks from file at svn/srem to dabs (rename),

View File

@@ -56,6 +56,8 @@ E_ADDR_IN_USE = _ens("EADDRINUSE WSAEADDRINUSE")
E_ACCESS = _ens("EACCES WSAEACCES") E_ACCESS = _ens("EACCES WSAEACCES")
E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH") E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH")
IP6ALL = "0:0:0:0:0:0:0:0"
try: try:
import ctypes import ctypes
@@ -66,7 +68,9 @@ except:
try: try:
HAVE_SQLITE3 = True HAVE_SQLITE3 = True
import sqlite3 # pylint: disable=unused-import # typechk import sqlite3
assert hasattr(sqlite3, "connect") # graalpy
except: except:
HAVE_SQLITE3 = False HAVE_SQLITE3 = False
@@ -294,11 +298,19 @@ REKOBO_KEY = {
REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()} REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()}
_exestr = "python3 python ffmpeg ffprobe cfssl cfssljson cfssl-certinfo"
CMD_EXEB = set(_exestr.encode("utf-8").split())
CMD_EXES = set(_exestr.split())
pybin = sys.executable or "" pybin = sys.executable or ""
if EXE: if EXE:
pybin = "" pybin = ""
for zsg in "python3 python".split(): for zsg in "python3 python".split():
try: try:
if ANYWIN:
zsg += ".exe"
zsg = shutil.which(zsg) zsg = shutil.which(zsg)
if zsg: if zsg:
pybin = zsg pybin = zsg
@@ -1555,15 +1567,18 @@ def rand_name(fdir: str, fn: str, rnd: int) -> str:
return fn return fn
def gen_filekey(salt: str, fspath: str, fsize: int, inode: int) -> str: def gen_filekey(alg: int, salt: str, fspath: str, fsize: int, inode: int) -> str:
return base64.urlsafe_b64encode( if alg == 1:
hashlib.sha512( zs = "%s %s %s %s" % (salt, fspath, fsize, inode)
("%s %s %s %s" % (salt, fspath, fsize, inode)).encode("utf-8", "replace") else:
).digest() zs = "%s %s" % (salt, fspath)
).decode("ascii")
zb = zs.encode("utf-8", "replace")
return base64.urlsafe_b64encode(hashlib.sha512(zb).digest()).decode("ascii")
def gen_filekey_dbg( def gen_filekey_dbg(
alg: int,
salt: str, salt: str,
fspath: str, fspath: str,
fsize: int, fsize: int,
@@ -1571,7 +1586,7 @@ def gen_filekey_dbg(
log: "NamedLogger", log: "NamedLogger",
log_ptn: Optional[Pattern[str]], log_ptn: Optional[Pattern[str]],
) -> str: ) -> str:
ret = gen_filekey(salt, fspath, fsize, inode) ret = gen_filekey(alg, salt, fspath, fsize, inode)
assert log_ptn assert log_ptn
if log_ptn.search(fspath): if log_ptn.search(fspath):
@@ -2125,7 +2140,7 @@ def list_ips() -> list[str]:
def yieldfile(fn: str) -> Generator[bytes, None, None]: def yieldfile(fn: str) -> Generator[bytes, None, None]:
with open(fsenc(fn), "rb", 512 * 1024) as f: with open(fsenc(fn), "rb", 512 * 1024) as f:
while True: while True:
buf = f.read(64 * 1024) buf = f.read(128 * 1024)
if not buf: if not buf:
break break
@@ -2446,6 +2461,14 @@ def runcmd(
bout: bytes bout: bytes
berr: bytes berr: bytes
if ANYWIN:
if isinstance(argv[0], (bytes, bytearray)):
if argv[0] in CMD_EXEB:
argv[0] += b".exe"
else:
if argv[0] in CMD_EXES:
argv[0] += ".exe"
p = sp.Popen(argv, stdout=cout, stderr=cerr, **ka) p = sp.Popen(argv, stdout=cout, stderr=cerr, **ka)
if not timeout or PY2: if not timeout or PY2:
bout, berr = p.communicate(sin) bout, berr = p.communicate(sin)

View File

@@ -261,7 +261,7 @@ window.baguetteBox = (function () {
setloop(1); setloop(1);
else if (k == "BracketRight") else if (k == "BracketRight")
setloop(2); setloop(2);
else if (e.shiftKey) else if (e.shiftKey && k != 'KeyR')
return; return;
else if (k == "ArrowLeft" || k == "KeyJ") else if (k == "ArrowLeft" || k == "KeyJ")
showPreviousImage(); showPreviousImage();
@@ -524,7 +524,7 @@ window.baguetteBox = (function () {
options[item] = newOptions[item]; options[item] = newOptions[item];
} }
var an = options.animation = sread('ganim') || anims[ANIM ? 0 : 2]; var an = options.animation = sread('ganim', anims) || anims[ANIM ? 0 : 2];
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)]; btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
btnAnim.setAttribute('tt', 'animation: ' + an); btnAnim.setAttribute('tt', 'animation: ' + an);
@@ -875,7 +875,7 @@ window.baguetteBox = (function () {
if (loopB !== null) { if (loopB !== null) {
timer.add(loopchk); timer.add(loopchk);
sethash(window.location.hash.slice(1).split('&')[0] + '&t=' + (loopA || 0) + '-' + loopB); sethash(location.hash.slice(1).split('&')[0] + '&t=' + (loopA || 0) + '-' + loopB);
} }
} }

View File

@@ -493,6 +493,7 @@ html.dz {
--err-ts: #500; --err-ts: #500;
text-shadow: none; text-shadow: none;
font-family: 'scp', monospace, monospace;
} }
html.dy { html.dy {
--fg: #000; --fg: #000;
@@ -727,6 +728,11 @@ a:hover {
html.y #files thead th { html.y #files thead th {
box-shadow: 0 1px 0 rgba(0,0,0,0.12); box-shadow: 0 1px 0 rgba(0,0,0,0.12);
} }
html #files.hhpick thead th {
color: #f7d;
background: #000;
box-shadow: .1em .2em 0 #f6c inset, -.1em -.1em 0 #f6c inset;
}
#files td { #files td {
margin: 0; margin: 0;
padding: .3em .5em; padding: .3em .5em;
@@ -1397,6 +1403,9 @@ input[type="checkbox"]:checked+label {
color: #0e0; color: #0e0;
color: var(--a); color: var(--a);
} }
html.dz input {
font-family: 'scp', monospace, monospace;
}
.opwide div>span>input+label { .opwide div>span>input+label {
padding: .3em 0 .3em .3em; padding: .3em 0 .3em .3em;
margin: 0 0 0 -.3em; margin: 0 0 0 -.3em;
@@ -1577,6 +1586,7 @@ html.cz .btn {
border-bottom: .2em solid #709; border-bottom: .2em solid #709;
} }
html.dz .btn { html.dz .btn {
font-size: 1em;
box-shadow: 0 0 0 .1em #080 inset; box-shadow: 0 0 0 .1em #080 inset;
} }
html.dz .tgl.btn.on { html.dz .tgl.btn.on {
@@ -2107,12 +2117,12 @@ html.y #bbox-overlay figcaption a {
} }
.bbox-btn, .bbox-btn,
#bbox-btns { #bbox-btns {
opacity: 1; opacity: 1;
animation: opacity .2s infinite ease-in-out; animation: opacity .2s infinite ease-in-out;
} }
.bbox-btn.off, .bbox-btn.off,
#bbox-btns.off { #bbox-btns.off {
opacity: 0; opacity: 0;
} }
#bbox-overlay button { #bbox-overlay button {
cursor: pointer; cursor: pointer;
@@ -2382,7 +2392,7 @@ html.y #bbox-overlay figcaption a {
display: block; display: block;
} }
#u2bm sup { #u2bm sup {
font-weight: bold; font-weight: bold;
} }
#u2notbtn { #u2notbtn {
display: none; display: none;
@@ -2766,6 +2776,9 @@ html.c .opbox,
html.a .opbox { html.a .opbox {
margin: 1.5em 0 0 0; margin: 1.5em 0 0 0;
} }
html.dz .opview input.i {
width: calc(100% - 18em);
}
html.c #tree, html.c #tree,
html.c #treeh, html.c #treeh,
html.a #tree, html.a #tree,
@@ -2818,6 +2831,9 @@ html.a #u2btn {
html.ay #u2btn { html.ay #u2btn {
box-shadow: .4em .4em 0 #ccc; box-shadow: .4em .4em 0 #ccc;
} }
html.dz #u2btn {
letter-spacing: -.033em;
}
html.c #u2conf.ww #u2btn, html.c #u2conf.ww #u2btn,
html.a #u2conf.ww #u2btn { html.a #u2conf.ww #u2btn {
margin: -2em .5em -3em 0; margin: -2em .5em -3em 0;
@@ -2980,13 +2996,13 @@ html.b .btn {
top: -.1em; top: -.1em;
} }
html.b #op_up2k.srch sup { html.b #op_up2k.srch sup {
color: #fc0; color: #fc0;
} }
html.by #u2btn sup { html.by #u2btn sup {
color: #06b; color: #06b;
} }
html.by #op_up2k.srch sup { html.by #op_up2k.srch sup {
color: #b70; color: #b70;
} }
html.bz #u2cards a.act { html.bz #u2cards a.act {
box-shadow: 0 -.1em .2em var(--bg-d2); box-shadow: 0 -.1em .2em var(--bg-d2);

View File

@@ -11,7 +11,7 @@
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}"> <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}"> <link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
{%- if css %} {%- if css %}
<link rel="stylesheet" media="screen" href="{{ css }}?_={{ ts }}"> <link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
{%- endif %} {%- endif %}
</head> </head>
@@ -166,14 +166,14 @@
readme = {{ readme|tojson }}, readme = {{ readme|tojson }},
ls0 = {{ ls0|tojson }}; ls0 = {{ ls0|tojson }};
document.documentElement.className = localStorage.theme || dtheme; document.documentElement.className = localStorage.cpp_thm || dtheme;
</script> </script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/baguettebox.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/browser.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/up2k.js?_={{ ts }}"></script>
{%- if js %} {%- if js %}
<script src="{{ js }}?_={{ ts }}"></script> <script src="{{ js }}_={{ ts }}"></script>
{%- endif %} {%- endif %}
</body> </body>

View File

@@ -80,6 +80,7 @@ var Ls = {
"textfile-viewer", "textfile-viewer",
["I/K", "prev/next file"], ["I/K", "prev/next file"],
["M", "close textfile"], ["M", "close textfile"],
["E", "edit textfile"],
["S", "select file (for cut/rename)"], ["S", "select file (for cut/rename)"],
] ]
], ],
@@ -186,7 +187,7 @@ var Ls = {
"cl_hiddenc": "hidden columns", "cl_hiddenc": "hidden columns",
"cl_hidec": "hide", "cl_hidec": "hide",
"cl_reset": "reset", "cl_reset": "reset",
"cl_hpick": "click one column header to hide in the table below", "cl_hpick": "tap on column headers to hide in the table below",
"cl_hcancel": "column hiding aborted", "cl_hcancel": "column hiding aborted",
"ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T", "ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T",
@@ -327,6 +328,7 @@ var Ls = {
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev", "tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
"tvt_next": "show next document$NHotkey: K\">⬇ next", "tvt_next": "show next document$NHotkey: K\">⬇ next",
"tvt_sel": "select file &nbsp; ( for cut / delete / ... )$NHotkey: S\">sel", "tvt_sel": "select file &nbsp; ( for cut / delete / ... )$NHotkey: S\">sel",
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
"gt_msel": "enable file selection; ctrl-click a file to override$N$N&lt;em&gt;when active: doubleclick a file / folder to open it&lt;/em&gt;$N$NHotkey: S\">multiselect", "gt_msel": "enable file selection; ctrl-click a file to override$N$N&lt;em&gt;when active: doubleclick a file / folder to open it&lt;/em&gt;$N$NHotkey: S\">multiselect",
"gt_zoom": "zoom", "gt_zoom": "zoom",
@@ -375,7 +377,10 @@ var Ls = {
"fu_xe1": "failed to load unpost list from server:\n\nerror ", "fu_xe1": "failed to load unpost list from server:\n\nerror ",
"fu_xe2": "404: File not found??", "fu_xe2": "404: File not found??",
"fz_tar": "plain gnutar file (linux / mac)", "fz_tar": "uncompressed gnu-tar file (linux / mac)",
"fz_pax": "uncompressed pax-format tar (slower)",
"fz_targz": "gnu-tar with gzip level 3 compression$N$Nthis is usually very slow, so$Nuse uncompressed tar instead",
"fz_tarxz": "gnu-tar with xz level 1 compression$N$Nthis is usually very slow, so$Nuse uncompressed tar instead",
"fz_zip8": "zip with utf8 filenames (maybe wonky on windows 7 and older)", "fz_zip8": "zip with utf8 filenames (maybe wonky on windows 7 and older)",
"fz_zipd": "zip with traditional cp437 filenames, for really old software", "fz_zipd": "zip with traditional cp437 filenames, for really old software",
"fz_zipc": "cp437 with crc32 computed early,$Nfor MS-DOS PKZIP v2.04g (october 1993)$N(takes longer to process before download can start)", "fz_zipc": "cp437 with crc32 computed early,$Nfor MS-DOS PKZIP v2.04g (october 1993)$N(takes longer to process before download can start)",
@@ -544,6 +549,7 @@ var Ls = {
"dokumentviser", "dokumentviser",
["I/K", "forr./neste fil"], ["I/K", "forr./neste fil"],
["M", "lukk tekstdokument"], ["M", "lukk tekstdokument"],
["E", "rediger tekstdokument"]
["S", "velg fil (for F2/ctrl-x/...)"] ["S", "velg fil (for F2/ctrl-x/...)"]
] ]
], ],
@@ -650,7 +656,7 @@ var Ls = {
"cl_hiddenc": "skjulte kolonner", "cl_hiddenc": "skjulte kolonner",
"cl_hidec": "skjul", "cl_hidec": "skjul",
"cl_reset": "nullstill", "cl_reset": "nullstill",
"cl_hpick": "klikk overskriften til kolonnen du ønsker å skjule i tabellen nedenfor", "cl_hpick": "klikk overskriften til kolonnene du ønsker å skjule i tabellen nedenfor",
"cl_hcancel": "kolonne-skjuling avbrutt", "cl_hcancel": "kolonne-skjuling avbrutt",
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T", "ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
@@ -786,11 +792,12 @@ var Ls = {
"tv_xe1": "kunne ikke laste tekstfil:\n\nfeil ", "tv_xe1": "kunne ikke laste tekstfil:\n\nfeil ",
"tv_xe2": "404, Fil ikke funnet", "tv_xe2": "404, Fil ikke funnet",
"tv_lst": "tekstfiler i mappen", "tv_lst": "tekstfiler i mappen",
"tvt_close": "gå tilbake til mappen$NSnarvei: M\">❌ close", "tvt_close": "gå tilbake til mappen$NSnarvei: M\">❌ lukk",
"tvt_dl": "last ned denne filen\">💾 last ned", "tvt_dl": "last ned denne filen\">💾 last ned",
"tvt_prev": "vis forrige dokument$NSnarvei: i\">⬆ prev", "tvt_prev": "vis forrige dokument$NSnarvei: i\">⬆ forr.",
"tvt_next": "vis neste dokument$NSnarvei: K\">⬇ next", "tvt_next": "vis neste dokument$NSnarvei: K\">⬇ neste",
"tvt_sel": "markér filen &nbsp; ( for utklipp / sletting / ... )$NSnarvei: S\">sel", "tvt_sel": "markér filen &nbsp; ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N&lt;em&gt;når aktiv: dobbelklikk en fil / mappe for å åpne&lt;/em&gt;$N$NSnarvei: S\">markering", "gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N&lt;em&gt;når aktiv: dobbelklikk en fil / mappe for å åpne&lt;/em&gt;$N$NSnarvei: S\">markering",
"gt_zoom": "zoom", "gt_zoom": "zoom",
@@ -840,6 +847,9 @@ var Ls = {
"fu_xe2": "404: Filen finnes ikke??", "fu_xe2": "404: Filen finnes ikke??",
"fz_tar": "ukomprimert gnu-tar arkiv, for linux og mac", "fz_tar": "ukomprimert gnu-tar arkiv, for linux og mac",
"fz_pax": "ukomprimert pax-tar arkiv, litt tregere",
"fz_targz": "gnu-tar pakket med gzip (nivå 3)$N$NNB: denne er veldig treg;$Nukomprimert tar er bedre",
"fz_tarxz": "gnu-tar pakket med xz (nivå 1)$N$NNB: denne er veldig treg;$Nukomprimert tar er bedre",
"fz_zip8": "zip med filnavn i utf8 (noe problematisk på windows 7 og eldre)", "fz_zip8": "zip med filnavn i utf8 (noe problematisk på windows 7 og eldre)",
"fz_zipd": "zip med filnavn i cp437, for høggamle maskiner", "fz_zipd": "zip med filnavn i cp437, for høggamle maskiner",
"fz_zipc": "cp437 med tidlig crc32,$Nfor MS-DOS PKZIP v2.04g (oktober 1993)$N(øker behandlingstid på server)", "fz_zipc": "cp437 med tidlig crc32,$Nfor MS-DOS PKZIP v2.04g (oktober 1993)$N(øker behandlingstid på server)",
@@ -931,12 +941,27 @@ var Ls = {
"lang_set": "passer det å laste siden på nytt?", "lang_set": "passer det å laste siden på nytt?",
}, },
}; };
var L = Ls[sread("lang") || lang]; var LANGS = ["eng", "nor"],
if (Ls.eng && L != Ls.eng) { L = Ls[sread("cpp_lang", LANGS) || lang] || Ls.eng || Ls.nor;
for (var k in Ls.eng)
if (!L[k]) for (var a = 0; a < LANGS.length; a++) {
L[k] = Ls.eng[k]; for (var b = a + 1; b < LANGS.length; b++) {
var i1 = Object.keys(Ls[LANGS[a]]).length > Object.keys(Ls[LANGS[b]]).length ? a : b,
i2 = i1 == a ? b : a,
t1 = Ls[LANGS[i1]],
t2 = Ls[LANGS[i2]];
for (var k in t1)
if (!t2[k]) {
console.log("E missing TL", LANGS[i2], k);
t2[k] = t1[k];
}
}
} }
if (!has(LANGS, lang))
alert('unsupported --lang "' + lang + '" specified in server args;\nplease use one of these: ' + LANGS);
modal.load(); modal.load();
@@ -1344,7 +1369,7 @@ var mpl = (function () {
'<div><h3>' + L.ml_eq + '</h3><div id="audio_eq"></div></div>'); '<div><h3>' + L.ml_eq + '</h3><div id="audio_eq"></div></div>');
var r = { var r = {
"pb_mode": (sread('pb_mode') || 'next').split('-')[0], "pb_mode": (sread('pb_mode', ['loop', 'next']) || 'next').split('-')[0],
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl, "os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
'traversals': 0, 'traversals': 0,
}; };
@@ -3049,7 +3074,7 @@ function eval_hash() {
goto('search'); goto('search');
var i = ebi('q_raw'); var i = ebi('q_raw');
i.value = uricom_dec(v.slice(3)); i.value = uricom_dec(v.slice(3));
return i.oninput(); return i.onkeydown({ 'key': 'Enter' });
} }
if (v.indexOf('#v=') === 0) { if (v.indexOf('#v=') === 0) {
@@ -3624,7 +3649,7 @@ var fileman = (function () {
if (!f.length) { if (!f.length) {
toast.ok(2, 'rename OK'); toast.ok(2, 'rename OK');
treectl.goto(get_evpath()); treectl.goto();
return rn_cancel(); return rn_cancel();
} }
@@ -3671,7 +3696,7 @@ var fileman = (function () {
if (err !== 'xbd') if (err !== 'xbd')
toast.ok(2, L.fd_ok); toast.ok(2, L.fd_ok);
treectl.goto(get_evpath()); treectl.goto();
return; return;
} }
toast.show('inf r', 0, esc(L.fd_busy.format(vps.length + 1, vp)), 'r'); toast.show('inf r', 0, esc(L.fd_busy.format(vps.length + 1, vp)), 'r');
@@ -3789,7 +3814,7 @@ var fileman = (function () {
if (!vp) { if (!vp) {
toast.ok(2, L.fp_ok); toast.ok(2, L.fp_ok);
treectl.goto(get_evpath()); treectl.goto();
r.tx(srcdir); r.tx(srcdir);
return; return;
} }
@@ -3905,9 +3930,9 @@ var showfile = (function () {
window.Prism = { 'manual': true }; window.Prism = { 'manual': true };
var em = QS('#bdoc>pre'); var em = QS('#bdoc>pre');
if (em) if (em)
em = [r.sname(window.location.search), location.hash, em.textContent]; em = [r.sname(location.search), location.hash, em.textContent];
else { else {
var m = /[?&]doc=([^&]+)/.exec(window.location.search); var m = /[?&]doc=([^&]+)/.exec(location.search);
if (m) { if (m) {
setTimeout(function () { setTimeout(function () {
r.show(uricom_dec(m[1]), true); r.show(uricom_dec(m[1]), true);
@@ -3927,7 +3952,7 @@ var showfile = (function () {
}; };
r.active = function () { r.active = function () {
return document.location.search.indexOf('doc=') + 1; return location.search.indexOf('doc=') + 1;
}; };
r.getlang = function (fn) { r.getlang = function (fn) {
@@ -4015,6 +4040,8 @@ var showfile = (function () {
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').style.display = (has(perms, 'write') && (is_md || has(perms, 'delete'))) ? '' : 'none';
var wr = ebi('bdoc'), var wr = ebi('bdoc'),
defer = !Prism.highlightElement; defer = !Prism.highlightElement;
@@ -4026,7 +4053,7 @@ var showfile = (function () {
el = el || QS('#doc>code'); el = el || QS('#doc>code');
Prism.highlightElement(el); Prism.highlightElement(el);
if (el.className == 'language-ans') if (el.className == 'language-ans' || (!lang && /\x1b\[[0-9;]{0,16}m/.exec(txt.slice(0, 4096))))
r.ansify(el); r.ansify(el);
} }
catch (ex) { } catch (ex) { }
@@ -4185,6 +4212,7 @@ var showfile = (function () {
'<a href="#" class="btn" id="prevdoc" tt="' + L.tvt_prev + '</a>\n' + '<a href="#" class="btn" id="prevdoc" tt="' + L.tvt_prev + '</a>\n' +
'<a href="#" class="btn" id="nextdoc" tt="' + L.tvt_next + '</a>\n' + '<a href="#" class="btn" id="nextdoc" tt="' + L.tvt_next + '</a>\n' +
'<a href="#" class="btn" id="seldoc" tt="' + L.tvt_sel + '</a>\n' + '<a href="#" class="btn" id="seldoc" tt="' + L.tvt_sel + '</a>\n' +
'<a href="#" class="btn" id="editdoc" tt="' + L.tvt_edit + '</a>\n' +
'</div>' '</div>'
); );
ebi('xdoc').onclick = function () { ebi('xdoc').onclick = function () {
@@ -4686,6 +4714,39 @@ function hkhelp() {
var fselgen, fselctr; var fselgen, fselctr;
function fselfunw(e, ae, d, rem) {
fselctr = 0;
var gen = fselgen = Date.now();
if (rem)
rem *= window.innerHeight;
var selfun = function () {
var el = ae[d + 'ElementSibling'];
if (!el || gen != fselgen)
return;
el.focus();
var elh = el.offsetHeight;
if (ctrl(e))
document.documentElement.scrollTop += (d == 'next' ? 1 : -1) * elh;
if (e.shiftKey) {
clmod(el, 'sel', 't');
msel.origin_tr(el);
msel.selui();
}
rem -= elh;
if (rem > 0) {
ae = document.activeElement;
if (++fselctr % 5 && rem > elh * (FIREFOX ? 5 : 2))
selfun();
else
setTimeout(selfun, 1);
}
}
selfun();
}
document.onkeydown = function (e) { document.onkeydown = function (e) {
if (e.altKey || e.isComposing) if (e.altKey || e.isComposing)
return; return;
@@ -4736,37 +4797,7 @@ document.onkeydown = function (e) {
if (k == 'PageUp') { d = 'previous'; rem = 0.6; } if (k == 'PageUp') { d = 'previous'; rem = 0.6; }
if (k == 'PageDown') { d = 'next'; rem = 0.6; } if (k == 'PageDown') { d = 'next'; rem = 0.6; }
if (d) { if (d) {
fselctr = 0; fselfunw(e, ae, d, rem);
var gen = fselgen = Date.now();
if (rem)
rem *= window.innerHeight;
function selfun() {
var el = ae[d + 'ElementSibling'];
if (!el || gen != fselgen)
return;
el.focus();
var elh = el.offsetHeight;
if (ctrl(e))
document.documentElement.scrollTop += (d == 'next' ? 1 : -1) * elh;
if (e.shiftKey) {
clmod(el, 'sel', 't');
msel.origin_tr(el);
msel.selui();
}
rem -= elh;
if (rem > 0) {
ae = document.activeElement;
if (++fselctr % 5 && rem > elh * (FIREFOX ? 5 : 2))
selfun();
else
setTimeout(selfun, 1);
}
}
selfun();
return ev(e); return ev(e);
} }
if (k == 'Space') { if (k == 'Space') {
@@ -4887,6 +4918,8 @@ document.onkeydown = function (e) {
if (showfile.active()) { if (showfile.active()) {
if (k == 'KeyS') if (k == 'KeyS')
showfile.tglsel(); showfile.tglsel();
if (k == 'KeyE' && ebi('editdoc').style.display != 'none')
ebi('editdoc').click();
} }
}; };
@@ -5253,7 +5286,10 @@ var filecolwidth = (function () {
return; return;
lastwidth = w; lastwidth = w;
document.documentElement.style.setProperty('--file-td-w', w + 'em'); try {
document.documentElement.style.setProperty('--file-td-w', w + 'em');
}
catch (ex) { }
} }
})(); })();
onresize100.add(filecolwidth, true); onresize100.add(filecolwidth, true);
@@ -5262,6 +5298,7 @@ onresize100.add(filecolwidth, true);
var treectl = (function () { var treectl = (function () {
var r = { var r = {
"hidden": true, "hidden": true,
"sb_msg": false,
"ls_cb": null, "ls_cb": null,
"dir_cb": tree_scrollto, "dir_cb": tree_scrollto,
"pdir": [] "pdir": []
@@ -5278,7 +5315,7 @@ var treectl = (function () {
bcfg_bind(r, 'dyn', 'dyntree', true, onresize); bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
bcfg_bind(r, 'csel', 'csel', false); bcfg_bind(r, 'csel', 'csel', false);
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) { bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
r.goto(get_evpath()); r.goto();
var xhr = new XHR(); var xhr = new XHR();
xhr.open('GET', SR + '/?setck=dots=' + (v ? 'y' : ''), true); xhr.open('GET', SR + '/?setck=dots=' + (v ? 'y' : ''), true);
xhr.send(); xhr.send();
@@ -5503,6 +5540,9 @@ var treectl = (function () {
}; };
r.goto = function (url, push, back) { r.goto = function (url, push, back) {
if (!url || !url.startsWith('/'))
url = get_evpath() + (url || '');
get_tree("", url, true); get_tree("", url, true);
r.reqls(url, push, back); r.reqls(url, push, back);
}; };
@@ -5681,7 +5721,7 @@ var treectl = (function () {
} }
var href = this.getAttribute('href'); var href = this.getAttribute('href');
if (R && !href.startsWith(SR)) { if (R && !href.startsWith(SR)) {
window.location = href; location = href;
return; return;
} }
r.reqls(href, true); r.reqls(href, true);
@@ -5700,6 +5740,7 @@ var treectl = (function () {
xhr.send(); xhr.send();
r.nvis = r.lim; r.nvis = r.lim;
r.sb_msg = false;
r.nextdir = xhr.top; r.nextdir = xhr.top;
enspin('#tree'); enspin('#tree');
enspin(thegrid.en ? '#gfiles' : '#files'); enspin(thegrid.en ? '#gfiles' : '#files');
@@ -5726,7 +5767,9 @@ var treectl = (function () {
return; return;
r.nextdir = null; r.nextdir = null;
var cur = ebi('files').getAttribute('ts'); var cdir = get_evpath(),
cur = ebi('files').getAttribute('ts');
if (cur && parseInt(cur) > this.ts) { if (cur && parseInt(cur) > this.ts) {
console.log("reject ls"); console.log("reject ls");
return; return;
@@ -5737,7 +5780,7 @@ var treectl = (function () {
var res = JSON.parse(this.responseText); var res = JSON.parse(this.responseText);
} }
catch (ex) { catch (ex) {
window.location = this.top; location = this.top;
return; return;
} }
@@ -5770,12 +5813,19 @@ var treectl = (function () {
despin('#files'); despin('#files');
despin('#gfiles'); despin('#gfiles');
sandbox(ebi('pro'), sb_lg, '', res.logues ? res.logues[0] || "" : ""); var lg0 = res.logues ? res.logues[0] || "" : "",
sandbox(ebi('epi'), sb_lg, '', res.logues ? res.logues[1] || "" : ""); lg1 = res.logues ? res.logues[1] || "" : "",
dirchg = get_evpath() != cdir;
sandbox(ebi('pro'), sb_lg, '', lg0);
if (dirchg)
sandbox(ebi('epi'), sb_lg, '', lg1);
clmod(ebi('epi'), 'mdo'); clmod(ebi('epi'), 'mdo');
if (res.readme) if (res.readme && treectl.ireadme)
show_readme(res.readme); show_readme(res.readme);
else if (!dirchg)
sandbox(ebi('epi'), sb_lg, '', lg1);
if (this.hpush && !this.back) { if (this.hpush && !this.back) {
var ofs = ebi('wrap').offsetTop; var ofs = ebi('wrap').offsetTop;
@@ -5797,7 +5847,7 @@ var treectl = (function () {
for (var a = 0; a < res.files.length; a++) for (var a = 0; a < res.files.length; a++)
if (/^index.html?(\?|$)/i.exec(res.files[a].href)) { if (/^index.html?(\?|$)/i.exec(res.files[a].href)) {
window.location = vjoin(top, res.files[a].href); location = vjoin(top, res.files[a].href);
return true; return true;
} }
}; };
@@ -6301,10 +6351,11 @@ var filecols = (function () {
var ths = QSA('#files>thead th>span'); var ths = QSA('#files>thead th>span');
for (var a = 0, aa = ths.length; a < aa; a++) { for (var a = 0, aa = ths.length; a < aa; a++) {
var th = ths[a].parentElement, var th = ths[a].parentElement,
toh = ths[a].outerHTML, // !ff10
ttv = L.cols[ths[a].textContent]; ttv = L.cols[ths[a].textContent];
if (!MOBILE) { if (!MOBILE && toh) {
th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + ths[a].outerHTML; th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + toh;
th.getElementsByTagName('a')[0].onclick = ev_row_tgl; th.getElementsByTagName('a')[0].onclick = ev_row_tgl;
} }
if (ttv) { if (ttv) {
@@ -6409,9 +6460,9 @@ var filecols = (function () {
ebi('hcolsh').onclick = function (e) { ebi('hcolsh').onclick = function (e) {
ev(e); ev(e);
if (r.picking) if (r.picking)
return r.unpick(false); return r.unpick();
var lbs = QSA('#files>thead th>span'); var lbs = QSA('#files>thead th');
for (var a = 0; a < lbs.length; a++) { for (var a = 0; a < lbs.length; a++) {
lbs[a].onclick = function (e) { lbs[a].onclick = function (e) {
ev(e); ev(e);
@@ -6419,19 +6470,20 @@ var filecols = (function () {
toast.hide(); toast.hide();
r.hide(e.target.textContent); r.hide(e.target.textContent);
r.unpick(true);
}; };
}; };
r.picking = true; r.picking = true;
clmod(ebi('files'), 'hhpick', 1);
toast.inf(0, L.cl_hpick, 'pickhide'); toast.inf(0, L.cl_hpick, 'pickhide');
}; };
r.unpick = function (picked) { r.unpick = function () {
r.picking = false; r.picking = false;
if (!picked) toast.inf(5, L.cl_hcancel);
toast.inf(5, L.cl_hcancel);
var lbs = QSA('#files>thead th>span'); clmod(ebi('files'), 'hhpick');
var lbs = QSA('#files>thead th');
for (var a = 0; a < lbs.length; a++) for (var a = 0; a < lbs.length; a++)
lbs[a].onclick = null; lbs[a].onclick = null;
}; };
@@ -6495,7 +6547,9 @@ var mukey = (function () {
"6d ", "7d ", "8d ", "9d ", "10d", "11d", "12d", "1d ", "2d ", "3d ", "4d ", "5d ", "6d ", "7d ", "8d ", "9d ", "10d", "11d", "12d", "1d ", "2d ", "3d ", "4d ", "5d ",
"6m ", "7m ", "8m ", "9m ", "10m", "11m", "12m", "1m ", "2m ", "3m ", "4m ", "5m " "6m ", "7m ", "8m ", "9m ", "10m", "11m", "12m", "1m ", "2m ", "3m ", "4m ", "5m "
] ]
}; },
defnot = 'rekobo_alnum';
var map = {}, var map = {},
html = []; html = [];
@@ -6520,7 +6574,7 @@ var mukey = (function () {
} }
function load_notation(notation) { function load_notation(notation) {
swrite("key_notation", notation); swrite("cpp_keynot", notation);
map = {}; map = {};
var dst = maps[notation]; var dst = maps[notation];
for (var k in maps) for (var k in maps)
@@ -6568,7 +6622,10 @@ var mukey = (function () {
} }
} }
var notation = sread("key_notation") || "rekobo_alnum"; var notation = sread("cpp_keynot") || defnot;
if (!maps[notation])
notation = defnot;
ebi('key_' + notation).checked = true; ebi('key_' + notation).checked = true;
load_notation(notation); load_notation(notation);
@@ -6587,7 +6644,7 @@ var light, theme, themen;
var settheme = (function () { var settheme = (function () {
var ax = 'abcdefghijklmnopqrstuvwx'; var ax = 'abcdefghijklmnopqrstuvwx';
theme = sread('theme') || 'a'; theme = sread('cpp_thm') || 'a';
if (!/^[a-x][yz]/.exec(theme)) if (!/^[a-x][yz]/.exec(theme))
theme = dtheme; theme = dtheme;
@@ -6629,7 +6686,7 @@ var settheme = (function () {
l = light ? 'y' : 'z'; l = light ? 'y' : 'z';
theme = c + l + ' ' + c + ' ' + l; theme = c + l + ' ' + c + ' ' + l;
themen = c + l; themen = c + l;
swrite('theme', theme); swrite('cpp_thm', theme);
freshen(); freshen();
} }
@@ -6640,7 +6697,7 @@ var settheme = (function () {
(function () { (function () {
function freshen() { function freshen() {
lang = sread("lang") || lang; lang = sread("cpp_lang", LANGS) || lang;
var html = []; var html = [];
for (var k in Ls) for (var k in Ls)
if (Ls.hasOwnProperty(k)) if (Ls.hasOwnProperty(k))
@@ -6656,7 +6713,7 @@ var settheme = (function () {
function setlang(e) { function setlang(e) {
ev(e); ev(e);
L = Ls[this.textContent]; L = Ls[this.textContent];
swrite("lang", this.textContent); swrite("cpp_lang", this.textContent);
freshen(); freshen();
modal.confirm(Ls.eng.lang_set + "\n\n" + Ls.nor.lang_set, location.reload.bind(location), null); modal.confirm(Ls.eng.lang_set + "\n\n" + Ls.nor.lang_set, location.reload.bind(location), null);
}; };
@@ -6672,6 +6729,9 @@ var arcfmt = (function () {
var html = [], var html = [],
fmts = [ fmts = [
["tar", "tar", L.fz_tar], ["tar", "tar", L.fz_tar],
["pax", "tar=pax", L.fz_pax],
["tgz", "tar=gz", L.fz_targz],
["txz", "tar=xz", L.fz_tarxz],
["zip", "zip=utf8", L.fz_zip8], ["zip", "zip=utf8", L.fz_zip8],
["zip_dos", "zip", L.fz_zipd], ["zip_dos", "zip", L.fz_zipd],
["zip_crc", "zip=crc", L.fz_zipc] ["zip_crc", "zip=crc", L.fz_zipc]
@@ -6701,7 +6761,7 @@ var arcfmt = (function () {
for (var a = 0, aa = tds.length; a < aa; a++) { for (var a = 0, aa = tds.length; a < aa; a++) {
var o = tds[a], txt = o.textContent, href = o.getAttribute('href'); var o = tds[a], txt = o.textContent, href = o.getAttribute('href');
if (txt != 'tar' && txt != 'zip') if (!/^(zip|tar|pax|tgz|txz)$/.exec(txt))
continue; continue;
var ofs = href.lastIndexOf('?'); var ofs = href.lastIndexOf('?');
@@ -7032,7 +7092,7 @@ var msel = (function () {
clmod(sf, 'vis'); clmod(sf, 'vis');
sf.textContent = 'sent: "' + this.msg + '"'; sf.textContent = 'sent: "' + this.msg + '"';
setTimeout(function () { setTimeout(function () {
treectl.goto(get_evpath()); treectl.goto();
}, 100); }, 100);
} }
})(); })();
@@ -7107,7 +7167,7 @@ function show_md(md, name, div, url, depth) {
wfp_debounce.hide(); wfp_debounce.hide();
if (!marked) { if (!marked) {
if (depth) if (depth)
return toast.warn(10, errmsg + 'failed to load marked.js') return toast.warn(10, errmsg + (window.WebAssembly ? 'failed to load marked.js' : 'your browser is too old'));
wfp_debounce.n--; wfp_debounce.n--;
return import_js(SR + '/.cpr/deps/marked.js', function () { return import_js(SR + '/.cpr/deps/marked.js', function () {
@@ -7224,7 +7284,7 @@ function sandbox(tgt, rules, cls, html) {
html = '<html class="iframe ' + document.documentElement.className + '"><head><style>' + globalcss() + html = '<html class="iframe ' + document.documentElement.className + '"><head><style>' + globalcss() +
'</style><base target="_parent"></head><body id="b" class="logue ' + cls + '">' + html + '</style><base target="_parent"></head><body id="b" class="logue ' + cls + '">' + html +
'<script>' + env + '</script>' + sandboxjs() + '<script>' + env + '</script>' + sandboxjs() +
'<script>var d=document.documentElement,' + '<script>var d=document.documentElement,TS="' + TS + '",' +
'loc=new URL("' + location.href.split('?')[0] + '");' + 'loc=new URL("' + location.href.split('?')[0] + '");' +
'function say(m){window.parent.postMessage(m,"*")};' + 'function say(m){window.parent.postMessage(m,"*")};' +
'setTimeout(function(){var its=0,pih=-1,f=function(){' + 'setTimeout(function(){var its=0,pih=-1,f=function(){' +
@@ -7234,6 +7294,7 @@ function sandbox(tgt, rules, cls, html) {
'};f();' + '};f();' +
'window.onfocus=function(){say("igot #' + tid + '")};' + 'window.onfocus=function(){say("igot #' + tid + '")};' +
'window.onblur=function(){say("ilost #' + tid + '")};' + 'window.onblur=function(){say("ilost #' + tid + '")};' +
'window.treectl={"goto":function(a){say("goto #' + tid + ' "+(a||""))}};' +
'var el="' + want + '"&&ebi("' + want + '");' + 'var el="' + want + '"&&ebi("' + want + '");' +
'if(el)say("iscroll #' + tid + ' "+el.offsetTop);' + 'if(el)say("iscroll #' + tid + ' "+el.offsetTop);' +
'md_th_set();' + 'md_th_set();' +
@@ -7247,16 +7308,24 @@ function sandbox(tgt, rules, cls, html) {
fr.setAttribute('title', 'folder ' + tid + 'logue'); fr.setAttribute('title', 'folder ' + tid + 'logue');
fr.setAttribute('sandbox', rules ? 'allow-' + rules.replace(/ /g, ' allow-') : ''); fr.setAttribute('sandbox', rules ? 'allow-' + rules.replace(/ /g, ' allow-') : '');
fr.setAttribute('srcdoc', html); fr.setAttribute('srcdoc', html);
tgt.innerHTML = '';
tgt.appendChild(fr); tgt.appendChild(fr);
treectl.sb_msg = true;
return true; return true;
} }
window.addEventListener("message", function (e) { window.addEventListener("message", function (e) {
if (!treectl.sb_msg)
return;
try { try {
console.log('msg:' + e.data); console.log('msg:' + e.data);
var t = e.data.split(/ /g); var t = e.data.split(/ /g);
if (t[0] == 'iheight') { if (t[0] == 'iheight') {
var el = QS(t[1] + '>iframe'); var el = QSA(t[1] + '>iframe');
el = el[el.length - 1];
if (wfp_debounce.n)
while (el.previousSibling)
el.parentNode.removeChild(el.previousSibling);
el.style.height = (parseInt(t[2]) + SBH) + 'px'; el.style.height = (parseInt(t[2]) + SBH) + 'px';
el.style.visibility = 'unset'; el.style.visibility = 'unset';
wfp_debounce.show(); wfp_debounce.show();
@@ -7273,6 +7342,10 @@ window.addEventListener("message", function (e) {
else if (t[0] == 'imshow') { else if (t[0] == 'imshow') {
thegrid.imshow(e.data.slice(7)); thegrid.imshow(e.data.slice(7));
} }
else if (t[0] == 'goto') {
var t = e.data.replace(/^[^ ]+ [^ ]+ /, '').split(/[?&]/)[0];
treectl.goto(t, !!t);
}
} catch (ex) { } catch (ex) {
console.log('msg-err: ' + ex); console.log('msg-err: ' + ex);
} }

View File

@@ -31,7 +31,7 @@
<span id="lno">L#</span> <span id="lno">L#</span>
{%- else %} {%- else %}
<a href="{{ arg_base }}edit" tt="good: higher performance$Ngood: same document width as viewer$Nbad: assumes you know markdown">edit (basic)</a> <a href="{{ arg_base }}edit" tt="good: higher performance$Ngood: same document width as viewer$Nbad: assumes you know markdown">edit (basic)</a>
<a href="{{ arg_base }}edit2" tt="not in-house so probably less buggy">edit (fancy)</a> <a href="{{ arg_base }}edit2" id="edit2" tt="not in-house so probably less buggy">edit (fancy)</a>
<a href="{{ arg_base }}">view raw</a> <a href="{{ arg_base }}">view raw</a>
{%- endif %} {%- endif %}
</div> </div>

View File

@@ -52,7 +52,7 @@ var img_load = (function () {
var r = {}; var r = {};
r.callbacks = []; r.callbacks = [];
function fire() { var fire = function () {
for (var a = 0; a < r.callbacks.length; a++) for (var a = 0; a < r.callbacks.length; a++)
r.callbacks[a](); r.callbacks[a]();
} }
@@ -472,7 +472,7 @@ img_load.callbacks = [toc.refresh];
// scroll handler // scroll handler
var redraw = (function () { var redraw = (function () {
var sbs = true; var sbs = true;
function onresize() { var onresize = function () {
if (window.matchMedia) if (window.matchMedia)
sbs = window.matchMedia('(min-width: 64em)').matches; sbs = window.matchMedia('(min-width: 64em)').matches;
@@ -485,7 +485,7 @@ var redraw = (function () {
onscroll(); onscroll();
} }
function onscroll() { var onscroll = function () {
toc.refresh(); toc.refresh();
} }
@@ -507,6 +507,13 @@ dom_navtgl.onclick = function () {
redraw(); redraw();
}; };
if (!HTTPS && location.hostname != '127.0.0.1') try {
ebi('edit2').onclick = function (e) {
toast.err(0, "the fancy editor is only available over https");
return ev(e);
}
} catch (ex) { }
if (sread('hidenav') == 1) if (sread('hidenav') == 1)
dom_navtgl.onclick(); dom_navtgl.onclick();

View File

@@ -92,7 +92,7 @@ var action_stack = null;
var nlines = 0; var nlines = 0;
var draw_md = (function () { var draw_md = (function () {
var delay = 1; var delay = 1;
function draw_md() { var draw_md = function () {
var t0 = Date.now(); var t0 = Date.now();
var src = dom_src.value; var src = dom_src.value;
convert_markdown(src, dom_pre); convert_markdown(src, dom_pre);
@@ -135,7 +135,7 @@ img_load.callbacks = [function () {
// resize handler // resize handler
redraw = (function () { redraw = (function () {
function onresize() { var onresize = function () {
var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px'; var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px';
dom_wrap.style.top = y; dom_wrap.style.top = y;
dom_swrap.style.top = y; dom_swrap.style.top = y;
@@ -143,12 +143,12 @@ redraw = (function () {
map_src = genmap(dom_ref, map_src); map_src = genmap(dom_ref, map_src);
map_pre = genmap(dom_pre, map_pre); map_pre = genmap(dom_pre, map_pre);
} }
function setsbs() { var setsbs = function () {
dom_wrap.className = ''; dom_wrap.className = '';
dom_swrap.className = ''; dom_swrap.className = '';
onresize(); onresize();
} }
function modetoggle() { var modetoggle = function () {
var mode = dom_nsbs.innerHTML; var mode = dom_nsbs.innerHTML;
dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor'; dom_nsbs.innerHTML = mode == 'editor' ? 'preview' : 'editor';
mode += ' single'; mode += ' single';
@@ -172,7 +172,7 @@ redraw = (function () {
(function () { (function () {
var skip_src = false, skip_pre = false; var skip_src = false, skip_pre = false;
function scroll(src, srcmap, dst, dstmap) { var scroll = function (src, srcmap, dst, dstmap) {
var y = src.scrollTop; var y = src.scrollTop;
if (y < 8) { if (y < 8) {
dst.scrollTop = 0; dst.scrollTop = 0;
@@ -900,12 +900,12 @@ var set_lno = (function () {
pv = null, pv = null,
lno = ebi('lno'); lno = ebi('lno');
function poke() { var poke = function () {
clearTimeout(t); clearTimeout(t);
t = setTimeout(fire, 20); t = setTimeout(fire, 20);
} }
function fire() { var fire = function () {
try { try {
clearTimeout(t); clearTimeout(t);
@@ -930,7 +930,7 @@ var set_lno = (function () {
// hotkeys / toolbar // hotkeys / toolbar
(function () { (function () {
function keydown(ev) { var keydown = function (ev) {
ev = ev || window.event; ev = ev || window.event;
var kc = ev.code || ev.keyCode || ev.which, var kc = ev.code || ev.keyCode || ev.which,
editing = document.activeElement == dom_src; editing = document.activeElement == dom_src;
@@ -1058,7 +1058,7 @@ action_stack = (function () {
var ignore = false; var ignore = false;
var ref = dom_src.value; var ref = dom_src.value;
function diff(from, to, cpos) { var diff = function (from, to, cpos) {
if (from === to) if (from === to)
return null; return null;
@@ -1089,14 +1089,14 @@ action_stack = (function () {
}; };
} }
function undiff(from, change) { var undiff = function (from, change) {
return { return {
txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr), txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr),
cpos: change.cpos cpos: change.cpos
}; };
} }
function apply(src, dst) { var apply = function (src, dst) {
dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length); dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
if (src.length === 0) if (src.length === 0)
@@ -1120,7 +1120,7 @@ action_stack = (function () {
return true; return true;
} }
function schedule_push() { var schedule_push = function () {
if (ignore) { if (ignore) {
ignore = false; ignore = false;
return; return;
@@ -1131,7 +1131,7 @@ action_stack = (function () {
sched_timer = setTimeout(push, 500); sched_timer = setTimeout(push, 500);
} }
function undo() { var undo = function () {
if (hist.re.length == 0) { if (hist.re.length == 0) {
clearTimeout(sched_timer); clearTimeout(sched_timer);
push(); push();
@@ -1139,11 +1139,11 @@ action_stack = (function () {
return apply(hist.un, hist.re); return apply(hist.un, hist.re);
} }
function redo() { var redo = function () {
return apply(hist.re, hist.un); return apply(hist.re, hist.un);
} }
function push() { var push = function () {
var newtxt = dom_src.value; var newtxt = dom_src.value;
var change = diff(ref, newtxt, sched_cpos); var change = diff(ref, newtxt, sched_cpos);
if (change !== null) if (change !== null)

View File

@@ -42,7 +42,7 @@
{%- if redir %} {%- if redir %}
<script> <script>
setTimeout(function() { setTimeout(function() {
window.location.replace("{{ redir }}"); location.replace("{{ redir }}");
}, 1000); }, 1000);
</script> </script>
{%- endif %} {%- endif %}

View File

@@ -110,7 +110,7 @@ var SR = {{ r|tojson }},
lang="{{ lang }}", lang="{{ lang }}",
dfavico="{{ favico }}"; dfavico="{{ favico }}";
document.documentElement.className=localStorage.theme||"{{ this.args.theme }}"; document.documentElement.className=localStorage.cpp_thm||"{{ this.args.theme }}";
</script> </script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -35,7 +35,7 @@ var Ls = {
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!", "v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
} }
}, },
d = Ls[sread("lang") || lang]; d = Ls[sread("cpp_lang", ["eng", "nor"]) || lang] || Ls.eng || Ls.nor;
for (var k in (d || {})) { for (var k in (d || {})) {
var f = k.slice(-1), var f = k.slice(-1),

View File

@@ -218,7 +218,7 @@ var SR = {{ r|tojson }},
lang="{{ lang }}", lang="{{ lang }}",
dfavico="{{ favico }}"; dfavico="{{ favico }}";
document.documentElement.className=localStorage.theme||"{{ args.theme }}"; document.documentElement.className=localStorage.cpp_thm||"{{ args.theme }}";
</script> </script>
<script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script> <script src="{{ r }}/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -588,7 +588,7 @@ function U2pvis(act, btns, uc, st) {
btns[a].onclick = function (e) { btns[a].onclick = function (e) {
ev(e); ev(e);
var newtab = this.getAttribute('act'); var newtab = this.getAttribute('act');
function go() { var go = function () {
for (var b = 0; b < btns.length; b++) { for (var b = 0; b < btns.length; b++) {
btns[b].className = ( btns[b].className = (
btns[b].getAttribute('act') == newtab) ? 'act' : ''; btns[b].getAttribute('act') == newtab) ? 'act' : '';
@@ -807,7 +807,7 @@ function up2k_init(subtle) {
function init_deps() { function init_deps() {
if (!loading_deps && !got_deps()) { if (!loading_deps && !got_deps()) {
var fn = 'sha512.' + sha_js + '.js', var fn = 'sha512.' + sha_js + '.js',
m = L.u_https1 + ' <a href="' + (window.location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3; m = L.u_https1 + ' <a href="' + (location + '').replace(':', 's:') + '">' + L.u_https2 + '</a> ' + L.u_https3;
showmodal('<h1>loading ' + fn + '</h1>'); showmodal('<h1>loading ' + fn + '</h1>');
import_js(SR + '/.cpr/deps/' + fn, unmodal); import_js(SR + '/.cpr/deps/' + fn, unmodal);
@@ -1588,7 +1588,7 @@ function up2k_init(subtle) {
return; return;
st.oserr = true; st.oserr = true;
var msg = HTTPS ? L.u_emtleak3 : L.u_emtleak2.format((window.location + '').replace(':', 's:')); var msg = HTTPS ? L.u_emtleak3 : L.u_emtleak2.format((location + '').replace(':', 's:'));
modal.alert(L.u_emtleak1 + msg + (CHROME ? L.u_emtleakc : FIREFOX ? L.u_emtleakf : '')); modal.alert(L.u_emtleak1 + msg + (CHROME ? L.u_emtleakc : FIREFOX ? L.u_emtleakf : ''));
} }
@@ -1654,11 +1654,11 @@ function up2k_init(subtle) {
var running = false, var running = false,
was_busy = false; was_busy = false;
function defer() { var defer = function () {
running = false; running = false;
} }
function taskerd() { var taskerd = function () {
if (running) if (running)
return; return;
@@ -1688,7 +1688,7 @@ function up2k_init(subtle) {
is_busy = st.todo.handshake.length; is_busy = st.todo.handshake.length;
try { try {
if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused)) if (!is_busy && !uc.fsearch && !msel.getsel().length && (!mp.au || mp.au.paused))
treectl.goto(get_evpath()); treectl.goto();
} }
catch (ex) { } catch (ex) { }
} }
@@ -1956,7 +1956,7 @@ function up2k_init(subtle) {
st.bytes.hashed += cdr - car; st.bytes.hashed += cdr - car;
st.etac.h++; st.etac.h++;
function orz(e) { var orz = function (e) {
bpend--; bpend--;
segm_next(); segm_next();
hash_calc(nch, e.target.result); hash_calc(nch, e.target.result);
@@ -2160,6 +2160,9 @@ function up2k_init(subtle) {
function exec_head() { function exec_head() {
var t = st.todo.head.shift(); var t = st.todo.head.shift();
if (t.done)
return console.log('done; skip head1', t.name, t);
st.busy.head.push(t); st.busy.head.push(t);
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@@ -2172,6 +2175,9 @@ function up2k_init(subtle) {
st.todo.head.unshift(t); st.todo.head.unshift(t);
}; };
function orz(e) { function orz(e) {
if (t.done)
return console.log('done; skip head2', t.name, t);
var ok = false; var ok = false;
if (xhr.status == 200) { if (xhr.status == 200) {
var srv_sz = xhr.getResponseHeader('Content-Length'), var srv_sz = xhr.getResponseHeader('Content-Length'),
@@ -2218,6 +2224,9 @@ function up2k_init(subtle) {
keepalive = t.keepalive, keepalive = t.keepalive,
me = Date.now(); me = Date.now();
if (t.done)
return console.log('done; skip hs', t.name, t);
st.busy.handshake.push(t); st.busy.handshake.push(t);
t.keepalive = undefined; t.keepalive = undefined;
t.t_busied = me; t.t_busied = me;
@@ -2227,10 +2236,9 @@ function up2k_init(subtle) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onerror = function () { xhr.onerror = function () {
if (t.t_busied != me) { if (t.t_busied != me) // t.done ok
console.log('zombie handshake onerror,', t.name, t); return console.log('zombie handshake onerror', t.name, t);
return;
}
if (!toast.visible) if (!toast.visible)
toast.warn(9.98, L.u_eneths + "\n\nfile: " + t.name, t); toast.warn(9.98, L.u_eneths + "\n\nfile: " + t.name, t);
@@ -2239,11 +2247,10 @@ function up2k_init(subtle) {
st.todo.handshake.unshift(t); st.todo.handshake.unshift(t);
t.keepalive = keepalive; t.keepalive = keepalive;
}; };
function orz(e) { var orz = function (e) {
if (t.t_busied != me) { if (t.t_busied != me || t.done)
console.log('zombie handshake onload,', t.name, t); return console.log('zombie handshake onload', t.name, t);
return;
}
if (xhr.status == 200) { if (xhr.status == 200) {
t.t_handshake = Date.now(); t.t_handshake = Date.now();
if (keepalive) { if (keepalive) {
@@ -2503,14 +2510,17 @@ function up2k_init(subtle) {
} }
function exec_upload() { function exec_upload() {
var upt = st.todo.upload.shift(); var upt = st.todo.upload.shift(),
t = st.files[upt.nfile],
npart = upt.npart,
tries = 0;
if (t.done)
return console.log('done; skip chunk', t.name, t);
st.busy.upload.push(upt); st.busy.upload.push(upt);
st.nfile.upload = upt.nfile; st.nfile.upload = upt.nfile;
var npart = upt.npart,
t = st.files[upt.nfile],
tries = 0;
if (!t.t_uploading) if (!t.t_uploading)
t.t_uploading = Date.now(); t.t_uploading = Date.now();
@@ -2523,7 +2533,7 @@ function up2k_init(subtle) {
if (cdr >= t.size) if (cdr >= t.size)
cdr = t.size; cdr = t.size;
function orz(xhr) { var orz = function (xhr) {
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + ''; var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
if (txt.indexOf('upload blocked by x') + 1) { if (txt.indexOf('upload blocked by x') + 1) {
apop(st.busy.upload, upt); apop(st.busy.upload, upt);
@@ -2552,7 +2562,7 @@ function up2k_init(subtle) {
} }
orz2(xhr); orz2(xhr);
} }
function orz2(xhr) { var orz2 = function (xhr) {
apop(st.busy.upload, upt); apop(st.busy.upload, upt);
apop(t.postlist, npart); apop(t.postlist, npart);
if (!t.postlist.length) { if (!t.postlist.length) {

View File

@@ -13,7 +13,7 @@ var wah = '',
R = SR.slice(1), R = SR.slice(1),
RS = R ? "/" + R : "", RS = R ? "/" + R : "",
HALFMAX = 8192 * 8192 * 8192 * 8192, HALFMAX = 8192 * 8192 * 8192 * 8192,
HTTPS = (window.location + '').indexOf('https:') === 0, HTTPS = ('' + location).indexOf('https:') === 0,
TOUCH = 'ontouchstart' in window, TOUCH = 'ontouchstart' in window,
MOBILE = TOUCH, MOBILE = TOUCH,
CHROME = !!window.chrome, CHROME = !!window.chrome,
@@ -140,29 +140,35 @@ catch (ex) {
} }
var crashed = false, ignexd = {}, evalex_fatal = false; var crashed = false, ignexd = {}, evalex_fatal = false;
function vis_exh(msg, url, lineNo, columnNo, error) { function vis_exh(msg, url, lineNo, columnNo, error) {
if ((msg + '').indexOf('ResizeObserver') + 1) msg = String(msg);
url = String(url);
if (msg.indexOf('ResizeObserver') + 1)
return; // chrome issue 809574 (benign, from <video>) return; // chrome issue 809574 (benign, from <video>)
if ((msg + '').indexOf('l2d.js') + 1) if (msg.indexOf('l2d.js') + 1)
return; // `t` undefined in tapEvent -> hitTestSimpleCustom return; // `t` undefined in tapEvent -> hitTestSimpleCustom
if (!/\.js($|\?)/.exec('' + url)) if (!/\.js($|\?)/.exec(url))
return; // chrome debugger return; // chrome debugger
if ((url + '').indexOf(' > eval') + 1 && !evalex_fatal) if (url.indexOf(' > eval') + 1 && !evalex_fatal)
return; // md timer return; // md timer
var ekey = url + '\n' + lineNo + '\n' + msg; var ekey = url + '\n' + lineNo + '\n' + msg;
if (ignexd[ekey] || crashed) if (ignexd[ekey] || crashed)
return; return;
if (url.indexOf('deps/marked.js') + 1 && !window.WebAssembly)
return; // ff<52
crashed = true; crashed = true;
window.onerror = undefined; window.onerror = undefined;
var html = [ var html = [
'<h1>you hit a bug!</h1>', '<h1>you hit a bug!</h1>',
'<p style="font-size:1.3em;margin:0;line-height:2em">try to <a href="#" onclick="localStorage.clear();location.reload();">reset copyparty settings</a> if you are stuck here, or <a href="#" onclick="ignex();">ignore this</a> / <a href="#" onclick="ignex(true);">ignore all</a> / <a href="?b=u">basic</a></p>', '<p style="font-size:1.3em;margin:0;line-height:2em">try to <a href="#" onclick="localStorage.clear();location.reload();">reset copyparty settings</a> if you are stuck here, or <a href="#" onclick="ignex();">ignore this</a> / <a href="#" onclick="ignex(true);">ignore all</a> / <a href="?b=u">basic</a></p>',
'<p style="color:#fff">please send me a screenshot arigathanks gozaimuch: <a href="<ghi>" target="_blank">new github issue</a></p>', '<p style="color:#fff">please send me a screenshot arigathanks gozaimuch: <a href="<ghi>" target="_blank">new github issue</a></p>',
'<p class="b">' + esc(url + ' @' + lineNo + ':' + columnNo), '<br />' + esc(String(msg)).replace(/\n/g, '<br />') + '</p>', '<p class="b">' + esc(url + ' @' + lineNo + ':' + columnNo), '<br />' + esc(msg).replace(/\n/g, '<br />') + '</p>',
'<p><b>UA:</b> ' + esc(navigator.userAgent + '') '<p><b>UA:</b> ' + esc(navigator.userAgent + '')
]; ];
@@ -354,13 +360,13 @@ catch (ex) {
} }
// https://stackoverflow.com/a/950146 // https://stackoverflow.com/a/950146
function import_js(url, cb) { function import_js(url, cb, ecb) {
var head = document.head || document.getElementsByTagName('head')[0]; var head = document.head || document.getElementsByTagName('head')[0];
var script = mknod('script'); var script = mknod('script');
script.type = 'text/javascript'; script.type = 'text/javascript';
script.src = url; script.src = url + '?_=' + (window.TS || 'a');
script.onload = cb; script.onload = cb;
script.onerror = function () { script.onerror = ecb || function () {
var m = 'Failed to load module:\n' + url; var m = 'Failed to load module:\n' + url;
console.log(m); console.log(m);
toast.err(0, m); toast.err(0, m);
@@ -876,9 +882,10 @@ function jcp(obj) {
} }
function sread(key) { function sread(key, al) {
try { try {
return localStorage.getItem(key); var ret = localStorage.getItem(key);
return (!al || has(al, ret)) ? ret : null;
} }
catch (e) { catch (e) {
return null; return null;
@@ -900,7 +907,13 @@ function jread(key, fb) {
if (!str) if (!str)
return fb; return fb;
return JSON.parse(str); try {
// '' throws, null is ok, sasuga
return JSON.parse(str);
}
catch (e) {
return fb;
}
} }
function jwrite(key, val) { function jwrite(key, val) {
@@ -1141,7 +1154,7 @@ var timer = (function () {
apop(r.q, fun); apop(r.q, fun);
}; };
function doevents() { var doevents = function () {
if (crashed) if (crashed)
return; return;
@@ -1352,7 +1365,7 @@ var toast = (function () {
r.p_sec = 0; r.p_sec = 0;
r.p_t = 0; r.p_t = 0;
function scrollchk() { var scrollchk = function () {
if (scrolling) if (scrolling)
return; return;
@@ -1367,7 +1380,7 @@ var toast = (function () {
scrolling = true; scrolling = true;
} }
function unscroll() { var unscroll = function () {
timer.rm(scrollchk); timer.rm(scrollchk);
clmod(obj, 'scroll'); clmod(obj, 'scroll');
scrolling = false; scrolling = false;
@@ -1486,7 +1499,7 @@ var modal = (function () {
r.busy = false; r.busy = false;
setTimeout(next, 50); setTimeout(next, 50);
}; };
function ok(e) { var ok = function (e) {
ev(e); ev(e);
var v = ebi('modali'); var v = ebi('modali');
v = v ? v.value : true; v = v ? v.value : true;
@@ -1494,14 +1507,14 @@ var modal = (function () {
if (cb_ok) if (cb_ok)
cb_ok(v); cb_ok(v);
} }
function ng(e) { var ng = function (e) {
ev(e); ev(e);
r.hide(); r.hide();
if (cb_ng) if (cb_ng)
cb_ng(null); cb_ng(null);
} }
function onfocus(e) { var onfocus = function (e) {
var ctr = ebi('modalc'); var ctr = ebi('modalc');
if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement)) if (!ctr || !ctr.contains || !document.activeElement || ctr.contains(document.activeElement))
return; return;
@@ -1513,7 +1526,7 @@ var modal = (function () {
ev(e); ev(e);
} }
function onkey(e) { var onkey = function (e) {
var k = e.code, var k = e.code,
eok = ebi('modal-ok'), eok = ebi('modal-ok'),
eng = ebi('modal-ng'), eng = ebi('modal-ng'),
@@ -1536,7 +1549,7 @@ var modal = (function () {
return ng(); return ng();
} }
function next() { var next = function () {
if (!r.busy && q.length) if (!r.busy && q.length)
q.shift()(); q.shift()();
} }
@@ -1547,7 +1560,7 @@ var modal = (function () {
}); });
next(); next();
}; };
function _alert(html, cb, fun) { var _alert = function (html, cb, fun) {
cb_ok = cb_ng = cb; cb_ok = cb_ng = cb;
cb_up = fun; cb_up = fun;
html += '<div id="modalb"><a href="#" id="modal-ok">OK</a></div>'; html += '<div id="modalb"><a href="#" id="modal-ok">OK</a></div>';
@@ -1560,7 +1573,7 @@ var modal = (function () {
}); });
next(); next();
} }
function _confirm(html, cok, cng, fun, btns) { var _confirm = function (html, cok, cng, fun, btns) {
cb_ok = cok; cb_ok = cok;
cb_ng = cng === undefined ? cok : cng; cb_ng = cng === undefined ? cok : cng;
cb_up = fun; cb_up = fun;
@@ -1574,7 +1587,7 @@ var modal = (function () {
}); });
next(); next();
} }
function _prompt(html, v, cok, cng, fun) { var _prompt = function (html, v, cok, cng, fun) {
cb_ok = cok; cb_ok = cok;
cb_ng = cng === undefined ? cok : null; cb_ng = cng === undefined ? cok : null;
cb_up = fun; cb_up = fun;
@@ -1706,7 +1719,7 @@ function load_md_plug(md_text, plug_type, defer) {
return md_text; return md_text;
var find = '\n```copyparty_' + plug_type + '\n', var find = '\n```copyparty_' + plug_type + '\n',
md = md_text.replace(/\r/g, ''), md = '\n' + md_text.replace(/\r/g, '') + '\n',
ofs = md.indexOf(find), ofs = md.indexOf(find),
ofs2 = md.indexOf('\n```', ofs + 1); ofs2 = md.indexOf('\n```', ofs + 1);
@@ -1793,7 +1806,7 @@ var favico = (function () {
r.en = true; r.en = true;
r.tag = null; r.tag = null;
function gx(txt) { var gx = function (txt) {
return (svg_decl + return (svg_decl +
'<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">\n' + '<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">\n' +
(r.bg ? '<rect width="100%" height="100%" rx="16" fill="#' + r.bg + '" />\n' : '') + (r.bg ? '<rect width="100%" height="100%" rx="16" fill="#' + r.bg + '" />\n' : '') +

View File

@@ -1,3 +1,160 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-1008-2051 `v1.9.10` badpwd
## new features
* argument `--log-badpwd` specifies how to log invalid login attempts;
* `0` = just a warning with no further information
* `1` = log incorrect password in plaintext (default)
* `2` = log sha512 hash of the incorrect password
* `1` and `2` are convenient for stuff like setting up autoban triggers for common passwords using fail2ban or similar
## bugfixes
* none!
* the formerly mentioned caching-directives bug turned out to be unreachable... oh well, better safe than sorry
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-1007-2229 `v1.9.9` fix cross-volume dedup moves
## bugfixes
* v1.6.2 introduced a bug which, when moving files between volumes, could cause the move operation to abort when it encounters a deduplicated file
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-1006-1750 `v1.9.8` static filekeys
## new features
* #52 add alternative filekey generator:
* volflag `fka` changes the calculation to ignore filesize and inode-number, only caring about the absolute-path on the filesystem and the `--fk-salt`
* good for linking to markdown files which might be edited, but reduces security a tiny bit
* add warning on startup if `--fk-salt` is too weak (for example when it was upgraded from before [v1.7.6](https://github.com/9001/copyparty/releases/tag/v1.7.6))
* removed the filekey upgrade feaure to ensure a weak fk-salt is not selected; a new filekey will be generated from scratch on startup if necessary
## other changes
* pyftpdlib upgraded to 1.5.8
* copyparty.exe built on python 3.11.6
* the exe in this release will be replaced with an 3.12.0 exe as soon as [pillow adds 3.12 support](https://github.com/python-pillow/Pillow/issues/6941)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0930-2332 `v1.9.7` better column hider
## new features
* column hiding on phones is much more intuitive
* since you usually want to hide multiple columns, the hiding mode must now be manually disengaged
* click-handler now covers the entire header cell, preventing a misclick from accidentally sorting the table instead
## bugfixes
* #51 running copyparty with an invalid value for `--lang` made it crash with a confusing error message
* also makes it more compatible with other localStorage-using webservices running on the same domain
## other changes
* CVE-2023-5217, a vulnerability in libvpx, was fixed by alpine recently and no longer present in the docker images
* unlike the fix in v1.9.6, this is irrelevant since it was impossible to reach in all conceivable setups, but still nice
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0923-1215 `v1.9.6` configurable x-forwarded-for
## new features
* rudimentary support for jython and graalpy, and directory tree sidebar in internet explorer 9 through 11, and firefox 10
* all older browsers (ie4, ie6, ie8, Netscape) get basic html instead
* #35 adds a [hook](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/msg-log.py) which extends the message-to-serverlog feature so it writes the message to a textfile on the server
* could theoretically be extended into a [full instant-messaging feature](https://github.com/9001/copyparty/blob/hovudstraum/srv/chat.md) but that's silly, [nobody would do that](https://ocv.me/stuff/cchat.webm)
* [r0c is much better](https://github.com/9001/r0c) than this joke
## bugfixes
* 163e3fce46122d64bf824762b6733ff2c3551ba5 the `x-forwarded-for` header was ignored if the nearest reverse-proxy is not asking from 127.0.0.1, which broke client IPs in containerized deployments
* the serverlog will now explain how to trust the reverse-proxy to provide client IPs, but basically,
* `--xff-hdr` specifies which header to read the client's real ip from
* `--xff-src` is an allowlist of IP-addresses to trust that header from
* a62f744a187bc9f821b540e8bb4e0b9a67bd01c8 if copyparty was started while an external HDD was not connected, and that volume's index was stored elsewhere, then the index would get wiped (since all the files are gone)
* 3b8f66c0d5c27a68841814ec06f1758f146a5ff5 javascript could crash while uploading from a very unreliable internet connection
## other changes
* copyparty.exe: updated pillow to 10.0.1 which fixes the webp cve
* alpine, which the docker images are based on, turns out to be fairly slow -- currently working on a new docker image (probably fedora-based) which will be 30% faster at analyzing multimedia files and in general 20% faster on average
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0909-1336 `v1.9.5` webhotell
[happy 9/9!](https://safebooru.org/index.php?page=post&s=view&id=4027419)
## new features
* new permission `h` disables directory listing (so works like `g`) except it redirects to the folder's index.html instead of 404
* index.html is accessible by anyone with `h` even if filekeys are enabled
* well suited for running a shared-webhosting gig (thx kipu) especially now that the...
* markdown editor can now be used on non-markdown files if account has `w`rite and `d`elete
* hotkey `e` to edit a textfile while it's open in the textfile viewer
* SMB: account permissions now work fully as intended, thanks to impacket 0.11
* but enabling `--smb` is still strongly discouraged as it's a massive security hazard
* download-as-zip can be 2.5x faster on tiny files, at least 15% faster in general
* download folders as pax-format tarfiles with `?tar=pax` or `?tar=pax,xz:9`
## bugfixes
* 422-autoban accidentally triggered when uploading lots of duplicate files (thx hiem!)
* `--css-browser` and `--js-browser` now accepts URLs with cache directives
* `--css-browser=/the.css?cache=600` (seconds) or `--js-browser=/.res/the.js?cache=i` (7 days)
* SMB: avoid windows freaking out and disconnecting if it hits an offline volume
* hotkey shift-r to rotate pictures counter-clockwise didn't do anything
* hacker theme wasn't hacker enough (everything is monospace now)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0902-0018 `v1.9.4` yes symlink times
hello! it's been a while, an entire day even...
## new features
* download folder as tar.gz, tar.bz2, tar.xz
* single-threaded, so extremely slow, but nice for easily compressed data or challenged networks
* append `?tar=gz`, `?tar=bz2` or `?tar=xz` to a folder URL to do it
* default compression levels are gz:3, bz2:2, xz:1; override with `?tar=gz:9`
# bugfixes
* c1efd227b7377144a5760bc6cff64f4e87b626d9 symlink-deduplicated files got indexed with the wrong last-modified timestamp
* mostly inconsequential; would cause the dupe's uploader-ip to be forgotten on the next server restart since it would reindex to "fix" the timestamp
* when linking [a search query](https://a.ocv.me/pub/#q=tags%20like%20soundsho*) it loads the results faster
# other changes
* update readme to mention that iPhones and iPads dislike the preload feature and respond by glitching the audio a bit when a song is exactly 20 seconds away from ending and yet how it's probably a bad idea to disable preloading since i bet it's load-bearing against other iOS bugs
* speaking of iPhones and iPads, the [previous version](https://github.com/9001/copyparty/releases/tag/v1.9.3) should have fixed album playback on those
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0831-2211 `v1.9.3` iOS and http fixes
## new features
* iPhones and iPads are now able to...
* 9986136dfb2364edb35aa9fbb87410641c6d6af3 play entire albums while the screen is off without the music randomly stopping
* apple keeps breaking AudioContext in new and interesting ways; time to give up (no more equalizer)
* 1c0d978979a703edeb792e552b18d3b7695b2d90 perform search queries and execude js code
* by translating [smart-quotes](https://stackoverflow.com/questions/48678359/ios-11-safari-html-disable-smart-punctuation) into regular `'` and `"` characters
* python 3.12 support
* technically a bugfix since it was added [a year ago](https://github.com/9001/copyparty/commit/32e22dfe84d5e0b13914b4d0e15c1b8c9725a76d) way before the first py3.12 alpha was released but turns out i botched it, oh well
* filter error messages so they never include the filesystem path where copyparty's python files reside
* print more context in server logs if someone hits an unexpected permission-denied
# bugfixes
found some iffy stuff combing over the code but, as far as I can tell, luckily none of these were dangerous:
* URL normalization was a bit funky, but it appears everything access-control-related was unaffected
* some url parameters were double-decoded, causing the unpost filtering and file renaming to fail if the values contained `%`
* clients could cause the server to return an invalid cache-control header, but newlines and control-characters got rejected correctly
* minor cosmetics / qol fixes:
* reduced flickering on page load in chrome
* fixed some console spam in search results
* markdown documents now have the same line-height in directory listings and the editor
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2023-0826-2116 `v1.9.2` bigger hammer # 2023-0826-2116 `v1.9.2` bigger hammer

View File

@@ -125,8 +125,14 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
| GET | `?b` | list files/folders at URL as simplified HTML | | GET | `?b` | list files/folders at URL as simplified HTML |
| GET | `?tree=.` | list one level of subdirectories inside URL | | GET | `?tree=.` | list one level of subdirectories inside URL |
| GET | `?tree` | list one level of subdirectories for each level until URL | | GET | `?tree` | list one level of subdirectories for each level until URL |
| GET | `?tar` | download everything below URL as a tar file | | GET | `?tar` | download everything below URL as a gnu-tar file |
| GET | `?zip=utf-8` | download everything below URL as a zip file | | GET | `?tar=gz:9` | ...as a gzip-level-9 gnu-tar file |
| GET | `?tar=xz:9` | ...as an xz-level-9 gnu-tar file |
| GET | `?tar=pax` | ...as a pax-tar file |
| GET | `?tar=pax,xz` | ...as an xz-level-1 pax-tar file |
| GET | `?zip=utf-8` | ...as a zip file |
| GET | `?zip` | ...as a WinXP-compatible zip file |
| GET | `?zip=crc` | ...as an MSDOS-compatible zip file |
| GET | `?ups` | show recent uploads from your IP | | GET | `?ups` | show recent uploads from your IP |
| GET | `?ups&filter=f` | ...where URL contains `f` | | GET | `?ups&filter=f` | ...where URL contains `f` |
| GET | `?mime=foo` | specify return mimetype `foo` | | GET | `?mime=foo` | specify return mimetype `foo` |

View File

@@ -255,7 +255,7 @@ symbol legend,
| per-file permissions | | | | █ | █ | | █ | | █ | | | | | per-file permissions | | | | █ | █ | | █ | | █ | | | |
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | | per-file passwords | █ | | | █ | █ | | █ | | █ | | | |
| unmap subfolders | █ | | | | | | █ | | | █ | | • | | unmap subfolders | █ | | | | | | █ | | | █ | | • |
| index.html blocks list | | | | | | | █ | | | • | | | | index.html blocks list | | | | | | | █ | | | • | | |
| write-only folders | █ | | | | | | | | | | █ | █ | | write-only folders | █ | | | | | | | | | | █ | █ |
| files stored as-is | █ | █ | █ | █ | | █ | █ | | | █ | █ | █ | | files stored as-is | █ | █ | █ | █ | | █ | █ | | | █ | █ | █ |
| file versioning | | | | █ | █ | | | | | | | | | file versioning | | | | █ | █ | | | | | | | |
@@ -291,6 +291,7 @@ symbol legend,
* one-way folder sync from local to server can be done efficiently with [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy), or with webdav and conventional rsync * one-way folder sync from local to server can be done efficiently with [u2c.py](https://github.com/9001/copyparty/tree/hovudstraum/bin#u2cpy), or with webdav and conventional rsync
* can hot-reload config files (with just a few exceptions) * can hot-reload config files (with just a few exceptions)
* can set per-folder permissions if that folder is made into a separate volume, so there is configuration overhead * can set per-folder permissions if that folder is made into a separate volume, so there is configuration overhead
* `index.html` on its own does not prevent directory listing, but permission `h` (instead of `r`) enforces index.html to be returned instead of folder contents
* [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) ([discord](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), [desktop](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png)) inspired by filebrowser, as well as the more complex [media parser](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) alternative * [event hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks) ([discord](https://user-images.githubusercontent.com/241032/215304439-1c1cb3c8-ec6f-4c17-9f27-81f969b1811a.png), [desktop](https://user-images.githubusercontent.com/241032/215335767-9c91ed24-d36e-4b6b-9766-fb95d12d163f.png)) inspired by filebrowser, as well as the more complex [media parser](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag) alternative
* upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py) * upload history can be visualized using [partyjournal](https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py)
* `k`/filegator remarks: * `k`/filegator remarks:

68
scripts/bench/filehash.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
set -euo pipefail
# check how fast copyparty is able to hash files during indexing
# assuming an infinitely fast HDD to read from (alternatively,
# checks whether you will be bottlenecked by CPU or HDD)
#
# uses copyparty's default config of using, well, it's complicated:
# * if you have more than 8 cores, then 5 threads,
# * if you have between 4 and 8, then 4 threads,
# * anything less and it takes your number of cores
#
# can be adjusted with --hash-mt (but alpine caps out at 5)
[ $# -ge 1 ] || {
echo 'need arg 1: path to copyparty-sfx.py'
echo ' (remaining args will be passed on to copyparty,'
echo ' for example to tweak the hasher settings)'
exit 1
}
sfx="$1"
shift
sfx="$(realpath "$sfx" || readlink -e "$sfx" || echo "$sfx")"
awk=$(command -v gawk || command -v awk)
# try to use /dev/shm to avoid hitting filesystems at all,
# otherwise fallback to mktemp which probably uses /tmp
td=/dev/shm/cppbenchtmp
mkdir $td || td=$(mktemp -d)
trap "rm -rf $td" INT TERM EXIT
cd $td
echo creating 256 MiB testfile in $td
head -c $((1024*1024*256)) /dev/urandom > 1
echo creating 127 symlinks to it
for n in $(seq 2 128); do ln -s 1 $n; done
echo warming up cache
cat 1 >/dev/null
echo ok lets go
python3 "$sfx" -p39204 -e2dsa --dbd=yolo --exit=idx -lo=t -q "$@"
echo and the results are...
$awk '/1 volumes in / {printf "%s MiB/s\n", 256*128/$(NF-1)}' <t
echo deleting $td and exiting
##
## some results:
# MiB/s @ cpu or device (copyparty, pythonver, distro/os) // comment
# 3608 @ Ryzen 5 4500U (cpp 1.9.5, py 3.11.5, fedora 38) // --hash-mt=6; laptop
# 2726 @ Ryzen 5 4500U (cpp 1.9.5, py 3.11.5, fedora 38) // --hash-mt=4 (old-default)
# 2202 @ Ryzen 5 4500U (cpp 1.9.5, py 3.11.5, docker-alpine 3.18.3) ??? alpine slow
# 2719 @ Ryzen 5 4500U (cpp 1.9.5, py 3.11.2, docker-debian 12.1)
# 5544 @ Intel i5-12500 (cpp 1.9.5, py 3.11.2, debian 12.0) // --hash-mt=12; desktop
# 5197 @ Ryzen 7 3700X (cpp 1.9.5, py 3.9.18, freebsd 13.2) // --hash-mt=8; 2u server
# 2606 @ Ryzen 7 3700X (cpp 1.9.5, py 3.9.18, freebsd 13.2) // --hash-mt=4 (old-default)
# 1436 @ Ryzen 5 5500U (cpp 1.9.5, py 3.11.4, alpine 3.18.3) // nuc
# 1065 @ Pixel 7 (cpp 1.9.5, py 3.11.5, termux 2023-09)
# notes,
# podman run --rm -it --shm-size 512m --entrypoint /bin/ash localhost/copyparty-min
# podman <filehash.sh run --rm -i --shm-size 512m --entrypoint /bin/ash localhost/copyparty-min -s - /z/copyparty-sfx.py

View File

@@ -25,6 +25,7 @@ ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
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 brotli py3-brotli \ && apk add cmake make g++ git bash npm patch wget tar pigz brotli gzip unzip python3 python3-dev brotli py3-brotli \
&& 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 \
&& wget https://github.com/Ionaru/easy-markdown-editor/archive/$ver_mde.tar.gz -O mde.tgz \ && wget https://github.com/Ionaru/easy-markdown-editor/archive/$ver_mde.tar.gz -O mde.tgz \
@@ -142,10 +143,11 @@ RUN ./genprism.sh $ver_prism
# compress # compress
COPY zopfli.makefile /z/dist/Makefile COPY brotli.makefile zopfli.makefile /z/dist/
RUN cd /z/dist \ RUN cd /z/dist \
&& make -j$(nproc) \ && make -j$(nproc) -f brotli.makefile \
&& rm Makefile \ && make -j$(nproc) -f zopfli.makefile \
&& rm *.makefile \
&& mv no-pk/* . \ && mv no-pk/* . \
&& rmdir no-pk && rmdir no-pk

View File

@@ -0,0 +1,4 @@
all: $(addsuffix .br, $(wildcard easymde*))
%.br: %
brotli -jZ $<

View File

@@ -1,10 +1,6 @@
all: $(addsuffix .gz, $(wildcard *.*)) all: $(addsuffix .gz, $(wildcard *.js *.css))
%.gz: % %.gz: %
#brotli -q 11 $<
pigz -11 -I 573 $< pigz -11 -I 573 $<
# pigz -11 -J 34 -I 100 -F < $< > $@.first # pigz -11 -J 34 -I 100 -F < $< > $@.first
# disabling brotli after all since the gain is meh
# and it bloats sfx and wheels by like 70%

View File

@@ -23,6 +23,7 @@ RUN apk add -U !pyc \
python3-dev ffmpeg-dev fftw-dev libsndfile-dev \ python3-dev ffmpeg-dev fftw-dev libsndfile-dev \
py3-wheel py3-numpy-dev \ py3-wheel py3-numpy-dev \
vamp-sdk-dev \ vamp-sdk-dev \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install pyvips \ && python3 -m pip install pyvips \
&& bash install-deps.sh \ && bash install-deps.sh \
&& apk del py3-pip .bd \ && apk del py3-pip .bd \
@@ -37,3 +38,7 @@ COPY i/dist/copyparty-sfx.py ./
WORKDIR /w WORKDIR /w
EXPOSE 3923 EXPOSE 3923
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"] ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
# size: 286 MB
# bpm/key: 529 sec
# idx-bench: 2352 MB/s

View File

@@ -0,0 +1,78 @@
FROM debian:12-slim
WORKDIR /z
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.licenses="MIT" \
org.opencontainers.image.title="copyparty-djd" \
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection, and higher performance than the other editions"
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
XDG_CONFIG_HOME=/cfg
COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/
# Suites: bookworm bookworm-updates
# Components: main
RUN sed -ri 's/( main)$/\1 contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources \
&& apt update \
&& DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \
wget \
python3-argon2 python3-pillow python3-pip \
ffmpeg libvips42 vamp-plugin-sdk \
python3-numpy libfftw3-double3 libsndfile1 \
gcc g++ make cmake patchelf jq \
libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev \
libfftw3-dev python3-dev libsndfile1-dev python3-pip \
patchelf cmake \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install --user pyvips \
&& bash install-deps.sh \
&& DEBIAN_FRONTEND=noninteractive apt purge -y \
gcc g++ make cmake patchelf jq \
libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev \
libfftw3-dev python3-dev libsndfile1-dev python3-pip \
patchelf cmake \
&& DEBIAN_FRONTEND=noninteractive apt-get autoremove -y \
&& dpkg -r --force-depends libraqm0 libgdbm-compat4 libgdbm6 libperl5.36 perl-modules-5.36 mailcap mime-support \
&& DEBIAN_FRONTEND=noninteractive apt-get clean -y \
&& find /usr/ -name __pycache__ | xargs rm -rf \
&& find /usr/ -type d -name tests | grep packages/numpy | xargs rm -rf \
&& rm -rf \
/var/lib/apt/lists/* \
/tmp/pyc \
/usr/lib/python*/dist-packages/pip \
/usr/lib/python*/dist-packages/setuptools \
/usr/lib/*/dri \
/usr/lib/*/mfx \
/usr/share/doc \
/usr/share/X11 \
/usr/share/fonts \
/usr/share/libmysofa \
/usr/share/libthai \
/usr/share/alsa \
/usr/share/bash-completion \
&& chmod 777 /root \
&& ln -s /root/vamp /root/.local / \
&& mkdir /cfg /w \
&& chmod 777 /cfg /w \
&& echo % /cfg > initcfg
COPY i/dist/copyparty-sfx.py ./
WORKDIR /w
EXPOSE 3923
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
# size: 598 MB
# bpm/key: 485 sec
# idx-bench: 2751 MB/s
# notes:
# libraqm0 (pillow dep) pulls in the other packages mentioned on the dpkg line; saves 50m
# advantage: official packages only
# advantage: ffmpeg with gme, codec2, radiance-hdr
# drawback: ffmpeg bloat; dc1394, flite, mfx, xorg
# drawback: python packaging is a bit jank
# drawback: they apply exciting patches due to old deps
# drawback: dropping perl the hard way might cause issues

View File

@@ -0,0 +1,69 @@
FROM fedora:38
WORKDIR /z
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.licenses="MIT" \
org.opencontainers.image.title="copyparty-djf" \
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection, and higher performance than the other editions"
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
XDG_CONFIG_HOME=/cfg
COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/
RUN dnf install -y \
https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm \
&& dnf install -y --setopt=install_weak_deps=False \
wget \
python3-argon2-cffi python3-pillow python3-pip python3-cffi \
ffmpeg \
vips vips-jxl vips-poppler vips-magick \
python3-numpy fftw libsndfile \
gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools \
vamp-plugin-sdk qm-vamp-plugins \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install --user pyvips \
&& bash install-deps.sh \
&& dnf erase -y \
gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-devel fftw-devel libsndfile-devel python3-setuptools \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& dnf clean all \
&& find /usr/ -name __pycache__ | xargs rm -rf \
&& find /usr/ -type d -name tests | grep packages/numpy | xargs rm -rf \
&& rm -rf \
/usr/share/adobe \
/usr/share/fonts \
/usr/share/graphviz \
/usr/share/poppler/cMap \
/usr/share/licenses \
/usr/share/ghostscript \
/usr/share/tesseract \
/usr/share/X11 \
/usr/share/hwdata \
/usr/share/python-wheels \
/usr/bin/cyrusbdb2current \
&& rm -rf /tmp/pyc \
&& chmod 777 /root \
&& ln -s /root/vamp /root/.local / \
&& mkdir /cfg /w \
&& chmod 777 /cfg /w \
&& echo % /cfg > initcfg
COPY i/dist/copyparty-sfx.py ./
WORKDIR /w
EXPOSE 3923
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
# size: 648 MB
# bpm/key: 410 sec
# idx-bench: 2744 MB/s
# advantage: fairly recent and sane ffmpeg build
# drawback: ffmpeg without gme, codec2, radiance-hdr
# drawback: ffmpeg from rpmfusion, which is both better and smaller than ffmpeg-free, can occasionally fail to install due to repo desync / conflicts
# drawback: ffmpeg bloat; samba, modplug, v4l2
# drawback: manual purging (graphviz/poppler/cmap) can break stuff

View File

@@ -0,0 +1,62 @@
FROM fedora:38
WORKDIR /z
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.licenses="MIT" \
org.opencontainers.image.title="copyparty-djff" \
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection, and higher performance than the other editions"
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
XDG_CONFIG_HOME=/cfg
COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/
RUN dnf install -y --setopt=install_weak_deps=False \
wget \
python3-argon2-cffi python3-pillow python3-pip python3-cffi \
ffmpeg-free \
vips vips-jxl vips-poppler vips-magick \
python3-numpy fftw libsndfile \
gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-free-devel fftw-devel libsndfile-devel python3-setuptools \
vamp-plugin-sdk qm-vamp-plugins \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install --user pyvips \
&& bash install-deps.sh \
&& dnf erase -y \
gcc gcc-c++ make cmake patchelf jq \
python3-devel ffmpeg-free-devel fftw-devel libsndfile-devel python3-setuptools \
vamp-plugin-sdk-devel vamp-plugin-sdk-static \
&& dnf clean all \
&& find /usr/ -name __pycache__ | xargs rm -rf \
&& find /usr/ -type d -name tests | grep packages/numpy | xargs rm -rf \
&& rm -rf \
/usr/share/adobe \
/usr/share/fonts \
/usr/share/graphviz \
/usr/share/poppler/cMap \
/usr/share/licenses \
/usr/share/ghostscript \
/usr/share/tesseract \
/usr/share/X11 \
/usr/share/hwdata \
/usr/share/python-wheels \
/usr/bin/cyrusbdb2current \
&& rm -rf /tmp/pyc \
&& chmod 777 /root \
&& ln -s /root/vamp /root/.local / \
&& mkdir /cfg /w \
&& chmod 777 /cfg /w \
&& echo % /cfg > initcfg
COPY i/dist/copyparty-sfx.py ./
WORKDIR /w
EXPOSE 3923
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
# size: 673 MB
# bpm/key: 408 sec
# idx-bench: 2744 MB/s
# drawback: less ffmpeg features we want, more features we don't (compared to rpmfusion)

View File

@@ -0,0 +1,69 @@
FROM ubuntu:23.04
WORKDIR /z
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.licenses="MIT" \
org.opencontainers.image.title="copyparty-dju" \
org.opencontainers.image.description="copyparty with all optional dependencies, including musical key / bpm detection, and higher performance than the other editions"
ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
XDG_CONFIG_HOME=/cfg
COPY i/bin/mtag/install-deps.sh ./
COPY i/bin/mtag/audio-bpm.py /mtag/
COPY i/bin/mtag/audio-key.py /mtag/
RUN apt update \
&& DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \
wget \
python3-argon2 python3-pillow python3-pip \
ffmpeg libvips42 vamp-plugin-sdk \
python3-numpy libfftw3-double3 libsndfile1 \
gcc g++ make cmake patchelf jq \
libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev \
libfftw3-dev python3-dev libsndfile1-dev python3-pip \
patchelf cmake \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED
RUN python3 -m pip install --user pyvips \
&& bash install-deps.sh \
&& DEBIAN_FRONTEND=noninteractive apt purge -y \
gcc g++ make cmake patchelf jq \
libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev \
libfftw3-dev python3-dev libsndfile1-dev python3-pip \
patchelf cmake \
&& DEBIAN_FRONTEND=noninteractive apt-get clean -y \
&& DEBIAN_FRONTEND=noninteractive apt-get autoremove -y \
&& find /usr/ -name __pycache__ | xargs rm -rf \
&& find /usr/ -type d -name tests | grep site-packages/numpy | xargs rm -rf \
&& rm -rf \
/var/lib/apt/lists/* \
/tmp/pyc \
/usr/lib/python*/dist-packages/pip \
/usr/lib/python*/dist-packages/setuptools \
/usr/lib/*/dri \
/usr/lib/*/mfx \
/usr/share/doc \
/usr/share/X11 \
/usr/share/fonts \
/usr/share/libmysofa \
/usr/share/libthai \
/usr/share/alsa \
/usr/share/bash-completion \
&& chmod 777 /root \
&& ln -s /root/vamp /root/.local / \
&& mkdir /cfg /w \
&& chmod 777 /cfg /w \
&& echo % /cfg > initcfg
COPY i/dist/copyparty-sfx.py ./
WORKDIR /w
EXPOSE 3923
ENTRYPOINT ["python3", "/z/copyparty-sfx.py", "--no-crt", "-c", "/z/initcfg"]
# size: 1198 MB (wowee)
# bpm/key: 516 sec
# idx-bench: 2751 MB/s
# advantage: official packages only
# advantage: ffmpeg with gme, codec2, radiance-hdr
# drawback: python packaging is a bit jank

View File

@@ -16,6 +16,7 @@ RUN apk add -U !pyc \
&& apk add -t .bd \ && apk add -t .bd \
bash wget gcc g++ make cmake patchelf \ bash wget gcc g++ make cmake patchelf \
python3-dev py3-wheel \ python3-dev py3-wheel \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install pyvips \ && python3 -m pip install pyvips \
&& apk del py3-pip .bd \ && apk del py3-pip .bd \
&& rm -rf /var/cache/apk/* /tmp/pyc \ && rm -rf /var/cache/apk/* /tmp/pyc \

View File

@@ -9,6 +9,7 @@ ENV PYTHONPYCACHEPREFIX=/tmp/pyc \
XDG_CONFIG_HOME=/cfg XDG_CONFIG_HOME=/cfg
RUN apk --no-cache add python3 py3-pip !pyc \ RUN apk --no-cache add python3 py3-pip !pyc \
&& rm -f /usr/lib/python3*/EXTERNALLY-MANAGED \
&& python3 -m pip install copyparty \ && python3 -m pip install copyparty \
&& apk del py3-pip \ && apk del py3-pip \
&& rm -rf /tmp/pyc \ && rm -rf /tmp/pyc \

View File

@@ -134,6 +134,14 @@ tmv() {
touch -r "$1" t touch -r "$1" t
mv t "$1" mv t "$1"
} }
iawk() {
awk "$1" <"$2" >t
tmv "$2"
}
ised() {
sed -r "$1" <"$2" >t
tmv "$2"
}
stamp=$( stamp=$(
for d in copyparty scripts; do for d in copyparty scripts; do
@@ -197,9 +205,9 @@ necho() {
mv {markupsafe,jinja2} j2/ mv {markupsafe,jinja2} j2/
necho collecting pyftpdlib necho collecting pyftpdlib
f="../build/pyftpdlib-1.5.7.tar.gz" f="../build/pyftpdlib-1.5.8.tar.gz"
[ -e "$f" ] || [ -e "$f" ] ||
(url=https://github.com/giampaolo/pyftpdlib/archive/refs/tags/release-1.5.7.tar.gz; (url=https://github.com/giampaolo/pyftpdlib/archive/refs/tags/release-1.5.8.tar.gz;
wget -O$f "$url" || curl -L "$url" >$f) wget -O$f "$url" || curl -L "$url" >$f)
tar -zxf $f tar -zxf $f
@@ -229,9 +237,7 @@ necho() {
mv python-magic-*/magic . mv python-magic-*/magic .
rm -rf python-magic-* rm -rf python-magic-*
rm magic/compat.py rm magic/compat.py
f=magic/__init__.py iawk '/^def _add_compat/{o=1} !o; /^_add_compat/{o=0}' magic/__init__.py
awk '/^def _add_compat/{o=1} !o; /^_add_compat/{o=0}' <$f >t
tmv "$f"
mv magic ftp/ # doesn't provide a version label anyways mv magic ftp/ # doesn't provide a version label anyways
# enable this to dynamically remove type hints at startup, # enable this to dynamically remove type hints at startup,
@@ -398,7 +404,7 @@ find -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16')
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
find copyparty | LC_ALL=C sort | sed 's/\.gz$//;s/$/,/' > have find copyparty | LC_ALL=C sort | sed -r 's/\.(gz|br)$//;s/$/,/' > have
cat have | while IFS= read -r x; do cat have | while IFS= read -r x; do
grep -qF -- "$x" ../scripts/sfx.ls || { grep -qF -- "$x" ../scripts/sfx.ls || {
echo "unexpected file: $x" echo "unexpected file: $x"
@@ -407,6 +413,11 @@ cat have | while IFS= read -r x; do
done done
rm have rm have
ised /fork_process/d ftp/pyftpdlib/servers.py
iawk '/^class _Base/{s=1}!s' ftp/pyftpdlib/authorizers.py
iawk '/^ {0,4}[^ ]/{s=0}/^ {4}def (serve_forever|_loop)/{s=1}!s' ftp/pyftpdlib/servers.py
rm -f ftp/pyftpdlib/{__main__,prefork}.py
[ $no_ftp ] && [ $no_ftp ] &&
rm -rf copyparty/ftpd.py ftp asyncore.py asynchat.py && rm -rf copyparty/ftpd.py ftp asyncore.py asynchat.py &&
sed -ri '/add_argument\("--ftp/d' copyparty/__main__.py && sed -ri '/add_argument\("--ftp/d' copyparty/__main__.py &&
@@ -423,9 +434,7 @@ rm have
[ $no_cm ] && { [ $no_cm ] && {
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde* rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
echo h > copyparty/web/mde.html echo h > copyparty/web/mde.html
f=copyparty/web/md.html ised '/edit2">edit \(fancy/d' copyparty/web/md.html
sed -r '/edit2">edit \(fancy/d' <$f >t
tmv "$f"
} }
[ $no_hl ] && [ $no_hl ] &&
@@ -435,23 +444,20 @@ rm have
rm -f copyparty/web/deps/scp.woff2 rm -f copyparty/web/deps/scp.woff2
f=copyparty/web/ui.css f=copyparty/web/ui.css
gzip -d "$f.gz" || true gzip -d "$f.gz" || true
sed -r "s/src:.*scp.*\)/src:local('Consolas')/" <$f >t ised "s/src:.*scp.*\)/src:local('Consolas')/" $f
tmv "$f"
} }
[ $no_dd ] && { [ $no_dd ] && {
rm -rf copyparty/web/dd rm -rf copyparty/web/dd
f=copyparty/web/browser.css f=copyparty/web/browser.css
gzip -d "$f.gz" || true gzip -d "$f.gz" || true
sed -r 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' <$f >t ised 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' $f
tmv "$f"
} }
[ $langs ] && [ $langs ] &&
for f in copyparty/web/{browser.js,splash.js}; do for f in copyparty/web/{browser.js,splash.js}; do
gzip -d "$f.gz" || true gzip -d "$f.gz" || true
awk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' <$f >t iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
tmv "$f"
done done
[ ! $repack ] && [ ! $use_ox ] && { [ ! $repack ] && [ ! $use_ox ] && {
@@ -466,10 +472,20 @@ rm have
# sed -ri '/: TypeAlias = /d' "$x"; done # sed -ri '/: TypeAlias = /d' "$x"; done
} }
f=j2/jinja2/constants.py rm -f j2/jinja2/constants.py
awk '/^LOREM_IPSUM_WORDS/{o=1;print "LOREM_IPSUM_WORDS = u\"a\"";next} !o; /"""/{o=0}' <$f >t iawk '/^ {4}def /{s=0}/^ {4}def compile_templates\(/{s=1}!s' j2/jinja2/environment.py
tmv "$f" ised '/generate_lorem_ipsum/d' j2/jinja2/defaults.py
rm -f j2/jinja2/async* iawk '/^def /{s=0}/^def generate_lorem_ipsum/{s=1}!s' j2/jinja2/utils.py
iawk '/^(class|def) /{s=0}/^(class InternationalizationExtension|def _make_new_n?gettext)/{s=1}!s' j2/jinja2/ext.py
iawk '/^[^ ]/{s=0}/^def babel_extract/{s=1}!s' j2/jinja2/ext.py
ised '/InternationalizationExtension/d' j2/jinja2/ext.py
iawk '/^class/{s=0}/^class (Package|Dict|Function|Prefix|Choice|Module)Loader/{s=1}!s' j2/jinja2/loaders.py
sed -ri '/^from .bccache | (Package|Dict|Function|Prefix|Choice|Module)Loader$/d' j2/jinja2/__init__.py
rm -f j2/jinja2/async* j2/jinja2/{bccache,sandbox}.py
cat > j2/jinja2/_identifier.py <<'EOF'
import re
pattern = re.compile(r"\w+")
EOF
grep -rLE '^#[^a-z]*coding: utf-8' j2 | grep -rLE '^#[^a-z]*coding: utf-8' j2 |
while IFS= read -r f; do while IFS= read -r f; do

View File

@@ -9,7 +9,7 @@ tee build2.sh | cmp build.sh && rm build2.sh || {
[[ $r =~ [yY] ]] && mv build{2,}.sh && exec ./build.sh [[ $r =~ [yY] ]] && mv build{2,}.sh && exec ./build.sh
} }
[ -e up2k.sh ] && ./up2k.sh [ -e up2k.sh ] && [ ! "$1" ] && ./up2k.sh
uname -s | grep WOW64 && m=64 || m=32 uname -s | grep WOW64 && m=64 || m=32
uname -s | grep NT-10 && w10=1 || w7=1 uname -s | grep NT-10 && w10=1 || w7=1
@@ -73,6 +73,7 @@ excl=(
multiprocessing multiprocessing
pdb pdb
pickle pickle
PIL.EpsImagePlugin
pyftpdlib.prefork pyftpdlib.prefork
urllib.request urllib.request
urllib.response urllib.response

View File

@@ -11,10 +11,14 @@ ckpypi() {
pyinstaller pyinstaller
pyinstaller-hooks-contrib pyinstaller-hooks-contrib
pywin32-ctypes pywin32-ctypes
certifi
charset_normalizer
idna
Jinja2 Jinja2
MarkupSafe MarkupSafe
mutagen mutagen
Pillow Pillow
requests
) )
for dep in "${deps[@]}"; do for dep in "${deps[@]}"; do
k= k=

View File

@@ -25,6 +25,6 @@ ba91ab0518c61eff13e5612d9e6b532940813f6b56e6ed81ea6c7c4d45acee4d98136a383a250675
# win10 # win10
00558cca2e0ac813d404252f6e5aeacb50546822ecb5d0570228b8ddd29d94e059fbeb6b90393dee5abcddaca1370aca784dc9b095cbb74e980b3c024767fb24 Jinja2-3.1.2-py3-none-any.whl 00558cca2e0ac813d404252f6e5aeacb50546822ecb5d0570228b8ddd29d94e059fbeb6b90393dee5abcddaca1370aca784dc9b095cbb74e980b3c024767fb24 Jinja2-3.1.2-py3-none-any.whl
7f8f4daa4f4f2dbf24cdd534b2952ee3fba6334eb42b37465ccda3aa1cccc3d6204aa6bfffb8a83bf42ec59c702b5b5247d4c8ee0d4df906334ae53072ef8c4c MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl 7f8f4daa4f4f2dbf24cdd534b2952ee3fba6334eb42b37465ccda3aa1cccc3d6204aa6bfffb8a83bf42ec59c702b5b5247d4c8ee0d4df906334ae53072ef8c4c MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl
4a20aeb52d4fde6aabcba05ee261595eeb5482c72ee27332690f34dd6e7a49c0b3ba3813202ac15c9d21e29f1cd803f2e79ccc1c45ec314fcd0a937016bcbc56 mutagen-1.46.0-py3-none-any.whl 8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
926d408a886059a75cf12706fa061146f9f042b27fb6e65be7d49f398ed23fb0227639d84804586ac014c6bcf7d08cd86a09c1a20793d341aa0802d3d32a546b Pillow-10.0.0-cp311-cp311-win_amd64.whl 08a033202b5c51e50609b2700dd69cbae30edb367f34762fd1633aae08b35949b4f67f12c75f25868a5b62b4956190d0cc8d201b170758d9c04a523bc8442b9b Pillow-10.0.1-cp311-cp311-win_amd64.whl
c86bbeacad3ae3c7bde747f5b4f09c11eced841add14e79ec4a064e5e29ebca35460e543ba735b11bfb882837d5ff4371ce64492d28d096b4686233c9a8cda6d python-3.11.5-amd64.exe c86bbeacad3ae3c7bde747f5b4f09c11eced841add14e79ec4a064e5e29ebca35460e543ba735b11bfb882837d5ff4371ce64492d28d096b4686233c9a8cda6d python-3.11.5-amd64.exe

View File

@@ -17,14 +17,14 @@ uname -s | grep NT-10 && w10=1 || {
fns=( fns=(
altgraph-0.17.3-py2.py3-none-any.whl altgraph-0.17.3-py2.py3-none-any.whl
pefile-2023.2.7-py3-none-any.whl pefile-2023.2.7-py3-none-any.whl
pyinstaller-5.13.1-py3-none-win_amd64.whl pyinstaller-5.13.2-py3-none-win_amd64.whl
pyinstaller_hooks_contrib-2023.7-py2.py3-none-any.whl pyinstaller_hooks_contrib-2023.7-py2.py3-none-any.whl
pywin32_ctypes-0.2.2-py3-none-any.whl pywin32_ctypes-0.2.2-py3-none-any.whl
upx-4.1.0-win32.zip upx-4.1.0-win32.zip
) )
[ $w10 ] && fns+=( [ $w10 ] && fns+=(
mutagen-1.46.0-py3-none-any.whl mutagen-1.47.0-py3-none-any.whl
Pillow-9.4.0-cp311-cp311-win_amd64.whl Pillow-10.0.1-cp311-cp311-win_amd64.whl
python-3.11.3-amd64.exe python-3.11.3-amd64.exe
} }
[ $w7 ] && fns+=( [ $w7 ] && fns+=(
@@ -47,7 +47,7 @@ fns=(
) )
[ $w7x32 ] && fns+=( [ $w7x32 ] && fns+=(
windows6.1-kb2533623-x86.msu windows6.1-kb2533623-x86.msu
pyinstaller-5.13.1-py3-none-win32.whl pyinstaller-5.13.2-py3-none-win32.whl
python-3.7.9.exe python-3.7.9.exe
) )
dl() { curl -fkLOC- "$1" && return 0; echo "$1"; return 1; } dl() { curl -fkLOC- "$1" && return 0; echo "$1"; return 1; }

View File

@@ -27,7 +27,7 @@ python3 ../scripts/strip_hints/a.py
pids=() pids=()
for py in python{2,3}; do for py in python{2,3}; do
[ ${1:0:6} = python ] && [ $1 != $py ] && continue [ "${1:0:6}" = python ] && [ "$1" != $py ] && continue
PYTHONPATH= PYTHONPATH=
[ $py = python2 ] && PYTHONPATH=../scripts/py2:../sfx/py37 [ $py = python2 ] && PYTHONPATH=../scripts/py2:../sfx/py37

View File

@@ -55,6 +55,11 @@ def uncomment(fpath):
out += '"a"' out += '"a"'
elif token_type != tokenize.COMMENT or is_legalese: elif token_type != tokenize.COMMENT or is_legalese:
out += token_string out += token_string
else:
if out.rstrip(" ").endswith("\n"):
out = out.rstrip() + "\n"
else:
out = out.rstrip()
prev_toktype = token_type prev_toktype = token_type
last_lineno = end_line last_lineno = end_line
@@ -81,7 +86,10 @@ def main():
except Exception as ex: except Exception as ex:
print("\nnon-mp fallback due to {}\n".format(ex)) print("\nnon-mp fallback due to {}\n".format(ex))
for f in sys.argv[1:]: for f in sys.argv[1:]:
uncomment(f) try:
uncomment(f)
except Exception as ex:
print("uncomment failed: [%s] %s" % (f, repr(ex)))
print("k") print("k")

18
srv/chat.md Normal file
View File

@@ -0,0 +1,18 @@
## chattyparty
this file, combined with the [msg-log](https://github.com/9001/copyparty/blob/hovudstraum/bin/hooks/msg-log.py) hook, turns copyparty into a makeshift instant-messaging / chat service
name this file `README.md` and run copyparty as such:
```bash
python copyparty-sfx.py -emp --xm j,bin/hooks/msg-log.py
```
only the stuff below is important; delete everything from this line up
```copyparty_post
render2(dom) {
if (/[?&]edit/.test(location)) return;
setTimeout(function() { treectl.goto(); }, 1000);
// if you wanna go to another folder: treectl.goto('foo <bar> baz/', true);
}
```

View File

@@ -119,8 +119,11 @@ class TestHttpCli(unittest.TestCase):
# with open(os.path.join(td, "tar"), "wb") as f: # with open(os.path.join(td, "tar"), "wb") as f:
# f.write(b) # f.write(b)
try: try:
tar = tarfile.open(fileobj=io.BytesIO(b)).getnames() tar = tarfile.open(fileobj=io.BytesIO(b), mode="r|").getnames()
except: except:
if "HTTP/1.1 403 Forbidden" not in h and b != b"\nJ2EOT":
eprint("bad tar?", url, h, b)
raise
tar = [] tar = []
tar = [x.split("/", 1)[1] for x in tar] tar = [x.split("/", 1)[1] for x in tar]
tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar] tar = ["/".join([y for y in [top, durl, x] if y]) for x in tar]

View File

@@ -20,7 +20,7 @@ ANYWIN = WINDOWS or sys.platform in ["msys"]
MACOS = platform.system() == "Darwin" MACOS = platform.system() == "Darwin"
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader)
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}") J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
def nah(*a, **ka): def nah(*a, **ka):
@@ -109,13 +109,13 @@ class Cfg(Namespace):
def __init__(self, a=None, v=None, c=None): def __init__(self, a=None, v=None, c=None):
ka = {} ka = {}
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp 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_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vague_403 vc ver xdev xlink xvol" ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp 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_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 rand smb th_no_crop 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 no_rescan no_sendfile no_voldump plain_ip" ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
ka.update(**{k: True for k in ex.split()}) ka.update(**{k: True for k in ex.split()})
ex = "css_browser hist js_browser no_forget no_hash no_idx" ex = "css_browser hist js_browser no_forget no_hash no_idx nonsus_urls"
ka.update(**{k: None for k in ex.split()}) ka.update(**{k: None for k in ex.split()})
ex = "s_thead s_tbody th_convt" ex = "s_thead s_tbody th_convt"
@@ -138,6 +138,7 @@ class Cfg(Namespace):
dbd="wal", dbd="wal",
s_wr_sz=512 * 1024, s_wr_sz=512 * 1024,
th_size="320x256", th_size="320x256",
fk_salt="a" * 16,
unpost=600, unpost=600,
u2sort="s", u2sort="s",
mtp=[], mtp=[],