mirror of
https://github.com/9001/copyparty.git
synced 2025-10-10 10:32:19 -06:00
Compare commits
110 commits
v1.19.9
...
hovudstrau
Author | SHA1 | Date | |
---|---|---|---|
|
dbd8f837e8 | ||
|
20ac117c32 | ||
|
cd3feaac86 | ||
|
f8e19815e1 | ||
|
76e9f23a6d | ||
|
4542ad3c01 | ||
|
50276c0cfa | ||
|
c5f1229685 | ||
|
73ec2d296f | ||
|
a499648291 | ||
|
4bb5baf1b8 | ||
|
efd19af7ca | ||
|
aace711eb9 | ||
|
39bd4e5b58 | ||
|
08ebb0b4c9 | ||
|
656f0a6c39 | ||
|
805a7054e9 | ||
|
01709691f2 | ||
|
41ed559faa | ||
|
a0f8f794a8 | ||
|
fbe5fa582e | ||
|
2248705e1a | ||
|
eb173be4f1 | ||
|
d05a88d2ee | ||
|
09e6f29e5e | ||
|
2ce32e4fb6 | ||
|
9b7f933b78 | ||
|
38cc809822 | ||
|
e9b6e645d3 | ||
|
0f9a239078 | ||
|
0453b7ac53 | ||
|
1bcdf8c9e3 | ||
|
4177c1d9ed | ||
|
171ca985c8 | ||
|
dacc64dd2e | ||
|
31f1b535b2 | ||
|
7fc379abc8 | ||
|
a8f53d5ef0 | ||
|
3f59710294 | ||
|
24e01221c5 | ||
|
daba1ab7bd | ||
|
1bca86c6e1 | ||
|
fc2754cba5 | ||
|
470b504843 | ||
|
435db14798 | ||
|
d08e872062 | ||
|
f91a653bde | ||
|
7d86f39a23 | ||
|
456addf26f | ||
|
4e38e4087e | ||
|
f0ecb08347 | ||
|
1193f9ba6c | ||
|
234eddec90 | ||
|
e3baf932f3 | ||
|
eb5d767b01 | ||
|
ec7418734d | ||
|
a3d9506783 | ||
|
57650a218f | ||
|
983865d96c | ||
|
6f6b70ad04 | ||
|
e187df28f2 | ||
|
df0fa9d1b7 | ||
|
397ed5653b | ||
|
9f46e4dbd7 | ||
|
6912e86747 | ||
|
80ca78516e | ||
|
a493cd6530 | ||
|
c72b62ad86 | ||
|
fdcd92bac8 | ||
|
b00dac997a | ||
|
9d066414c6 | ||
|
5e4ff90b1c | ||
|
deb8a4a86e | ||
|
733e85c040 | ||
|
892a452446 | ||
|
38df223b8f | ||
|
b136a5b042 | ||
|
377eddcd06 | ||
|
549fe33f51 | ||
|
c214a93caa | ||
|
0941fd4ec1 | ||
|
6dbd9901b2 | ||
|
e9ca36fa88 | ||
|
a053a663b4 | ||
|
1923a25879 | ||
|
4cce799012 | ||
|
1460fe97ac | ||
|
ca872c4055 | ||
|
3ddb4c042a | ||
|
15d3c2fbff | ||
|
6a24432019 | ||
|
8f6194fe77 | ||
|
260da2f45c | ||
|
a9e02ce753 | ||
|
70c088aeca | ||
|
280815f158 | ||
|
e1ea9852c6 | ||
|
2ee9c80d3b | ||
|
4b2ff3a196 | ||
|
538a205ce4 | ||
|
6559152882 | ||
|
669b10754d | ||
|
478f1c764e | ||
|
a043d7cfb6 | ||
|
ee5f31908f | ||
|
35326a6fb8 | ||
|
59a0122179 | ||
|
5996a58b20 | ||
|
fd331a545d | ||
|
e7ef31ee33 |
70
README.md
70
README.md
|
@ -50,6 +50,7 @@ made in Norway 🇳🇴
|
|||
* [shares](#shares) - share a file or folder by creating a temporary link
|
||||
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
||||
* [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
|
||||
* [opds feeds](#opds-feeds) - browse and download files from your e-book reader
|
||||
* [recent uploads](#recent-uploads) - list all recent uploads
|
||||
* [media player](#media-player) - plays almost every audio format there is
|
||||
* [playlists](#playlists) - create and play [m3u8](https://en.wikipedia.org/wiki/M3U) playlists
|
||||
|
@ -137,6 +138,7 @@ made in Norway 🇳🇴
|
|||
* [dependencies](#dependencies) - mandatory deps
|
||||
* [optional dependencies](#optional-dependencies) - install these to enable bonus features
|
||||
* [dependency chickenbits](#dependency-chickenbits) - prevent loading an optional dependency
|
||||
* [dependency unvendoring](#dependency-unvendoring) - force use of system modules
|
||||
* [optional gpl stuff](#optional-gpl-stuff)
|
||||
* [sfx](#sfx) - the self-contained "binary" (recommended!)
|
||||
* [copyparty.exe](#copypartyexe) - download [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) (win8+) or [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) (win7+)
|
||||
|
@ -212,6 +214,7 @@ you may also want these, especially on servers:
|
|||
|
||||
* [contrib/systemd/copyparty.service](contrib/systemd/copyparty.service) to run copyparty as a systemd service (see guide inside)
|
||||
* [contrib/systemd/prisonparty.service](contrib/systemd/prisonparty.service) to run it in a chroot (for extra security)
|
||||
* [contrib/podman-systemd/](contrib/podman-systemd/) to run copyparty in a Podman container as a systemd service (see guide inside)
|
||||
* [contrib/openrc/copyparty](contrib/openrc/copyparty) to run copyparty on Alpine / Gentoo
|
||||
* [contrib/rc/copyparty](contrib/rc/copyparty) to run copyparty on FreeBSD
|
||||
* [nixos module](#nixos-module) to run copyparty on NixOS hosts
|
||||
|
@ -728,6 +731,10 @@ to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` an
|
|||
* the supported image formats are [jpg, png, gif, webp, ico](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types)
|
||||
* be careful with svg; chrome will crash if you have too many unique svg files showing on the same page (the limit is 250 or so) -- showing the same handful of svg files thousands of times is ok however
|
||||
|
||||
note:
|
||||
* heif/heifs/heic/heics images usually require the `libvips` [optional dependency](#optional-dependencies) (available in the `iv` docker image, `withFastThumbnails` in nixos)
|
||||
* technical trivia: FFmpeg has basic support for tiled heic as of v7.0; need `-show_stream_groups` for correct resolution
|
||||
|
||||
config file example:
|
||||
|
||||
```yaml
|
||||
|
@ -868,6 +875,8 @@ the files will be hashed on the client-side, and each hash is sent to the server
|
|||
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
|
||||
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
|
||||
|
||||
if you have a "wark" (file-identifier/checksum) then you can also search for that in the [🔎] tab by putting `w = kFpDiztbZc8Z1Lzi` in the `raw` field
|
||||
|
||||
|
||||
### unpost
|
||||
|
||||
|
@ -994,6 +1003,8 @@ available functions:
|
|||
* `$lpad(text, length, pad_char)`
|
||||
* `$rpad(text, length, pad_char)`
|
||||
|
||||
two counters are available; `.n.s` is the nth file in the selection, and `.n.d` the nth file in the folder, for example rename-output `file(.n.d).(ext)` gives `file5.bin`, and `beach-$lpad((.n.s),3,0).(ext)` is `beach-017.jpg` and the initial value of each counter can be set in the textboxes underneath the preset dropdown
|
||||
|
||||
so,
|
||||
|
||||
say you have a file named [`meganeko - Eclipse - 07 Sirius A.mp3`](https://www.youtube.com/watch?v=-dtb0vDPruI) (absolutely fantastic album btw) and the tags are: `Album:Eclipse`, `Artist:meganeko`, `Title:Sirius A`, `tn:7`
|
||||
|
@ -1029,6 +1040,8 @@ url parameters:
|
|||
|
||||
* `pw=hunter2` for password auth
|
||||
* if you enabled `--usernames` then do `pw=username:password` instead
|
||||
* `nopw` disables embedding the password (if provided) into item-URLs in the feed
|
||||
* `nopw=a` disables mentioning the password anywhere at all in the feed; may break some readers
|
||||
* `recursive` to also include subfolders
|
||||
* `title=foo` changes the feed title (default: folder name)
|
||||
* `fext=mp3,opus` only include mp3 and opus files (default: all)
|
||||
|
@ -1040,6 +1053,28 @@ url parameters:
|
|||
* uppercase = reverse-sort; `M` = oldest file first
|
||||
|
||||
|
||||
## opds feeds
|
||||
|
||||
browse and download files from your e-book reader
|
||||
|
||||
enabled with the `opds` volflag or `--opds` global option
|
||||
|
||||
add `?opds` to the end of the url you would like to browse, then input that in your opds client.
|
||||
for example: `https://copyparty.example/books/?opds`.
|
||||
|
||||
to log in with a password, enter it into either of the username or password fields in your client.
|
||||
|
||||
- if you've enabled `--usernames`, then you need to enter both username and password .
|
||||
|
||||
note: some clients (e.g. Moon+ Reader) will not send the password when downloading cover images, which will
|
||||
cause your ip to be banned by copyparty. to work around this, you can grant the [`g` permission](#accounts-and-volumes)
|
||||
to unauthenticated requests and enable [filekeys](#filekeys) to prevent guessing filenames. for example:
|
||||
`-vbooks:books:r,ed:g:c,fk,opds`
|
||||
|
||||
by default, not all file types will be listed in opds feeds. to change this, add the extension to
|
||||
`--opds-exts` (volflag: `opds_exts`), or empty the list to list everything
|
||||
|
||||
|
||||
## recent uploads
|
||||
|
||||
list all recent uploads by clicking "show recent uploads" in the controlpanel
|
||||
|
@ -1073,6 +1108,7 @@ some highlights:
|
|||
* shows the audio waveform in the seekbar
|
||||
* not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
|
||||
* videos can be played as audio, without wasting bandwidth on the video
|
||||
* adding `?v` to the end of an audio/video/image link will make it open in the mediaplayer
|
||||
|
||||
click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)
|
||||
|
||||
|
@ -1353,7 +1389,7 @@ general usage:
|
|||
on macos, connect from finder:
|
||||
* [Go] -> [Connect to Server...] -> http://192.168.123.1:3923/
|
||||
|
||||
in order to grant full write-access to webdav clients, the volflag `daw` must be set and the account must also have delete-access (otherwise the client won't be allowed to replace the contents of existing files, which is how webdav works)
|
||||
to upload or edit files with WebDAV clients, enable the `daw` volflag (because most WebDAV clients expect this) and give your account the delete-permission. This avoids getting several copies of the same file on the server. HOWEVER: This will also make all PUT-uploads overwrite existing files if the user has delete-access, so use with caution.
|
||||
|
||||
> note: if you have enabled [IdP authentication](#identity-providers) then that may cause issues for some/most webdav clients; see [the webdav section in the IdP docs](https://github.com/9001/copyparty/blob/hovudstraum/docs/idp.md#connecting-webdav-clients)
|
||||
|
||||
|
@ -1554,6 +1590,7 @@ the same arguments can be set as volflags, in addition to `d2d`, `d2ds`, `d2t`,
|
|||
|
||||
note:
|
||||
* upload-times can be displayed in the file listing by enabling the `.up_at` metadata key, either globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at` (will have a ~17% performance impact on directory listings)
|
||||
* and file checksums can be shown with global-option `-e2d -mte +w` or volflag `e2d,mte=+w` (always active for users with permission `a`)
|
||||
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
||||
|
||||
config file example (these options are recommended btw):
|
||||
|
@ -1637,6 +1674,7 @@ set upload rules using volflags, some examples:
|
|||
* `:c,nosub` disallow uploading into subdirectories; goes well with `rotn` and `rotf`:
|
||||
* `:c,rotn=1000,2` moves uploads into subfolders, up to 1000 files in each folder before making a new one, two levels deep (must be at least 1)
|
||||
* `:c,rotf=%Y/%m/%d/%H` enforces files to be uploaded into a structure of subfolders according to that date format
|
||||
* `:c,rotf_tz=Europe/Oslo` sets the timezone (default is UTC unless global-option `rotf-tz` is changed)
|
||||
* if someone uploads to `/foo/bar` the path would be rewritten to `/foo/bar/2021/08/06/23` for example
|
||||
* but the actual value is not verified, just the structure, so the uploader can choose any values which conform to the format string
|
||||
* just to avoid additional complexity in up2k which is enough of a mess already
|
||||
|
@ -2438,6 +2476,10 @@ copyparty on NixOS is configured via `services.copyparty` options, for example:
|
|||
```nix
|
||||
services.copyparty = {
|
||||
enable = true;
|
||||
# the user to run the service as
|
||||
user = "copyparty";
|
||||
# the group to run the service as
|
||||
group = "copyparty";
|
||||
# directly maps to values in the [global] section of the copyparty config.
|
||||
# see `copyparty --help` for available options
|
||||
settings = {
|
||||
|
@ -2462,6 +2504,12 @@ services.copyparty = {
|
|||
k.passwordFile = "/run/keys/copyparty/k_password";
|
||||
};
|
||||
|
||||
# create a group
|
||||
groups = {
|
||||
# users "ed" and "k" are part of the group g1
|
||||
g1 = [ "ed" "k" ];
|
||||
};
|
||||
|
||||
# create a volume
|
||||
volumes = {
|
||||
# create a volume at "/" (the webroot), which will
|
||||
|
@ -2706,7 +2754,7 @@ below are some tweaks roughly ordered by usefulness:
|
|||
* and pypy can sometimes crash on startup with `-j0` (TODO make issue)
|
||||
|
||||
* if you are running the copyparty server **on Windows or Macos:**
|
||||
* `--casechk=y` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
|
||||
* `--casechk=n` makes it much faster, but also awakens [the usual surprises](https://github.com/9001/copyparty/issues/781) you expect from a case-insensitive filesystem
|
||||
* this is the same as `casechk: n` in a config-file
|
||||
|
||||
|
||||
|
@ -2951,6 +2999,20 @@ example: `PRTY_NO_PIL=1 python3 copyparty-sfx.py`
|
|||
* python2.7 on windows: `PRTY_NO_FFMPEG` + `PRTY_NO_FFPROBE` saves startup time
|
||||
|
||||
|
||||
### dependency unvendoring
|
||||
|
||||
force use of system modules instead of the vendored versions:
|
||||
|
||||
| env-var | what it does |
|
||||
| -------------------- | ------------ |
|
||||
| `PRTY_SYS_ALL` | all of the below |
|
||||
| `PRTY_SYS_DNSLIB` | replace [stolen/dnslib](./copyparty/stolen/dnslib) with [upstream](https://pypi.org/project/dnslib/) |
|
||||
| `PRTY_SYS_IFADDR` | replace [stolen/ifaddr](./copyparty/stolen/ifaddr) with [upstream](https://pypi.org/project/ifaddr/) |
|
||||
| `PRTY_SYS_QRCG` | replace [stolen/qrcodegen.py](./copyparty/stolen/qrcodegen.py) with [upstream](https://github.com/nayuki/QR-Code-generator/blob/master/python/qrcodegen.py) |
|
||||
|
||||
to debug, run copyparty with `PRTY_MODSPEC=1` to see where it's getting each module from
|
||||
|
||||
|
||||
## optional gpl stuff
|
||||
|
||||
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
|
||||
|
@ -3013,6 +3075,8 @@ if you want thumbnails (photos+videos) and you're okay with spending another 132
|
|||
|
||||
* or if you want to use `vips` for photo-thumbs instead, `pkg install libvips && python -m pip install --user -U wheel && python -m pip install --user -U pyvips && (cd /data/data/com.termux/files/usr/lib/; ln -s libgobject-2.0.so{,.0}; ln -s libvips.so{,.42})`
|
||||
|
||||
if you are suddenly unable to access storage (permission issues), try forcequitting termux, revoke all of its permissions in android settings, and run the command `termux-setup-storage`
|
||||
|
||||
|
||||
# install on iOS
|
||||
|
||||
|
@ -3023,7 +3087,7 @@ first install one of the following:
|
|||
and then copypaste the following command into `a-Shell`:
|
||||
|
||||
```sh
|
||||
curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
|
||||
curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh | sh
|
||||
```
|
||||
|
||||
what this does:
|
||||
|
|
|
@ -28,10 +28,19 @@ these are `--xiu` hooks; unlike `xbu` and `xau` (which get executed on every sin
|
|||
* [reject-extension.py](reject-extension.py) rejects uploads if they match a list of file extensions
|
||||
* [reloc-by-ext.py](reloc-by-ext.py) redirects an upload to another destination based on the file extension
|
||||
* good example of the `reloc` [hook effect](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#hook-effects)
|
||||
* [reject-and-explain.py](reject-and-explain.py) shows a custom error-message when it rejects an upload
|
||||
* [reject-ramdisk.py](reject-ramdisk.py) rejects the upload if the destination is a ramdisk
|
||||
* this hook uses the `I` flag which makes it 140x faster, but if the plugin has a bug it may crash copyparty
|
||||
|
||||
|
||||
# on message
|
||||
* [wget.py](wget.py) lets you download files by POSTing URLs to copyparty
|
||||
* [wget-i.py](wget-i.py) is an import-safe modification of this hook (starts 140x faster, but higher chance of bugs)
|
||||
* [qbittorrent-magnet.py](qbittorrent-magnet.py) starts downloading a torrent if you post a magnet url
|
||||
* [usb-eject.py](usb-eject.py) adds web-UI buttons to safe-remove usb flashdrives shared through copyparty
|
||||
* [msg-log.py](msg-log.py) is a guestbook; logs messages to a doc in the same folder
|
||||
|
||||
|
||||
# general concept demos
|
||||
* [import-me.py](import-me.py) shows how the `I` flag makes the hook 140x faster (but you need to be Very Careful when writing the plugin)
|
||||
* [wget-i.py](wget-i.py) is an import-safe modification of [wget.py](wget.py)
|
||||
|
|
55
bin/hooks/import-me.py
Normal file
55
bin/hooks/import-me.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from typing import Any
|
||||
|
||||
_ = r"""
|
||||
the fastest hook in the west
|
||||
(runs directly inside copyparty, not as a subprocess)
|
||||
|
||||
example usage as global config:
|
||||
--xbu I,bin/hooks/import-me.py
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,xbu=I,bin/hooks/import-me.py
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
(share filesystem-path srv/inc as volume /inc,
|
||||
readable by everyone, read-write for user 'ed',
|
||||
running this plugin on all uploads with the params listed below)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
xbu: I,bin/hooks/import-me.py
|
||||
|
||||
parameters explained,
|
||||
I = import; do not fork / subprocess
|
||||
|
||||
IMPORTANT NOTE:
|
||||
because this hook is running inside copyparty, you need to
|
||||
be EXCEPTIONALLY CAREFUL to avoid side-effects, for example
|
||||
DO NOT os.chdir() or anything like that, and also make sure
|
||||
that the name of this file is unique (cannot be the same as
|
||||
an existing python module/library)
|
||||
"""
|
||||
|
||||
|
||||
def main(ka: dict[str, Any]) -> dict[str, Any]:
|
||||
# "ka" is a dictionary with info from copyparty...
|
||||
|
||||
# but because we are running inside copyparty, we don't need such courtesies;
|
||||
import inspect
|
||||
|
||||
cf = inspect.currentframe().f_back.f_back.f_back
|
||||
t = "hello from hook; I am able to peek into copyparty's memory like so:\n function name: %s\n variables:\n %s\n"
|
||||
t2 = "\n ".join([("%r: %r" % (k, v))[:99] for k, v in cf.f_locals.items()][:9])
|
||||
logger = ka["log"]
|
||||
logger(t % (cf.f_code, t2))
|
||||
|
||||
# must return a dictionary with:
|
||||
# "rc": the retcode; 0 is ok
|
||||
return {"rc": 0}
|
60
bin/hooks/reject-and-explain.py
Normal file
60
bin/hooks/reject-and-explain.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_ = r"""
|
||||
reject file upload (with a nice explanation why)
|
||||
|
||||
example usage as global config:
|
||||
--xbu j,c1,bin/hooks/reject-and-explain.py
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reject-and-explain.py
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
(share filesystem-path srv/inc as volume /inc,
|
||||
readable by everyone, read-write for user 'ed',
|
||||
running this plugin on all uploads with the params listed below)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
xbu: j,c1,bin/hooks/reject-and-explain.py
|
||||
|
||||
parameters explained,
|
||||
xbu = execute-before-upload (can also be xau, execute-after-upload)
|
||||
j = this hook needs upload information as json (not just the filename)
|
||||
c1 = this hook returns json on stdout, so tell copyparty to read that
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
inf = json.loads(sys.argv[1])
|
||||
vdir, fn = os.path.split(inf["vp"])
|
||||
print("inf[vp] = %r" % (inf["vp"],), file=sys.stderr)
|
||||
|
||||
# the following is what decides if we'll accept the upload or reject it:
|
||||
# we check if the upload-folder url matches the following regex-pattern:
|
||||
ok = re.search(r"(^|/)day[0-9]+$", vdir, re.IGNORECASE)
|
||||
|
||||
if ok:
|
||||
# allow the upload
|
||||
print("{}")
|
||||
return
|
||||
|
||||
# the upload was rejected; display the following errortext:
|
||||
errmsg = "Files can only be uploaded into a folder named 'DayN' where N is a number, for example 'Day573'. This file was REJECTED: "
|
||||
errmsg += inf["vp"] # if you want to mention the file's url
|
||||
print(json.dumps({"rejectmsg": errmsg}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
72
bin/hooks/reject-ramdisk.py
Normal file
72
bin/hooks/reject-ramdisk.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import threading
|
||||
from argparse import Namespace
|
||||
|
||||
from jinja2.nodes import Name
|
||||
from copyparty.fsutil import Fstab
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
_ = r"""
|
||||
reject an upload if the target folder is on a ramdisk; useful when you
|
||||
have a volume where some folders inside are ramdisks but others aren't
|
||||
|
||||
example usage as global config:
|
||||
--xbu I,bin/hooks/reject-ramdisk.py
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,xbu=I,bin/hooks/reject-ramdisk.py
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
(share filesystem-path srv/inc as volume /inc,
|
||||
readable by everyone, read-write for user 'ed',
|
||||
running this plugin on all uploads with the params listed below)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
xbu: I,bin/hooks/reject-ramdisk.py
|
||||
|
||||
parameters explained,
|
||||
I = import; do not fork / subprocess
|
||||
|
||||
IMPORTANT NOTE:
|
||||
because this hook is imported inside copyparty, you need to
|
||||
be EXCEPTIONALLY CAREFUL to avoid side-effects, for example
|
||||
DO NOT os.chdir() or anything like that, and also make sure
|
||||
that the name of this file is unique (cannot be the same as
|
||||
an existing python module/library)
|
||||
"""
|
||||
|
||||
|
||||
mutex = threading.Lock()
|
||||
fstab: Optional[Fstab] = None
|
||||
|
||||
|
||||
def main(ka: dict[str, Any]) -> dict[str, Any]:
|
||||
global fstab
|
||||
with mutex:
|
||||
log = ka["log"] # this is a copyparty NamedLogger function
|
||||
if not fstab:
|
||||
log("<HOOK:RAMDISK> creating fstab", 6)
|
||||
args = Namespace()
|
||||
args.mtab_age = 1 # cache the filesystem info for 1 sec
|
||||
fstab = Fstab(log, args, False)
|
||||
|
||||
ap = ka["ap"] # abspath the upload is going to
|
||||
fs, mp = fstab.get(ap) # figure out what the filesystem is
|
||||
ramdisk = fs in ("tmpfs", "overlay") # looks like a ramdisk?
|
||||
|
||||
# log("<HOOK:RAMDISK> fs=%r" % (fs,))
|
||||
|
||||
if ramdisk:
|
||||
t = "Upload REJECTED because destination is a ramdisk"
|
||||
return {"rc": 1, "rejectmsg": t}
|
||||
|
||||
return {"rc": 0}
|
97
bin/hooks/wget-i.py
Executable file
97
bin/hooks/wget-i.py
Executable file
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import threading
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
_ = r"""
|
||||
use copyparty as a file downloader by POSTing URLs as
|
||||
application/x-www-form-urlencoded (for example using the
|
||||
📟 message-to-server-log in the web-ui)
|
||||
|
||||
this hook is a modified copy of wget.py, modified to
|
||||
make it import-safe so it can be run with the 'I' flag,
|
||||
which speeds up the startup time of the hook by 140x
|
||||
|
||||
example usage as global config:
|
||||
--xm aw,I,bin/hooks/wget-i.py
|
||||
|
||||
parameters explained,
|
||||
xm = execute on message-to-server-log
|
||||
aw = only users with write-access can use this
|
||||
I = import; do not fork / subprocess
|
||||
|
||||
example usage as a volflag (per-volume config):
|
||||
-v srv/inc:inc:r:rw,ed:c,xm=aw,I,bin/hooks/wget.py
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
(share filesystem-path srv/inc as volume /inc,
|
||||
readable by everyone, read-write for user 'ed',
|
||||
running this plugin on all messages with the params explained above)
|
||||
|
||||
example usage as a volflag in a copyparty config file:
|
||||
[/inc]
|
||||
srv/inc
|
||||
accs:
|
||||
r: *
|
||||
rw: ed
|
||||
flags:
|
||||
xm: aw,I,bin/hooks/wget.py
|
||||
|
||||
the volflag examples only kicks in if you send the message
|
||||
while you're in the /inc folder (or any folder below there)
|
||||
|
||||
IMPORTANT NOTE:
|
||||
because this hook uses the 'I' flag to run inside copyparty,
|
||||
many other flags will not work (f,j,c3,t3600 as seen in the
|
||||
original wget.py), and furthermore + more importantly we
|
||||
need to be EXCEPTIONALLY CAREFUL to avoid side-effects, so
|
||||
the os.chdir has been replaced with cwd=dirpath for example
|
||||
"""
|
||||
|
||||
|
||||
def do_stuff(inf):
|
||||
"""
|
||||
worker function which is executed in another thread to
|
||||
avoid blocking copyparty while the download is running,
|
||||
since we cannot use the 'f,t3600' hook-flags with 'I'
|
||||
"""
|
||||
|
||||
# first things first; grab the logger-function which copyparty is letting us borrow
|
||||
log = inf["log"]
|
||||
|
||||
url = inf["txt"]
|
||||
if "://" not in url:
|
||||
url = "https://" + url
|
||||
|
||||
proto = url.split("://")[0].lower()
|
||||
if proto not in ("http", "https", "ftp", "ftps"):
|
||||
raise Exception("bad proto {}".format(proto))
|
||||
|
||||
dirpath = inf["ap"]
|
||||
|
||||
name = url.split("?")[0].split("/")[-1]
|
||||
msg = "-- DOWNLOADING " + name
|
||||
log(msg)
|
||||
tfn = os.path.join(dirpath, msg)
|
||||
open(tfn, "wb").close()
|
||||
|
||||
cmd = ["wget", "--trust-server-names", "-nv", "--", url]
|
||||
|
||||
try:
|
||||
# two things to note here:
|
||||
# - cannot use the `c3` hook-flag with `I` so mute output with stdout=sp.DEVNULL instead;
|
||||
# - MUST NOT use os.chdir with 'I' so use cwd=dirpath instead
|
||||
sp.check_call(cmd, cwd=dirpath, stdout=sp.DEVNULL)
|
||||
except:
|
||||
t = "-- FAILED TO DOWNLOAD " + name
|
||||
log(t, 3) # 3=yellow=warning
|
||||
open(os.path.join(dirpath, t), "wb").close()
|
||||
raise # have copyparty scream about the details in the log
|
||||
|
||||
os.unlink(tfn)
|
||||
|
||||
|
||||
def main(inf):
|
||||
threading.Thread(target=do_stuff, args=(inf,), daemon=True).start()
|
|
@ -7,6 +7,12 @@
|
|||
* works on windows, linux and macos
|
||||
* assumes `copyparty-sfx.py` was renamed to `copyparty.py` in the same folder as `copyparty.bat`
|
||||
|
||||
### [`setup-ashell.sh`](setup-ashell.sh)
|
||||
* run copyparty on an iPhone/iPad using [a-Shell](https://holzschu.github.io/a-Shell_iOS/)
|
||||
* not very useful due to limitations in iOS:
|
||||
* not able to share all of your phone's storage
|
||||
* cannot run in the background
|
||||
|
||||
### [`index.html`](index.html)
|
||||
* drop-in redirect from an httpd to copyparty
|
||||
* assumes the webserver and copyparty is running on the same server/IP
|
||||
|
|
|
@ -52,6 +52,7 @@ let
|
|||
${mkSection "global" cfg.settings}
|
||||
${cfg.globalExtraConfig}
|
||||
${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
|
||||
${mkSection "groups" cfg.groups}
|
||||
${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
|
||||
'';
|
||||
|
||||
|
@ -167,6 +168,19 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
description = ''
|
||||
A set of copyparty groups to create and the users that should be part of each group.
|
||||
'';
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
group_name = [ "user1" "user2" ];
|
||||
};
|
||||
'';
|
||||
};
|
||||
|
||||
volumes = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
|
@ -356,13 +370,17 @@ in
|
|||
) cfg.volumes
|
||||
);
|
||||
|
||||
users.groups.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") { };
|
||||
users.users.copyparty = lib.mkIf (cfg.user == "copyparty" && cfg.group == "copyparty") {
|
||||
users.groups = lib.mkIf (cfg.group == "copyparty") {
|
||||
copyparty = { };
|
||||
};
|
||||
users.users = lib.mkIf (cfg.user == "copyparty") {
|
||||
copyparty = {
|
||||
description = "Service user for copyparty";
|
||||
group = "copyparty";
|
||||
group = cfg.group;
|
||||
home = externalStateDir;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
|
||||
(pkgs.writeShellScriptBin "copyparty-hash" ''
|
||||
set -a # automatically export variables
|
||||
|
@ -380,4 +398,3 @@ in
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver="1.19.8"
|
||||
pkgver="1.19.16"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
|
@ -23,7 +23,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")
|
||||
backup=("etc/${pkgname}/copyparty.conf" )
|
||||
sha256sums=("3143ba5216c8d4cf1fbc58fa08c6ecef955de04b5e34b3910ab0b71cffec88ef")
|
||||
sha256sums=("d8cc10d3623eaccc8acaebdd3b0102a1ebf878a03ffe6e737d132c66f799d682")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
pkgname=copyparty
|
||||
pkgver=1.19.8
|
||||
pkgver=1.19.16
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
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")
|
||||
backup=("/etc/${pkgname}.d/init" )
|
||||
sha256sums=("3143ba5216c8d4cf1fbc58fa08c6ecef955de04b5e34b3910ab0b71cffec88ef")
|
||||
sha256sums=("d8cc10d3623eaccc8acaebdd3b0102a1ebf878a03ffe6e737d132c66f799d682")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.8/copyparty-1.19.8.tar.gz",
|
||||
"version": "1.19.8",
|
||||
"hash": "sha256-MUO6UhbI1M8fvFj6CMbs75Vd4EteNLORCrC3HP/siO8="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.19.16/copyparty-1.19.16.tar.gz",
|
||||
"version": "1.19.16",
|
||||
"hash": "sha256-2MwQ02I+rMyKyuvdOwECoev4eKA//m5zfRMsZveZ1oI="
|
||||
}
|
173
contrib/podman-systemd/README.md
Normal file
173
contrib/podman-systemd/README.md
Normal file
|
@ -0,0 +1,173 @@
|
|||
# copyparty with Podman and Systemd
|
||||
|
||||
Use this configuration if you want to run copyparty in a Podman container, with the reliability of running the container under a systemd service.
|
||||
|
||||
Documentation for `.container` files can be found in the [Container unit](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#container-units-container) docs. Systemd does not understand `.container` files natively, so Podman converts these to `.service` files with a [systemd-generator](https://www.freedesktop.org/software/systemd/man/latest/systemd.generator.html). This process is transparent, but sometimes needs to be debugged in case your `.container` file is malformed. There are instructions to debug the systemd generator in the Troubleshooting section below.
|
||||
|
||||
To run copyparty in this way, you must already have podman installed. To install Podman, see: https://podman.io/docs/installation
|
||||
|
||||
There is a sample configuration file in the same directory as this file (`copyparty.conf`).
|
||||
|
||||
## Run the container as root
|
||||
|
||||
Running the container as the root user is easy to set up, but less secure. There are instructions in the next section to run the container as a rootless user if you'd rather run the container like that.
|
||||
|
||||
First, change this line in the `copyparty.container` file to reflect the directory you want to share. By default, it shares `/mnt/` but you'll probably want to change that.
|
||||
|
||||
```
|
||||
# Change /mnt to something you want to share
|
||||
Volume=/mnt:/w:z
|
||||
```
|
||||
|
||||
Note that you can select the owner and group of this volume by changing the `uid:` and `gid:` of the volume in `copyparty.conf`, but for simplicity let's assume you want it to be owned by `root:root`.
|
||||
|
||||
To install and start copyparty with Podman and systemd as the root user, run the following:
|
||||
|
||||
```shell
|
||||
sudo mkdir -pv /etc/systemd/container/ /etc/copyparty/
|
||||
sudo cp -v copyparty.container /etc/systemd/containers/
|
||||
sudo cp -v copyparty.conf /etc/copyparty/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl start copyparty
|
||||
```
|
||||
|
||||
Note: You can't "enable" this kind of Podman service. The `[Install]` section of the `.container` file effectively handles enabling the service so that it starts when the server reboots.
|
||||
|
||||
You can see the status of the service with:
|
||||
|
||||
```shell
|
||||
sudo systemctl status -a copyparty
|
||||
```
|
||||
|
||||
You can see (and follow) the logs with either of these commands:
|
||||
|
||||
```shell
|
||||
sudo podman logs -f copyparty
|
||||
|
||||
# -a is required or else you'll get output like: copyparty[549025]: [649B blob data]
|
||||
sudo journalctl -a -f -u copyparty
|
||||
```
|
||||
|
||||
## Run the container as a non-root user
|
||||
|
||||
This configuration is more secure, but is more involved and requires ensuring files have proper permissions. You will need a root user account to do some of this setup.
|
||||
|
||||
First, you need a user to run the container as. In this example we'll create a "podman" user with UID=1001 and GID=1001.
|
||||
|
||||
```shell
|
||||
sudo groupadd -g 1001 podman
|
||||
sudo useradd -u 1001 -m podman
|
||||
sudo usermod -aG podman podman
|
||||
sudo loginctl enable-linger podman
|
||||
# Set a strong password for this user
|
||||
sudo -u podman passwd
|
||||
```
|
||||
|
||||
The `enable-linger` command allows the podman user to run systemd user services that persist even when the user is not logged in. You could use a user that already exists in the system to run this service as, just make sure to run `loginctl enable-linger USERNAME` for that user.
|
||||
|
||||
Next, change these lines in the `copyparty.container` file to reflect the config directory and the directory you want to share. By default, the config shares `/home/podman/copyparty/sharing/` but you'll probably want to change this:
|
||||
|
||||
```
|
||||
# Change to reflect your non-root user's home directory
|
||||
Volume=/home/podman/copyparty/config:/cfg:z
|
||||
|
||||
# Change to the directory you want to share
|
||||
Volume=/home/podman/copyparty/sharing:/w:z
|
||||
```
|
||||
|
||||
Make sure the podman user has read/write access to both of these directories.
|
||||
|
||||
Next, **log in to the server as the podman user**.
|
||||
|
||||
To install and start copyparty as the non-root podman user, run the following:
|
||||
|
||||
```shell
|
||||
mkdir -pv /home/podman/.config/containers/systemd/ /home/podman/copyparty/config
|
||||
cp -v copyparty.container /home/podman/.config/containers/systemd/copyparty.container
|
||||
cp -v copyparty.conf /home/podman/copyparty/config
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user start copyparty
|
||||
```
|
||||
|
||||
**Important note: Never use `sudo` with `systemctl --user`!**
|
||||
|
||||
You can check the status of the user service with:
|
||||
|
||||
```shell
|
||||
systemctl --user status -a copyparty
|
||||
```
|
||||
|
||||
You can see (and follow) the logs with:
|
||||
|
||||
```shell
|
||||
podman logs -f copyparty
|
||||
|
||||
journalctl --user -a -f -u copyparty
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the container fails to start, and you've modified the `.container` service, it's likely that your `.container` file failed to be translated into a `.service` file. You can debug the podman service generator with this command:
|
||||
|
||||
```shell
|
||||
sudo /usr/lib/systemd/system-generators/podman-system-generator --dryrun
|
||||
```
|
||||
|
||||
## Allowing Traffic from Outside your Server
|
||||
|
||||
To allow traffic on port 3923 of your server, you should run:
|
||||
|
||||
```shell
|
||||
sudo firewall-cmd --permanent --add-port=3923/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
Otherwise, you won't be able to access the copyparty server from anywhere other than the server itself.
|
||||
|
||||
## Updating copyparty
|
||||
|
||||
To update the version of copyparty used in the container, you can:
|
||||
|
||||
```shell
|
||||
# If root:
|
||||
sudo podman pull docker.io/copyparty/ac:latest
|
||||
sudo systemctl restart copyparty
|
||||
|
||||
# If non-root:
|
||||
podman pull docker.io/copyparty/ac:latest
|
||||
systemctl --user restart copyparty
|
||||
```
|
||||
|
||||
Or, you can change the pinned version of the image in the `[Container]` section of the `.container` file and run:
|
||||
|
||||
```shell
|
||||
# If root:
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart copyparty
|
||||
|
||||
# If non-root:
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart copyparty
|
||||
```
|
||||
|
||||
Podman will pull the image you've specified when restarting. If you have it set to `:latest`, Podman does not know to re-pull the container.
|
||||
|
||||
### Enabling auto-update
|
||||
|
||||
Alternatively, you can enable auto-updates by un-commenting this line:
|
||||
|
||||
```
|
||||
# AutoUpdate=registry
|
||||
```
|
||||
|
||||
You will also need to enable the [podman auto-updater service](https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html) with:
|
||||
|
||||
```shell
|
||||
# If root:
|
||||
sudo systemctl enable podman-auto-update.timer podman-auto-update.service
|
||||
|
||||
# If non-root:
|
||||
systemctl --user enable podman-auto-update.timer podman-auto-update.service
|
||||
```
|
||||
|
||||
This works best if you always want the latest version of copyparty. The auto-updater runs once every 24 hours.
|
36
contrib/podman-systemd/copyparty.conf
Normal file
36
contrib/podman-systemd/copyparty.conf
Normal file
|
@ -0,0 +1,36 @@
|
|||
[global]
|
||||
e2dsa # enable file indexing and filesystem scanning
|
||||
e2ts # and enable multimedia indexing
|
||||
ansi # and colors in log messages
|
||||
|
||||
# uncomment the line starting with q, lo: to log to a file instead of stdout/journalctl;
|
||||
# $LOGS_DIRECTORY is usually /var/log/copyparty (comes from systemd)
|
||||
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
|
||||
# full path will be something like /var/log/copyparty/2023-1130.txt
|
||||
# (note: enable compression by adding .xz at the end)
|
||||
# q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
||||
|
||||
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
||||
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
||||
# ftp: 3921 # enable ftp server on port 3921
|
||||
# p: 3939 # listen on another port
|
||||
# df: 16 # stop accepting uploads if less than 16 GB free disk space
|
||||
# ver # show copyparty version in the controlpanel
|
||||
# grid # show thumbnails/grid-view by default
|
||||
# theme: 2 # monokai
|
||||
# name: datasaver # change the server-name that's displayed in the browser
|
||||
# stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow)
|
||||
# no-robots, force-js # make it harder for search engines to read your server
|
||||
|
||||
|
||||
[accounts]
|
||||
ed: wark # username: password
|
||||
|
||||
|
||||
[/] # create a volume at "/" (the webroot), which will
|
||||
/w # share the contents of the "/w" folder
|
||||
accs:
|
||||
rw: * # everyone gets read-write access, but
|
||||
rwmda: ed # the user "ed" gets read-write-move-delete-admin
|
||||
# uid: 1000 # If you're running as root, you can change the owner of this volume here
|
||||
# gid: 1000 # If you're running as root, you can change the group of this volume here
|
55
contrib/podman-systemd/copyparty.container
Normal file
55
contrib/podman-systemd/copyparty.container
Normal file
|
@ -0,0 +1,55 @@
|
|||
[Container]
|
||||
# It's recommended to replace :latest with a specific version
|
||||
# for example: docker.io/copyparty/ac:1.19.15
|
||||
Image=docker.io/copyparty/ac:latest
|
||||
ContainerName=copyparty
|
||||
|
||||
# Uncomment to enable auto-updates
|
||||
# AutoUpdate=registry
|
||||
|
||||
# Environment variables
|
||||
# enable mimalloc by replacing "NOPE" with "2" for a nice speed-boost (will use twice as much ram)
|
||||
Environment=LD_PRELOAD=/usr/lib/libmimalloc-secure.so.NOPE
|
||||
# ensures log-messages are not delayed (but can reduce speed a tiny bit)
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# Ports
|
||||
PublishPort=3923:3923
|
||||
|
||||
|
||||
# Volumes (PLEASE LOOK!)
|
||||
|
||||
# Rootful setup:
|
||||
# Leave as-is
|
||||
# Non-root setup:
|
||||
# Change /etc/copyparty to /home/<USER>/copyparty/config
|
||||
Volume=/etc/copyparty:/cfg:z
|
||||
|
||||
# Rootful setup:
|
||||
# Change /mnt to the directory you want to share
|
||||
# Non-root setup:
|
||||
# Change /mnt to something owned by your user, e.g., /home/<USER>/copyparty/sharing:/w:z
|
||||
Volume=/mnt:/w:z
|
||||
|
||||
|
||||
# Give the container time to stop in case the thumbnailer is still running.
|
||||
# It's allowed to continue finishing up for 10s after the shutdown signal, give it a 5s buffer
|
||||
StopTimeout=15
|
||||
|
||||
# hide it from logs with "/._" so it matches the default --lf-url filter
|
||||
HealthCmd="wget --spider -q 127.0.0.1:3923/?reset=/._"
|
||||
HealthInterval=1m
|
||||
HealthTimeout=2s
|
||||
HealthRetries=5
|
||||
HealthStartPeriod=15s
|
||||
|
||||
[Unit]
|
||||
After=default.target
|
||||
|
||||
[Install]
|
||||
# Start by default on boot
|
||||
WantedBy=default.target
|
||||
|
||||
[Service]
|
||||
# Give the container time to start in case it needs to pull the image
|
||||
TimeoutStartSec=600
|
|
@ -6,7 +6,7 @@
|
|||
# https://apps.apple.com/us/app/a-shell/id1473805438
|
||||
#
|
||||
# step 2: copypaste the following command into a-Shell:
|
||||
# curl https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh
|
||||
# curl -L https://github.com/9001/copyparty/raw/refs/heads/hovudstraum/contrib/setup-ashell.sh
|
||||
#
|
||||
# step 3: launch copyparty with this command: cpp
|
||||
#
|
||||
|
|
|
@ -88,6 +88,7 @@ web/mde.html
|
|||
web/mde.js
|
||||
web/msg.css
|
||||
web/msg.html
|
||||
web/opds.xml
|
||||
web/rups.css
|
||||
web/rups.html
|
||||
web/rups.js
|
||||
|
|
|
@ -813,9 +813,11 @@ def get_sects():
|
|||
\033[36mf\033[35m forks the process, doesn't wait for completion
|
||||
\033[36mc\033[35m checks return code, blocks the action if non-zero
|
||||
\033[36mj\033[35m provides json with info as 1st arg instead of filepath
|
||||
\033[36ms\033[35m provides input data on stdin (instead of 1st arg)
|
||||
\033[36mwN\033[35m waits N sec after command has been started before continuing
|
||||
\033[36mtN\033[35m sets an N sec timeout before the command is abandoned
|
||||
\033[36miN\033[35m xiu only: volume must be idle for N sec (default = 5)
|
||||
\033[36mI\033[35m import and run as module, not as subprocess
|
||||
|
||||
\033[36mar\033[35m only run hook if user has read-access
|
||||
\033[36marw\033[35m only run hook if user has read-write-access
|
||||
|
@ -845,6 +847,9 @@ def get_sects():
|
|||
the \033[33m--\033[35m stops notify-send from reading the message as args
|
||||
and the alert will be "hey" followed by the messagetext
|
||||
|
||||
\033[36m--xm s,,tee,-a,log.txt\033[35m appends each msg to log.txt;
|
||||
\033[36m--xm s,j,,tee,-a,log.txt\033[35m writes it as json instead
|
||||
|
||||
\033[36m--xau zmq:pub:tcp://*:5556\033[35m announces uploads on zeromq;
|
||||
\033[36m--xau t3,zmq:push:tcp://*:5557\033[35m also works, and you can
|
||||
\033[36m--xau t3,j,zmq:req:tcp://localhost:5555\033[35m too for example
|
||||
|
@ -854,7 +859,8 @@ def get_sects():
|
|||
as soon as the volume has been idle for iN seconds (5 by default)
|
||||
|
||||
\033[36mxiu\033[0m is also unique in that it will pass the metadata to the
|
||||
executed program on STDIN instead of as argv arguments, and
|
||||
executed program on STDIN instead of as argv arguments (so
|
||||
just like the \033[36ms\033[0m option does for the other hook types), and
|
||||
it also includes the wark (file-id/hash) as a json property
|
||||
|
||||
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
|
||||
|
@ -865,6 +871,12 @@ def get_sects():
|
|||
on new uploads, but with certain limitations. See
|
||||
bin/hooks/reloc* and docs/devnotes.md#hook-effects
|
||||
|
||||
the \033[36mI\033[0m option will override most other options, because
|
||||
it entirely hands over control to the hook, which is
|
||||
then able to tamper with copyparty's internal memory
|
||||
and wreck havoc if it wants to -- but this is worh it
|
||||
because it makes the hook 140x faster
|
||||
|
||||
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
|
||||
so it's recommended to use the \033[36mf\033[0m flag unless you really need
|
||||
to wait for the hook to finish before continuing (without \033[36mf\033[0m
|
||||
|
@ -956,7 +968,7 @@ def get_sects():
|
|||
\033[36m{{vf.thsize}} \033[35mthumbnail size
|
||||
\033[36m{{srv.itime}} \033[35mserver time in seconds
|
||||
\033[36m{{srv.htime}} \033[35mserver time as YY-mm-dd, HH:MM:SS (UTC)
|
||||
\033[36m{{hdr.cf_ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank)
|
||||
\033[36m{{hdr.cf-ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank)
|
||||
\033[0m
|
||||
so the following types of placeholders can be added to the lists:
|
||||
* any client header can be accessed through {{hdr.*}}
|
||||
|
@ -1155,6 +1167,8 @@ def add_general(ap, nc, srvname):
|
|||
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,xm", help="how to handle url-form POSTs; see \033[33m--help-urlform\033[0m")
|
||||
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="server terminal title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
|
||||
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
||||
ap2.add_argument("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)")
|
||||
ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS)
|
||||
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
|
||||
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
||||
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
|
||||
|
@ -1220,6 +1234,7 @@ def add_upload(ap):
|
|||
ap2.add_argument("--chmod-d", metavar="UGO", type=u, default="755", help="unix file permissions to use when creating directories; see --help-chmod. Examples: [\033[32m755\033[0m] = owner-RW + all-R, [\033[32m777\033[0m] = full-yolo (volflag=chmod_d)")
|
||||
ap2.add_argument("--uid", metavar="N", type=int, default=-1, help="unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid)")
|
||||
ap2.add_argument("--gid", metavar="N", type=int, default=-1, help="unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid)")
|
||||
ap2.add_argument("--wram", action="store_true", help="allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram)")
|
||||
ap2.add_argument("--dedup", action="store_true", help="enable symlink-based upload deduplication (volflag=dedup)")
|
||||
ap2.add_argument("--safe-dedup", metavar="N", type=int, default=50, help="how careful to be when deduplicating files; [\033[32m1\033[0m] = just verify the filesize, [\033[32m50\033[0m] = verify file contents have not been altered (volflag=safededup)")
|
||||
ap2.add_argument("--hardlink", action="store_true", help="enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink)")
|
||||
|
@ -1230,7 +1245,9 @@ def add_upload(ap):
|
|||
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("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash")
|
||||
ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)")
|
||||
ap2.add_argument("--rm-partial", action="store_true", help="delete the .PARTIAL file when an unfinished upload expires after \033[33m--snap-drop\033[0m (volflag=rm_partial)")
|
||||
ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)")
|
||||
ap2.add_argument("--rotf-tz", metavar="TXT", type=u, default="UTC", help="default timezone for the rotf upload rule; examples: [\033[32mEurope/Oslo\033[0m], [\033[32mAmerica/Toronto\033[0m], [\033[32mAntarctica/South_Pole\033[0m] (volflag=rotf_tz)")
|
||||
ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)")
|
||||
ap2.add_argument("--nrand", metavar="NUM", type=int, default=9, help="randomized filenames length (volflag=nrand)")
|
||||
ap2.add_argument("--magic", action="store_true", help="enable filetype detection on nameless uploads (volflag=magic)")
|
||||
|
@ -1255,6 +1272,7 @@ def add_network(ap):
|
|||
ap2.add_argument("--xff-src", metavar="CIDR", type=u, default="127.0.0.0/8, ::1/128", help="list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (\033[33m--xff-hdr\033[0m) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [\033[32mlan\033[0m] to allow all LAN / private / non-internet IPs. Can be disabled with [\033[32many\033[0m] if you are behind cloudflare (or similar) and are using \033[32m--xff-hdr=cf-connecting-ip\033[0m (or similar)")
|
||||
ap2.add_argument("--ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
|
||||
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; example: [\033[32m/foo/bar\033[0m]")
|
||||
ap2.add_argument("--http-no-tcp", action="store_true", help="do not listen on TCP/IP for http/https; only listen on unix-domain-sockets")
|
||||
if ANYWIN:
|
||||
ap2.add_argument("--reuseaddr", action="store_true", help="set reuseaddr on listening sockets on windows; allows rapid restart of copyparty at the expense of being able to accidentally start multiple instances")
|
||||
elif not MACOS:
|
||||
|
@ -1362,6 +1380,8 @@ def add_zc_mdns(ap):
|
|||
ap2.add_argument("--zm6", action="store_true", help="IPv6 only")
|
||||
ap2.add_argument("--zmv", action="store_true", help="verbose mdns")
|
||||
ap2.add_argument("--zmvv", action="store_true", help="verboser mdns")
|
||||
ap2.add_argument("--zm-http", metavar="PORT", type=int, default=-1, help="port to announce for http/webdav; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649")
|
||||
ap2.add_argument("--zm-https", metavar="PORT", type=int, default=-1, help="port to announce for https/webdavs; [\033[32m-1\033[0m] = auto, [\033[32m0\033[0m] = disabled, [\033[32m4649\033[0m] = port 4649")
|
||||
ap2.add_argument("--zm-no-pe", action="store_true", help="mute parser errors (invalid incoming MDNS packets)")
|
||||
ap2.add_argument("--zm-nwa-1", action="store_true", help="disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature)")
|
||||
ap2.add_argument("--zms", metavar="dhf", type=u, default="", help="list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (\033[32mDdfs\033[0m if \033[33m--ftp\033[0m and \033[33m--smb\033[0m is set, \033[32mDd\033[0m otherwise)")
|
||||
|
@ -1435,6 +1455,10 @@ def add_smb(ap):
|
|||
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||
|
||||
def add_opds(ap):
|
||||
ap2 = ap.add_argument_group("OPDS options")
|
||||
ap2.add_argument("--opds", action="store_true", help="enable opds -- allows e-book readers to browse and download files (volflag=opds)")
|
||||
ap2.add_argument("--opds-exts", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts)")
|
||||
|
||||
def add_handlers(ap):
|
||||
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
|
||||
|
@ -1509,7 +1533,7 @@ def add_optouts(ap):
|
|||
def add_safety(ap):
|
||||
ap2 = ap.add_argument_group("safety options")
|
||||
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
||||
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --dav-auth --vague-403 -nih")
|
||||
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih")
|
||||
ap2.add_argument("-sss", action="store_true", help="further increase safety: Enable logging to disk, scan for dangerous symlinks.\n └─Alias of\033[32m -ss --no-dav --no-logues --no-readme -lo=cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz --ls=**,*,ln,p,r")
|
||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, default="", help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m (see \033[33m--help-ls\033[0m); example [\033[32m**,*,ln,p,r\033[0m]")
|
||||
ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
|
||||
|
@ -1529,7 +1553,7 @@ def add_safety(ap):
|
|||
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 (invalid requests, attempted exploits ++)")
|
||||
ap2.add_argument("--ban-url", metavar="N,W,B", type=u, default="9,2,1440", help="hitting more than \033[33mN\033[0m sus URL's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; applies only to permissions g/G/h (decent replacement for \033[33m--ban-404\033[0m if that can't be used)")
|
||||
ap2.add_argument("--sus-urls", metavar="R", type=u, default=r"\.php$|(^|/)wp-(admin|content|includes)/", help="URLs which are considered sus / eligible for banning; disable with blank or [\033[32mno\033[0m]")
|
||||
ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\.ico|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 404-bans; disable with blank or [\033[32mno\033[0m]")
|
||||
ap2.add_argument("--nonsus-urls", metavar="R", type=u, default=r"^(favicon\..{3}|robots\.txt)$|^apple-touch-icon|^\.well-known", help="harmless URLs ignored from 403/404-bans; disable with blank or [\033[32mno\033[0m]")
|
||||
ap2.add_argument("--early-ban", action="store_true", help="if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy")
|
||||
ap2.add_argument("--cookie-nmax", metavar="N", type=int, default=50, help="reject HTTP-request from client if they send more than N cookies")
|
||||
ap2.add_argument("--cookie-cmax", metavar="N", type=int, default=8192, help="reject HTTP-request from client if more than N characters in Cookie header")
|
||||
|
@ -1577,6 +1601,9 @@ def add_logging(ap):
|
|||
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="print request \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
|
||||
ap2.add_argument("--ohead", metavar="HEADER", type=u, action='append', help="print response \033[33mHEADER\033[0m; [\033[32m*\033[0m]=all")
|
||||
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|[?&]th=[wjp]|/\.(_|ql_|DS_Store$|localized$)", help="dont log URLs matching regex \033[33mRE\033[0m")
|
||||
ap2.add_argument("--scan-st-r", metavar="SEC", type=float, default=0.1, help="fs-indexing: wait \033[33mSEC\033[0m between each status-message")
|
||||
ap2.add_argument("--scan-pr-r", metavar="SEC", type=float, default=10, help="fs-indexing: wait \033[33mSEC\033[0m between each 'progress:' message")
|
||||
ap2.add_argument("--scan-pr-s", metavar="MiB", type=float, default=1, help="fs-indexing: say 'file: <name>' when a file larger than \033[33mMiB\033[0m is about to be hashed")
|
||||
|
||||
|
||||
def add_admin(ap):
|
||||
|
@ -1745,6 +1772,7 @@ def add_ui(ap, retry: int):
|
|||
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
||||
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
||||
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
|
||||
ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, fuzzy (see UI)")
|
||||
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
|
||||
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
|
||||
ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
|
||||
|
@ -1756,6 +1784,7 @@ def add_ui(ap, retry: int):
|
|||
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
|
||||
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
||||
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
||||
ap2.add_argument("--ufavico", metavar="TXT", type=u, default="", help="URL to .ico/png/gif/svg file; \033[33m--favico\033[0m takes precedence unless disabled (volflag=ufavico)")
|
||||
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="\033[34mREPEATABLE:\033[0m use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
|
||||
ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
|
||||
ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
|
||||
|
@ -1763,6 +1792,7 @@ def add_ui(ap, retry: int):
|
|||
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
|
||||
ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages")
|
||||
ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with %% to render as jinja2 template (volflag=html_head)")
|
||||
ap2.add_argument("--html-head-s", metavar="T", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s)")
|
||||
ap2.add_argument("--ih", action="store_true", help="if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override)")
|
||||
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
|
||||
ap2.add_argument("--txt-max", metavar="KiB", type=int, default=64, help="max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS)")
|
||||
|
@ -1860,6 +1890,7 @@ def run_argparse(
|
|||
add_webdav(ap)
|
||||
add_tftp(ap)
|
||||
add_smb(ap)
|
||||
add_opds(ap)
|
||||
add_safety(ap)
|
||||
add_salt(ap, fk_salt, dk_salt, ah_salt)
|
||||
add_optouts(ap)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 19, 9)
|
||||
VERSION = (1, 19, 16)
|
||||
CODENAME = "usernames"
|
||||
BUILD_DT = (2025, 9, 15)
|
||||
BUILD_DT = (2025, 10, 5)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
|
|
@ -21,8 +21,10 @@ from .util import (
|
|||
DEF_MTE,
|
||||
DEF_MTH,
|
||||
EXTS,
|
||||
FAVICON_MIMES,
|
||||
HAVE_SQLITE3,
|
||||
IMPLICATIONS,
|
||||
META_NOBOTS,
|
||||
MIMES,
|
||||
SQLITE_VER,
|
||||
UNPLICATIONS,
|
||||
|
@ -169,14 +171,19 @@ class Lim(object):
|
|||
self.rotn = 0 # rot num files
|
||||
self.rotl = 0 # rot depth
|
||||
self.rotf = "" # rot datefmt
|
||||
self.rotf_tz = UTC # rot timezone
|
||||
self.rot_re = re.compile("") # rotf check
|
||||
|
||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||
if self.log_func:
|
||||
self.log_func("up-lim", msg, c)
|
||||
|
||||
def set_rotf(self, fmt: str) -> None:
|
||||
def set_rotf(self, fmt: str, tz: str) -> None:
|
||||
self.rotf = fmt
|
||||
if tz != "UTC":
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
self.rotf_tz = ZoneInfo(tz)
|
||||
r = re.escape(fmt).replace("%Y", "[0-9]{4}").replace("%j", "[0-9]{3}")
|
||||
r = re.sub("%[mdHMSWU]", "[0-9]{2}", r)
|
||||
self.rot_re = re.compile("(^|/)" + r + "$")
|
||||
|
@ -280,7 +287,7 @@ class Lim(object):
|
|||
if self.rot_re.search(path.replace("\\", "/")):
|
||||
return path, ""
|
||||
|
||||
suf = datetime.now(UTC).strftime(self.rotf)
|
||||
suf = datetime.now(self.rotf_tz).strftime(self.rotf)
|
||||
if path:
|
||||
path += "/"
|
||||
|
||||
|
@ -635,6 +642,8 @@ class VFS(object):
|
|||
if do_stat and not bos.path.exists(ap):
|
||||
return True # doesn't exist at all; good to go
|
||||
dp, fn = os.path.split(ap)
|
||||
if not fn:
|
||||
return True # filesystem root
|
||||
try:
|
||||
fns = os.listdir(dp)
|
||||
except:
|
||||
|
@ -647,6 +656,8 @@ class VFS(object):
|
|||
if lfn == zs.lower():
|
||||
hit = zs
|
||||
break
|
||||
if not hit:
|
||||
return True # NFC/NFD or something, can't be helped either way
|
||||
if self.log:
|
||||
t = "returning 404 due to underlying case-insensitive filesystem:\n http-req: %r\n local-fs: %r"
|
||||
self.log("vfs", t % (fn, hit))
|
||||
|
@ -1808,8 +1819,7 @@ class AuthSrv(object):
|
|||
self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns)))
|
||||
raise
|
||||
|
||||
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
|
||||
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
|
||||
derive_args(self.args)
|
||||
self.setup_auth_ord()
|
||||
|
||||
self.setup_pwhash(acct)
|
||||
|
@ -2214,7 +2224,7 @@ class AuthSrv(object):
|
|||
zs = vol.flags.get("rotf")
|
||||
if zs:
|
||||
use = True
|
||||
lim.set_rotf(zs)
|
||||
lim.set_rotf(zs, vol.flags.get("rotf_tz") or "UTC")
|
||||
|
||||
zs = vol.flags.get("maxn")
|
||||
if zs:
|
||||
|
@ -2485,6 +2495,45 @@ class AuthSrv(object):
|
|||
t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
|
||||
self.log(t % (vol.vpath, etv), 3)
|
||||
|
||||
zs = str(vol.flags.get("html_head") or "")
|
||||
if zs and zs[:1] in "%@":
|
||||
vol.flags["html_head_d"] = zs
|
||||
head_s = str(vol.flags.get("html_head_s") or "")
|
||||
else:
|
||||
zs2 = str(vol.flags.get("html_head_s") or "")
|
||||
if zs2 and zs:
|
||||
head_s = "%s\n%s\n" % (zs2.strip(), zs.strip())
|
||||
else:
|
||||
head_s = zs2 or zs
|
||||
|
||||
if head_s and not head_s.endswith("\n"):
|
||||
head_s += "\n"
|
||||
|
||||
if "norobots" in vol.flags:
|
||||
head_s += META_NOBOTS
|
||||
|
||||
ico_url = vol.flags.get("ufavico")
|
||||
if ico_url:
|
||||
ico_h = ""
|
||||
ico_ext = ico_url.split("?")[0].split(".")[-1].lower()
|
||||
if ico_ext in FAVICON_MIMES:
|
||||
zs = '<link rel="icon" type="%s" href="%s">\n'
|
||||
ico_h = zs % (FAVICON_MIMES[ico_ext], ico_url)
|
||||
elif ico_ext == "ico":
|
||||
zs = '<link rel="shortcut icon" href="%s">\n'
|
||||
ico_h = zs % (ico_url,)
|
||||
if ico_h:
|
||||
vol.flags["ufavico_h"] = ico_h
|
||||
head_s += ico_h
|
||||
|
||||
if head_s:
|
||||
vol.flags["html_head_s"] = head_s
|
||||
else:
|
||||
vol.flags.pop("html_head_s", None)
|
||||
|
||||
if not vol.flags.get("html_head_d"):
|
||||
vol.flags.pop("html_head_d", None)
|
||||
|
||||
vol.check_landmarks()
|
||||
|
||||
# d2d drops all database features for a volume
|
||||
|
@ -2718,7 +2767,11 @@ class AuthSrv(object):
|
|||
|
||||
if "dedup" in zv.flags:
|
||||
have_dedup = True
|
||||
if "e2d" not in zv.flags and "hardlink" not in zv.flags:
|
||||
if (
|
||||
"e2d" not in zv.flags
|
||||
and "hardlink" not in zv.flags
|
||||
and "reflink" not in zv.flags
|
||||
):
|
||||
unsafe_dedup.append("/" + zv.vpath)
|
||||
|
||||
t += "\n"
|
||||
|
@ -2893,6 +2946,11 @@ class AuthSrv(object):
|
|||
shn.shr_src = (s_vfs, s_rem)
|
||||
shn.realpath = s_vfs.canonical(s_rem)
|
||||
|
||||
o_vn, _ = shn._get_share_src("")
|
||||
shn.flags = o_vn.flags.copy()
|
||||
shn.dbpath = o_vn.dbpath
|
||||
shn.histpath = o_vn.histpath
|
||||
|
||||
# root.all_aps doesn't include any shares, so make a copy where the
|
||||
# share appears in all abspaths it can provide (for example for chk_ap)
|
||||
ap = shn.realpath
|
||||
|
@ -2956,6 +3014,8 @@ class AuthSrv(object):
|
|||
"unlist": vf.get("unlist") or "",
|
||||
"sb_lg": "" if "no_sb_lg" in vf else (vf.get("lg_sbf") or "y"),
|
||||
}
|
||||
if "ufavico_h" in vf:
|
||||
vn.js_ls["ufavico"] = vf["ufavico_h"]
|
||||
js_htm = {
|
||||
"SPINNER": self.args.spinner,
|
||||
"s_name": self.args.bname,
|
||||
|
@ -2991,6 +3051,7 @@ class AuthSrv(object):
|
|||
"dvol": self.args.au_vol,
|
||||
"idxh": int(self.args.ih),
|
||||
"dutc": not self.args.localtime,
|
||||
"dfszf": self.args.ui_filesz.strip("-"),
|
||||
"themes": self.args.themes,
|
||||
"turbolvl": self.args.turbo,
|
||||
"nosubtle": self.args.nosubtle,
|
||||
|
@ -3626,6 +3687,11 @@ class AuthSrv(object):
|
|||
self.log("generated config:\n\n" + "\n".join(ret))
|
||||
|
||||
|
||||
def derive_args(args: argparse.Namespace) -> None:
|
||||
args.have_idp_hdrs = bool(args.idp_h_usr or args.idp_hm_usr)
|
||||
args.have_ipu_or_ipr = bool(args.ipu or args.ipr)
|
||||
|
||||
|
||||
def n_du_who(s: str) -> int:
|
||||
if s == "all":
|
||||
return 9
|
||||
|
|
|
@ -12,6 +12,7 @@ import queue
|
|||
from .__init__ import ANYWIN
|
||||
from .authsrv import AuthSrv
|
||||
from .broker_util import BrokerCli, ExceptionalQueue, NotExQueue
|
||||
from .fsutil import ramdisk_chk
|
||||
from .httpsrv import HttpSrv
|
||||
from .util import FAKE_MP, Daemon, HMaccas
|
||||
|
||||
|
@ -56,6 +57,7 @@ class MpWorker(BrokerCli):
|
|||
|
||||
# starting to look like a good idea
|
||||
self.asrv = AuthSrv(args, None, False)
|
||||
ramdisk_chk(self.asrv)
|
||||
|
||||
# instantiate all services here (TODO: inheritance?)
|
||||
self.iphash = HMaccas(os.path.join(self.args.E.cfg, "iphash"), 8)
|
||||
|
@ -99,6 +101,7 @@ class MpWorker(BrokerCli):
|
|||
if dest == "reload":
|
||||
self.logw("mpw.asrv reloading")
|
||||
self.asrv.reload()
|
||||
ramdisk_chk(self.asrv)
|
||||
self.logw("mpw.asrv reloaded")
|
||||
continue
|
||||
|
||||
|
|
|
@ -52,11 +52,14 @@ def vf_bmap() -> dict[str, str]:
|
|||
"og",
|
||||
"og_no_head",
|
||||
"og_s_title",
|
||||
"opds",
|
||||
"rand",
|
||||
"reflink",
|
||||
"rm_partial",
|
||||
"rmagic",
|
||||
"rss",
|
||||
"wo_up_readme",
|
||||
"wram",
|
||||
"xdev",
|
||||
"xlink",
|
||||
"xvol",
|
||||
|
@ -86,9 +89,11 @@ def vf_vmap() -> dict[str, str]:
|
|||
"chmod_f",
|
||||
"dbd",
|
||||
"du_who",
|
||||
"ufavico",
|
||||
"forget_ip",
|
||||
"hsortn",
|
||||
"html_head",
|
||||
"html_head_s",
|
||||
"lg_sbf",
|
||||
"md_sbf",
|
||||
"lg_sba",
|
||||
|
@ -105,6 +110,7 @@ def vf_vmap() -> dict[str, str]:
|
|||
"og_title_i",
|
||||
"og_tpl",
|
||||
"og_ua",
|
||||
"opds_exts",
|
||||
"put_ck",
|
||||
"put_name",
|
||||
"mv_retry",
|
||||
|
@ -187,8 +193,10 @@ flagcats = {
|
|||
"chmod_f=644": "unix-permission for new files",
|
||||
"uid=573": "change owner of new files/folders to unix-user 573",
|
||||
"gid=999": "change owner of new files/folders to unix-group 999",
|
||||
"wram": "allow uploading into ramdisks",
|
||||
"sparse": "force use of sparse files, mainly for s3-backed storage",
|
||||
"nosparse": "deny use of sparse files, mainly for slow storage",
|
||||
"rm_partial": "delete unfinished uploads from HDD when they timeout",
|
||||
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
||||
"nosub": "forces all uploads into the top folder of the vfs",
|
||||
"magic": "enables filetype detection for nameless uploads",
|
||||
|
@ -217,6 +225,7 @@ flagcats = {
|
|||
"upload rotation\n(moves all uploads into the specified folder structure)": {
|
||||
"rotn=100,3": "3 levels of subfolders with 100 entries in each",
|
||||
"rotf=%Y-%m/%d-%H": "date-formatted organizing",
|
||||
"rotf_tz=Europe/Oslo": "timezone (default=UTC)",
|
||||
"lifetime=3600": "uploads are deleted after 1 hour",
|
||||
},
|
||||
"database, general": {
|
||||
|
@ -296,8 +305,10 @@ flagcats = {
|
|||
"sort": "default sort order",
|
||||
"nsort": "natural-sort of leading digits in filenames",
|
||||
"hsortn": "number of sort-rules to add to media URLs",
|
||||
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
|
||||
"unlist": "dont list files matching REGEX",
|
||||
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
|
||||
"html_head_s=TXT": "additional static text in the html <head>",
|
||||
"tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
|
||||
"nodirsz": "don't show total folder size",
|
||||
"du_who=all": "show disk-usage info to everyone",
|
||||
|
@ -329,6 +340,10 @@ flagcats = {
|
|||
"og_no_head": "you want to add tags manually with og_tpl",
|
||||
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
||||
},
|
||||
"opds": {
|
||||
"opds": "enable OPDS",
|
||||
"opds_exts": "file formats to list in OPDS feeds; leave empty to show everything",
|
||||
},
|
||||
"textfiles": {
|
||||
"md_no_br": "newline only on double-newline or two tailing spaces",
|
||||
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
|
||||
|
|
|
@ -7,7 +7,7 @@ import re
|
|||
import time
|
||||
|
||||
from .__init__ import ANYWIN, MACOS
|
||||
from .authsrv import AXS, VFS
|
||||
from .authsrv import AXS, VFS, AuthSrv
|
||||
from .bos import bos
|
||||
from .util import chkcmd, min_ex, undot
|
||||
|
||||
|
@ -18,22 +18,25 @@ if True: # pylint: disable=using-constant-test
|
|||
|
||||
|
||||
class Fstab(object):
|
||||
def __init__(self, log: "RootLogger", args: argparse.Namespace):
|
||||
def __init__(self, log: "RootLogger", args: argparse.Namespace, verbose: bool):
|
||||
self.log_func = log
|
||||
self.verbose = verbose
|
||||
|
||||
self.warned = False
|
||||
self.trusted = False
|
||||
self.tab: Optional[VFS] = None
|
||||
self.oldtab: Optional[VFS] = None
|
||||
self.srctab = "a"
|
||||
self.cache: dict[str, str] = {}
|
||||
self.cache: dict[str, tuple[str, str]] = {}
|
||||
self.age = 0.0
|
||||
self.maxage = args.mtab_age
|
||||
|
||||
def log(self, msg: str, c: Union[int, str] = 0) -> None:
|
||||
if not c or self.verbose:
|
||||
return
|
||||
self.log_func("fstab", msg, c)
|
||||
|
||||
def get(self, path: str) -> str:
|
||||
def get(self, path: str) -> tuple[str, str]:
|
||||
now = time.time()
|
||||
if now - self.age > self.maxage or len(self.cache) > 9000:
|
||||
self.age = now
|
||||
|
@ -41,6 +44,7 @@ class Fstab(object):
|
|||
self.tab = None
|
||||
self.cache = {}
|
||||
|
||||
mp = ""
|
||||
fs = "ext4"
|
||||
msg = "failed to determine filesystem at %r; assuming %s\n%s"
|
||||
|
||||
|
@ -50,7 +54,7 @@ class Fstab(object):
|
|||
path = self._winpath(path)
|
||||
except:
|
||||
self.log(msg % (path, fs, min_ex()), 3)
|
||||
return fs
|
||||
return fs, ""
|
||||
|
||||
path = undot(path)
|
||||
try:
|
||||
|
@ -59,14 +63,14 @@ class Fstab(object):
|
|||
pass
|
||||
|
||||
try:
|
||||
fs = self.get_w32(path) if ANYWIN else self.get_unix(path)
|
||||
fs, mp = self.get_w32(path) if ANYWIN else self.get_unix(path)
|
||||
except:
|
||||
self.log(msg % (path, fs, min_ex()), 3)
|
||||
|
||||
fs = fs.lower()
|
||||
self.cache[path] = fs
|
||||
self.log("found %s at %r" % (fs, path))
|
||||
return fs
|
||||
self.cache[path] = (fs, mp)
|
||||
self.log("found %s at %r, %r" % (fs, mp, path))
|
||||
return fs, mp
|
||||
|
||||
def _winpath(self, path: str) -> str:
|
||||
# try to combine volume-label + st_dev (vsn)
|
||||
|
@ -81,34 +85,49 @@ class Fstab(object):
|
|||
self.tab = VFS(self.log_func, "idk", "/", "/", AXS(), {})
|
||||
self.trusted = False
|
||||
|
||||
def build_tab(self) -> None:
|
||||
self.log("inspecting mtab for changes")
|
||||
|
||||
def _from_sp_mount(self) -> dict[str, str]:
|
||||
sptn = r"^.*? on (.*) type ([^ ]+) \(.*"
|
||||
if MACOS:
|
||||
sptn = r"^.*? on (.*) \(([^ ]+), .*"
|
||||
|
||||
ptn = re.compile(sptn)
|
||||
so, _ = chkcmd(["mount"])
|
||||
tab1: list[tuple[str, str]] = []
|
||||
atab = []
|
||||
dtab: dict[str, str] = {}
|
||||
for ln in so.split("\n"):
|
||||
m = ptn.match(ln)
|
||||
if not m:
|
||||
continue
|
||||
|
||||
zs1, zs2 = m.groups()
|
||||
tab1.append((str(zs1), str(zs2)))
|
||||
atab.append(ln)
|
||||
dtab[str(zs1)] = str(zs2)
|
||||
|
||||
return dtab
|
||||
|
||||
def _from_proc(self) -> dict[str, str]:
|
||||
ret: dict[str, str] = {}
|
||||
with open("/proc/self/mounts", "rb", 262144) as f:
|
||||
src = f.read(262144).decode("utf-8", "replace").split("\n")
|
||||
for zsl in [x.split(" ") for x in src]:
|
||||
if len(zsl) < 3:
|
||||
continue
|
||||
zs = zsl[1]
|
||||
zs = zs.replace("\\011", "\t").replace("\\040", " ").replace("\\134", "\\")
|
||||
ret[zs] = zsl[2]
|
||||
return ret
|
||||
|
||||
def build_tab(self) -> None:
|
||||
self.log("inspecting mtab for changes")
|
||||
dtab = self._from_sp_mount() if MACOS else self._from_proc()
|
||||
|
||||
# keep empirically-correct values if mounttab unchanged
|
||||
srctab = "\n".join(sorted(atab))
|
||||
srctab = str(sorted(dtab.items()))
|
||||
if srctab == self.srctab:
|
||||
self.tab = self.oldtab
|
||||
return
|
||||
|
||||
self.log("mtab has changed; reevaluating support for sparse files")
|
||||
|
||||
tab1 = list(dtab.items())
|
||||
tab1.sort(key=lambda x: (len(x[0]), x[0]))
|
||||
path1, fs1 = tab1[0]
|
||||
tab = VFS(self.log_func, fs1, path1, path1, AXS(), {})
|
||||
|
@ -146,7 +165,7 @@ class Fstab(object):
|
|||
vn.realpath = ptn.sub(nval, vn.realpath)
|
||||
visit.extend(list(vn.nodes.values()))
|
||||
|
||||
def get_unix(self, path: str) -> str:
|
||||
def get_unix(self, path: str) -> tuple[str, str]:
|
||||
if not self.tab:
|
||||
try:
|
||||
self.build_tab()
|
||||
|
@ -155,20 +174,44 @@ class Fstab(object):
|
|||
# prisonparty or other restrictive environment
|
||||
if not self.warned:
|
||||
self.warned = True
|
||||
self.log("failed to build tab:\n{}".format(min_ex()), 3)
|
||||
t = "failed to associate fs-mounts with the VFS (this is fine):\n%s"
|
||||
self.log(t % (min_ex(),), 6)
|
||||
self.build_fallback()
|
||||
|
||||
assert self.tab # !rm
|
||||
ret = self.tab._find(path)[0]
|
||||
if self.trusted or path == ret.vpath:
|
||||
return ret.realpath.split("/")[0]
|
||||
return ret.realpath.split("/")[0], ret.vpath
|
||||
else:
|
||||
return "idk"
|
||||
return "idk", ""
|
||||
|
||||
def get_w32(self, path: str) -> str:
|
||||
def get_w32(self, path: str) -> tuple[str, str]:
|
||||
if not self.tab:
|
||||
self.build_fallback()
|
||||
|
||||
assert self.tab # !rm
|
||||
ret = self.tab._find(path)[0]
|
||||
return ret.realpath
|
||||
return ret.realpath, ""
|
||||
|
||||
|
||||
def ramdisk_chk(asrv: AuthSrv) -> None:
|
||||
# should have been in authsrv but that's a circular import
|
||||
mods = []
|
||||
ramfs = ("tmpfs", "overlay")
|
||||
log = asrv.log_func or print
|
||||
fstab = Fstab(log, asrv.args, False)
|
||||
for vn in asrv.vfs.all_nodes.values():
|
||||
if not vn.axs.uwrite or "wram" in vn.flags:
|
||||
continue
|
||||
ap = vn.realpath
|
||||
if not ap or os.path.isfile(ap):
|
||||
continue
|
||||
fs, mp = fstab.get(ap)
|
||||
mp = "/" + mp.strip("/")
|
||||
if fs == "tmpfs" or (mp == "/" and fs in ramfs):
|
||||
mods.append((vn.vpath, ap, fs, mp))
|
||||
vn.axs.uwrite.clear()
|
||||
if mods:
|
||||
t = "WARNING: write-access was removed from the following volumes because they are not mapped to an actual HDD for storage! All uploaded data would live in RAM only, and all uploaded files would be LOST on next reboot. To allow uploading and ignore this hazard, enable the 'wram' option (global/volflag). List of affected volumes:"
|
||||
t2 = ["\n volume=[/%s], abspath=%r, type=%s, root=%r" % x for x in mods]
|
||||
log("vfs", t + "".join(t2) + "\n", 1)
|
||||
|
|
|
@ -279,6 +279,10 @@ class FtpFs(AbstractedFS):
|
|||
def chdir(self, path: str) -> None:
|
||||
nwd = join(self.cwd, path)
|
||||
vfs, rem = self.hub.asrv.vfs.get(nwd, self.uname, False, False)
|
||||
if not vfs.realpath:
|
||||
self.cwd = nwd
|
||||
return
|
||||
|
||||
ap = vfs.canonical(rem)
|
||||
try:
|
||||
st = bos.stat(ap)
|
||||
|
@ -288,12 +292,9 @@ class FtpFs(AbstractedFS):
|
|||
# returning 550 is library-default and suitable
|
||||
raise FSE("No such file or directory")
|
||||
|
||||
if vfs.realpath:
|
||||
avfs = vfs.chk_ap(ap, st)
|
||||
if not avfs:
|
||||
raise FSE("Permission denied", 1)
|
||||
else:
|
||||
avfs = vfs
|
||||
|
||||
self.cwd = nwd
|
||||
|
||||
|
@ -492,7 +493,8 @@ class FtpHandler(FTPHandler):
|
|||
return
|
||||
self.vfs_map[ap] = vp
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
if xbu:
|
||||
hr = runhook(
|
||||
None,
|
||||
None,
|
||||
self.hub.up2k,
|
||||
|
@ -508,8 +510,13 @@ class FtpHandler(FTPHandler):
|
|||
self.cli_ip,
|
||||
time.time(),
|
||||
"",
|
||||
):
|
||||
raise FSE("Upload blocked by xbu server config")
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "Upload blocked by xbu server config: %r" % (vp,)
|
||||
self.respond("550 %s" % (t,), logging.info)
|
||||
return
|
||||
|
||||
# print("ftp_STOR: {} {} => {}".format(vp, mode, ap))
|
||||
ret = FTPHandler.ftp_STOR(self, file, mode)
|
||||
|
|
|
@ -34,8 +34,8 @@ from .__init__ import ANYWIN, RES, TYPE_CHECKING, EnvParams, unicode
|
|||
from .__version__ import S_VERSION
|
||||
from .authsrv import LEELOO_DALLAS, VFS # typechk
|
||||
from .bos import bos
|
||||
from .qrkode import QrCode, qr2svg, qrgen
|
||||
from .star import StreamTar
|
||||
from .stolen.qrcodegen import QrCode, qr2svg
|
||||
from .sutil import StreamArc, gfilter
|
||||
from .szip import StreamZip
|
||||
from .up2k import up2k_chunksize
|
||||
|
@ -48,7 +48,6 @@ from .util import (
|
|||
FN_EMB,
|
||||
HAVE_SQLITE3,
|
||||
HTTPCODE,
|
||||
META_NOBOTS,
|
||||
UTC,
|
||||
Garda,
|
||||
MultipartParser,
|
||||
|
@ -286,10 +285,9 @@ class HttpCli(object):
|
|||
zs += "&" if "?" in zs else "?"
|
||||
ka["js"] = zs
|
||||
|
||||
zso = self.vn.flags.get("html_head")
|
||||
if zso:
|
||||
if "html_head_d" in self.vn.flags:
|
||||
ka["this"] = self
|
||||
self._build_html_head(zso, ka)
|
||||
self._build_html_head(ka)
|
||||
|
||||
ka["html_head"] = self.html_head
|
||||
return tpl.render(**ka) # type: ignore
|
||||
|
@ -333,6 +331,7 @@ class HttpCli(object):
|
|||
k, zs = header_line.split(":", 1)
|
||||
self.headers[k.lower()] = zs.strip()
|
||||
except:
|
||||
headerlines = [repr(x) for x in headerlines]
|
||||
msg = "#[ " + " ]\n#[ ".join(headerlines) + " ]"
|
||||
raise Pebkac(400, "bad headers", log=msg)
|
||||
|
||||
|
@ -757,9 +756,11 @@ class HttpCli(object):
|
|||
self.s.settimeout(self.args.s_tbody or None)
|
||||
|
||||
if "norobots" in vn.flags:
|
||||
self.html_head += META_NOBOTS
|
||||
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
||||
|
||||
if "html_head_s" in vn.flags:
|
||||
self.html_head += vn.flags["html_head_s"]
|
||||
|
||||
try:
|
||||
cors_k = self._cors()
|
||||
if self.mode in ("GET", "HEAD"):
|
||||
|
@ -796,7 +797,7 @@ class HttpCli(object):
|
|||
elif self.mode in ("MOVE", "COPY"):
|
||||
return self.handle_cpmv() and self.keepalive
|
||||
else:
|
||||
raise Pebkac(400, 'invalid HTTP verb "{0}"'.format(self.mode))
|
||||
raise Pebkac(400, "invalid HTTP verb %r" % (self.mode,))
|
||||
|
||||
except Exception as ex:
|
||||
if not isinstance(ex, Pebkac):
|
||||
|
@ -930,8 +931,8 @@ class HttpCli(object):
|
|||
no304 = self.cookies.get("no304")
|
||||
return no304 == "y" or (self.args.no304 == 2 and no304 != "n")
|
||||
|
||||
def _build_html_head(self, maybe_html: Any, kv: dict[str, Any]) -> None:
|
||||
html = str(maybe_html)
|
||||
def _build_html_head(self, kv: dict[str, Any]) -> None:
|
||||
html = str(self.vn.flags["html_head_d"])
|
||||
is_jinja = html[:2] in "%@%"
|
||||
if is_jinja:
|
||||
html = html.replace("%", "", 1)
|
||||
|
@ -1274,7 +1275,7 @@ class HttpCli(object):
|
|||
if not self.can_read and not self.can_write and not self.can_get:
|
||||
t = "@%s has no access to %r"
|
||||
|
||||
if "on403" in self.vn.flags:
|
||||
if self.vn.realpath and "on403" in self.vn.flags:
|
||||
t += " (on403)"
|
||||
self.log(t % (self.uname, "/" + self.vpath))
|
||||
ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
|
||||
|
@ -1308,6 +1309,9 @@ class HttpCli(object):
|
|||
self.reply(html.encode("utf-8", "replace"), 500)
|
||||
return True
|
||||
|
||||
if "ls" in self.uparam:
|
||||
return self.tx_ls_vols()
|
||||
|
||||
if self.vpath:
|
||||
ptn = self.args.nonsus_urls
|
||||
if not ptn or not ptn.search(self.vpath):
|
||||
|
@ -1421,10 +1425,10 @@ class HttpCli(object):
|
|||
|
||||
hits = idx.run_query(self.uname, [self.vn], uq, uv, False, False, nmax)[0]
|
||||
|
||||
pw = self.ouparam.get("pw")
|
||||
if pw:
|
||||
q_pw = "?pw=%s" % (html_escape(pw, True, True),)
|
||||
a_pw = "&pw=%s" % (html_escape(pw, True, True),)
|
||||
if "pw" in self.ouparam and "nopw" not in self.ouparam:
|
||||
zs = self.ouparam["pw"]
|
||||
q_pw = "?pw=%s" % (quotep(zs),)
|
||||
a_pw = "&pw=%s" % (quotep(zs),)
|
||||
for i in hits:
|
||||
i["rp"] += a_pw if "?" in i["rp"] else q_pw
|
||||
else:
|
||||
|
@ -1438,6 +1442,8 @@ class HttpCli(object):
|
|||
self.host,
|
||||
)
|
||||
feed = baseurl + self.req[1:]
|
||||
if "pw" in self.ouparam and self.ouparam.get("nopw") == "a":
|
||||
feed = re.sub(r"&pw=[^&]*", "", feed)
|
||||
if self.is_vproxied:
|
||||
baseurl += self.args.RS
|
||||
efeed = html_escape(feed, True, True)
|
||||
|
@ -2121,9 +2127,9 @@ class HttpCli(object):
|
|||
if "get" in opt:
|
||||
return self.handle_get()
|
||||
|
||||
raise Pebkac(405, "POST({}) is disabled in server config".format(ctype))
|
||||
raise Pebkac(405, "POST(%r) is disabled in server config" % (ctype,))
|
||||
|
||||
raise Pebkac(405, "don't know how to handle POST({})".format(ctype))
|
||||
raise Pebkac(405, "don't know how to handle POST(%r)" % (ctype,))
|
||||
|
||||
def get_xml_enc(self, txt: str) -> str:
|
||||
ofs = txt[:512].find(' encoding="')
|
||||
|
@ -2265,8 +2271,10 @@ class HttpCli(object):
|
|||
at,
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xbu server config"
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vp,)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
if hr.get("reloc"):
|
||||
|
@ -2298,7 +2306,11 @@ class HttpCli(object):
|
|||
|
||||
if (
|
||||
self.can_delete
|
||||
and (vfs.flags.get("daw") or "x-oc-mtime" in self.headers)
|
||||
and (
|
||||
vfs.flags.get("daw")
|
||||
or "replace" in self.headers
|
||||
or "x-oc-mtime" in self.headers
|
||||
)
|
||||
) or (
|
||||
not bos.path.exists(os.path.join(fdir, tnam))
|
||||
and not bos.path.getsize(path)
|
||||
|
@ -2394,8 +2406,10 @@ class HttpCli(object):
|
|||
at,
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xau server config"
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r" % (vp,)
|
||||
self.log(t, 1)
|
||||
wunlink(self.log, path, vfs.flags)
|
||||
raise Pebkac(403, t)
|
||||
|
@ -2589,7 +2603,7 @@ class HttpCli(object):
|
|||
if act == "logout":
|
||||
return self.handle_logout()
|
||||
|
||||
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
||||
raise Pebkac(422, "invalid action %r" % (act,))
|
||||
|
||||
def handle_zip_post(self) -> bool:
|
||||
assert self.parser # !rm
|
||||
|
@ -2833,6 +2847,7 @@ class HttpCli(object):
|
|||
bail1 = False # used in sad path to avoid contradicting error-text
|
||||
treport = time.time() # ratelimit up2k reporting to reduce overhead
|
||||
|
||||
try:
|
||||
if "x-up2k-subc" in self.headers:
|
||||
sc_ofs = int(self.headers["x-up2k-subc"])
|
||||
chash = chashes[0]
|
||||
|
@ -2877,7 +2892,6 @@ class HttpCli(object):
|
|||
hasher = None
|
||||
final_subchunk = True
|
||||
|
||||
try:
|
||||
if self.args.nw:
|
||||
path = os.devnull
|
||||
|
||||
|
@ -2949,7 +2963,9 @@ class HttpCli(object):
|
|||
if now - treport < 1:
|
||||
continue
|
||||
treport = now
|
||||
x = broker.ask("up2k.fast_confirm_chunks", ptop, wark, written)
|
||||
x = broker.ask(
|
||||
"up2k.fast_confirm_chunks", ptop, wark, written, locked
|
||||
)
|
||||
num_left, t = x.get()
|
||||
if num_left < -1:
|
||||
self.loud_reply(t, status=500)
|
||||
|
@ -3205,11 +3221,38 @@ class HttpCli(object):
|
|||
new_file += ".md"
|
||||
|
||||
sanitized = sanitize_fn(new_file, "")
|
||||
|
||||
if not nullwrite:
|
||||
fdir = vfs.canonical(rem)
|
||||
fn = os.path.join(fdir, sanitized)
|
||||
|
||||
for hn in ("xbu", "xau"):
|
||||
xxu = vfs.flags.get(hn)
|
||||
if xxu:
|
||||
hr = runhook(
|
||||
self.log,
|
||||
self.conn.hsrv.broker,
|
||||
None,
|
||||
"%s.http.new-md" % (hn,),
|
||||
xxu,
|
||||
fn,
|
||||
vjoin(self.vpath, sanitized),
|
||||
self.host,
|
||||
self.uname,
|
||||
self.asrv.vfs.get_perms(self.vpath, self.uname),
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
time.time(),
|
||||
"",
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "new-md blocked by " + hn + " server config: %r"
|
||||
t = t % (vjoin(vfs.vpath, rem),)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
|
||||
if not nullwrite:
|
||||
if bos.path.exists(fn):
|
||||
raise Pebkac(500, "that file exists already")
|
||||
|
||||
|
@ -3339,7 +3382,7 @@ class HttpCli(object):
|
|||
|
||||
open_args = {"fdir": fdir, "suffix": suffix, "vf": vfs.flags}
|
||||
|
||||
if "replace" in self.uparam:
|
||||
if "replace" in self.uparam or "replace" in self.headers:
|
||||
if not self.can_delete:
|
||||
self.log("user not allowed to overwrite with ?replace")
|
||||
elif bos.path.exists(abspath):
|
||||
|
@ -3373,8 +3416,11 @@ class HttpCli(object):
|
|||
at,
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xbu server config"
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r"
|
||||
t = t % (vjoin(upload_vpath, fname),)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
if hr.get("reloc"):
|
||||
|
@ -3477,8 +3523,11 @@ class HttpCli(object):
|
|||
at,
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xau server config"
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r"
|
||||
t = t % (vjoin(upload_vpath, fname),)
|
||||
self.log(t, 1)
|
||||
wunlink(self.log, abspath, vfs.flags)
|
||||
raise Pebkac(403, t)
|
||||
|
@ -3763,14 +3812,14 @@ class HttpCli(object):
|
|||
assert self.parser.gen # !rm
|
||||
p_field, _, p_data = next(self.parser.gen)
|
||||
if p_field != "body":
|
||||
raise Pebkac(400, "expected body, got {}".format(p_field))
|
||||
raise Pebkac(400, "expected body, got %r" % (p_field,))
|
||||
|
||||
if "txt_eol" in vfs.flags:
|
||||
p_data = eol_conv(p_data, vfs.flags["txt_eol"])
|
||||
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu:
|
||||
if not runhook(
|
||||
hr = runhook(
|
||||
self.log,
|
||||
self.conn.hsrv.broker,
|
||||
None,
|
||||
|
@ -3786,7 +3835,10 @@ class HttpCli(object):
|
|||
self.ip,
|
||||
time.time(),
|
||||
"",
|
||||
):
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "save blocked by xbu server config"
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
|
@ -3814,7 +3866,8 @@ class HttpCli(object):
|
|||
sha512 = sha512[:56]
|
||||
|
||||
xau = vfs.flags.get("xau")
|
||||
if xau and not runhook(
|
||||
if xau:
|
||||
hr = runhook(
|
||||
self.log,
|
||||
self.conn.hsrv.broker,
|
||||
None,
|
||||
|
@ -3830,7 +3883,10 @@ class HttpCli(object):
|
|||
self.ip,
|
||||
new_lastmod,
|
||||
"",
|
||||
):
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "save blocked by xau server config"
|
||||
self.log(t, 1)
|
||||
wunlink(self.log, fp, vfs.flags)
|
||||
|
@ -4897,7 +4953,7 @@ class HttpCli(object):
|
|||
url += "#" + uhash
|
||||
|
||||
self.log("qrcode(%r)" % (url,))
|
||||
ret = qr2svg(QrCode.encode_binary(url.encode("utf-8")), 2)
|
||||
ret = qr2svg(qrgen(url.encode("utf-8")), 2)
|
||||
self.reply(ret.encode("utf-8"), mime="image/svg+xml")
|
||||
return True
|
||||
|
||||
|
@ -4978,10 +5034,9 @@ class HttpCli(object):
|
|||
zs += "&" if "?" in zs else "?"
|
||||
targs["js"] = zs
|
||||
|
||||
zfv = self.vn.flags.get("html_head")
|
||||
if zfv:
|
||||
if "html_head_d" in self.vn.flags:
|
||||
targs["this"] = self
|
||||
self._build_html_head(zfv, targs)
|
||||
self._build_html_head(targs)
|
||||
|
||||
targs["html_head"] = self.html_head
|
||||
zs = template.render(**targs).encode("utf-8", "replace")
|
||||
|
@ -5932,7 +5987,7 @@ class HttpCli(object):
|
|||
vp2, fn = zs.rsplit("/", 1)
|
||||
fns.append(fn)
|
||||
if vp != vp2:
|
||||
t = "mismatching base paths in selection:\n [%s]\n [%s]"
|
||||
t = "mismatching base paths in selection:\n %r\n %r"
|
||||
raise Pebkac(400, t % (vp, vp2))
|
||||
|
||||
vp = vp.strip("/")
|
||||
|
@ -5941,7 +5996,7 @@ class HttpCli(object):
|
|||
|
||||
m = re.search(r"([^0-9a-zA-Z_-])", skey)
|
||||
if m:
|
||||
raise Pebkac(400, "sharekey has illegal character [%s]" % (m[1],))
|
||||
raise Pebkac(400, "sharekey has illegal character %r" % (m[1],))
|
||||
|
||||
if vp.startswith(self.args.shr1):
|
||||
raise Pebkac(400, "yo dawg...")
|
||||
|
@ -5954,7 +6009,7 @@ class HttpCli(object):
|
|||
qr = cur.execute(q, (skey,)).fetchall()
|
||||
if qr and qr[0]:
|
||||
self.log("sharekey taken by %r" % (qr,))
|
||||
raise Pebkac(400, "sharekey [%s] is already in use" % (skey,))
|
||||
raise Pebkac(400, "sharekey %r is already in use" % (skey,))
|
||||
|
||||
# ensure user has requested perms
|
||||
s_rd = "read" in req["perms"]
|
||||
|
@ -5980,7 +6035,7 @@ class HttpCli(object):
|
|||
rfns = set([x[0] for x in reals])
|
||||
for fn in fns:
|
||||
if fn not in rfns:
|
||||
raise Pebkac(400, "selected file not found on disk: [%s]" % (fn,))
|
||||
raise Pebkac(400, "selected file not found on disk: %r" % (fn,))
|
||||
|
||||
pw = req.get("pw") or ""
|
||||
pw = self.asrv.ah.hash(pw)
|
||||
|
@ -6116,6 +6171,47 @@ class HttpCli(object):
|
|||
self.loud_reply("aborting", status=200)
|
||||
return True
|
||||
|
||||
def tx_ls_vols(self) -> bool:
|
||||
e_d = {}
|
||||
eses = ["", ""]
|
||||
rvol = self.rvol
|
||||
wvol = self.wvol
|
||||
if self.args.have_unlistc:
|
||||
allvols = self.asrv.vfs.all_vols
|
||||
rvol = [x for x in rvol if "unlistcr" not in allvols[x[1:-1]].flags]
|
||||
wvol = [x for x in wvol if "unlistcw" not in allvols[x[1:-1]].flags]
|
||||
vols = list(set(rvol + wvol))
|
||||
if self.vpath:
|
||||
zs = "%s/" % (self.vpath,)
|
||||
vols = [x[len(zs) :] for x in vols if x.startswith(zs)]
|
||||
vols = [x.split("/", 1)[0] for x in vols if x]
|
||||
if not vols and self.vpath:
|
||||
return self.tx_404(True)
|
||||
dirs = [
|
||||
{
|
||||
"lead": "",
|
||||
"href": "%s/" % (x,),
|
||||
"ext": "---",
|
||||
"sz": 0,
|
||||
"ts": 0,
|
||||
"tags": e_d,
|
||||
"dt": 0,
|
||||
"name": 0,
|
||||
}
|
||||
for x in sorted(vols)
|
||||
]
|
||||
ls = {
|
||||
"dirs": dirs,
|
||||
"files": [],
|
||||
"acct": self.uname,
|
||||
"perms": [],
|
||||
"taglist": [],
|
||||
"logues": eses,
|
||||
"readmes": eses,
|
||||
"srvinf": "" if self.args.nih else self.args.name,
|
||||
}
|
||||
return self.tx_ls(ls)
|
||||
|
||||
def tx_ls(self, ls: dict[str, Any]) -> bool:
|
||||
dirs = ls["dirs"]
|
||||
files = ls["files"]
|
||||
|
@ -6234,7 +6330,7 @@ class HttpCli(object):
|
|||
|
||||
add_og = "og" in vn.flags
|
||||
if add_og:
|
||||
if "th" in self.uparam or "raw" in self.uparam:
|
||||
if "th" in self.uparam or "raw" in self.uparam or "opds" in self.uparam:
|
||||
add_og = False
|
||||
elif vn.flags["og_ua"]:
|
||||
add_og = vn.flags["og_ua"].search(self.ua)
|
||||
|
@ -6399,7 +6495,7 @@ class HttpCli(object):
|
|||
|
||||
try:
|
||||
if not self.args.nih:
|
||||
srv_info.append(self.args.name)
|
||||
srv_info.append(self.args.name_html)
|
||||
except:
|
||||
self.log("#wow #whoa")
|
||||
|
||||
|
@ -6439,6 +6535,7 @@ class HttpCli(object):
|
|||
|
||||
url_suf = self.urlq({}, ["k"])
|
||||
is_ls = "ls" in self.uparam
|
||||
is_opds = "opds" in self.uparam
|
||||
is_js = self.args.force_js or self.cookies.get("js") == "y"
|
||||
|
||||
if not is_ls and not add_og and self.ua.startswith(("curl/", "fetch")):
|
||||
|
@ -6449,6 +6546,13 @@ class HttpCli(object):
|
|||
if "b" in self.uparam:
|
||||
tpl = "browser2"
|
||||
is_js = False
|
||||
elif is_opds:
|
||||
# Display directory listing as OPDS v1.2 catalog feed
|
||||
if not (self.args.opds or "opds" in self.vn.flags):
|
||||
raise Pebkac(405, "OPDS is disabled in server config")
|
||||
if not self.can_read:
|
||||
raise Pebkac(401, "OPDS requires read permission")
|
||||
is_js = is_ls = False
|
||||
|
||||
vf = vn.flags
|
||||
ls_ret = {
|
||||
|
@ -6578,10 +6682,17 @@ class HttpCli(object):
|
|||
dirs = []
|
||||
files = []
|
||||
ptn_hr = RE_HR
|
||||
use_abs_url = (
|
||||
not is_opds
|
||||
and not is_ls
|
||||
and not is_js
|
||||
and not self.trailing_slash
|
||||
and vpath
|
||||
)
|
||||
for fn in ls_names:
|
||||
base = ""
|
||||
href = fn
|
||||
if not is_ls and not is_js and not self.trailing_slash and vpath:
|
||||
if use_abs_url:
|
||||
base = "/" + vpath + "/"
|
||||
href = base + fn
|
||||
|
||||
|
@ -6681,6 +6792,7 @@ class HttpCli(object):
|
|||
self.cookies.get("idxh") == "y"
|
||||
and "ls" not in self.uparam
|
||||
and "v" not in self.uparam
|
||||
and not is_opds
|
||||
):
|
||||
idx_html = set(["index.htm", "index.html"])
|
||||
for item in files:
|
||||
|
@ -6691,62 +6803,63 @@ class HttpCli(object):
|
|||
ap = vn.canonical(rem)
|
||||
return self.tx_file(ap) # is no-cache
|
||||
|
||||
mte = vn.flags.get("mte", {})
|
||||
add_up_at = ".up_at" in mte
|
||||
is_admin = self.can_admin
|
||||
if icur:
|
||||
mte = vn.flags.get("mte") or {}
|
||||
tagset: set[str] = set()
|
||||
rd = vrem
|
||||
for fe in files if icur else []:
|
||||
assert icur # !rm
|
||||
if self.can_admin:
|
||||
up_q = "select substr(w,1,16), ip, at, un from up where rd=? and fn=?"
|
||||
up_m = ["w", "up_ip", ".up_at", "up_by"]
|
||||
elif ".up_at" in mte:
|
||||
if "w" in mte:
|
||||
up_q = "select substr(w,1,16), at from up where rd=? and fn=?"
|
||||
up_m = ["w", ".up_at"]
|
||||
else:
|
||||
up_q = "select at from up where rd=? and fn=?"
|
||||
up_m = [".up_at"]
|
||||
elif "w" in mte:
|
||||
up_q = "select substr(w,1,16) from up where rd=? and fn=?"
|
||||
up_m = ["w"]
|
||||
else:
|
||||
up_q = ""
|
||||
|
||||
mt_q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'"
|
||||
for fe in files:
|
||||
fn = fe["name"]
|
||||
erd_efn = (rd, fn)
|
||||
q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'"
|
||||
try:
|
||||
r = icur.execute(q, erd_efn)
|
||||
r = icur.execute(mt_q, erd_efn)
|
||||
except Exception as ex:
|
||||
if "database is locked" in str(ex):
|
||||
break
|
||||
|
||||
try:
|
||||
erd_efn = s3enc(idx.mem_cur, rd, fn)
|
||||
r = icur.execute(q, erd_efn)
|
||||
r = icur.execute(mt_q, erd_efn)
|
||||
except:
|
||||
self.log("tag read error, %r / %r\n%s" % (rd, fn, min_ex()))
|
||||
break
|
||||
|
||||
tags = {k: v for k, v in r}
|
||||
|
||||
if is_admin:
|
||||
q = "select ip, at, un from up where rd=? and fn=?"
|
||||
if up_q:
|
||||
try:
|
||||
zs1, zs2, zs3 = icur.execute(q, erd_efn).fetchone()
|
||||
if zs1:
|
||||
tags["up_ip"] = zs1
|
||||
up_v = icur.execute(up_q, erd_efn).fetchone()
|
||||
for zs1, zs2 in zip(up_m, up_v):
|
||||
if zs2:
|
||||
tags[".up_at"] = zs2
|
||||
if zs3:
|
||||
tags["up_by"] = zs3
|
||||
except:
|
||||
pass
|
||||
elif add_up_at:
|
||||
q = "select at from up where rd=? and fn=?"
|
||||
try:
|
||||
(zs1,) = icur.execute(q, erd_efn).fetchone()
|
||||
if zs1:
|
||||
tags[".up_at"] = zs1
|
||||
tags[zs1] = zs2
|
||||
except:
|
||||
pass
|
||||
|
||||
_ = [tagset.add(k) for k in tags]
|
||||
fe["tags"] = tags
|
||||
|
||||
if icur:
|
||||
for fe in dirs:
|
||||
fe["tags"] = ODict()
|
||||
|
||||
lmte = list(mte)
|
||||
if self.can_admin:
|
||||
lmte.extend(("up_by", "up_ip", ".up_at"))
|
||||
lmte.extend(("w", "up_by", "up_ip", ".up_at"))
|
||||
|
||||
if "nodirsz" not in vf:
|
||||
tagset.add(".files")
|
||||
|
@ -6761,7 +6874,7 @@ class HttpCli(object):
|
|||
|
||||
taglist = [k for k in lmte if k in tagset]
|
||||
else:
|
||||
taglist = list(tagset)
|
||||
taglist = []
|
||||
|
||||
logues, readmes = self._add_logues(vn, abspath, lnames)
|
||||
ls_ret["logues"] = j2a["logues"] = logues
|
||||
|
@ -6849,6 +6962,48 @@ class HttpCli(object):
|
|||
|
||||
dirs.sort(key=itemgetter("name"))
|
||||
|
||||
if is_opds:
|
||||
# exclude files which don't match --opds-exts
|
||||
allowed_exts = vf.get("opds_exts") or self.args.opds_exts
|
||||
if allowed_exts:
|
||||
files = [
|
||||
x for x in files if x["name"].rsplit(".", 1)[-1] in allowed_exts
|
||||
]
|
||||
for item in dirs:
|
||||
href = item["href"]
|
||||
href += ("&" if "?" in href else "?") + "opds"
|
||||
item["href"] = href
|
||||
item["iso8601"] = "%sZ" % (item["dt"].replace(" ", "T"),)
|
||||
|
||||
for item in files:
|
||||
href = item["href"]
|
||||
href += ("&" if "?" in href else "?") + "dl"
|
||||
item["href"] = href
|
||||
item["iso8601"] = "%sZ" % (item["dt"].replace(" ", "T"),)
|
||||
|
||||
if "rmagic" in self.vn.flags:
|
||||
ap = "%s/%s" % (fsroot, item["name"])
|
||||
item["mime"] = guess_mime(item["name"], ap)
|
||||
else:
|
||||
item["mime"] = guess_mime(item["name"])
|
||||
|
||||
# Make sure we can actually generate JPEG thumbnails
|
||||
if (
|
||||
not self.args.th_no_jpg
|
||||
and self.thumbcli
|
||||
and "dthumb" not in dbv.flags
|
||||
and "dithumb" not in dbv.flags
|
||||
):
|
||||
item["jpeg_thumb_href"] = href + "&th=jf"
|
||||
item["jpeg_thumb_href_hires"] = item["jpeg_thumb_href"] + "3"
|
||||
|
||||
j2a["files"] = files
|
||||
j2a["dirs"] = dirs
|
||||
html = self.j2s("opds", **j2a)
|
||||
mime = "application/atom+xml;profile=opds-catalog"
|
||||
self.reply(html.encode("utf-8", "replace"), mime=mime)
|
||||
return True
|
||||
|
||||
if is_js:
|
||||
j2a["ls0"] = cgv["ls0"] = {
|
||||
"dirs": dirs,
|
||||
|
@ -6948,7 +7103,15 @@ class HttpCli(object):
|
|||
if zs not in ("", "-"):
|
||||
ogh["og:site_name"] = zs
|
||||
|
||||
try:
|
||||
zs1, zs2 = file["tags"]["res"].split("x")
|
||||
file["tags"][".resw"] = zs1
|
||||
file["tags"][".resh"] = zs2
|
||||
except:
|
||||
pass
|
||||
|
||||
tagmap = {}
|
||||
|
||||
if is_au:
|
||||
title = str(vn.flags.get("og_title_a") or "")
|
||||
ogh["og:type"] = "music.song"
|
||||
|
@ -6962,15 +7125,23 @@ class HttpCli(object):
|
|||
title = str(vn.flags.get("og_title_v") or "")
|
||||
ogh["og:type"] = "video.other"
|
||||
ogh["og:video"] = j2a["og_raw"]
|
||||
|
||||
tagmap = {
|
||||
"title": "og:title",
|
||||
".dur": "og:video:duration",
|
||||
".resw": "og:video:width",
|
||||
".resh": "og:video:height",
|
||||
}
|
||||
elif is_pic:
|
||||
title = str(vn.flags.get("og_title_i") or "")
|
||||
ogh["twitter:card"] = "summary_large_image"
|
||||
ogh["twitter:image"] = ogh["og:image"] = j2a["og_raw"]
|
||||
|
||||
tagmap = {
|
||||
".resw": "og:image:width",
|
||||
".resh": "og:image:height",
|
||||
}
|
||||
|
||||
try:
|
||||
for k, v in file["tags"].items():
|
||||
zs = "{{ %s }}" % (k,)
|
||||
|
|
|
@ -187,6 +187,7 @@ class HttpSrv(object):
|
|||
"svcs",
|
||||
]
|
||||
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
||||
self.j2["opds"] = env.get_template("opds.xml")
|
||||
self.prism = has_resource(self.E, "web/deps/prism.js.gz")
|
||||
|
||||
if self.args.ipu:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import errno
|
||||
import os
|
||||
import random
|
||||
import select
|
||||
import socket
|
||||
|
@ -12,6 +13,11 @@ from ipaddress import IPv4Network, IPv6Network
|
|||
from .__init__ import TYPE_CHECKING
|
||||
from .__init__ import unicode as U
|
||||
from .multicast import MC_Sck, MCast
|
||||
from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex
|
||||
|
||||
try:
|
||||
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_DNSLIB"):
|
||||
raise ImportError()
|
||||
from .stolen.dnslib import (
|
||||
AAAA,
|
||||
)
|
||||
|
@ -29,7 +35,30 @@ from .stolen.dnslib import (
|
|||
DNSRecord,
|
||||
set_avahi_379,
|
||||
)
|
||||
from .util import IP6_LL, CachedSet, Daemon, Netdev, list_ips, min_ex
|
||||
|
||||
DNS_VND = True
|
||||
except ImportError:
|
||||
DNS_VND = False
|
||||
from dnslib import (
|
||||
AAAA,
|
||||
)
|
||||
from dnslib import CLASS as DC
|
||||
from dnslib import (
|
||||
NSEC,
|
||||
PTR,
|
||||
QTYPE,
|
||||
RR,
|
||||
SRV,
|
||||
TXT,
|
||||
A,
|
||||
Bimap,
|
||||
DNSHeader,
|
||||
DNSQuestion,
|
||||
DNSRecord,
|
||||
)
|
||||
|
||||
DC.forward[0x8001] = "F_IN"
|
||||
DC.reverse["F_IN"] = 0x8001
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .svchub import SvcHub
|
||||
|
@ -37,6 +66,11 @@ if TYPE_CHECKING:
|
|||
if True: # pylint: disable=using-constant-test
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
if os.getenv("PRTY_MODSPEC"):
|
||||
from inspect import getsourcefile
|
||||
|
||||
print("PRTY_MODSPEC: dnslib:", getsourcefile(A))
|
||||
|
||||
|
||||
MDNS4 = "224.0.0.251"
|
||||
MDNS6 = "ff02::fb"
|
||||
|
@ -75,7 +109,7 @@ class MDNS(MCast):
|
|||
self.ngen = ngen
|
||||
self.ttl = 300
|
||||
|
||||
if not self.args.zm_nwa_1:
|
||||
if not self.args.zm_nwa_1 and DNS_VND:
|
||||
set_avahi_379()
|
||||
|
||||
zs = self.args.zm_fqdn or (self.args.name + ".local")
|
||||
|
@ -102,9 +136,14 @@ class MDNS(MCast):
|
|||
self.log_func(self.logsrc, msg, c)
|
||||
|
||||
def build_svcs(self) -> tuple[dict[str, dict[str, Any]], set[str]]:
|
||||
ar = self.args
|
||||
zms = self.args.zms
|
||||
http = {"port": 80 if 80 in self.args.p else self.args.p[0]}
|
||||
https = {"port": 443 if 443 in self.args.p else self.args.p[0]}
|
||||
|
||||
zi = ar.zm_http
|
||||
http = {"port": zi if zi != -1 else 80 if 80 in ar.p else ar.p[0]}
|
||||
zi = ar.zm_https
|
||||
https = {"port": zi if zi != -1 else 443 if 443 in ar.p else ar.p[0]}
|
||||
|
||||
webdav = http.copy()
|
||||
webdavs = https.copy()
|
||||
webdav["u"] = webdavs["u"] = "u" # KDE requires username
|
||||
|
@ -129,16 +168,16 @@ class MDNS(MCast):
|
|||
|
||||
svcs: dict[str, dict[str, Any]] = {}
|
||||
|
||||
if "d" in zms:
|
||||
if "d" in zms and http["port"]:
|
||||
svcs["_webdav._tcp.local."] = webdav
|
||||
|
||||
if "D" in zms:
|
||||
if "D" in zms and https["port"]:
|
||||
svcs["_webdavs._tcp.local."] = webdavs
|
||||
|
||||
if "h" in zms:
|
||||
if "h" in zms and http["port"]:
|
||||
svcs["_http._tcp.local."] = http
|
||||
|
||||
if "H" in zms:
|
||||
if "H" in zms and https["port"]:
|
||||
svcs["_https._tcp.local."] = https
|
||||
|
||||
if "f" in zms.lower():
|
||||
|
|
|
@ -200,9 +200,10 @@ def au_unpk(
|
|||
|
||||
except Exception as ex:
|
||||
if ret:
|
||||
t = "failed to decompress audio file %r: %r"
|
||||
t = "failed to decompress file %r: %r"
|
||||
log(t % (abspath, ex))
|
||||
wunlink(log, ret, vn.flags if vn else VF_CAREFUL)
|
||||
return ""
|
||||
|
||||
return abspath
|
||||
|
||||
|
@ -422,10 +423,17 @@ def get_cover_from_epub(log: "NamedLogger", abspath: str) -> Optional[IO[bytes]]
|
|||
# This might be an EPUB2 file, try the legacy way of specifying covers
|
||||
coverimage_path = _get_cover_from_epub2(log, package_root, package_ns)
|
||||
|
||||
if not coverimage_path:
|
||||
raise Exception("no cover inside epub")
|
||||
|
||||
# This url is either absolute (in the .epub) or relative to the package document
|
||||
adjusted_cover_path = urljoin(rootfile_path, coverimage_path)
|
||||
|
||||
try:
|
||||
return z.open(adjusted_cover_path)
|
||||
except KeyError:
|
||||
t = "epub: cover specified in package document, but doesn't exist: %s"
|
||||
log(t % (adjusted_cover_path,))
|
||||
|
||||
|
||||
def _get_cover_from_epub2(
|
||||
|
@ -433,9 +441,8 @@ def _get_cover_from_epub2(
|
|||
) -> Optional[str]:
|
||||
# <meta name="cover" content="id-to-cover-image"> in <metadata>, then
|
||||
# <item> in <manifest>
|
||||
cover_id = package_root.find("./metadata/meta[@name='cover']", package_ns).get(
|
||||
"content"
|
||||
)
|
||||
xn = package_root.find("./metadata/meta[@name='cover']", package_ns)
|
||||
cover_id = xn.get("content") if xn is not None else None
|
||||
|
||||
if not cover_id:
|
||||
return None
|
||||
|
@ -644,6 +651,9 @@ class MTag(object):
|
|||
return self._get(abspath)
|
||||
|
||||
ap = au_unpk(self.log, self.args.au_unpk, abspath)
|
||||
if not ap:
|
||||
return {}
|
||||
|
||||
ret = self._get(ap)
|
||||
if ap != abspath:
|
||||
wunlink(self.log, ap, VF_CAREFUL)
|
||||
|
@ -749,6 +759,9 @@ class MTag(object):
|
|||
ap = abspath
|
||||
|
||||
ret: dict[str, Any] = {}
|
||||
if not ap:
|
||||
return ret
|
||||
|
||||
for tagname, parser in sorted(parsers.items(), key=lambda x: (x[1].pri, x[0])):
|
||||
try:
|
||||
cmd = [parser.bin, ap]
|
||||
|
|
|
@ -96,7 +96,10 @@ class MCast(object):
|
|||
def create_servers(self) -> list[str]:
|
||||
bound: list[str] = []
|
||||
netdevs = self.hub.tcpsrv.netdevs
|
||||
ips = [x[0] for x in self.hub.tcpsrv.bound]
|
||||
blist = self.hub.tcpsrv.bound
|
||||
if self.args.http_no_tcp:
|
||||
blist = self.hub.tcpsrv.seen_eps
|
||||
ips = [x[0] for x in blist]
|
||||
|
||||
if "::" in ips:
|
||||
ips = [x for x in ips if x != "::"] + list(
|
||||
|
|
112
copyparty/qrkode.py
Normal file
112
copyparty/qrkode.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
# coding: utf-8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_QRCG"):
|
||||
raise ImportError()
|
||||
from .stolen.qrcodegen import QrCode
|
||||
|
||||
qrgen = QrCode.encode_binary
|
||||
VENDORED = True
|
||||
except ImportError:
|
||||
VENDORED = False
|
||||
from qrcodegen import QrCode
|
||||
|
||||
if os.getenv("PRTY_MODSPEC"):
|
||||
from inspect import getsourcefile
|
||||
|
||||
print("PRTY_MODSPEC: qrcode:", getsourcefile(QrCode))
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
import typing
|
||||
from typing import Any, Optional, Sequence, Union
|
||||
|
||||
|
||||
if not VENDORED:
|
||||
|
||||
def _qrgen(data: Union[bytes, Sequence[int]]) -> "QrCode":
|
||||
ret = None
|
||||
V = QrCode.Ecc
|
||||
for e in [V.HIGH, V.QUARTILE, V.MEDIUM, V.LOW]:
|
||||
qr = QrCode.encode_binary(data, e)
|
||||
qr.size = qr._size
|
||||
qr.modules = qr._modules
|
||||
if not ret or ret.size > qr.size:
|
||||
ret = qr
|
||||
return ret
|
||||
|
||||
qrgen = _qrgen
|
||||
|
||||
|
||||
def qr2txt(qr: QrCode, zoom: int = 1, pad: int = 4) -> str:
|
||||
tab = qr.modules
|
||||
sz = qr.size
|
||||
if sz % 2 and zoom == 1:
|
||||
tab.append([False] * sz)
|
||||
|
||||
tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
|
||||
tab = [[False] * pad + x + [False] * pad for x in tab]
|
||||
|
||||
rows: list[str] = []
|
||||
if zoom == 1:
|
||||
for y in range(0, len(tab), 2):
|
||||
row = ""
|
||||
for x in range(len(tab[y])):
|
||||
v = 2 if tab[y][x] else 0
|
||||
v += 1 if tab[y + 1][x] else 0
|
||||
row += " ▄▀█"[v]
|
||||
rows.append(row)
|
||||
else:
|
||||
for tr in tab:
|
||||
row = ""
|
||||
for zb in tr:
|
||||
row += " █"[int(zb)] * 2
|
||||
rows.append(row)
|
||||
|
||||
return "\n".join(rows)
|
||||
|
||||
|
||||
def qr2png(
|
||||
qr: QrCode,
|
||||
zoom: int,
|
||||
pad: int,
|
||||
bg: Optional[tuple[int, int, int]],
|
||||
fg: Optional[tuple[int, int, int]],
|
||||
ap: str,
|
||||
) -> None:
|
||||
from PIL import Image
|
||||
|
||||
tab = qr.modules
|
||||
sz = qr.size
|
||||
psz = sz + pad * 2
|
||||
if bg:
|
||||
img = Image.new("RGB", (psz, psz), bg)
|
||||
else:
|
||||
img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
|
||||
fg = (fg[0], fg[1], fg[2], 255)
|
||||
for y in range(sz):
|
||||
for x in range(sz):
|
||||
if tab[y][x]:
|
||||
img.putpixel((x + pad, y + pad), fg)
|
||||
if zoom != 1:
|
||||
img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
|
||||
img.save(ap)
|
||||
|
||||
|
||||
def qr2svg(qr: QrCode, border: int) -> str:
|
||||
parts: list[str] = []
|
||||
for y in range(qr.size):
|
||||
sy = border + y
|
||||
for x in range(qr.size):
|
||||
if qr.modules[y][x]:
|
||||
parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
|
||||
t = """\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {0} {0}" stroke="none">
|
||||
<rect width="100%" height="100%" fill="#F7F7F7"/>
|
||||
<path d="{1}" fill="#111111"/>
|
||||
</svg>
|
||||
"""
|
||||
return t.format(qr.size + border * 2, " ".join(parts))
|
|
@ -246,7 +246,8 @@ class SMB(object):
|
|||
|
||||
ap = absreal(ap)
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
if xbu:
|
||||
hr = runhook(
|
||||
self.nlog,
|
||||
None,
|
||||
self.hub.up2k,
|
||||
|
@ -262,8 +263,12 @@ class SMB(object):
|
|||
"1.7.6.2",
|
||||
time.time(),
|
||||
"",
|
||||
):
|
||||
yeet("blocked by xbu server config: %r" % (vpath,))
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "blocked by xbu server config: %r" % (vpath,)
|
||||
yeet(t)
|
||||
|
||||
ret = bos.open(ap, flags, *a, mode=chmod, **ka)
|
||||
if wr:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# https://github.com/nayuki/QR-Code-generator/blob/daa3114/python/qrcodegen.py
|
||||
# the original ^ is extremely well commented so refer to that for explanations
|
||||
|
||||
# hacks: binary-only, auto-ecc, render, py2-compat
|
||||
# hacks: binary-only, auto-ecc, py2-compat
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
|
@ -173,52 +173,6 @@ class QrCode(object):
|
|||
self._apply_mask(msk) # Apply the final choice of mask
|
||||
self._draw_format_bits(msk) # Overwrite old format bits
|
||||
|
||||
def render(self, zoom=1, pad=4) -> str:
|
||||
tab = self.modules
|
||||
sz = self.size
|
||||
if sz % 2 and zoom == 1:
|
||||
tab.append([False] * sz)
|
||||
|
||||
tab = [[False] * sz] * pad + tab + [[False] * sz] * pad
|
||||
tab = [[False] * pad + x + [False] * pad for x in tab]
|
||||
|
||||
rows: list[str] = []
|
||||
if zoom == 1:
|
||||
for y in range(0, len(tab), 2):
|
||||
row = ""
|
||||
for x in range(len(tab[y])):
|
||||
v = 2 if tab[y][x] else 0
|
||||
v += 1 if tab[y + 1][x] else 0
|
||||
row += " ▄▀█"[v]
|
||||
rows.append(row)
|
||||
else:
|
||||
for tr in tab:
|
||||
row = ""
|
||||
for zb in tr:
|
||||
row += " █"[int(zb)] * 2
|
||||
rows.append(row)
|
||||
|
||||
return "\n".join(rows)
|
||||
|
||||
def to_png(self, zoom, pad, bg, fg, ap) -> None:
|
||||
from PIL import Image
|
||||
|
||||
tab = self.modules
|
||||
sz = self.size
|
||||
psz = sz + pad * 2
|
||||
if bg:
|
||||
img = Image.new("RGB", (psz, psz), bg)
|
||||
else:
|
||||
img = Image.new("RGBA", (psz, psz), (0, 0, 0, 0))
|
||||
fg = (fg[0], fg[1], fg[2], 255)
|
||||
for y in range(sz):
|
||||
for x in range(sz):
|
||||
if tab[y][x]:
|
||||
img.putpixel((x + pad, y + pad), fg)
|
||||
if zoom != 1:
|
||||
img = img.resize((sz * zoom, sz * zoom), Image.Resampling.NEAREST)
|
||||
img.save(ap)
|
||||
|
||||
def _draw_function_patterns(self) -> None:
|
||||
# Draw horizontal and vertical timing patterns
|
||||
for i in range(self.size):
|
||||
|
@ -613,20 +567,3 @@ def _get_bit(x: int, i: int) -> bool:
|
|||
|
||||
class DataTooLongError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def qr2svg(qr: QrCode, border: int) -> str:
|
||||
parts: list[str] = []
|
||||
for y in range(qr.size):
|
||||
sy = border + y
|
||||
for x in range(qr.size):
|
||||
if qr.modules[y][x]:
|
||||
parts.append("M%d,%dh1v1h-1z" % (border + x, sy))
|
||||
t = """\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {0} {0}" stroke="none">
|
||||
<rect width="100%" height="100%" fill="#F7F7F7"/>
|
||||
<path d="{1}" fill="#111111"/>
|
||||
</svg>
|
||||
"""
|
||||
return t.format(qr.size + border * 2, " ".join(parts))
|
||||
|
|
|
@ -27,9 +27,10 @@ if True: # pylint: disable=using-constant-test
|
|||
from typing import Any, Optional, Union
|
||||
|
||||
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
|
||||
from .authsrv import BAD_CFG, AuthSrv, n_du_who, n_ver_who
|
||||
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
|
||||
from .bos import bos
|
||||
from .cert import ensure_cert
|
||||
from .fsutil import ramdisk_chk
|
||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, HAVE_MUTAGEN
|
||||
from .pwhash import HAVE_ARGON2
|
||||
from .tcpsrv import TcpSrv
|
||||
|
@ -66,6 +67,7 @@ from .util import (
|
|||
build_netmap,
|
||||
expat_ver,
|
||||
gzip,
|
||||
html_escape,
|
||||
load_ipr,
|
||||
load_ipu,
|
||||
lock_file,
|
||||
|
@ -156,7 +158,7 @@ class SvcHub(object):
|
|||
args.unpost = 0
|
||||
args.no_del = True
|
||||
args.no_mv = True
|
||||
args.hardlink = True
|
||||
args.reflink = True
|
||||
args.dav_auth = True
|
||||
args.vague_403 = True
|
||||
args.nih = True
|
||||
|
@ -246,8 +248,8 @@ class SvcHub(object):
|
|||
t = "WARNING: --th-ram-max is very small (%.2f GiB); will not be able to %s"
|
||||
self.log("root", t % (args.th_ram_max, zs), 3)
|
||||
|
||||
if args.chpw and args.have_idp_hdrs:
|
||||
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||
if args.chpw and args.have_idp_hdrs and "pw" not in args.auth_ord.split(","):
|
||||
t = "ERROR: user-changeable passwords is not compatible with your current configuration. Choose one of these options to fix it:\n option1: disable --chpw\n option2: remove all use of IdP features; --idp-*\n option3: change --auth-ord to something like pw,idp,ipu"
|
||||
self.log("root", t, 1)
|
||||
raise Exception(t)
|
||||
|
||||
|
@ -310,6 +312,7 @@ class SvcHub(object):
|
|||
|
||||
# initiate all services to manage
|
||||
self.asrv = AuthSrv(self.args, self.log, dargs=self.dargs)
|
||||
ramdisk_chk(self.asrv)
|
||||
|
||||
if args.cgen:
|
||||
self.asrv.cgen()
|
||||
|
@ -389,7 +392,10 @@ class SvcHub(object):
|
|||
t = "invalid mp3 transcoding quality [%s] specified; only supports [0] to disable, a CBR value such as [192k], or a CQ/CRF value such as [v2]"
|
||||
raise Exception(t % (args.q_mp3,))
|
||||
else:
|
||||
args.au_unpk = {}
|
||||
zss = set(args.th_r_ffa.split(",") + args.th_r_ffv.split(","))
|
||||
args.au_unpk = {
|
||||
k: v for k, v in args.au_unpk.items() if v.split(".")[0] not in zss
|
||||
}
|
||||
|
||||
args.th_poke = min(args.th_poke, args.th_maxage, args.ac_maxage)
|
||||
|
||||
|
@ -1166,6 +1172,13 @@ class SvcHub(object):
|
|||
if len(al.tcolor) == 3: # fc5 => ffcc55
|
||||
al.tcolor = "".join([x * 2 for x in al.tcolor])
|
||||
|
||||
if self.args.name_url:
|
||||
zs = html_escape(self.args.name_url, True, True)
|
||||
zs = '<a href="%s">%s</a>' % (zs, self.args.name)
|
||||
else:
|
||||
zs = self.args.name
|
||||
self.args.name_html = zs
|
||||
|
||||
zs = al.u2sz
|
||||
zsl = [x.strip() for x in zs.split(",")]
|
||||
if len(zsl) not in (1, 3):
|
||||
|
@ -1184,6 +1197,7 @@ class SvcHub(object):
|
|||
zi2 = zi
|
||||
al.u2sz = ",".join(zsl)
|
||||
|
||||
derive_args(al)
|
||||
return True
|
||||
|
||||
def _ipa2re(self, txt) -> Optional[re.Pattern]:
|
||||
|
@ -1359,6 +1373,7 @@ class SvcHub(object):
|
|||
with self.reload_mutex:
|
||||
self.log("root", "reloading config")
|
||||
self.asrv.reload(9 if up2k else 4)
|
||||
ramdisk_chk(self.asrv)
|
||||
if up2k:
|
||||
self.up2k.reload(rescan_all_vols)
|
||||
t += "; volumes are now reinitializing"
|
||||
|
|
|
@ -9,7 +9,7 @@ import time
|
|||
|
||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
|
||||
from .cert import gencert
|
||||
from .stolen.qrcodegen import QrCode, qr2svg
|
||||
from .qrkode import QrCode, qr2png, qr2svg, qr2txt, qrgen
|
||||
from .util import (
|
||||
E_ACCESS,
|
||||
E_ADDR_IN_USE,
|
||||
|
@ -21,6 +21,7 @@ from .util import (
|
|||
VF_CAREFUL,
|
||||
Netdev,
|
||||
atomic_move,
|
||||
get_adapters,
|
||||
min_ex,
|
||||
sunpack,
|
||||
termsize,
|
||||
|
@ -59,6 +60,7 @@ class TcpSrv(object):
|
|||
self.stopping = False
|
||||
self.srv: list[socket.socket] = []
|
||||
self.bound: list[tuple[str, int]] = []
|
||||
self.seen_eps: list[tuple[str, int]] = [] # also skipped by uds-only
|
||||
self.netdevs: dict[str, Netdev] = {}
|
||||
self.netlist = ""
|
||||
self.nsrv = 0
|
||||
|
@ -299,6 +301,9 @@ class TcpSrv(object):
|
|||
|
||||
try:
|
||||
if tcp:
|
||||
if self.args.http_no_tcp:
|
||||
self.seen_eps.append((ip, port))
|
||||
return
|
||||
srv.bind((ip, port))
|
||||
else:
|
||||
if ANYWIN or self.args.rm_sck:
|
||||
|
@ -409,6 +414,7 @@ class TcpSrv(object):
|
|||
|
||||
self.srv = srvs
|
||||
self.bound = bound
|
||||
self.seen_eps = list(set(self.seen_eps + bound))
|
||||
self.nsrv = len(srvs)
|
||||
self._distribute_netdevs()
|
||||
|
||||
|
@ -451,8 +457,6 @@ class TcpSrv(object):
|
|||
self._distribute_netdevs()
|
||||
|
||||
def detect_interfaces(self, listen_ips: list[str]) -> dict[str, Netdev]:
|
||||
from .stolen.ifaddr import get_adapters
|
||||
|
||||
listen_ips = [x for x in listen_ips if not x.startswith(("unix:", "fd:"))]
|
||||
|
||||
nics = get_adapters(True)
|
||||
|
@ -625,7 +629,7 @@ class TcpSrv(object):
|
|||
|
||||
pad = self.args.qrp
|
||||
zoom = self.args.qrz
|
||||
qrc = QrCode.encode_binary(btxt)
|
||||
qrc = qrgen(btxt)
|
||||
|
||||
for zs in self.args.qr_file or []:
|
||||
self._qr2file(qrc, zs)
|
||||
|
@ -638,7 +642,7 @@ class TcpSrv(object):
|
|||
except:
|
||||
zoom = 1
|
||||
|
||||
qr = qrc.render(zoom, pad)
|
||||
qr = qr2txt(qrc, zoom, pad)
|
||||
if self.args.no_ansi:
|
||||
return "{}\n{}".format(txt, qr)
|
||||
|
||||
|
@ -679,12 +683,12 @@ class TcpSrv(object):
|
|||
if zoom not in (1, 2):
|
||||
raise Exception("invalid zoom for qr.txt; must be 1 or 2")
|
||||
with open(ap, "wb") as f:
|
||||
f.write(qrc.render(zoom, pad).encode("utf-8"))
|
||||
f.write(qr2txt(qrc, zoom, pad).encode("utf-8"))
|
||||
elif ap.endswith(".svg"):
|
||||
with open(ap, "wb") as f:
|
||||
f.write(qr2svg(qrc, pad).encode("utf-8"))
|
||||
else:
|
||||
qrc.to_png(zoom, pad, self._h2i(bg), self._h2i(fg), ap)
|
||||
qr2png(qrc, zoom, pad, self._h2i(bg), self._h2i(fg), ap)
|
||||
|
||||
def _h2i(self, hs):
|
||||
try:
|
||||
|
|
|
@ -363,7 +363,8 @@ class Tftpd(object):
|
|||
yeet("blocked write; folder not world-deletable: /%s" % (vpath,))
|
||||
|
||||
xbu = vfs.flags.get("xbu")
|
||||
if xbu and not runhook(
|
||||
if xbu:
|
||||
hr = runhook(
|
||||
self.nlog,
|
||||
None,
|
||||
self.hub.up2k,
|
||||
|
@ -379,8 +380,12 @@ class Tftpd(object):
|
|||
"8.3.8.7",
|
||||
time.time(),
|
||||
"",
|
||||
):
|
||||
yeet("blocked by xbu server config: %r" % (vpath,))
|
||||
)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vpath,)
|
||||
yeet(t)
|
||||
|
||||
if not self.args.tftp_nols and bos.path.isdir(ap):
|
||||
return self._ls(vpath, "", 0, True)
|
||||
|
|
|
@ -381,7 +381,7 @@ class ThumbSrv(object):
|
|||
else:
|
||||
ap_unpk = abspath
|
||||
|
||||
if not bos.path.exists(tpath):
|
||||
if ap_unpk and not bos.path.exists(tpath):
|
||||
tex = tpath.rsplit(".", 1)[-1]
|
||||
want_mp3 = tex == "mp3"
|
||||
want_opus = tex in ("opus", "owa", "caf")
|
||||
|
@ -424,12 +424,14 @@ class ThumbSrv(object):
|
|||
except:
|
||||
pass
|
||||
|
||||
conv_ok = False
|
||||
for fun in funs:
|
||||
try:
|
||||
if not png_ok and tpath.endswith(".png"):
|
||||
raise Exception("png only allowed for waveforms")
|
||||
|
||||
fun(ap_unpk, ttpath, fmt, vn)
|
||||
conv_ok = True
|
||||
break
|
||||
except Exception as ex:
|
||||
msg = "%s could not create thumbnail of %r\n%s"
|
||||
|
@ -451,15 +453,19 @@ class ThumbSrv(object):
|
|||
except:
|
||||
pass
|
||||
|
||||
if abspath != ap_unpk:
|
||||
if abspath != ap_unpk and ap_unpk:
|
||||
wunlink(self.log, ap_unpk, vn.flags)
|
||||
|
||||
try:
|
||||
atomic_move(self.log, ttpath, tpath, vn.flags)
|
||||
except Exception as ex:
|
||||
if not os.path.exists(tpath):
|
||||
if conv_ok and not os.path.exists(tpath):
|
||||
t = "failed to move [%s] to [%s]: %r"
|
||||
self.log(t % (ttpath, tpath, ex), 3)
|
||||
elif not conv_ok:
|
||||
try:
|
||||
open(tpath, "ab").close()
|
||||
except:
|
||||
pass
|
||||
|
||||
untemp = []
|
||||
|
@ -682,7 +688,7 @@ class ThumbSrv(object):
|
|||
return
|
||||
|
||||
c: Union[str, int] = "90"
|
||||
t = "FFmpeg failed (probably a corrupt video file):\n"
|
||||
t = "FFmpeg failed (probably a corrupt file):\n"
|
||||
if (
|
||||
(not self.args.th_ff_jpg or time.time() - int(self.args.th_ff_jpg) < 60)
|
||||
and cmd[-1].lower().endswith(b".webp")
|
||||
|
|
|
@ -196,6 +196,7 @@ class U2idx(object):
|
|||
is_key = True
|
||||
is_size = False
|
||||
is_date = False
|
||||
is_wark = False
|
||||
field_end = "" # closing parenthesis or whatever
|
||||
kw_key = ["(", ")", "and ", "or ", "not "]
|
||||
kw_val = ["==", "=", "!=", ">", ">=", "<", "<=", "like "]
|
||||
|
@ -214,6 +215,8 @@ class U2idx(object):
|
|||
is_key = kw in kw_key
|
||||
uq = uq[len(kw) :]
|
||||
ok = True
|
||||
if is_wark:
|
||||
kw = "= "
|
||||
q += kw
|
||||
break
|
||||
|
||||
|
@ -256,6 +259,10 @@ class U2idx(object):
|
|||
if icase:
|
||||
v = "casefold(%s)" % (v,)
|
||||
|
||||
elif v == "w":
|
||||
v = "substr(up.w,1,16)"
|
||||
is_wark = True
|
||||
|
||||
elif v == "tags" or ptn_mt.match(v):
|
||||
have_mt = True
|
||||
field_end = ") "
|
||||
|
@ -267,7 +274,7 @@ class U2idx(object):
|
|||
v = "exists(select 1 from mt where mt.w = mtw and " + vq
|
||||
|
||||
else:
|
||||
raise Pebkac(400, "invalid key [{}]".format(v))
|
||||
raise Pebkac(400, "invalid key %r" % (v,))
|
||||
|
||||
q += v + " "
|
||||
continue
|
||||
|
@ -296,6 +303,14 @@ class U2idx(object):
|
|||
is_size = False
|
||||
v = int(float(v) * 1024 * 1024)
|
||||
|
||||
elif is_wark:
|
||||
is_wark = False
|
||||
v = v.strip("*")
|
||||
if len(v) > 16:
|
||||
v = v[:16]
|
||||
if len(v) < 16:
|
||||
raise Pebkac(400, "w/filehash must be 16+ chars")
|
||||
|
||||
else:
|
||||
if v.startswith("*"):
|
||||
head = "'%'||"
|
||||
|
|
|
@ -18,13 +18,15 @@ from copy import deepcopy
|
|||
|
||||
from queue import Queue
|
||||
|
||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
|
||||
from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, WINDOWS, E
|
||||
from .authsrv import LEELOO_DALLAS, SEESLOG, VFS, AuthSrv
|
||||
from .bos import bos
|
||||
from .cfg import vf_bmap, vf_cmap, vf_vmap
|
||||
from .fsutil import Fstab
|
||||
from .mtag import MParser, MTag
|
||||
from .util import (
|
||||
E_FS_CRIT,
|
||||
E_FS_MEH,
|
||||
HAVE_SQLITE3,
|
||||
SYMTIME,
|
||||
VF_CAREFUL,
|
||||
|
@ -92,7 +94,7 @@ ICV_EXTS = set(zsg.split(","))
|
|||
zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
|
||||
VCV_EXTS = set(zsg.split(","))
|
||||
|
||||
zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv"
|
||||
zsg = "aif,aiff,alac,ape,flac,m4a,mp3,oga,ogg,opus,tak,tta,wav,wma,wv,cbz,epub"
|
||||
ACV_EXTS = set(zsg.split(","))
|
||||
|
||||
zsg = "nohash noidx xdev xvol"
|
||||
|
@ -213,7 +215,7 @@ class Up2k(object):
|
|||
t = "could not initialize sqlite3, will use in-memory registry only"
|
||||
self.log(t, 3)
|
||||
|
||||
self.fstab = Fstab(self.log_func, self.args)
|
||||
self.fstab = Fstab(self.log_func, self.args, True)
|
||||
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
|
||||
|
||||
if self.args.hash_mt < 2:
|
||||
|
@ -1140,7 +1142,7 @@ class Up2k(object):
|
|||
ft = "\033[0;32m{}{:.0}"
|
||||
ff = "\033[0;35m{}{:.0}"
|
||||
fv = "\033[0;36m{}:\033[90m{}"
|
||||
zs = "bcasechk du_iwho ext_th_d html_head put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
|
||||
zs = "bcasechk du_iwho ext_th_d html_head html_head_d html_head_s put_name2 mv_re_r mv_re_t rm_re_r rm_re_t srch_re_dots srch_re_nodot zipmax zipmaxn_v zipmaxs_v"
|
||||
fx = set(zs.split())
|
||||
fd = vf_bmap()
|
||||
fd.update(vf_cmap())
|
||||
|
@ -1499,6 +1501,7 @@ class Up2k(object):
|
|||
|
||||
th_cvd = self.args.th_coversd
|
||||
th_cvds = self.args.th_coversd_set
|
||||
scan_pr_s = self.args.scan_pr_s
|
||||
|
||||
assert self.pp and self.mem_cur # !rm
|
||||
self.pp.msg = "a%d %s" % (self.pp.n, cdir)
|
||||
|
@ -1711,7 +1714,7 @@ class Up2k(object):
|
|||
if nohash or not sz:
|
||||
wark = up2k_wark_from_metadata(self.salt, sz, lmod, rd, fn)
|
||||
else:
|
||||
if sz > 1024 * 1024:
|
||||
if sz > 1024 * 1024 * scan_pr_s:
|
||||
self.log("file: %r" % (abspath,))
|
||||
|
||||
try:
|
||||
|
@ -1719,7 +1722,7 @@ class Up2k(object):
|
|||
abspath, "a{}, ".format(self.pp.n)
|
||||
)
|
||||
except Exception as ex:
|
||||
self.log("hash: %r @ %r" % (ex, abspath))
|
||||
self._ex_hash(ex, abspath)
|
||||
continue
|
||||
|
||||
if not hashes:
|
||||
|
@ -1980,7 +1983,7 @@ class Up2k(object):
|
|||
try:
|
||||
hashes, _ = self._hashlist_from_file(abspath, pf)
|
||||
except Exception as ex:
|
||||
self.log("hash: %r @ %r" % (ex, abspath))
|
||||
self._ex_hash(ex, abspath)
|
||||
continue
|
||||
|
||||
if not hashes:
|
||||
|
@ -3006,7 +3009,7 @@ class Up2k(object):
|
|||
|
||||
# check if filesystem supports sparse files;
|
||||
# refuse out-of-order / multithreaded uploading if sprs False
|
||||
sprs = self.fstab.get(pdir) != "ng"
|
||||
sprs = self.fstab.get(pdir)[0] != "ng"
|
||||
|
||||
if True:
|
||||
jcur = self.cur.get(ptop)
|
||||
|
@ -3297,8 +3300,11 @@ class Up2k(object):
|
|||
job["at"],
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xbu server config: %r" % (dst,)
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r"
|
||||
t = t % (vp,)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
if hr.get("reloc"):
|
||||
|
@ -3678,6 +3684,7 @@ class Up2k(object):
|
|||
t = t.format(job["name"], nchunks[0][0], coffsets[0][0], cur_sz)
|
||||
raise Pebkac(400, t)
|
||||
|
||||
for chash in chashes:
|
||||
job["busy"][chash] = 1
|
||||
|
||||
job["poke"] = time.time()
|
||||
|
@ -3685,7 +3692,7 @@ class Up2k(object):
|
|||
return chashes, chunksize, coffsets, path, job["lmod"], job["size"], job["sprs"]
|
||||
|
||||
def fast_confirm_chunks(
|
||||
self, ptop: str, wark: str, chashes: list[str]
|
||||
self, ptop: str, wark: str, chashes: list[str], locked: list[str]
|
||||
) -> tuple[int, str]:
|
||||
if not self.mutex.acquire(False):
|
||||
return -1, ""
|
||||
|
@ -3693,7 +3700,7 @@ class Up2k(object):
|
|||
self.mutex.release()
|
||||
return -1, ""
|
||||
try:
|
||||
return self._confirm_chunks(ptop, wark, chashes, chashes)
|
||||
return self._confirm_chunks(ptop, wark, chashes, locked, False)
|
||||
finally:
|
||||
self.reg_mutex.release()
|
||||
self.mutex.release()
|
||||
|
@ -3702,10 +3709,10 @@ class Up2k(object):
|
|||
self, ptop: str, wark: str, written: list[str], locked: list[str]
|
||||
) -> tuple[int, str]:
|
||||
with self.mutex, self.reg_mutex:
|
||||
return self._confirm_chunks(ptop, wark, written, locked)
|
||||
return self._confirm_chunks(ptop, wark, written, locked, True)
|
||||
|
||||
def _confirm_chunks(
|
||||
self, ptop: str, wark: str, written: list[str], locked: list[str]
|
||||
self, ptop: str, wark: str, written: list[str], locked: list[str], final: bool
|
||||
) -> tuple[int, str]:
|
||||
if True:
|
||||
self.db_act = self.vol_act[ptop] = time.time()
|
||||
|
@ -3717,14 +3724,16 @@ class Up2k(object):
|
|||
except Exception as ex:
|
||||
return -2, "confirm_chunk, wark(%r)" % (ex,) # type: ignore
|
||||
|
||||
for chash in locked:
|
||||
for chash in locked if final else written:
|
||||
job["busy"].pop(chash, None)
|
||||
|
||||
try:
|
||||
for chash in written:
|
||||
job["need"].remove(chash)
|
||||
except Exception as ex:
|
||||
# dead tcp connections can get here by timeout (OK)
|
||||
for zs in locked:
|
||||
if job["busy"].pop(zs, None):
|
||||
self.log("panic-unlock wark(%s) chunk(%s)" % (wark, zs), 1)
|
||||
return -2, "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore
|
||||
|
||||
ret = len(job["need"])
|
||||
|
@ -3975,8 +3984,11 @@ class Up2k(object):
|
|||
at or time.time(),
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = "upload blocked by xau server config"
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xau server config: %r"
|
||||
t = t % (djoin(vtop, rd, fn),)
|
||||
self.log(t, 1)
|
||||
wunlink(self.log, dst, vflags)
|
||||
self.registry[ptop].pop(wark, None)
|
||||
|
@ -5026,7 +5038,7 @@ class Up2k(object):
|
|||
for k in cj["hash"]:
|
||||
if not self.r_hash.match(k):
|
||||
raise Pebkac(
|
||||
400, "at least one hash is not according to spec: {}".format(k)
|
||||
400, "at least one hash is not according to spec: %r" % (k,)
|
||||
)
|
||||
|
||||
# try to use client-provided timestamp, don't care if it fails somehow
|
||||
|
@ -5083,6 +5095,16 @@ class Up2k(object):
|
|||
|
||||
return ret, st
|
||||
|
||||
def _ex_hash(self, ex: Exception, ap: str) -> None:
|
||||
eno = getattr(ex, "errno", 0)
|
||||
if eno in E_FS_MEH:
|
||||
return self.log("hashing failed; %r @ %r" % (ex, ap))
|
||||
if eno not in E_FS_CRIT:
|
||||
return self.log("hashing failed; %r @ %r\n%s" % (ex, ap, min_ex()), 3)
|
||||
t = "hashing failed; %r @ %r\n%s\nWARNING: This MAY indicate a serious issue with your harddisk or filesystem! Please investigate %sOS-logs\n"
|
||||
t2 = "" if ANYWIN or MACOS else "dmesg and "
|
||||
return self.log(t % (ex, ap, min_ex(), t2), 1)
|
||||
|
||||
def _new_upload(self, job: dict[str, Any], vfs: VFS, depth: int) -> dict[str, str]:
|
||||
pdir = djoin(job["ptop"], job["prel"])
|
||||
if not job["size"]:
|
||||
|
@ -5116,7 +5138,9 @@ class Up2k(object):
|
|||
job["t0"],
|
||||
"",
|
||||
)
|
||||
if not hr:
|
||||
t = hr.get("rejectmsg") or ""
|
||||
if t or not hr:
|
||||
if not t:
|
||||
t = "upload blocked by xbu server config: %r" % (vp_chk,)
|
||||
self.log(t, 1)
|
||||
raise Pebkac(403, t)
|
||||
|
@ -5179,7 +5203,7 @@ class Up2k(object):
|
|||
sprs = False
|
||||
|
||||
if not ANYWIN and sprs and sz > 1024 * 1024:
|
||||
fs = self.fstab.get(pdir)
|
||||
fs, mnt = self.fstab.get(pdir)
|
||||
if fs == "ok":
|
||||
pass
|
||||
elif "nosparse" in vf:
|
||||
|
@ -5265,17 +5289,21 @@ class Up2k(object):
|
|||
self.log("\n".join([t] + vis))
|
||||
for job in rm:
|
||||
del reg[job["wark"]]
|
||||
rsv_cleared = False
|
||||
try:
|
||||
# remove the filename reservation
|
||||
path = djoin(job["ptop"], job["prel"], job["name"])
|
||||
if bos.path.getsize(path) == 0:
|
||||
bos.unlink(path)
|
||||
rsv_cleared = True
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if len(job["hash"]) == len(job["need"]):
|
||||
# PARTIAL is empty, delete that too
|
||||
if len(job["hash"]) == len(job["need"]) or (
|
||||
rsv_cleared and "rm_partial" in self.flags[job["ptop"]]
|
||||
):
|
||||
# PARTIAL is empty (hash==need) or --rm-partial, so delete that too
|
||||
path = djoin(job["ptop"], job["prel"], job["tnam"])
|
||||
bos.unlink(path)
|
||||
except:
|
||||
|
|
|
@ -55,7 +55,6 @@ from .__init__ import (
|
|||
unicode,
|
||||
)
|
||||
from .__version__ import S_BUILD_DT, S_VERSION
|
||||
from .stolen import surrogateescape
|
||||
|
||||
try:
|
||||
from datetime import datetime, timezone
|
||||
|
@ -81,6 +80,9 @@ except:
|
|||
|
||||
if PY2:
|
||||
range = xrange # type: ignore
|
||||
from .stolen import surrogateescape
|
||||
|
||||
surrogateescape.register_surrogateescape()
|
||||
|
||||
|
||||
if sys.version_info >= (3, 7) or (
|
||||
|
@ -111,6 +113,8 @@ E_ADDR_NOT_AVAIL = _ens("EADDRNOTAVAIL WSAEADDRNOTAVAIL")
|
|||
E_ADDR_IN_USE = _ens("EADDRINUSE WSAEADDRINUSE")
|
||||
E_ACCESS = _ens("EACCES WSAEACCES")
|
||||
E_UNREACH = _ens("EHOSTUNREACH WSAEHOSTUNREACH ENETUNREACH WSAENETUNREACH")
|
||||
E_FS_MEH = _ens("EPERM EACCES ENOENT ENOTCAPABLE")
|
||||
E_FS_CRIT = _ens("EIO EFAULT EUCLEAN ENOTBLK")
|
||||
|
||||
IP6ALL = "0:0:0:0:0:0:0:0"
|
||||
IP6_LL = ("fe8", "fe9", "fea", "feb")
|
||||
|
@ -134,6 +138,25 @@ try:
|
|||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if os.environ.get("PRTY_NO_IFADDR"):
|
||||
raise Exception()
|
||||
try:
|
||||
if os.getenv("PRTY_SYS_ALL") or os.getenv("PRTY_SYS_IFADDR"):
|
||||
raise ImportError()
|
||||
|
||||
from .stolen.ifaddr import get_adapters
|
||||
except ImportError:
|
||||
from ifaddr import get_adapters
|
||||
|
||||
HAVE_IFADDR = True
|
||||
except:
|
||||
HAVE_IFADDR = False
|
||||
|
||||
def get_adapters(include_unconfigured=False):
|
||||
return []
|
||||
|
||||
|
||||
try:
|
||||
if os.environ.get("PRTY_NO_SQLITE"):
|
||||
raise Exception()
|
||||
|
@ -171,6 +194,11 @@ try:
|
|||
except:
|
||||
pass
|
||||
|
||||
if os.getenv("PRTY_MODSPEC"):
|
||||
from inspect import getsourcefile
|
||||
|
||||
print("PRTY_MODSPEC: ifaddr:", getsourcefile(get_adapters))
|
||||
|
||||
if True: # pylint: disable=using-constant-test
|
||||
import types
|
||||
from collections.abc import Callable, Iterable
|
||||
|
@ -267,7 +295,6 @@ RE_MEMAVAIL = re.compile("^MemAvailable:.* kB")
|
|||
BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
|
||||
|
||||
|
||||
surrogateescape.register_surrogateescape()
|
||||
if WINDOWS and PY2:
|
||||
FS_ENCODING = "utf-8"
|
||||
else:
|
||||
|
@ -376,6 +403,13 @@ DAV_ALLPROP_L = [
|
|||
DAV_ALLPROPS = set(DAV_ALLPROP_L)
|
||||
|
||||
|
||||
FAVICON_MIMES = {
|
||||
"gif": "image/gif",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
}
|
||||
|
||||
|
||||
MIMES = {
|
||||
"opus": "audio/ogg; codecs=opus",
|
||||
"owa": "audio/webm; codecs=opus",
|
||||
|
@ -430,7 +464,7 @@ EXTS["vnd.mozilla.apng"] = "png"
|
|||
MAGIC_MAP = {"jpeg": "jpg"}
|
||||
|
||||
|
||||
DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf_ipcountry srv.itime srv.htime"
|
||||
DEF_EXP = "self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime"
|
||||
|
||||
DEF_MTE = ".files,circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
|
||||
|
||||
|
@ -546,6 +580,8 @@ def py_desc() -> str:
|
|||
ofs = py_ver.find(".final.")
|
||||
if ofs > 0:
|
||||
py_ver = py_ver[:ofs]
|
||||
if "free-threading" in sys.version:
|
||||
py_ver += "t"
|
||||
|
||||
host_os = platform.system()
|
||||
compiler = platform.python_compiler().split("http")[0]
|
||||
|
@ -656,6 +692,9 @@ def read_utf8(log: Optional["NamedLogger"], ap: Union[str, bytes], strict: bool)
|
|||
with open(ap, "rb") as f:
|
||||
buf = f.read()
|
||||
|
||||
if buf.startswith(b"\xef\xbb\xbf"):
|
||||
buf = buf[3:]
|
||||
|
||||
try:
|
||||
return buf.decode("utf-8", "strict")
|
||||
except UnicodeDecodeError as ex:
|
||||
|
@ -1121,16 +1160,18 @@ class ProgressPrinter(threading.Thread):
|
|||
sigblock()
|
||||
tp = 0
|
||||
msg = None
|
||||
no_stdout = self.args.q
|
||||
slp_pr = self.args.scan_pr_r
|
||||
slp_ps = min(slp_pr, self.args.scan_st_r)
|
||||
no_stdout = self.args.q or slp_pr == slp_ps
|
||||
fmt = " {}\033[K\r" if VT100 else " {} $\r"
|
||||
while not self.end:
|
||||
time.sleep(0.1)
|
||||
time.sleep(slp_ps)
|
||||
if msg == self.msg or self.end:
|
||||
continue
|
||||
|
||||
msg = self.msg
|
||||
now = time.time()
|
||||
if msg and now - tp > 10:
|
||||
if msg and now - tp >= slp_pr:
|
||||
tp = now
|
||||
self.log("progress: %r" % (msg,), 6)
|
||||
|
||||
|
@ -1188,21 +1229,21 @@ class MTHash(object):
|
|||
for nch in range(nchunks):
|
||||
self.work_q.put(nch)
|
||||
|
||||
ex = ""
|
||||
ex: Optional[Exception] = None
|
||||
for nch in range(nchunks):
|
||||
qe = self.done_q.get()
|
||||
try:
|
||||
nch, dig, ofs, csz = qe
|
||||
chunks[nch] = (dig, ofs, csz)
|
||||
except:
|
||||
ex = ex or str(qe)
|
||||
ex = ex or qe # type: ignore
|
||||
|
||||
if pp:
|
||||
mb = (fsz - nch * chunksz) // (1024 * 1024)
|
||||
pp.msg = prefix + str(mb) + suffix
|
||||
|
||||
if ex:
|
||||
raise Exception(ex)
|
||||
raise ex
|
||||
|
||||
ret = []
|
||||
for n in range(nchunks):
|
||||
|
@ -1219,7 +1260,7 @@ class MTHash(object):
|
|||
try:
|
||||
v = self.hash_at(ofs)
|
||||
except Exception as ex:
|
||||
v = str(ex) # type: ignore
|
||||
v = ex # type: ignore
|
||||
|
||||
self.done_q.put(v)
|
||||
|
||||
|
@ -1761,12 +1802,12 @@ class MultipartParser(object):
|
|||
continue
|
||||
|
||||
if m.group(1).lower() != "form-data":
|
||||
raise Pebkac(400, "not form-data: {}".format(ln))
|
||||
raise Pebkac(400, "not form-data: %r" % (ln,))
|
||||
|
||||
try:
|
||||
field = self.re_cdisp_field.match(ln).group(1) # type: ignore
|
||||
except:
|
||||
raise Pebkac(400, "missing field name: {}".format(ln))
|
||||
raise Pebkac(400, "missing field name: %r" % (ln,))
|
||||
|
||||
try:
|
||||
fn = self.re_cdisp_file.match(ln).group(1) # type: ignore
|
||||
|
@ -1938,7 +1979,7 @@ def get_boundary(headers: dict[str, str]) -> str:
|
|||
ct = headers["content-type"]
|
||||
m = re.match(ptn, ct, re.IGNORECASE)
|
||||
if not m:
|
||||
raise Pebkac(400, "invalid content-type for a multipart post: {}".format(ct))
|
||||
raise Pebkac(400, "invalid content-type for a multipart post: %r" % (ct,))
|
||||
|
||||
return m.group(2)
|
||||
|
||||
|
@ -2912,8 +2953,6 @@ def read_socket_chunked(
|
|||
|
||||
|
||||
def list_ips() -> list[str]:
|
||||
from .stolen.ifaddr import get_adapters
|
||||
|
||||
ret: set[str] = set()
|
||||
for nic in get_adapters():
|
||||
for ipo in nic.ips:
|
||||
|
@ -3590,11 +3629,13 @@ def retchk(
|
|||
|
||||
def _parsehook(
|
||||
log: Optional["NamedLogger"], cmd: str
|
||||
) -> tuple[str, bool, bool, bool, float, dict[str, Any], list[str]]:
|
||||
) -> tuple[str, bool, bool, bool, bool, bool, float, dict[str, Any], list[str]]:
|
||||
areq = ""
|
||||
chk = False
|
||||
fork = False
|
||||
jtxt = False
|
||||
imp = False
|
||||
sin = False
|
||||
wait = 0.0
|
||||
tout = 0.0
|
||||
kill = "t"
|
||||
|
@ -3608,6 +3649,10 @@ def _parsehook(
|
|||
fork = True
|
||||
elif arg == "j":
|
||||
jtxt = True
|
||||
elif arg == "I":
|
||||
imp = True
|
||||
elif arg == "s":
|
||||
sin = True
|
||||
elif arg.startswith("w"):
|
||||
wait = float(arg[1:])
|
||||
elif arg.startswith("t"):
|
||||
|
@ -3652,7 +3697,7 @@ def _parsehook(
|
|||
|
||||
argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
|
||||
|
||||
return areq, chk, fork, jtxt, wait, sp_ka, argv
|
||||
return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv
|
||||
|
||||
|
||||
def runihook(
|
||||
|
@ -3662,7 +3707,7 @@ def runihook(
|
|||
vol: "VFS",
|
||||
ups: list[tuple[str, int, int, str, str, str, int, str]],
|
||||
) -> bool:
|
||||
_, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
_, chk, _, fork, _, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
bcmd = [sfsenc(x) for x in acmd]
|
||||
if acmd[0].endswith(".py"):
|
||||
bcmd = [sfsenc(pybin)] + bcmd
|
||||
|
@ -3841,7 +3886,7 @@ def _runhook(
|
|||
txt: str,
|
||||
) -> dict[str, Any]:
|
||||
ret = {"rc": 0}
|
||||
areq, chk, fork, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
areq, chk, imp, fork, sin, jtxt, wait, sp_ka, acmd = _parsehook(log, cmd)
|
||||
if areq:
|
||||
for ch in areq:
|
||||
if ch not in perms:
|
||||
|
@ -3849,7 +3894,7 @@ def _runhook(
|
|||
if log:
|
||||
log(t % (uname, cmd, areq, perms))
|
||||
return ret # fallthrough to next hook
|
||||
if jtxt:
|
||||
if imp or jtxt:
|
||||
ja = {
|
||||
"ap": ap,
|
||||
"vp": vp,
|
||||
|
@ -3863,6 +3908,10 @@ def _runhook(
|
|||
"src": src,
|
||||
"txt": txt,
|
||||
}
|
||||
if imp:
|
||||
ja["log"] = log
|
||||
mod = loadpy(acmd[0], False)
|
||||
return mod.main(ja)
|
||||
arg = json.dumps(ja)
|
||||
else:
|
||||
arg = txt or ap
|
||||
|
@ -3873,7 +3922,11 @@ def _runhook(
|
|||
raise Exception("zmq says %d" % (zi,))
|
||||
return {"rc": 0, "stdout": zs}
|
||||
|
||||
if sin:
|
||||
sp_ka["sin"] = (arg + "\n").encode("utf-8", "replace")
|
||||
else:
|
||||
acmd += [arg]
|
||||
|
||||
if acmd[0].endswith(".py"):
|
||||
acmd = [pybin] + acmd
|
||||
|
||||
|
@ -3958,7 +4011,7 @@ def runhook(
|
|||
else:
|
||||
ret[k] = v
|
||||
except Exception as ex:
|
||||
(log or print)("hook: {}".format(ex))
|
||||
(log or print)("hook: %r, %s" % (ex, ex))
|
||||
if ",c," in "," + cmd:
|
||||
return {}
|
||||
break
|
||||
|
|
|
@ -22,8 +22,9 @@ window.baguetteBox = (function () {
|
|||
afterHide: null,
|
||||
duringHide: null,
|
||||
onChange: null,
|
||||
readDirRtl: false,
|
||||
},
|
||||
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnClose,
|
||||
overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnVmode, btnReadDir, btnClose,
|
||||
currentGallery = [],
|
||||
currentIndex = 0,
|
||||
isOverlayVisible = false,
|
||||
|
@ -73,10 +74,10 @@ window.baguetteBox = (function () {
|
|||
var touchEvent = e.touches[0] || e.changedTouches[0];
|
||||
if (touchEvent.pageX - touch.startX > 40) {
|
||||
touchFlag = true;
|
||||
showPreviousImage();
|
||||
showLeftImage();
|
||||
} else if (touchEvent.pageX - touch.startX < -40) {
|
||||
touchFlag = true;
|
||||
showNextImage();
|
||||
showRightImage();
|
||||
} else if (touch.startY - touchEvent.pageY > 100) {
|
||||
hideOverlay();
|
||||
}
|
||||
|
@ -114,9 +115,9 @@ window.baguetteBox = (function () {
|
|||
scrollTimer = Date.now();
|
||||
|
||||
if (d > 0)
|
||||
showNextImage();
|
||||
showNextImageIgnoreReadDir();
|
||||
else
|
||||
showPreviousImage();
|
||||
showPreviousImageIgnoreReadDir();
|
||||
};
|
||||
|
||||
var trapFocusInsideOverlay = function (e) {
|
||||
|
@ -212,6 +213,7 @@ window.baguetteBox = (function () {
|
|||
'<div id="bbox-btns">' +
|
||||
'<button id="bbox-help" type="button">?</button>' +
|
||||
'<button id="bbox-anim" type="button" tt="a">-</button>' +
|
||||
'<button id="bbox-readdir" type="button" tt="a">ltr</button>' +
|
||||
'<button id="bbox-rotl" type="button">↶</button>' +
|
||||
'<button id="bbox-rotr" type="button">↷</button>' +
|
||||
'<button id="bbox-tsel" type="button">sel</button>' +
|
||||
|
@ -229,6 +231,7 @@ window.baguetteBox = (function () {
|
|||
btnNext = ebi('bbox-next');
|
||||
btnHelp = ebi('bbox-help');
|
||||
btnAnim = ebi('bbox-anim');
|
||||
btnReadDir = ebi('bbox-readdir');
|
||||
btnRotL = ebi('bbox-rotl');
|
||||
btnRotR = ebi('bbox-rotr');
|
||||
btnSel = ebi('bbox-tsel');
|
||||
|
@ -301,9 +304,9 @@ window.baguetteBox = (function () {
|
|||
else if (e.shiftKey && kl != "r")
|
||||
return;
|
||||
else if (k == "ArrowLeft" || k == "Left" || kl == "j")
|
||||
showPreviousImage();
|
||||
showLeftImage();
|
||||
else if (k == "ArrowRight" || k == "Right" || kl == "l")
|
||||
showNextImage();
|
||||
showRightImage();
|
||||
else if (k == "Escape" || k == "Esc")
|
||||
hideOverlay();
|
||||
else if (k == "Home")
|
||||
|
@ -353,6 +356,21 @@ window.baguetteBox = (function () {
|
|||
tt.show.call(this);
|
||||
}
|
||||
|
||||
function toggleReadDir() {
|
||||
var o = options,
|
||||
next = options.readDirRtl ? "ltr" : "rtl";
|
||||
swrite('greaddir', next);
|
||||
slider.className = "no-transition";
|
||||
options = {};
|
||||
setOptions(o);
|
||||
updateOffset(true);
|
||||
window.getComputedStyle(slider).opacity; // force a restyle
|
||||
slider.className = "";
|
||||
|
||||
if (tt.en)
|
||||
tt.show.call(this);
|
||||
}
|
||||
|
||||
function setVmode() {
|
||||
var v = vid();
|
||||
ebi('bbox-vmode').style.display = v ? '' : 'none';
|
||||
|
@ -492,12 +510,13 @@ window.baguetteBox = (function () {
|
|||
bind(document, 'fullscreenchange', onFSC);
|
||||
bind(overlay, 'click', overlayClickHandler);
|
||||
bind(overlay, 'wheel', overlayWheelHandler);
|
||||
bind(btnPrev, 'click', showPreviousImage);
|
||||
bind(btnNext, 'click', showNextImage);
|
||||
bind(btnPrev, 'click', showLeftImage);
|
||||
bind(btnNext, 'click', showRightImage);
|
||||
bind(btnClose, 'click', hideOverlay);
|
||||
bind(btnVmode, 'click', tglVmode);
|
||||
bind(btnHelp, 'click', halp);
|
||||
bind(btnAnim, 'click', anim);
|
||||
bind(btnReadDir, 'click', toggleReadDir);
|
||||
bind(btnRotL, 'click', rotl);
|
||||
bind(btnRotR, 'click', rotr);
|
||||
bind(btnSel, 'click', tglsel);
|
||||
|
@ -515,12 +534,13 @@ window.baguetteBox = (function () {
|
|||
unbind(document, 'fullscreenchange', onFSC);
|
||||
unbind(overlay, 'click', overlayClickHandler);
|
||||
unbind(overlay, 'wheel', overlayWheelHandler);
|
||||
unbind(btnPrev, 'click', showPreviousImage);
|
||||
unbind(btnNext, 'click', showNextImage);
|
||||
unbind(btnPrev, 'click', showLeftImage);
|
||||
unbind(btnNext, 'click', showRightImage);
|
||||
unbind(btnClose, 'click', hideOverlay);
|
||||
unbind(btnVmode, 'click', tglVmode);
|
||||
unbind(btnHelp, 'click', halp);
|
||||
unbind(btnAnim, 'click', anim);
|
||||
unbind(btnReadDir, 'click', toggleReadDir);
|
||||
unbind(btnRotL, 'click', rotl);
|
||||
unbind(btnRotR, 'click', rotr);
|
||||
unbind(btnSel, 'click', tglsel);
|
||||
|
@ -571,6 +591,23 @@ window.baguetteBox = (function () {
|
|||
btnAnim.textContent = ['⇄', '⮺', '⚡'][anims.indexOf(an)];
|
||||
btnAnim.setAttribute('tt', 'animation: ' + an);
|
||||
|
||||
options.readDirRtl = sread('greaddir') === "rtl";
|
||||
var msg;
|
||||
if (options.readDirRtl) {
|
||||
btnReadDir.innerText = "rtl";
|
||||
msg = "browse from right to left";
|
||||
slider.style.display = "flex";
|
||||
slider.style.flexDirection = "row-reverse";
|
||||
} else {
|
||||
btnReadDir.innerText = "ltr";
|
||||
msg = "browse from left to right";
|
||||
slider.style.flexDirection = "";
|
||||
slider.style.display = "block";
|
||||
}
|
||||
btnReadDir.setAttribute("tt", msg);
|
||||
btnReadDir.setAttribute("aria-label", msg);
|
||||
|
||||
|
||||
slider.style.transition = (options.animation === 'fadeIn' ? 'opacity .3s ease' :
|
||||
options.animation === 'slideIn' ? '' : 'none');
|
||||
|
||||
|
@ -815,12 +852,24 @@ window.baguetteBox = (function () {
|
|||
show_buttons(this.paused ? 1 : 0);
|
||||
}
|
||||
|
||||
function showNextImage(e) {
|
||||
function showRightImage(e) {
|
||||
ev(e);
|
||||
var dir = options.readDirRtl ? -1 : 1;
|
||||
return show(currentIndex + dir);
|
||||
}
|
||||
|
||||
function showLeftImage(e) {
|
||||
ev(e);
|
||||
var dir = options.readDirRtl ? 1 : -1;
|
||||
return show(currentIndex + dir);
|
||||
}
|
||||
|
||||
function showNextImageIgnoreReadDir(e) {
|
||||
ev(e);
|
||||
return show(currentIndex + 1);
|
||||
}
|
||||
|
||||
function showPreviousImage(e) {
|
||||
function showPreviousImageIgnoreReadDir(e) {
|
||||
ev(e);
|
||||
return show(currentIndex - 1);
|
||||
}
|
||||
|
@ -848,10 +897,10 @@ window.baguetteBox = (function () {
|
|||
}
|
||||
|
||||
if (index < 0)
|
||||
return bounceAnimation('left');
|
||||
return bounceAnimation(options.readDirRtl ? 'right' : 'left');
|
||||
|
||||
if (index >= imagesElements.length)
|
||||
return bounceAnimation('right');
|
||||
return bounceAnimation(options.readDirRtl ? 'left' : 'right');
|
||||
|
||||
try {
|
||||
vid().pause();
|
||||
|
@ -1005,7 +1054,7 @@ window.baguetteBox = (function () {
|
|||
|
||||
function vidEnd() {
|
||||
if (this == vid() && vnext)
|
||||
showNextImage();
|
||||
showNextImageIgnoreReadDir();
|
||||
}
|
||||
|
||||
function setloop(side) {
|
||||
|
@ -1066,11 +1115,12 @@ window.baguetteBox = (function () {
|
|||
return false;
|
||||
}
|
||||
|
||||
function updateOffset() {
|
||||
var offset = -currentIndex * 100 + '%',
|
||||
function updateOffset(noTransition) {
|
||||
var dir = options.readDirRtl ? 1 : -1,
|
||||
offset = dir * currentIndex * 100 + '%',
|
||||
xform = slider.style.perspective !== undefined;
|
||||
|
||||
if (options.animation === 'fadeIn') {
|
||||
if (options.animation === 'fadeIn' && !noTransition) {
|
||||
slider.style.opacity = 0;
|
||||
setTimeout(function () {
|
||||
xform ?
|
||||
|
@ -1116,10 +1166,10 @@ window.baguetteBox = (function () {
|
|||
fx = x / (rc.right - rc.left);
|
||||
|
||||
if (fx < 0.3)
|
||||
return showPreviousImage();
|
||||
return showLeftImage();
|
||||
|
||||
if (fx > 0.7)
|
||||
return showNextImage();
|
||||
return showRightImage();
|
||||
|
||||
show_buttons('t');
|
||||
|
||||
|
@ -1175,8 +1225,8 @@ window.baguetteBox = (function () {
|
|||
return {
|
||||
run: run,
|
||||
show: show,
|
||||
showNext: showNextImage,
|
||||
showPrevious: showPreviousImage,
|
||||
showNext: showRightImage,
|
||||
showPrevious: showLeftImage,
|
||||
relseek: relseek,
|
||||
urltime: urltime,
|
||||
playpause: playpause,
|
||||
|
|
|
@ -84,6 +84,13 @@
|
|||
--sort-1: #fb0;
|
||||
--sort-2: #d09;
|
||||
|
||||
--sz-b: #aaa;
|
||||
--sz-k: #4ff;
|
||||
--sz-m: var(--tab-alt);
|
||||
--sz-g: var(--a);
|
||||
--sz-t: var(--sz-g);
|
||||
--sz-p: var(--sz-t);
|
||||
|
||||
--srv-1: #aaa;
|
||||
--srv-2: #a73;
|
||||
--srv-3: #f4c;
|
||||
|
@ -187,6 +194,9 @@ html.y {
|
|||
--sort-1: #059;
|
||||
--sort-2: #f5d;
|
||||
|
||||
--sz-b: #777;
|
||||
--sz-k: #380;
|
||||
|
||||
--srv-1: #555;
|
||||
--srv-2: #c83;
|
||||
--srv-3: #c0a;
|
||||
|
@ -344,6 +354,9 @@ html.cz {
|
|||
--btn-1-bb: .2em solid #e90;
|
||||
--btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9);
|
||||
|
||||
--sz-b: #ddd;
|
||||
--sz-k: #c9f;
|
||||
|
||||
--srv-3: #fff;
|
||||
|
||||
--u2-tab-b1: var(--bg-d3);
|
||||
|
@ -740,6 +753,15 @@ html.y #files tr.fade a {
|
|||
#files tbody tr td:last-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#files span.fsz_B { color: var(--sz-b); }
|
||||
#files span.fsz_K { color: var(--sz-k); }
|
||||
#files span.fsz_M { color: var(--sz-m); }
|
||||
#files span.fsz_G { color: var(--sz-g); }
|
||||
#files span.fsz_T { color: var(--sz-t); }
|
||||
#files span.fsz_P { color: var(--sz-p); }
|
||||
html.y #files span.fsz_G,
|
||||
html.y #files span.fsz_T,
|
||||
html.y #files span.fsz_P { font-weight: bold }
|
||||
#files thead th[style] {
|
||||
width: auto !important;
|
||||
}
|
||||
|
@ -865,6 +887,9 @@ html.y #path a:hover {
|
|||
#srv_info2 span {
|
||||
color: var(--srv-1);
|
||||
}
|
||||
#srv_info2 a {
|
||||
padding: 0;
|
||||
}
|
||||
#srv_info2 {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1155,6 +1180,9 @@ html.y #widget.open {
|
|||
border: 1px solid var(--bg-u5);
|
||||
border-width: 0 .1em 0 0;
|
||||
}
|
||||
#wzip1 {
|
||||
margin-right: .2em;
|
||||
}
|
||||
#wfm.act+#wzip1+#wzip,
|
||||
#wfm.act+#wzip1+#wzip+#wnp {
|
||||
margin-left: .2em;
|
||||
|
@ -1911,6 +1939,8 @@ html.y #tree.nowrap .ntree a+a:hover {
|
|||
#rui td input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
#rui #rn_n_d,
|
||||
#rui #rn_n_s,
|
||||
#shui td.exs input[type="text"] {
|
||||
width: 3em;
|
||||
}
|
||||
|
@ -2115,6 +2145,7 @@ html.noscroll .sbar::-webkit-scrollbar {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
flex: none;
|
||||
}
|
||||
.full-image figure {
|
||||
display: inline;
|
||||
|
@ -2309,6 +2340,9 @@ html.y #bbox-overlay figcaption a {
|
|||
0%, 100% {transform: scale(0)}
|
||||
50% {transform: scale(1)}
|
||||
}
|
||||
.no-transition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/browser.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
{%- if css %}
|
||||
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
|
||||
{%- endif %}
|
||||
|
@ -134,7 +134,6 @@
|
|||
CGV = {{ cgv|tojson }},
|
||||
TS = "{{ ts }}",
|
||||
dtheme = "{{ dtheme }}",
|
||||
srvinf = "{{ srv_info }}",
|
||||
lang = "{{ lang }}",
|
||||
dfavico = "{{ favico }}",
|
||||
have_tags_idx = {{ have_tags_idx }},
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@
|
|||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{%- if edit %}
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/md2.css?_={{ ts }}">
|
||||
{%- endif %}
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
<body>
|
||||
<div id="mn"></div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="stylesheet" href="{{ r }}/.cpr/mde.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/mini-fa.css?_={{ ts }}">
|
||||
<link rel="stylesheet" href="{{ r }}/.cpr/deps/easymde.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
<body>
|
||||
<div id="mw">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/msg.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
31
copyparty/web/opds.xml
Normal file
31
copyparty/web/opds.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
{%- for d in dirs %}
|
||||
<entry>
|
||||
<title>{{ d.name }}</title>
|
||||
<link rel="subsection"
|
||||
href="{{ d.href | e }}"
|
||||
type="application/atom+xml;profile=opds-catalog"/>
|
||||
<updated>{{ d.iso8601 }}</updated>
|
||||
</entry>
|
||||
{%- endfor %}
|
||||
{%- for f in files %}
|
||||
<entry>
|
||||
<title>{{ f.name }}</title>
|
||||
<updated>{{ f.iso8601 }}</updated>
|
||||
<link rel="http://opds-spec.org/acquisition"
|
||||
href="{{ f.href | e }}"
|
||||
type="{{ f.mime }}"/>
|
||||
{%- if f.jpeg_thumb_href != None %}
|
||||
<link rel="http://opds-spec.org/image/thumbnail"
|
||||
href="{{ f.jpeg_thumb_href | e }}"
|
||||
type="image/jpeg"/>
|
||||
{%- endif %}
|
||||
{%- if f.jpeg_thumb_href_hires != None %}
|
||||
<link rel="http://opds-spec.org/image"
|
||||
href="{{ f.jpeg_thumb_href_hires | e }}"
|
||||
type="image/jpeg"/>
|
||||
{%- endif %}
|
||||
</entry>
|
||||
{%- endfor %}
|
||||
</feed>
|
|
@ -10,7 +10,7 @@
|
|||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/rups.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/shares.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -118,6 +118,26 @@ table {
|
|||
.btns>a:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.agr br {
|
||||
display: none;
|
||||
}
|
||||
#lo,
|
||||
.agr a,
|
||||
.agr form {
|
||||
margin: 0 .5em 0 0;
|
||||
line-height: 4em;
|
||||
}
|
||||
.agr form,
|
||||
.agr input {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#lo,
|
||||
.agr input {
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
#msg {
|
||||
margin: 3em 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<meta name="theme-color" content="#{{ tcolor }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -151,19 +151,31 @@
|
|||
{%- endif %}
|
||||
|
||||
<h1 id="cc">other stuff:</h1>
|
||||
<ul>
|
||||
<div class="agr">
|
||||
{%- if ahttps %}
|
||||
<li><a id="wb" href="{{ ahttps }}">switch to https</a></li>
|
||||
<a id="wb" href="{{ ahttps }}">switch to https</a><br />
|
||||
{%- endif %}
|
||||
|
||||
<a id="af" href="{{ r }}/?ru">show recent uploads</a><br />
|
||||
|
||||
{%- if this.uname != '*' and this.args.shr %}
|
||||
<a id="y" href="{{ r }}/?shares">edit shares</a><br />
|
||||
{%- endif %}
|
||||
|
||||
{%- if this.uname in this.args.idp_adm_set %}
|
||||
<li><a id="ag" href="{{ r }}/?idp">view idp cache</a></li>
|
||||
<a id="ag" href="{{ r }}/?idp">view idp cache</a><br />
|
||||
{%- endif %}
|
||||
|
||||
{%- if this.uname != '*' and this.args.shr %}
|
||||
<li><a id="y" href="{{ r }}/?shares">edit shares</a></li>
|
||||
{%- endif %}
|
||||
<a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a><br />
|
||||
|
||||
{%- if this.uname != '*' and not in_shr %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="act" value="logout" />
|
||||
<input type="submit" id="lo" value="logout “{{ this.uname|e }}” everywhere" />
|
||||
</form>
|
||||
{%- endif %}
|
||||
</div>
|
||||
<ul>
|
||||
{%- if k304 or k304vis %}
|
||||
{%- if k304 %}
|
||||
<li><a id="h" href="{{ r }}/?cc&setck=k304=n">disable k304</a> (currently enabled)
|
||||
|
@ -181,16 +193,6 @@
|
|||
{%- endif %}
|
||||
<blockquote id="ad">enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!</blockquote></li>
|
||||
{%- endif %}
|
||||
|
||||
<li><a id="af" href="{{ r }}/?ru">show recent uploads</a></li>
|
||||
<li><a id="k" href="{{ r }}/?reset" class="r" onclick="localStorage.clear();return true">reset client settings</a></li>
|
||||
|
||||
{%- if this.uname != '*' and not in_shr %}
|
||||
<li><form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="act" value="logout" />
|
||||
<input type="submit" id="lo" value="logout “{{ this.uname|e }}” everywhere" />
|
||||
</form></li>
|
||||
{%- endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -197,6 +197,53 @@ var Ls = {
|
|||
"ae1": "Aktive Downloads:",
|
||||
"af1": "Zeige neue Uploads",
|
||||
},
|
||||
"epo": {
|
||||
"a1": "reŝargi",
|
||||
"b1": "sal, nekonatulo <small>(vi ne estas ensalutita)</small>",
|
||||
"c1": "elsaluti",
|
||||
"d1": "montru stakon", // TLNote: "d2" is the tooltip for this button
|
||||
"d2": "montras la staton de ĉiuj aktivaj fadenoj",
|
||||
"e1": "reŝargi CFGon",
|
||||
"e2": "reŝargas la agordo-dosierojn (kontoj/portiloj/portilo-flagoj),$Nkaj reskanas ĉiuj portiloj de ed2s$N$Nnoto: ĉiuj ŝanĝoj de ĝeneralaj agordoj postulas$Npostulas tutan restartigon por efektiviĝi",
|
||||
"f1": "vi povas vidi:",
|
||||
"g1": "vi povas alŝuti al:",
|
||||
"cc1": "aliaĵoj:",
|
||||
"h1": "malŝalti k304-on", // TLNote: "j1" explains what k304 is
|
||||
"i1": "ŝalti k304-on",
|
||||
"j1": "k304 malkonektas vian klienton je ĉiu HTTP-eraro 304; tio povas eviti paraliziĝon dum uzado de difektitaj retperantoj (paĝoj subite ne ŝargiĝas), <em>sed</em> ĝi ankaŭ plimalrapidigas ĉion",
|
||||
"k1": "rekomenci agordojn de kliento",
|
||||
"l1": "ensaluti por pli da opcioj:",
|
||||
"ls3": "ensaluti", //m
|
||||
"lu4": "uzantnomo", //m
|
||||
"lp4": "pasvorto", //m
|
||||
"lo3": "ensaluti kiel “{0}” ĉie", //m
|
||||
"lo2": "ĉi tiu finigos seancon en ĉiuj retumiloj", //m
|
||||
"m1": "bonvenon denove,", // TLNote: "welcome back, USERNAME"
|
||||
"n1": "404 ne trovita ┐( ´ -`)┌",
|
||||
"o1": 'aŭ eble vi ne havas rajton -- provu uzi pasvorton aŭ <a href="' + SR + '/?h">iri hejmen</a>',
|
||||
"p1": "403 ne permesita ~┻━┻",
|
||||
"q1": 'uzu pasvorton aŭ <a href="' + SR + '/?h">iru hejmen</a>',
|
||||
"r1": "hejmen",
|
||||
".s1": "reskani",
|
||||
"t1": "ago", // TLNote: this is the header above the "rescan" buttons
|
||||
"u2": "tempo post lasta skribo (alŝuto / alinomado / ...) je servilo$N( upload / rename / ... )$N$N17d = 17 tagoj$N1h23 = 1 horo 23 minutoj$N4m56 = 4 minutoj 56 sekundoj",
|
||||
"v1": "konekti",
|
||||
"v2": "uzi ĉi tiun servilon kiel loka disko",
|
||||
"w1": "uzi HTTPS-protokolon",
|
||||
"x1": "ŝanĝi pasvorton",
|
||||
"y1": "redakti komunaĵojn", // TLNote: shows the list of folders that the user has decided to share
|
||||
"z1": "malŝlosi ĉi tiun komunaĵon:", // TLNote: the password prompt to see a hidden share
|
||||
"ta1": "entajpu novan pasvorton unue",
|
||||
"ta2": "retajpu por konfirmi:",
|
||||
"ta3": "tajpo-eraro; bonvolu provu denove",
|
||||
"aa1": "aktivaj alŝutoj:",
|
||||
"ab1": "malŝalti no304-on",
|
||||
"ac1": "ŝalti no304-on",
|
||||
"ad1": "no304 malŝaltas ĉiun kaŝmemoradon; provu ĉi tion, se k304 ne riparis la difektojn. Ĉi tiu agordo malŝparas multon da datumtrafiko!",
|
||||
"ae1": "aktivaj elŝutoj:",
|
||||
"af1": "montri lastajn alŝutojn",
|
||||
"ag1": "montri kaŝmemoron de idp",
|
||||
},
|
||||
"fin": {
|
||||
"a1": "päivitä",
|
||||
"b1": "hei sie muukalainen <small>(et ole kirjautunut sisään)</small>",
|
||||
|
@ -806,6 +853,47 @@ var Ls = {
|
|||
"af1": "показать недавние загрузки",
|
||||
"ag1": "показать известных IdP-пользователей",
|
||||
},
|
||||
"tur": {
|
||||
"a1": "yenile",
|
||||
"b1": "N'aber aga <small>(giriş yapmamışsın)</small>",
|
||||
"c1": "çıkış yap",
|
||||
"d1": "yığını yolla",
|
||||
"d2": "tüm aktif iş parçacıklarının durumunu gösterir",
|
||||
"e1": "cfg'yi yenile",
|
||||
"e2": "yapılandırma dosyalarını yenile (hesaplar/hacimler/hacim bayrakları),$Nve tüm e2ds hacimlerini yeniden tarayın$N$Nnot: global ayarlardaki herhangi bir değişiklik$Netkili hale gelmesi için tam bir yeniden başlatma gerektirir",
|
||||
"f1": "göz atabilirsiniz:",
|
||||
"g1": "yükleyebilirsiniz:",
|
||||
"cc1": "diğer şeyler:",
|
||||
"h1": "k304'ü devre dışı bırak",
|
||||
"i1": "k304'ü etkinleştir",
|
||||
"j1": "k304'ü etkinleştirmek, her HTTP 304'te istemcinizin bağlantısını keser, bu da bazı hatalı proxy'lerin takılmasını önleyebilir (sayfaların birdenbire yüklenmesinin durması gibi); <em>ama</em> bu, aynı zamanda genel olarak işleyişi yavaşlatır",
|
||||
"k1": "istemci ayarlarını sıfırla",
|
||||
"l1": "daha fazlası için giriş yap:",
|
||||
"m1": "hoş geldin,",
|
||||
"n1": "404 bulunamadı ┐( ´ -`)┌",
|
||||
"o1": 'ya da erişim iznin yok -- bir şifre dene veya <a href="' + SR + '/?h">ana sayfaya dön</a>',
|
||||
"p1": "403 yasaklandı ~┻━┻",
|
||||
"q1": 'bir şifre kullan veya <a href="' + SR + '/?h">ana sayfaya dön</a>',
|
||||
"r1": "ana sayfaya dön",
|
||||
".s1": "yeniden tara",
|
||||
"t1": "işlem",
|
||||
"u2": "son sunucu yazma zamanı$N( yükleme / yeniden adlandırma / ... )$N$N17d = 17 gün$N1h23 = 1 saat 23 dakika$N4m56 = 4 dakika 56 saniye",
|
||||
"v1": "bağlan",
|
||||
"v2": "bu sunucuyu yerel HDD olarak kullan",
|
||||
"w1": "https'ye geç",
|
||||
"x1": "şifreyi değiştir",
|
||||
"y1": "paylaşılanları düzenle",
|
||||
"z1": "gizli paylaşımın kilidini aç:",
|
||||
"ta1": "ilk önce yeni şifreyi doldur",
|
||||
"ta2": "yeni şifreyi onaylamak için tekrar girin:",
|
||||
"ta3": "bir yazım hatası bulundu; lütfen tekrar deneyin",
|
||||
"aa1": "gelen dosyalar:",
|
||||
"ab1": "no304'ü devre dışı bırak",
|
||||
"ac1": "no304'ü etkinleştir",
|
||||
"ad1": "no304'ü etkinleştirmek, tüm önbelleği devre dışı bırakır; bunu k304 yeterli olmadıysa deneyin. Bu, büyük miktarda ağ trafiği israf edecektir!",
|
||||
"ae1": "aktif indirmeler:",
|
||||
"af1": "son yüklemeleri göster",
|
||||
},
|
||||
};
|
||||
|
||||
if (window.langmod)
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||
<style>ul{padding-left:1.3em}li{margin:.4em 0}.txa{float:right;margin:0 0 0 1em}</style>
|
||||
{{- html_head }}
|
||||
{{ html_head }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -1803,7 +1803,7 @@ function up2k_init(subtle) {
|
|||
while (true) {
|
||||
var now = Date.now(),
|
||||
blocktime = now - r.tact,
|
||||
was_busy = st.is_busy,
|
||||
was_busy = !!st.is_busy,
|
||||
is_busy = !!( // gzip take the wheel
|
||||
st.car < st.files.length ||
|
||||
st.busy.hash.length ||
|
||||
|
@ -3427,6 +3427,7 @@ if (QS('#op_up2k.act'))
|
|||
goto_up2k();
|
||||
|
||||
apply_perms({ "perms": perms, "frand": frand, "u2ts": u2ts });
|
||||
if (ls0)
|
||||
fileman.render();
|
||||
|
||||
|
||||
|
|
|
@ -960,15 +960,95 @@ function f2f(val, nd) {
|
|||
}
|
||||
|
||||
|
||||
var HSZ_U = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
function humansize(b, terse) {
|
||||
var i = 0, u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
while (b >= 1000 && i < u.length - 1) {
|
||||
b /= 1024;
|
||||
i += 1;
|
||||
}
|
||||
var i = 0;
|
||||
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
|
||||
return (f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) +
|
||||
' ' + (terse ? u[i].charAt(0) : u[i]));
|
||||
' ' + (terse ? HSZ_U[i].charAt(0) : HSZ_U[i]));
|
||||
}
|
||||
function humansize_su(b) {
|
||||
var i = 0;
|
||||
while (b >= 1000 && i < 5) { b /= 1024; i += 1; }
|
||||
return [b, HSZ_U[i]];
|
||||
}
|
||||
function humansize_0(b) {
|
||||
return '' + b;
|
||||
}
|
||||
function humansize_1(b) {
|
||||
return ('' + b).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||
}
|
||||
function humansize_2g(b) {
|
||||
var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
|
||||
return [f2f(b, b >= 100 ? 0 : b >= 10 ? 1 : 2) + ' ' + u, u];
|
||||
}
|
||||
function humansize_3g(b) {
|
||||
var z = humansize_su(b), u = z[1].charAt(0); b = z[0];
|
||||
return [f2f(b, b >= 10 ? 0 : 1) + ' ' + u, u];
|
||||
}
|
||||
function humansize_4g(b) {
|
||||
var z = humansize_su(b), u = z[1]; b = z[0];
|
||||
return [parseFloat(b.toFixed(b >= 100 ? 0 : b >= 10 ? 1 : 2)) + ' ' + u, u.charAt(0)];
|
||||
}
|
||||
function humansize_5g(b) {
|
||||
var z = humansize_su(b), u = z[1]; b = z[0];
|
||||
return [parseFloat(b.toFixed(b >= 10 ? 0 : 1)) + ' ' + u, u.charAt(0)];
|
||||
}
|
||||
function humansize_2(b) {
|
||||
return humansize_2g(b)[0];
|
||||
}
|
||||
function humansize_3(b) {
|
||||
return humansize_3g(b)[0];
|
||||
}
|
||||
function humansize_4(b) {
|
||||
return humansize_4g(b)[0];
|
||||
}
|
||||
function humansize_5(b) {
|
||||
return humansize_5g(b)[0];
|
||||
}
|
||||
function humansize_2c(b) {
|
||||
var v = humansize_2g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_3c(b) {
|
||||
var v = humansize_3g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_4c(b) {
|
||||
var v = humansize_4g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_5c(b) {
|
||||
var v = humansize_5g(b);
|
||||
return '<span class="fsz_' + v[1].charAt(0) + '">' + v[0] + '</span>';
|
||||
}
|
||||
function humansize_fuzzy(b) {
|
||||
if (b <= 0) return "yes";
|
||||
if (b <= 80) return "hullkort";
|
||||
if (b <= 368640) return "5¼ DD";
|
||||
if (b <= 1474560) return "save icon";
|
||||
if (b <= 2880000) return "3½ Extended";
|
||||
if (b <= 13107200) return "C90 Tape";
|
||||
if (b <= 21000000) return "Floptical";
|
||||
if (b <= 33554432) return "MPMan F10";
|
||||
if (b <= 50000000) return "creditcardCD";
|
||||
if (b <= 100663296) return "Zipdisk";
|
||||
if (b <= 170000000) return "MD";
|
||||
if (b <= 220200960) return "8cm CD";
|
||||
if (b <= 737280000) return "CD-R";
|
||||
if (b <= 900000000) return "UMD";
|
||||
if (b <= 1300000000) return "GD-ROM";
|
||||
if (b <= 4700000000) return "DVD";
|
||||
if (b <= 9400000000) return "DVD-DL";
|
||||
if (b <= 25025000000) return "BluRei";
|
||||
if (b <= 50050000000) return "BD-DL";
|
||||
return "LTO";
|
||||
}
|
||||
var humansize_fmts = ['0', '1', '2', '2c', '3', '3c', '4', '4c', '5', '5c', 'fuzzy'];
|
||||
window.filesizefun = (function () {
|
||||
var v = sread('fszfmt', humansize_fmts);
|
||||
return window['humansize_' + (v || window.dfszf)] || humansize_1;
|
||||
})();
|
||||
|
||||
|
||||
function humantime(v) {
|
||||
|
@ -2181,6 +2261,19 @@ function bchrome() {
|
|||
}
|
||||
bchrome();
|
||||
|
||||
var XC_CMSG = {
|
||||
502: "bad gateway (server offline)",
|
||||
503: "server offline",
|
||||
504: "gateway timeout (server busy)",
|
||||
529: "gateway timeout (server busy)",
|
||||
520: "unknown error from server",
|
||||
521: "server offline",
|
||||
523: "server offline",
|
||||
522: "proxy timeout (server busy)",
|
||||
524: "proxy timeout (server busy)",
|
||||
598: "proxy timeout (server busy)",
|
||||
599: "proxy timeout (server busy)",
|
||||
};
|
||||
var cf_cha_t = 0;
|
||||
function xhrchk(xhr, prefix, e404, lvl, tag) {
|
||||
if (xhr.status < 400 && xhr.status >= 200)
|
||||
|
@ -2223,5 +2316,8 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
|
|||
document.body.appendChild(fr);
|
||||
}
|
||||
|
||||
if (XC_CMSG[xhr.status] && (errtxt.indexOf('<html') + 1))
|
||||
errtxt = XC_CMSG[xhr.status];
|
||||
|
||||
return fun(0, prefix + xhr.status + ": " + errtxt, tag);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,169 @@
|
|||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0929-2310 `v1.19.15` merry christmas
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #184 add various human-readable formats for filesizes 234eddec
|
||||
* search for files by their identifier ("wark"/checksum) 4e38e408
|
||||
* and those are displayed in file-listings now too 456addf2
|
||||
* PUT-upload with header `Replace` will overwrite any existing files 397ed565
|
||||
* xbu/xau hooks can reject uploads with a custom message df0fa9d1
|
||||
* #855 mDNS options to change the announced http/https port a3d95067
|
||||
* #473 #383 custom favicons per-volume (.ico/png/gif/svg) 470b5048
|
||||
* doesn't seem to work in internet explorer... ah whatever, go next
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #849 create IdP-db for `--idp-store` when necessary 80ca7851
|
||||
* #859 cbz-thumbnailing had an accidental dependency on FFmpeg 983865d9
|
||||
* docs: misleading markdown-expansion example e187df28
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* #851 show a huge warning when copyparty accidentally detects a failing HDD and/or filesystem-corruption during indexing 6912e867 eb5d767b
|
||||
* #870 improved discord video embeds (thx @tsuza!) f0ecb083
|
||||
* #858 prefer reflinks (not hardlinks) in the `-ss` security option 57650a21
|
||||
* improved controlpanel action-buttons layout 9f46e4db
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* includes (a tiny bit of) code written at [koie ramen](https://a.ocv.me/pub/g/2025/09/PXL_20250925_151716836.jpg)
|
||||
* [according to Biltema](https://a.ocv.me/pub/g/2025/09/PXL_20250927_160446367~2.jpg), september is an excellent time to start decorating for xmas
|
||||
|
||||
<img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" /> <img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" /> <img src="https://a.ocv.me/pub/stuff/padoru.gif" alt="padoru" />
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0923-2247 `v1.19.14` Voile, the Magic Library
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #779 add [OPDS](https://opds.io/) support (thx @Scotsguy!) 6dbd9901
|
||||
* copyparty can now serve books for [KOReader](https://koreader.rocks/)
|
||||
* [the mandatory soundtrack](https://www.youtube.com/watch?v=F8Aex6tzH-s)
|
||||
* #786 add Turkish translation (thx @NandeMD!) 549fe33f
|
||||
* #808 support reading config-files in UTF8-BOM 5e4ff90b
|
||||
* make more http-errors return a friendly errortext rather than the scary wall of html 9d066414
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #842 could not navpane into webroot if webroot is unmapped 0941fd4e
|
||||
* upload-resume becomes funky when the OS/network is overloaded to the point where it starts dropping connections left and right -- the issue was reported on discord and I don't have a good way to reproduce it, but these changes may help and/or fix it:
|
||||
* b136a5b0 panic and drop chunk reservations if client or connection glitches out
|
||||
* 38df223b also drop reservations if subchunk logic hits an edgecase
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* [versus.md](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) tweaks:
|
||||
* #840 tooltips in the table headers (thx @guano!) e9ca36fa
|
||||
* #839 sftpgo updates (thx @augustanational!) a053a663
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* this release is identical to v1.19.13 except [the pypi package isn't messed up](https://github.com/9001/copyparty/issues/847) 👉😎👉
|
||||
* as if the 13 wasn't foreshadowing enough
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0921-2211 `v1.19.12` conlangparty
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #787 add Esperanto translation (thx @slashdevslashurandom!) 15d3c2fb
|
||||
* #802 timezone can be specified for the rotf upload rule (thx @Lehmustus!) 1460fe97
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #837 sharing an entire HDD on Windows ([v1.19.9](https://github.com/9001/copyparty/releases/tag/v1.19.9) regression) 6a244320
|
||||
* sharing your whole [【Dドライブ】](https://www.youtube.com/watch?v=BFfYrxm2t58) is once again possible
|
||||
* TLNote: `Dドライブ` means "D:\ drive"
|
||||
* if you can't upgrade, a workaround is global-option `casechk: n`
|
||||
* `/?ls` on an unmapped root didn't give a sensible response; now it should be okay except it won't have a `cfg` field 8f6194fe
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* #836 hide the unpost tab in folders where user has no write-access ca872c40
|
||||
* #835 fix webdep buildscript to avoid an edgecase on some platforms (thx @25huizengek1!) 260da2f4
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* the esperanto translation was the final straw; `copyparty-sfx.py` is now 1 MiB large
|
||||
* `copyparty-en.py` is still a comfy 759 KiB
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0920-1011 `v1.19.11` ftp fix
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #827 ftp on servers with unmapped root broke in v1.19.9 280815f1
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0919-2244 `v1.19.10` ramdisk kinshi
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* prevent uploading into ramdisks by default 59a01221 538a205c
|
||||
* safeguard against misconfigured docker containers, where certain parts of the vfs has not been mapped to actual storage, for example `/w/music` is but `/w/` itself isn't
|
||||
* can be disabled with `wram` (global-option and/or volflag), mainly for ephemeral servers
|
||||
* #799 nixos: groups can be specified (thx @AnyTimeTraveler!) ee5f3190
|
||||
* the logspam from the filesystem indexer can be reduced/disabled 478f1c76
|
||||
* new options `scan-st-r`, `scan-pr-r`, `scan-pr-s`
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #809 medialinks (`#af-badf00d`) would fail on the very first pageload from a new browser 5996a58b
|
||||
* #806 instructions for running on iOS was bad (thx @GhelloZ!) 35326a6f
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* copyparty32.exe is now english-only, to save space 669b1075
|
||||
* version info on startup indicates free-threading or not 65591528
|
||||
* docs: explain the `daw` option better a043d7cf
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0915-0019 `v1.19.9` case-sensitivity, give or take
|
||||
|
||||
## 🧪 new features
|
||||
|
||||
* #781 case-sensitive behavior is now simulated on Windows/Macos/Fat32/NTFS 8b66874b
|
||||
* avoids some of the scary issues associated with case-insensitive filesystems
|
||||
* unfortunately this is expensive and may be **noticeably slower in large folders;** disable the safeguard with `casechk: n` if you know you don't need it
|
||||
* #789 case-insensitive search for unicode filenames/paths (thx @km-clay!) e2aa8fc1 ecd18adc
|
||||
* default-disabled because it is somewhat expensive; enable with global-option `srch-icase`
|
||||
* [CB-1](https://codeberg.org/9001/copyparty/issues/1) add `--qr-stdout` and `--qr-stderr` to show qr-code even with `-q` d7887f3d
|
||||
|
||||
## 🩹 bugfixes
|
||||
|
||||
* #775 the basic-uploader didn't accept empty files 25749b4b
|
||||
* opt-out from index.html with `?v` did not work as documented 3d09bec1
|
||||
* Windows: dedup could get rejected by the filesystem if the origin file had a timestamp from the cambrian era e09f3c9e
|
||||
* webdav would incorrectly return an error for Depth:0 on an unmapped root 3a2381ff
|
||||
* markdown-editor would waste another http roundtrip on certain documents 14b7e514
|
||||
* `--help` didn't render if terminal was non-UTF8 3f454927
|
||||
|
||||
## 🔧 other changes
|
||||
|
||||
* #788 fixed a hotkey typo in the imageviewer (thx @tkroo!) 5c1a43c7
|
||||
* #778 improved polish translation (thx @daimond113!) 52438bcc
|
||||
* #798 debian: fixed an issue in the systemd script (thx @Beethoven-n, and congrats on commit number 4000!) dfd9e007
|
||||
* media-tag `conductor` is no longer mapped to `circle` (album-artist) 9c9e4057
|
||||
* "download-selection-as-zip" now produces a better filename, `sel-FOLDERNAME.zip` instead of `FIRSTFILE.zip` 8f587627
|
||||
* detect and warn if IdP volumes are misconfigured in a particular way 83bd1974
|
||||
|
||||
## 🌠 fun facts
|
||||
|
||||
* the themesong of this release is [KO3 - Give it up?](https://www.youtube.com/watch?v=8w_na7HAppU) because that's what the car mechanic got to enjoy when i forgot to unplug the flashdrive before handing in the shitbox for service
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2025-0907-2300 `v1.19.8` SECURITY: fix single-file shares
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* [general](#general)
|
||||
* [event hooks](#event-hooks) - on writing your own [hooks](../README.md#event-hooks)
|
||||
* [hook effects](#hook-effects) - hooks can cause intentional side-effects
|
||||
* [hook import](#hook-import) - the `I` flag runs the hook inside copyparty
|
||||
* [assumptions](#assumptions)
|
||||
* [mdns](#mdns)
|
||||
* [sfx repack](#sfx-repack) - reduce the size of an sfx by removing features
|
||||
|
@ -165,6 +166,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
|||
| GET | `?ls&dots` | list files/folders at URL as JSON, including dotfiles |
|
||||
| GET | `?ls=t` | list files/folders at URL as plaintext |
|
||||
| GET | `?ls=v` | list files/folders at URL, terminal-formatted |
|
||||
| GET | `?opds` | list files/folders at URL as opds feed, for e-readers |
|
||||
| GET | `?lt` | in listings, use symlink timestamps rather than targets |
|
||||
| GET | `?b` | list files/folders at URL as simplified HTML |
|
||||
| GET | `?tree=.` | list one level of subdirectories inside URL |
|
||||
|
@ -245,6 +247,7 @@ upload modifiers:
|
|||
| `Accept: json` | `want=json` | return upload info as json; same as `?j` |
|
||||
| `Rand: 4` | `rand=4` | generate random filename with 4 characters |
|
||||
| `Life: 30` | `life=30` | delete file after 30 seconds |
|
||||
| `Replace: 1` | `replace` | overwrite file if exists |
|
||||
| `CK: no` | `ck` | disable serverside checksum (maybe faster) |
|
||||
| `CK: md5` | `ck=md5` | return md5 checksum instead of sha512 |
|
||||
| `CK: sha1` | `ck=sha1` | return sha1 checksum |
|
||||
|
@ -253,7 +256,9 @@ upload modifiers:
|
|||
| `CK: b2s` | `ck=b2s` | return blake2s checksum |
|
||||
|
||||
* `life` only has an effect if the volume has a lifetime, and the volume lifetime must be greater than the file's
|
||||
|
||||
* `replace` upload-modifier:
|
||||
* the header `replace: 1` works for both PUT and multipart-post
|
||||
* the url-param `replace` only works for multipart-post
|
||||
* server behavior of `msg` can be reconfigured with `--urlform`
|
||||
|
||||
## admin
|
||||
|
@ -306,6 +311,14 @@ a subset of effect types are available for a subset of hook types,
|
|||
to trigger indexing of files `/foo/1.txt` and `/foo/bar/2.txt`, a hook can `print(json.dumps({"idx":{"vp":["/foo/1.txt","/foo/bar/2.txt"]}}))` (and replace "idx" with "del" to delete instead)
|
||||
* note: paths starting with `/` are absolute URLs, but you can also do `../3.txt` relative to the destination folder of each uploaded file
|
||||
|
||||
## hook import
|
||||
|
||||
the `I` flag runs the hook inside copyparty, which can be very useful and dangerous:
|
||||
|
||||
* around 140x faster because it doesn't need to launch a new subprocess
|
||||
* the hook can intentionally (or accidentally) mess with copyparty's internals
|
||||
* very easy to crash things if not careful
|
||||
|
||||
|
||||
# assumptions
|
||||
|
||||
|
|
|
@ -6,15 +6,15 @@ L: MIT
|
|||
|
||||
https://github.com/pallets/jinja/
|
||||
C: 2007 Pallets
|
||||
L: BSD 3-Clause
|
||||
L: BSD-3-Clause
|
||||
|
||||
https://github.com/pallets/markupsafe/
|
||||
C: 2010 Pallets
|
||||
L: BSD 3-Clause
|
||||
L: BSD-3-Clause
|
||||
|
||||
https://github.com/paulc/dnslib/
|
||||
C: 2010-2017 Paul Chakravarti
|
||||
L: BSD 2-Clause
|
||||
L: BSD-2-Clause
|
||||
|
||||
https://github.com/pydron/ifaddr/
|
||||
C: 2014 Stefan C. Mueller
|
||||
|
@ -36,6 +36,10 @@ https://github.com/ahupp/python-magic/
|
|||
C: 2001-2014 Adam Hupp
|
||||
L: MIT
|
||||
|
||||
https://github.com/fusepy/fusepy
|
||||
C: 2012 Giorgos Verigakis
|
||||
L: ISC
|
||||
|
||||
--- client-side --- software ---
|
||||
|
||||
https://github.com/Daninet/hash-wasm/
|
||||
|
@ -50,6 +54,10 @@ https://github.com/feimosi/baguetteBox.js/
|
|||
C: 2017 Marek Grzybek
|
||||
L: MIT
|
||||
|
||||
https://github.com/cure53/DOMPurify/
|
||||
C: 2025 Cure53 / Mario Heiderich
|
||||
L: Apache-2.0 or MPL-2.0
|
||||
|
||||
https://github.com/markedjs/marked/
|
||||
C: 2018+, MarkedJS
|
||||
C: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
@ -68,8 +76,8 @@ L: MIT
|
|||
|
||||
https://github.com/adobe-fonts/source-code-pro/
|
||||
C: 2010-2019 Adobe
|
||||
L: SIL OFL 1.1
|
||||
L: OFL-1.1
|
||||
|
||||
https://github.com/FortAwesome/Font-Awesome/
|
||||
C: 2022 Fonticons, Inc.
|
||||
L: SIL OFL 1.1
|
||||
L: OFL-1.1
|
||||
|
|
|
@ -64,6 +64,8 @@ if you give it the value `@ASDF` it will try to open a file named ASDF and send
|
|||
|
||||
if the value starts with `%` it will assume a jinja2 template and expand it; the template has access to the `HttpCli` object through a property named `this` as well as everything in `j2a` and the stuff added by `self.j2s`; see [browser.html](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/web/browser.html) for inspiration or look under the hood in [httpcli.py](https://github.com/9001/copyparty/blob/hovudstraum/copyparty/httpcli.py)
|
||||
|
||||
there is also `--html-head-s` and volflag `html_head_s` to add a plain static bit of text, possibly in addition to `--html-head`
|
||||
|
||||
|
||||
# translations
|
||||
|
||||
|
|
|
@ -85,19 +85,34 @@ currently up to date with [awesome-selfhosted](https://github.com/awesome-selfho
|
|||
the table headers in the matrixes below are the different softwares, with a quick review of each software in the next section
|
||||
|
||||
the softwares,
|
||||
* `a` = [copyparty](https://github.com/9001/copyparty)
|
||||
* `b` = [hfs2](https://github.com/rejetto/hfs2/) 🔥
|
||||
* `c` = [hfs3](https://rejetto.com/hfs/)
|
||||
* `d` = [nextcloud](https://github.com/nextcloud/server)
|
||||
* `e` = [seafile](https://github.com/haiwen/seafile)
|
||||
* `f` = [rclone](https://github.com/rclone/rclone), specifically `rclone serve webdav .`
|
||||
* `g` = [dufs](https://github.com/sigoden/dufs)
|
||||
* `h` = [chibisafe](https://github.com/chibisafe/chibisafe)
|
||||
* `i` = [kodbox](https://github.com/kalcaddle/kodbox)
|
||||
* `j` = [filebrowser](https://github.com/filebrowser/filebrowser)
|
||||
* `k` = [filegator](https://github.com/filegator/filegator)
|
||||
* `l` = [sftpgo](https://github.com/drakkan/sftpgo)
|
||||
* `m` = [arozos](https://github.com/tobychui/arozos)
|
||||
|
||||
[a]: https://github.com/9001/copyparty "copyparty"
|
||||
[b]: https://github.com/rejetto/hfs2/ "hfs2"
|
||||
[c]: https://rejetto.com/hfs/ "hfs3"
|
||||
[d]: https://github.com/nextcloud/server "nextcloud"
|
||||
[e]: https://github.com/haiwen/seafile "seafile"
|
||||
[f]: https://github.com/rclone/rclone "rclone"
|
||||
[g]: https://github.com/sigoden/dufs "dufs"
|
||||
[h]: https://github.com/chibisafe/chibisafe "chibisafe"
|
||||
[i]: https://github.com/kalcaddle/kodbox "kodbox"
|
||||
[j]: https://github.com/filebrowser/filebrowser "filebrowser"
|
||||
[k]: https://github.com/filegator/filegator "filegator"
|
||||
[l]: https://github.com/drakkan/sftpgo "sftpgo"
|
||||
[m]: https://github.com/tobychui/arozos "arozos"
|
||||
|
||||
* `a` = [copyparty][a]
|
||||
* `b` = [hfs2][b] 🔥
|
||||
* `c` = [hfs3][c]
|
||||
* `d` = [nextcloud][d]
|
||||
* `e` = [seafile][e]
|
||||
* `f` = [rclone][f], specifically `rclone serve webdav .`
|
||||
* `g` = [dufs][g]
|
||||
* `h` = [chibisafe][h]
|
||||
* `i` = [kodbox][i]
|
||||
* `j` = [filebrowser][j]
|
||||
* `k` = [filegator][k]
|
||||
* `l` = [sftpgo][l]
|
||||
* `m` = [arozos][m]
|
||||
|
||||
some softwares not in the matrixes,
|
||||
* [updog](#updog)
|
||||
|
@ -119,13 +134,13 @@ symbol legend,
|
|||
|
||||
## general
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| intuitive UX | | ╱ | █ | █ | █ | | █ | █ | █ | █ | █ | █ | █ |
|
||||
| config GUI | | █ | █ | █ | █ | | | █ | █ | █ | | █ | █ |
|
||||
| good documentation | | | █ | █ | █ | █ | █ | | | █ | █ | ╱ | ╱ |
|
||||
| runs on iOS | ╱ | | | | | ╱ | | | | | | | |
|
||||
| runs on Android | █ | | █ | | | █ | | | | | | | |
|
||||
| runs on Android | █ | | █ | | | █ | | | | | | █ | |
|
||||
| runs on WinXP | █ | █ | | | | █ | | | | | | | |
|
||||
| runs on Windows | █ | █ | █ | █ | █ | █ | █ | ╱ | █ | █ | █ | █ | ╱ |
|
||||
| runs on Linux | █ | ╱ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||
|
@ -146,7 +161,9 @@ symbol legend,
|
|||
* `b`/hfs2 runs on linux through wine
|
||||
* `f`/rclone must be started with the command `rclone serve webdav .` or similar
|
||||
* `h`/chibisafe has undocumented windows support
|
||||
* `i`/sftpgo must be launched with a command
|
||||
* `l`/sftpgo:
|
||||
* Must be launched with a command
|
||||
* On Termux, just run `pkg in sftpgo`
|
||||
* `m`/arozos has partial windows support
|
||||
|
||||
|
||||
|
@ -154,7 +171,7 @@ symbol legend,
|
|||
|
||||
*the thing that copyparty is actually kinda good at*
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| download folder as zip | █ | █ | █ | █ | ╱ | | █ | | █ | █ | ╱ | █ | ╱ |
|
||||
| download folder as tar | █ | | | | | | | | | | | | |
|
||||
|
@ -218,7 +235,7 @@ symbol legend,
|
|||
|
||||
## protocols and client support
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| serve https | █ | | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||
| serve webdav | █ | | | █ | █ | █ | █ | | █ | | | █ | █ |
|
||||
|
@ -249,7 +266,7 @@ symbol legend,
|
|||
|
||||
## server configuration
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| config from cmd args | █ | | █ | | | █ | █ | | | █ | | ╱ | ╱ |
|
||||
| config files | █ | █ | █ | ╱ | ╱ | █ | | █ | | █ | • | ╱ | ╱ |
|
||||
|
@ -262,7 +279,7 @@ symbol legend,
|
|||
|
||||
* `folder-rproxy` = reverse-proxying without dedicating an entire (sub)domain, using a subfolder instead
|
||||
* `l`/sftpgo:
|
||||
* config: users must be added through gui / api calls
|
||||
* config: user can be added by cmd command in [Portable mode](https://docs.sftpgo.com/2.6/cli/#portable-mode); if not in Portable mode users must be added through gui / api calls
|
||||
* `m`/arozos:
|
||||
* configuration is primarily through GUI
|
||||
* reverse-proxy is not guaranteed to see the correct client IP
|
||||
|
@ -270,7 +287,7 @@ symbol legend,
|
|||
|
||||
## server capabilities
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| accounts | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ | █ |
|
||||
| per-account chroot | | | | | | | | | | | | █ | |
|
||||
|
@ -280,7 +297,7 @@ symbol legend,
|
|||
| per-volume permissions | █ | █ | █ | █ | █ | █ | █ | | █ | █ | ╱ | █ | █ |
|
||||
| per-folder permissions | ╱ | | █ | █ | █ | | █ | | █ | █ | ╱ | █ | █ |
|
||||
| per-file permissions | | | █ | █ | █ | | █ | | █ | | | | █ |
|
||||
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | | █ |
|
||||
| per-file passwords | █ | | | █ | █ | | █ | | █ | | | █ | █ |
|
||||
| unmap subfolders | █ | | █ | | | | █ | | | █ | ╱ | • | |
|
||||
| index.html blocks list | ╱ | | | | | | █ | | | • | | | |
|
||||
| write-only folders | █ | | █ | | █ | | | | | | █ | █ | |
|
||||
|
@ -337,23 +354,23 @@ symbol legend,
|
|||
|
||||
## client features
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ---------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| single-page app | █ | | █ | █ | █ | | | █ | █ | █ | █ | | █ |
|
||||
| themes | █ | █ | █ | █ | | | | | █ | | | | |
|
||||
| directory tree nav | █ | ╱ | | | █ | | | | █ | | ╱ | | |
|
||||
| multi-column sorting | █ | | | | | | | | | | | | |
|
||||
| multi-column sorting | █ | | | | | | | | | | | █ | |
|
||||
| thumbnails | █ | | / | ╱ | ╱ | | | █ | █ | ╱ | | | █ |
|
||||
| ┗ image thumbnails | █ | | / | █ | █ | | | █ | █ | █ | | | █ |
|
||||
| ┗ video thumbnails | █ | | | █ | █ | | | | █ | | | | █ |
|
||||
| ┗ audio spectrograms | █ | | | | | | | | | | | | |
|
||||
| audio player | █ | | ╱ | █ | █ | | | | █ | ╱ | | | █ |
|
||||
| audio player | █ | | ╱ | █ | █ | | | | █ | ╱ | | ╱ | █ |
|
||||
| ┗ gapless playback | █ | | | | | | | | • | | | | |
|
||||
| ┗ audio equalizer | █ | | | | | | | | | | | | |
|
||||
| ┗ waveform seekbar | █ | | | | | | | | | | | | |
|
||||
| ┗ OS integration | █ | | █ | | | | | | | | | | |
|
||||
| ┗ transcode to lossy | █ | | | | | | | | | | | | |
|
||||
| video player | █ | | █ | █ | █ | | | | █ | █ | | | █ |
|
||||
| video player | █ | | █ | █ | █ | | | | █ | █ | | ╱ | █ |
|
||||
| ┗ video transcoding | | | / | | | | | | █ | | | | |
|
||||
| audio BPM detector | █ | | | | | | | | | | | | |
|
||||
| audio key detector | █ | | | | | | | | | | | | |
|
||||
|
@ -366,16 +383,16 @@ symbol legend,
|
|||
| find local file | █ | | | | | | | | | | | | |
|
||||
| undo recent uploads | █ | | | | | | | | | | | | |
|
||||
| create directories | █ | | █ | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
||||
| image viewer | █ | | █ | █ | █ | | | | █ | █ | █ | | █ |
|
||||
| image viewer | █ | | █ | █ | █ | | | | █ | █ | █ | █ | █ |
|
||||
| markdown viewer | █ | | / | | █ | | | | █ | ╱ | ╱ | | █ |
|
||||
| markdown editor | █ | | | | █ | | | | █ | ╱ | ╱ | | █ |
|
||||
| markdown editor | █ | | | | █ | | | | █ | ╱ | ╱ | ╱ | █ |
|
||||
| readme.md in listing | █ | | / | █ | | | | | | | | | |
|
||||
| rename files | █ | █ | █ | █ | █ | ╱ | █ | | █ | █ | █ | █ | █ |
|
||||
| batch rename | █ | | | | | | | | █ | | | | |
|
||||
| cut / paste files | █ | █ | █ | █ | █ | | | | █ | | | | █ |
|
||||
| move files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | | █ |
|
||||
| move files | █ | █ | █ | █ | █ | | █ | | █ | █ | █ | █ | █ |
|
||||
| delete files | █ | █ | █ | █ | █ | ╱ | █ | █ | █ | █ | █ | █ | █ |
|
||||
| copy files | | | / | | █ | | | | █ | █ | █ | | █ |
|
||||
| copy files | | | / | | █ | | | | █ | █ | █ | █ | █ |
|
||||
|
||||
* `single-page app` = multitasking; possible to continue navigating while uploading
|
||||
* `audio player » os-integration` = use the [lockscreen](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) or [media hotkeys](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) to play/pause, prev/next song
|
||||
|
@ -389,11 +406,14 @@ symbol legend,
|
|||
* audio playback does not continue into next song
|
||||
* plaintext viewer/editor
|
||||
* `k`/filegator directory tree is a modal window
|
||||
* `l`/sftpgo remarks:
|
||||
* audio/video playback does not continue into next song/video
|
||||
* plaintext viewer/editor
|
||||
|
||||
|
||||
## integration
|
||||
|
||||
| feature / software | a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
| feature / software |[a]|[b]|[c]|[d]|[e]|[f]|[g]|[h]|[i]|[j]|[k]|[l]|[m]|
|
||||
| ----------------------- | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| OS alert on upload | ╱ | | | | | | | | | ╱ | | ╱ | |
|
||||
| discord | ╱ | | | | | | | | | ╱ | | ╱ | |
|
||||
|
@ -614,10 +634,8 @@ symbol legend,
|
|||
* ⚠️ across the atlantic, copyparty is 2.5x faster
|
||||
* 🔵 sftp uploads are resumable
|
||||
* ⚠️ web UI is very minimal + a bit slow
|
||||
* ⚠️ no thumbnails / image viewer / audio player
|
||||
* ⚠️ basic file manager (no cut/paste/move)
|
||||
* ⚠️ no thumbnails
|
||||
* ⚠️ no filesystem indexing / search
|
||||
* ⚠️ doesn't run on phones, tablets
|
||||
* ⚠️ no zeroconf (mdns/ssdp)
|
||||
* ⚠️ impractical directory URLs
|
||||
* ⚠️ AGPL licensed
|
||||
|
|
|
@ -93,6 +93,7 @@ copyparty = [
|
|||
"web/*.js",
|
||||
"web/*.css",
|
||||
"web/*.html",
|
||||
"web/*.xml",
|
||||
"web/a/*.bat",
|
||||
"web/deps/*.gz",
|
||||
"web/deps/*.woff*",
|
||||
|
|
|
@ -3,7 +3,7 @@ WORKDIR /z
|
|||
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
|
||||
ver_hashwasm=4.12.0 \
|
||||
ver_marked=4.3.0 \
|
||||
ver_dompf=3.2.6 \
|
||||
ver_dompf=3.2.7 \
|
||||
ver_mde=2.18.0 \
|
||||
ver_codemirror=5.65.18 \
|
||||
ver_fontawesome=5.13.0 \
|
||||
|
@ -41,25 +41,25 @@ RUN mkdir -p /z/dist/no-pk \
|
|||
&& (mkdir hash-wasm \
|
||||
&& cd hash-wasm \
|
||||
&& unzip ../hash-wasm.zip) \
|
||||
&& (tar -xf asmcrypto.tgz \
|
||||
&& (tar --no-same-owner -xf asmcrypto.tgz \
|
||||
&& cd asmcrypto.js-$ver_asmcrypto \
|
||||
&& npm install ) \
|
||||
&& (tar -xf marked.tgz \
|
||||
&& (tar --no-same-owner -xf marked.tgz \
|
||||
&& cd marked-$ver_marked \
|
||||
&& npm install \
|
||||
&& npm i grunt uglify-js -g ) \
|
||||
&& (tar -xf codemirror.tgz \
|
||||
&& (tar --no-same-owner -xf codemirror.tgz \
|
||||
&& cd codemirror5-$ver_codemirror \
|
||||
&& npm install ) \
|
||||
&& (tar -xf mde.tgz \
|
||||
&& (tar --no-same-owner -xf mde.tgz \
|
||||
&& cd easy-markdown-editor* \
|
||||
&& npm install \
|
||||
&& npm i gulp-cli -g ) \
|
||||
&& tar -xf dompurify.tgz \
|
||||
&& tar -xf prism.tgz \
|
||||
&& tar -xf fusepy.tgz \
|
||||
&& tar --no-same-owner -xf dompurify.tgz \
|
||||
&& tar --no-same-owner -xf prism.tgz \
|
||||
&& tar --no-same-owner -xf fusepy.tgz \
|
||||
&& unzip fontawesome.zip \
|
||||
&& tar -xf zopfli.tgz
|
||||
&& tar --no-same-owner -xf zopfli.tgz
|
||||
|
||||
|
||||
COPY busy-mp3.sh /z/
|
||||
|
@ -68,7 +68,7 @@ RUN /z/busy-mp3.sh \
|
|||
|
||||
|
||||
# build fonttools (which needs zopfli)
|
||||
RUN tar -xf zopfli.tgz \
|
||||
RUN tar --no-same-owner -xf zopfli.tgz \
|
||||
&& cd zopfli* \
|
||||
&& cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
#!/bin/ash
|
||||
set -ex
|
||||
|
||||
tmv() {
|
||||
touch -r "$1" t
|
||||
mv t "$1"
|
||||
}
|
||||
iawk() {
|
||||
awk "$1" <"$2" >t
|
||||
tmv "$2"
|
||||
}
|
||||
ised() {
|
||||
sed -r "$1" <"$2" >t
|
||||
tmv "$2"
|
||||
}
|
||||
|
||||
# use zlib-ng if available
|
||||
f=/z/base/zlib_ng-0.5.1-cp312-cp312-linux_$(cat /etc/apk/arch).whl
|
||||
[ "$1" != min ] && [ -e $f ] && {
|
||||
|
@ -38,8 +51,13 @@ rm -rf \
|
|||
/tmp/pe-* /z/copyparty-sfx.py \
|
||||
ensurepip pydoc_data turtle.py turtledemo lib2to3
|
||||
|
||||
cd /usr/lib/python3.*/site-packages/copyparty/
|
||||
rm stolen/surrogateescape.py
|
||||
iawk '/^[^ ]/{s=0}/^if not VENDORED:/{s=1}!s' qrkode.py
|
||||
iawk '/^[^ ]/{s=0}/^ DNS_VND = False/{s=1;print" raise"}!s' mdns.py
|
||||
|
||||
# speedhack
|
||||
sed -ri 's/os.environ.get\("PRTY_NO_IMPRESO"\)/"1"/' /usr/lib/python3.*/site-packages/copyparty/util.py
|
||||
ised 's/os.environ.get\("PRTY_NO_IMPRESO"\)/"1"/' util.py
|
||||
|
||||
# drop bytecode
|
||||
find / -xdev -name __pycache__ -print0 | xargs -0 rm -rf
|
||||
|
@ -52,6 +70,12 @@ find -name __pycache__ |
|
|||
grep -E 'ty/web/|/pycpar' |
|
||||
tr '\n' '\0' | xargs -0 rm -rf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
smoketest() {
|
||||
|
||||
# two-for-one:
|
||||
# 1) smoketest copyparty even starts
|
||||
# 2) build any bytecode we missed
|
||||
|
@ -88,5 +112,95 @@ kill $pid; wait $pid
|
|||
# output from -e2d
|
||||
rm -rf .hist /cfg/copyparty
|
||||
|
||||
}
|
||||
|
||||
smoketest
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[ "$1" == min ] && {
|
||||
# shrink amd64 from 45.5 to 33.2 MiB
|
||||
|
||||
# libstdc++ is pulled in by libmpdec++ in libmpdec; keep libmpdec.so
|
||||
cd /usr/lib ; rm -rf \
|
||||
libmpdec++.so* \
|
||||
libncurses* \
|
||||
libpanelw* \
|
||||
libreadline* \
|
||||
libstdc++.so* \
|
||||
--
|
||||
|
||||
cd /usr/lib/python3.*/lib-dynload/ ; rm -rf \
|
||||
*audioop.* \
|
||||
_asyncio.* \
|
||||
_ctypes_test.* \
|
||||
_curses* \
|
||||
_test* \
|
||||
_xx* \
|
||||
ossaudio.* \
|
||||
readline.* \
|
||||
xx* \
|
||||
--
|
||||
|
||||
# keep http/client for u2c
|
||||
cd /usr/lib/python3.*/ ; rm -rf \
|
||||
site-packages/*.dist-info \
|
||||
aifc.py \
|
||||
asyncio \
|
||||
bdb.py \
|
||||
cgi.py \
|
||||
config-3.*/Makefile \
|
||||
ctypes/macholib \
|
||||
dbm \
|
||||
difflib.py \
|
||||
doctest.py \
|
||||
email/_header_value_parser.py \
|
||||
html \
|
||||
http/cookiejar.* \
|
||||
http/server.* \
|
||||
imaplib.py \
|
||||
importlib/resources \
|
||||
mailbox.py \
|
||||
nntplib.py \
|
||||
pickletools.py \
|
||||
pydoc.py \
|
||||
smtplib.py \
|
||||
statistics.py \
|
||||
tomllib \
|
||||
unittest \
|
||||
urllib/request.* \
|
||||
venv \
|
||||
wsgiref \
|
||||
xml/dom \
|
||||
xml/sax \
|
||||
xmlrpc \
|
||||
--
|
||||
|
||||
set +x
|
||||
find -iname '*.pyc' |
|
||||
grep -viE 'tftpy' |
|
||||
while IFS= read -r x; do
|
||||
y="$(printf '%s\n' "$x" | sed -r 's`/__pycache__/([^/]+)\.cpython-312\.pyc$`/\1.py`')"
|
||||
[ -e "$y" ] || continue
|
||||
[ "$y" = "$x" ] && continue
|
||||
rm "$y"
|
||||
mv "$x" "${y}c"
|
||||
done
|
||||
find -iname __pycache__ -print0 | xargs -0 rm -rf --
|
||||
rm -rf /a
|
||||
set -x
|
||||
|
||||
smoketest
|
||||
|
||||
# printf '%s\n' 'FROM localhost/copyparty-min-amd64' 'COPY a /' 'RUN /bin/ash /a' >Dockerfile
|
||||
# podman rmi localhost/m2 ; podman build --squash-all -t m2 . && podman images && podman run --rm -it localhost/m2 --exit=idx && podman images
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# goodbye
|
||||
exec rm innvikler.sh
|
||||
|
|
|
@ -16,6 +16,8 @@ lics = [
|
|||
"MIT License",
|
||||
"BSD 2-Clause License",
|
||||
"BSD 3-Clause License",
|
||||
"ISC License",
|
||||
"Apache License v2.0",
|
||||
"SIL Open Font License v1.1",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,39 +1,3 @@
|
|||
CERNZOYR
|
||||
Crezvffvba gb hfr, pbcl, zbqvsl, naq/be qvfgevohgr guvf fbsgjner sbe nal checbfr jvgu be jvgubhg srr vf urerol tenagrq, cebivqrq gung gur nobir pbclevtug abgvpr naq guvf crezvffvba abgvpr nccrne va nyy pbcvrf.
|
||||
|
||||
Gur tbnyf bs gur Bcra Sbag Yvprafr (BSY) ner gb fgvzhyngr jbeyqjvqr qrirybczrag bs pbyynobengvir sbag cebwrpgf, gb fhccbeg gur sbag perngvba rssbegf bs npnqrzvp naq yvathvfgvp pbzzhavgvrf, naq gb cebivqr n serr naq bcra senzrjbex va juvpu sbagf znl or funerq naq vzcebirq va cnegarefuvc jvgu bguref.
|
||||
|
||||
Gur BSY nyybjf gur yvprafrq sbagf gb or hfrq, fghqvrq, zbqvsvrq naq erqvfgevohgrq serryl nf ybat nf gurl ner abg fbyq ol gurzfryirf. Gur sbagf, vapyhqvat nal qrevingvir jbexf, pna or ohaqyrq, rzorqqrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner cebivqrq gung nal erfreirq anzrf ner abg hfrq ol qrevingvir jbexf. Gur sbagf naq qrevingvirf, ubjrire, pnaabg or eryrnfrq haqre nal bgure glcr bs yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur sbagf be gurve qrevingvirf.
|
||||
|
||||
QRSVAVGVBAF
|
||||
|
||||
"Sbag Fbsgjner" ersref gb gur frg bs svyrf eryrnfrq ol gur Pbclevtug Ubyqre(f) haqre guvf yvprafr naq pyrneyl znexrq nf fhpu. Guvf znl vapyhqr fbhepr svyrf, ohvyq fpevcgf naq qbphzragngvba.
|
||||
|
||||
"Erfreirq Sbag Anzr" ersref gb nal anzrf fcrpvsvrq nf fhpu nsgre gur pbclevtug fgngrzrag(f).
|
||||
|
||||
"Bevtvany Irefvba" ersref gb gur pbyyrpgvba bs Sbag Fbsgjner pbzcbaragf nf qvfgevohgrq ol gur Pbclevtug Ubyqre(f).
|
||||
|
||||
"Zbqvsvrq Irefvba" ersref gb nal qrevingvir znqr ol nqqvat gb, qryrgvat, be fhofgvghgvat - va cneg be va jubyr - nal bs gur pbzcbaragf bs gur Bevtvany Irefvba, ol punatvat sbezngf be ol cbegvat gur Sbag Fbsgjner gb n arj raivebazrag.
|
||||
|
||||
"Nhgube" ersref gb nal qrfvtare, ratvarre, cebtenzzre, grpuavpny jevgre be bgure crefba jub pbagevohgrq gb gur Sbag Fbsgjner.
|
||||
|
||||
CREZVFFVBA & PBAQVGVBAF
|
||||
|
||||
Crezvffvba vf urerol tenagrq, serr bs punetr, gb nal crefba bognvavat n pbcl bs gur Sbag Fbsgjner, gb hfr, fghql, pbcl, zretr, rzorq, zbqvsl, erqvfgevohgr, naq fryy zbqvsvrq naq hazbqvsvrq pbcvrf bs gur Sbag Fbsgjner, fhowrpg gb gur sbyybjvat pbaqvgvbaf:
|
||||
|
||||
1) Arvgure gur Sbag Fbsgjner abe nal bs vgf vaqvivqhny pbzcbaragf, va Bevtvany be Zbqvsvrq Irefvbaf, znl or fbyq ol vgfrys.
|
||||
|
||||
2) Bevtvany be Zbqvsvrq Irefvbaf bs gur Sbag Fbsgjner znl or ohaqyrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner, cebivqrq gung rnpu pbcl pbagnvaf gur nobir pbclevtug abgvpr naq guvf yvprafr. Gurfr pna or vapyhqrq rvgure nf fgnaq-nybar grkg svyrf, uhzna-ernqnoyr urnqref be va gur nccebcevngr znpuvar-ernqnoyr zrgnqngn svryqf jvguva grkg be ovanel svyrf nf ybat nf gubfr svryqf pna or rnfvyl ivrjrq ol gur hfre.
|
||||
|
||||
3) Ab Zbqvsvrq Irefvba bs gur Sbag Fbsgjner znl hfr gur Erfreirq Sbag Anzr(f) hayrff rkcyvpvg jevggra crezvffvba vf tenagrq ol gur pbeerfcbaqvat Pbclevtug Ubyqre. Guvf erfgevpgvba bayl nccyvrf gb gur cevznel sbag anzr nf cerfragrq gb gur hfref.
|
||||
|
||||
4) Gur anzr(f) bs gur Pbclevtug Ubyqre(f) be gur Nhgube(f) bs gur Sbag Fbsgjner funyy abg or hfrq gb cebzbgr, raqbefr be nqiregvfr nal Zbqvsvrq Irefvba, rkprcg gb npxabjyrqtr gur pbagevohgvba(f) bs gur Pbclevtug Ubyqre(f) naq gur Nhgube(f) be jvgu gurve rkcyvpvg jevggra crezvffvba.
|
||||
|
||||
5) Gur Sbag Fbsgjner, zbqvsvrq be hazbqvsvrq, va cneg be va jubyr, zhfg or qvfgevohgrq ragveryl haqre guvf yvprafr, naq zhfg abg or qvfgevohgrq haqre nal bgure yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur Sbag Fbsgjner.
|
||||
|
||||
GREZVANGVBA
|
||||
|
||||
Guvf yvprafr orpbzrf ahyy naq ibvq vs nal bs gur nobir pbaqvgvbaf ner abg zrg.
|
||||
|
||||
QVFPYNVZRE
|
||||
|
||||
GUR SBAG FBSGJNER VF CEBIVQRQ "NF VF", JVGUBHG JNEENAGL BS NAL XVAQ, RKCERFF BE VZCYVRQ, VAPYHQVAT OHG ABG YVZVGRQ GB NAL JNEENAGVRF BS ZREPUNAGNOVYVGL, SVGARFF SBE N CNEGVPHYNE CHECBFR NAQ ABAVASEVATRZRAG BS PBCLEVTUG, CNGRAG, GENQRZNEX, BE BGURE EVTUG. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE OR YVNOYR SBE NAL PYNVZ, QNZNTRF BE BGURE YVNOVYVGL, VAPYHQVAT NAL TRARENY, FCRPVNY, VAQVERPG, VAPVQRAGNY, BE PBAFRDHRAGVNY QNZNTRF, JURGURE VA NA NPGVBA BS PBAGENPG, GBEG BE BGUREJVFR, NEVFVAT SEBZ, BHG BS GUR HFR BE VANOVYVGL GB HFR GUR SBAG FBSGJNER BE SEBZ BGURE QRNYVATF VA GUR SBAG FBSGJNER.
|
||||
GUR FBSGJNER VF CEBIVQRQ "NF VF" NAQ VFP QVFPYNVZF NYY JNEENAGVRF JVGU ERTNEQ GB GUVF FBSGJNER VAPYHQVAT NYY VZCYVRQ JNEENAGVRF BS ZREPUNAGNOVYVGL NAQ SVGARFF. VA AB RIRAG FUNYY VFP OR YVNOYR SBE NAL FCRPVNY, QVERPG, VAQVERPG, BE PBAFRDHRAGVNY QNZNTRF BE NAL QNZNTRF JUNGFBRIRE ERFHYGVAT SEBZ YBFF BS HFR, QNGN BE CEBSVGF, JURGURE VA NA NPGVBA BS PBAGENPG, ARTYVTRAPR BE BGURE GBEGVBHF NPGVBA, NEVFVAT BHG BS BE VA PBAARPGVBA JVGU GUR HFR BE CRESBEZNAPR BS GUVF FBSGJNER.
|
53
scripts/lics/5.r13
Normal file
53
scripts/lics/5.r13
Normal file
|
@ -0,0 +1,53 @@
|
|||
Ncnpur Yvprafr
|
||||
Irefvba 2.0, Wnahnel 2004
|
||||
uggc://jjj.ncnpur.bet/yvprafrf/
|
||||
|
||||
GREZF NAQ PBAQVGVBAF SBE HFR, ERCEBQHPGVBA, NAQ QVFGEVOHGVBA
|
||||
|
||||
1. Qrsvavgvbaf.
|
||||
|
||||
"Yvprafr" funyy zrna gur grezf naq pbaqvgvbaf sbe hfr, ercebqhpgvba, naq qvfgevohgvba nf qrsvarq ol Frpgvbaf 1 guebhtu 9 bs guvf qbphzrag.
|
||||
|
||||
"Yvprafbe" funyy zrna gur pbclevtug bjare be ragvgl nhgubevmrq ol gur pbclevtug bjare gung vf tenagvat gur Yvprafr.
|
||||
|
||||
"Yrtny Ragvgl" funyy zrna gur havba bs gur npgvat ragvgl naq nyy bgure ragvgvrf gung pbageby, ner pbagebyyrq ol, be ner haqre pbzzba pbageby jvgu gung ragvgl. Sbe gur checbfrf bs guvf qrsvavgvba, "pbageby" zrnaf (v) gur cbjre, qverpg be vaqverpg, gb pnhfr gur qverpgvba be znantrzrag bs fhpu ragvgl, jurgure ol pbagenpg be bgurejvfr, be (vv) bjarefuvc bs svsgl creprag (50%) be zber bs gur bhgfgnaqvat funerf, be (vvv) orarsvpvny bjarefuvc bs fhpu ragvgl.
|
||||
|
||||
"Lbh" (be "Lbhe") funyy zrna na vaqvivqhny be Yrtny Ragvgl rkrepvfvat crezvffvbaf tenagrq ol guvf Yvprafr.
|
||||
|
||||
"Fbhepr" sbez funyy zrna gur cersreerq sbez sbe znxvat zbqvsvpngvbaf, vapyhqvat ohg abg yvzvgrq gb fbsgjner fbhepr pbqr, qbphzragngvba fbhepr, naq pbasvthengvba svyrf.
|
||||
|
||||
"Bowrpg" sbez funyy zrna nal sbez erfhygvat sebz zrpunavpny genafsbezngvba be genafyngvba bs n Fbhepr sbez, vapyhqvat ohg abg yvzvgrq gb pbzcvyrq bowrpg pbqr, trarengrq qbphzragngvba, naq pbairefvbaf gb bgure zrqvn glcrf.
|
||||
|
||||
"Jbex" funyy zrna gur jbex bs nhgubefuvc, jurgure va Fbhepr be Bowrpg sbez, znqr ninvynoyr haqre gur Yvprafr, nf vaqvpngrq ol n pbclevtug abgvpr gung vf vapyhqrq va be nggnpurq gb gur jbex (na rknzcyr vf cebivqrq va gur Nccraqvk orybj).
|
||||
|
||||
"Qrevingvir Jbexf" funyy zrna nal jbex, jurgure va Fbhepr be Bowrpg sbez, gung vf onfrq ba (be qrevirq sebz) gur Jbex naq sbe juvpu gur rqvgbevny erivfvbaf, naabgngvbaf, rynobengvbaf, be bgure zbqvsvpngvbaf ercerfrag, nf n jubyr, na bevtvany jbex bs nhgubefuvc. Sbe gur checbfrf bs guvf Yvprafr, Qrevingvir Jbexf funyy abg vapyhqr jbexf gung erznva frcnenoyr sebz, be zreryl yvax (be ovaq ol anzr) gb gur vagresnprf bs, gur Jbex naq Qrevingvir Jbexf gurerbs.
|
||||
|
||||
"Pbagevohgvba" funyy zrna nal jbex bs nhgubefuvc, vapyhqvat gur bevtvany irefvba bs gur Jbex naq nal zbqvsvpngvbaf be nqqvgvbaf gb gung Jbex be Qrevingvir Jbexf gurerbs, gung vf vagragvbanyyl fhozvggrq gb Yvprafbe sbe vapyhfvba va gur Jbex ol gur pbclevtug bjare be ol na vaqvivqhny be Yrtny Ragvgl nhgubevmrq gb fhozvg ba orunys bs gur pbclevtug bjare. Sbe gur checbfrf bs guvf qrsvavgvba, "fhozvggrq" zrnaf nal sbez bs ryrpgebavp, ireony, be jevggra pbzzhavpngvba frag gb gur Yvprafbe be vgf ercerfragngvirf, vapyhqvat ohg abg yvzvgrq gb pbzzhavpngvba ba ryrpgebavp znvyvat yvfgf, fbhepr pbqr pbageby flfgrzf, naq vffhr genpxvat flfgrzf gung ner znantrq ol, be ba orunys bs, gur Yvprafbe sbe gur checbfr bs qvfphffvat naq vzcebivat gur Jbex, ohg rkpyhqvat pbzzhavpngvba gung vf pbafcvphbhfyl znexrq be bgurejvfr qrfvtangrq va jevgvat ol gur pbclevtug bjare nf "Abg n Pbagevohgvba."
|
||||
|
||||
"Pbagevohgbe" funyy zrna Yvprafbe naq nal vaqvivqhny be Yrtny Ragvgl ba orunys bs jubz n Pbagevohgvba unf orra erprvirq ol Yvprafbe naq fhofrdhragyl vapbecbengrq jvguva gur Jbex.
|
||||
|
||||
2. Tenag bs Pbclevtug Yvprafr. Fhowrpg gb gur grezf naq pbaqvgvbaf bs guvf Yvprafr, rnpu Pbagevohgbe urerol tenagf gb Lbh n crecrghny, jbeyqjvqr, aba-rkpyhfvir, ab-punetr, eblnygl-serr, veeribpnoyr pbclevtug yvprafr gb ercebqhpr, cercner Qrevingvir Jbexf bs, choyvpyl qvfcynl, choyvpyl cresbez, fhoyvprafr, naq qvfgevohgr gur Jbex naq fhpu Qrevingvir Jbexf va Fbhepr be Bowrpg sbez.
|
||||
|
||||
3. Tenag bs Cngrag Yvprafr. Fhowrpg gb gur grezf naq pbaqvgvbaf bs guvf Yvprafr, rnpu Pbagevohgbe urerol tenagf gb Lbh n crecrghny, jbeyqjvqr, aba-rkpyhfvir, ab-punetr, eblnygl-serr, veeribpnoyr (rkprcg nf fgngrq va guvf frpgvba) cngrag yvprafr gb znxr, unir znqr, hfr, bssre gb fryy, fryy, vzcbeg, naq bgurejvfr genafsre gur Jbex, jurer fhpu yvprafr nccyvrf bayl gb gubfr cngrag pynvzf yvprafnoyr ol fhpu Pbagevohgbe gung ner arprffnevyl vasevatrq ol gurve Pbagevohgvba(f) nybar be ol pbzovangvba bs gurve Pbagevohgvba(f) jvgu gur Jbex gb juvpu fhpu Pbagevohgvba(f) jnf fhozvggrq. Vs Lbh vafgvghgr cngrag yvgvtngvba ntnvafg nal ragvgl (vapyhqvat n pebff-pynvz be pbhagrepynvz va n ynjfhvg) nyyrtvat gung gur Jbex be n Pbagevohgvba vapbecbengrq jvguva gur Jbex pbafgvghgrf qverpg be pbagevohgbel cngrag vasevatrzrag, gura nal cngrag yvprafrf tenagrq gb Lbh haqre guvf Yvprafr sbe gung Jbex funyy grezvangr nf bs gur qngr fhpu yvgvtngvba vf svyrq.
|
||||
|
||||
4. Erqvfgevohgvba. Lbh znl ercebqhpr naq qvfgevohgr pbcvrf bs gur Jbex be Qrevingvir Jbexf gurerbs va nal zrqvhz, jvgu be jvgubhg zbqvsvpngvbaf, naq va Fbhepr be Bowrpg sbez, cebivqrq gung Lbh zrrg gur sbyybjvat pbaqvgvbaf:
|
||||
|
||||
(n) Lbh zhfg tvir nal bgure erpvcvragf bs gur Jbex be Qrevingvir Jbexf n pbcl bs guvf Yvprafr; naq
|
||||
|
||||
(o) Lbh zhfg pnhfr nal zbqvsvrq svyrf gb pneel cebzvarag abgvprf fgngvat gung Lbh punatrq gur svyrf; naq
|
||||
|
||||
(p) Lbh zhfg ergnva, va gur Fbhepr sbez bs nal Qrevingvir Jbexf gung Lbh qvfgevohgr, nyy pbclevtug, cngrag, genqrznex, naq nggevohgvba abgvprf sebz gur Fbhepr sbez bs gur Jbex, rkpyhqvat gubfr abgvprf gung qb abg cregnva gb nal cneg bs gur Qrevingvir Jbexf; naq
|
||||
|
||||
(q) Vs gur Jbex vapyhqrf n "ABGVPR" grkg svyr nf cneg bs vgf qvfgevohgvba, gura nal Qrevingvir Jbexf gung Lbh qvfgevohgr zhfg vapyhqr n ernqnoyr pbcl bs gur nggevohgvba abgvprf pbagnvarq jvguva fhpu ABGVPR svyr, rkpyhqvat gubfr abgvprf gung qb abg cregnva gb nal cneg bs gur Qrevingvir Jbexf, va ng yrnfg bar bs gur sbyybjvat cynprf: jvguva n ABGVPR grkg svyr qvfgevohgrq nf cneg bs gur Qrevingvir Jbexf; jvguva gur Fbhepr sbez be qbphzragngvba, vs cebivqrq nybat jvgu gur Qrevingvir Jbexf; be, jvguva n qvfcynl trarengrq ol gur Qrevingvir Jbexf, vs naq jurerire fhpu guveq-cnegl abgvprf abeznyyl nccrne. Gur pbagragf bs gur ABGVPR svyr ner sbe vasbezngvbany checbfrf bayl naq qb abg zbqvsl gur Yvprafr. Lbh znl nqq Lbhe bja nggevohgvba abgvprf jvguva Qrevingvir Jbexf gung Lbh qvfgevohgr, nybatfvqr be nf na nqqraqhz gb gur ABGVPR grkg sebz gur Jbex, cebivqrq gung fhpu nqqvgvbany nggevohgvba abgvprf pnaabg or pbafgehrq nf zbqvslvat gur Yvprafr.
|
||||
|
||||
Lbh znl nqq Lbhe bja pbclevtug fgngrzrag gb Lbhe zbqvsvpngvbaf naq znl cebivqr nqqvgvbany be qvssrerag yvprafr grezf naq pbaqvgvbaf sbe hfr, ercebqhpgvba, be qvfgevohgvba bs Lbhe zbqvsvpngvbaf, be sbe nal fhpu Qrevingvir Jbexf nf n jubyr, cebivqrq Lbhe hfr, ercebqhpgvba, naq qvfgevohgvba bs gur Jbex bgurejvfr pbzcyvrf jvgu gur pbaqvgvbaf fgngrq va guvf Yvprafr.
|
||||
|
||||
5. Fhozvffvba bs Pbagevohgvbaf. Hayrff Lbh rkcyvpvgyl fgngr bgurejvfr, nal Pbagevohgvba vagragvbanyyl fhozvggrq sbe vapyhfvba va gur Jbex ol Lbh gb gur Yvprafbe funyy or haqre gur grezf naq pbaqvgvbaf bs guvf Yvprafr, jvgubhg nal nqqvgvbany grezf be pbaqvgvbaf. Abgjvgufgnaqvat gur nobir, abguvat urerva funyy fhcrefrqr be zbqvsl gur grezf bs nal frcnengr yvprafr nterrzrag lbh znl unir rkrphgrq jvgu Yvprafbe ertneqvat fhpu Pbagevohgvbaf.
|
||||
|
||||
6. Genqrznexf. Guvf Yvprafr qbrf abg tenag crezvffvba gb hfr gur genqr anzrf, genqrznexf, freivpr znexf, be cebqhpg anzrf bs gur Yvprafbe, rkprcg nf erdhverq sbe ernfbanoyr naq phfgbznel hfr va qrfpevovat gur bevtva bs gur Jbex naq ercebqhpvat gur pbagrag bs gur ABGVPR svyr.
|
||||
|
||||
7. Qvfpynvzre bs Jneenagl. Hayrff erdhverq ol nccyvpnoyr ynj be nterrq gb va jevgvat, Yvprafbe cebivqrf gur Jbex (naq rnpu Pbagevohgbe cebivqrf vgf Pbagevohgvbaf) ba na "NF VF" ONFVF, JVGUBHG JNEENAGVRF BE PBAQVGVBAF BS NAL XVAQ, rvgure rkcerff be vzcyvrq, vapyhqvat, jvgubhg yvzvgngvba, nal jneenagvrf be pbaqvgvbaf bs GVGYR, ABA-VASEVATRZRAG, ZREPUNAGNOVYVGL, be SVGARFF SBE N CNEGVPHYNE CHECBFR. Lbh ner fbyryl erfcbafvoyr sbe qrgrezvavat gur nccebcevngrarff bs hfvat be erqvfgevohgvat gur Jbex naq nffhzr nal evfxf nffbpvngrq jvgu Lbhe rkrepvfr bs crezvffvbaf haqre guvf Yvprafr.
|
||||
|
||||
8. Yvzvgngvba bs Yvnovyvgl. Va ab rirag naq haqre ab yrtny gurbel, jurgure va gbeg (vapyhqvat artyvtrapr), pbagenpg, be bgurejvfr, hayrff erdhverq ol nccyvpnoyr ynj (fhpu nf qryvorengr naq tebffyl artyvtrag npgf) be nterrq gb va jevgvat, funyy nal Pbagevohgbe or yvnoyr gb Lbh sbe qnzntrf, vapyhqvat nal qverpg, vaqverpg, fcrpvny, vapvqragny, be pbafrdhragvny qnzntrf bs nal punenpgre nevfvat nf n erfhyg bs guvf Yvprafr be bhg bs gur hfr be vanovyvgl gb hfr gur Jbex (vapyhqvat ohg abg yvzvgrq gb qnzntrf sbe ybff bs tbbqjvyy, jbex fgbccntr, pbzchgre snvyher be znyshapgvba, be nal naq nyy bgure pbzzrepvny qnzntrf be ybffrf), rira vs fhpu Pbagevohgbe unf orra nqivfrq bs gur cbffvovyvgl bs fhpu qnzntrf.
|
||||
|
||||
9. Npprcgvat Jneenagl be Nqqvgvbany Yvnovyvgl. Juvyr erqvfgevohgvat gur Jbex be Qrevingvir Jbexf gurerbs, Lbh znl pubbfr gb bssre, naq punetr n srr sbe, npprcgnapr bs fhccbeg, jneenagl, vaqrzavgl, be bgure yvnovyvgl boyvtngvbaf naq/be evtugf pbafvfgrag jvgu guvf Yvprafr. Ubjrire, va npprcgvat fhpu boyvtngvbaf, Lbh znl npg bayl ba Lbhe bja orunys naq ba Lbhe fbyr erfcbafvovyvgl, abg ba orunys bs nal bgure Pbagevohgbe, naq bayl vs Lbh nterr gb vaqrzavsl, qrsraq, naq ubyq rnpu Pbagevohgbe unezyrff sbe nal yvnovyvgl vapheerq ol, be pynvzf nffregrq ntnvafg, fhpu Pbagevohgbe ol ernfba bs lbhe npprcgvat nal fhpu jneenagl be nqqvgvbany yvnovyvgl.
|
39
scripts/lics/6.r13
Normal file
39
scripts/lics/6.r13
Normal file
|
@ -0,0 +1,39 @@
|
|||
CERNZOYR
|
||||
|
||||
Gur tbnyf bs gur Bcra Sbag Yvprafr (BSY) ner gb fgvzhyngr jbeyqjvqr qrirybczrag bs pbyynobengvir sbag cebwrpgf, gb fhccbeg gur sbag perngvba rssbegf bs npnqrzvp naq yvathvfgvp pbzzhavgvrf, naq gb cebivqr n serr naq bcra senzrjbex va juvpu sbagf znl or funerq naq vzcebirq va cnegarefuvc jvgu bguref.
|
||||
|
||||
Gur BSY nyybjf gur yvprafrq sbagf gb or hfrq, fghqvrq, zbqvsvrq naq erqvfgevohgrq serryl nf ybat nf gurl ner abg fbyq ol gurzfryirf. Gur sbagf, vapyhqvat nal qrevingvir jbexf, pna or ohaqyrq, rzorqqrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner cebivqrq gung nal erfreirq anzrf ner abg hfrq ol qrevingvir jbexf. Gur sbagf naq qrevingvirf, ubjrire, pnaabg or eryrnfrq haqre nal bgure glcr bs yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur sbagf be gurve qrevingvirf.
|
||||
|
||||
QRSVAVGVBAF
|
||||
|
||||
"Sbag Fbsgjner" ersref gb gur frg bs svyrf eryrnfrq ol gur Pbclevtug Ubyqre(f) haqre guvf yvprafr naq pyrneyl znexrq nf fhpu. Guvf znl vapyhqr fbhepr svyrf, ohvyq fpevcgf naq qbphzragngvba.
|
||||
|
||||
"Erfreirq Sbag Anzr" ersref gb nal anzrf fcrpvsvrq nf fhpu nsgre gur pbclevtug fgngrzrag(f).
|
||||
|
||||
"Bevtvany Irefvba" ersref gb gur pbyyrpgvba bs Sbag Fbsgjner pbzcbaragf nf qvfgevohgrq ol gur Pbclevtug Ubyqre(f).
|
||||
|
||||
"Zbqvsvrq Irefvba" ersref gb nal qrevingvir znqr ol nqqvat gb, qryrgvat, be fhofgvghgvat - va cneg be va jubyr - nal bs gur pbzcbaragf bs gur Bevtvany Irefvba, ol punatvat sbezngf be ol cbegvat gur Sbag Fbsgjner gb n arj raivebazrag.
|
||||
|
||||
"Nhgube" ersref gb nal qrfvtare, ratvarre, cebtenzzre, grpuavpny jevgre be bgure crefba jub pbagevohgrq gb gur Sbag Fbsgjner.
|
||||
|
||||
CREZVFFVBA & PBAQVGVBAF
|
||||
|
||||
Crezvffvba vf urerol tenagrq, serr bs punetr, gb nal crefba bognvavat n pbcl bs gur Sbag Fbsgjner, gb hfr, fghql, pbcl, zretr, rzorq, zbqvsl, erqvfgevohgr, naq fryy zbqvsvrq naq hazbqvsvrq pbcvrf bs gur Sbag Fbsgjner, fhowrpg gb gur sbyybjvat pbaqvgvbaf:
|
||||
|
||||
1) Arvgure gur Sbag Fbsgjner abe nal bs vgf vaqvivqhny pbzcbaragf, va Bevtvany be Zbqvsvrq Irefvbaf, znl or fbyq ol vgfrys.
|
||||
|
||||
2) Bevtvany be Zbqvsvrq Irefvbaf bs gur Sbag Fbsgjner znl or ohaqyrq, erqvfgevohgrq naq/be fbyq jvgu nal fbsgjner, cebivqrq gung rnpu pbcl pbagnvaf gur nobir pbclevtug abgvpr naq guvf yvprafr. Gurfr pna or vapyhqrq rvgure nf fgnaq-nybar grkg svyrf, uhzna-ernqnoyr urnqref be va gur nccebcevngr znpuvar-ernqnoyr zrgnqngn svryqf jvguva grkg be ovanel svyrf nf ybat nf gubfr svryqf pna or rnfvyl ivrjrq ol gur hfre.
|
||||
|
||||
3) Ab Zbqvsvrq Irefvba bs gur Sbag Fbsgjner znl hfr gur Erfreirq Sbag Anzr(f) hayrff rkcyvpvg jevggra crezvffvba vf tenagrq ol gur pbeerfcbaqvat Pbclevtug Ubyqre. Guvf erfgevpgvba bayl nccyvrf gb gur cevznel sbag anzr nf cerfragrq gb gur hfref.
|
||||
|
||||
4) Gur anzr(f) bs gur Pbclevtug Ubyqre(f) be gur Nhgube(f) bs gur Sbag Fbsgjner funyy abg or hfrq gb cebzbgr, raqbefr be nqiregvfr nal Zbqvsvrq Irefvba, rkprcg gb npxabjyrqtr gur pbagevohgvba(f) bs gur Pbclevtug Ubyqre(f) naq gur Nhgube(f) be jvgu gurve rkcyvpvg jevggra crezvffvba.
|
||||
|
||||
5) Gur Sbag Fbsgjner, zbqvsvrq be hazbqvsvrq, va cneg be va jubyr, zhfg or qvfgevohgrq ragveryl haqre guvf yvprafr, naq zhfg abg or qvfgevohgrq haqre nal bgure yvprafr. Gur erdhverzrag sbe sbagf gb erznva haqre guvf yvprafr qbrf abg nccyl gb nal qbphzrag perngrq hfvat gur Sbag Fbsgjner.
|
||||
|
||||
GREZVANGVBA
|
||||
|
||||
Guvf yvprafr orpbzrf ahyy naq ibvq vs nal bs gur nobir pbaqvgvbaf ner abg zrg.
|
||||
|
||||
QVFPYNVZRE
|
||||
|
||||
GUR SBAG FBSGJNER VF CEBIVQRQ "NF VF", JVGUBHG JNEENAGL BS NAL XVAQ, RKCERFF BE VZCYVRQ, VAPYHQVAT OHG ABG YVZVGRQ GB NAL JNEENAGVRF BS ZREPUNAGNOVYVGL, SVGARFF SBE N CNEGVPHYNE CHECBFR NAQ ABAVASEVATRZRAG BS PBCLEVTUG, CNGRAG, GENQRZNEX, BE BGURE EVTUG. VA AB RIRAG FUNYY GUR PBCLEVTUG UBYQRE OR YVNOYR SBE NAL PYNVZ, QNZNTRF BE BGURE YVNOVYVGL, VAPYHQVAT NAL TRARENY, FCRPVNY, VAQVERPG, VAPVQRAGNY, BE PBAFRDHRAGVNY QNZNTRF, JURGURE VA NA NPGVBA BS PBAGENPG, GBEG BE BGUREJVFR, NEVFVAT SEBZ, BHG BS GUR HFR BE VANOVYVGL GB HFR GUR SBAG FBSGJNER BE SEBZ BGURE QRNYVATF VA GUR SBAG FBSGJNER.
|
|
@ -1,3 +1,3 @@
|
|||
these are foss licenses in rot13 so scanners don't think copyparty isn't mit
|
||||
|
||||
1=mit 2=2bsd 3=3bsd 4=ofl
|
||||
1=mit 2=2bsd 3=3bsd 4=isc 5=apache2 6=ofl
|
||||
|
|
|
@ -17,6 +17,7 @@ uname -s | grep NT-10 && w10=1 || w7=1
|
|||
[ $w7 ] && [ -e up2k.sh ] && [ ! "$1" ] && ./up2k.sh
|
||||
|
||||
[ $w7 ] && pyv=37 || pyv=313
|
||||
[ $w7 ] && sfx=en || sfx=sfx
|
||||
esuf=
|
||||
[ $w7 ] && [ $m = 32 ] && esuf=32
|
||||
[ $w7 ] && [ $m = 64 ] && esuf=-winpe64
|
||||
|
@ -33,8 +34,16 @@ dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.ico
|
|||
dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.py
|
||||
dl https://192.168.123.1:3923/cpp/scripts/pyinstaller/loader.rc
|
||||
|
||||
[ $sfx = en ] && {
|
||||
dl https://192.168.123.1:3923/cpp/dist/copyparty-en.py
|
||||
|
||||
st_en=$(cat copyparty-en.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null
|
||||
st_sfx=$(cat copyparty-sfx.py | awk '/^STAMP = [0-9]+/{print$3;exit}') 2>/dev/null
|
||||
[ $st_en ] && [ $st_en -ge $st_sfx ] || sfx=sfx
|
||||
}
|
||||
|
||||
rm -rf $TEMP/pe-copyparty*
|
||||
python copyparty-sfx.py --version
|
||||
python copyparty-$sfx.py --version
|
||||
|
||||
rm -rf mods; mkdir mods
|
||||
cp -pR $TEMP/pe-copyparty/{copyparty,partftpy}/ $TEMP/pe-copyparty/{ftp,j2}/* mods/
|
||||
|
|
BIN
scripts/pyinstaller/up2k.ico
Normal file
BIN
scripts/pyinstaller/up2k.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -25,6 +25,7 @@ copyparty/metrics.py,
|
|||
copyparty/mtag.py,
|
||||
copyparty/multicast.py,
|
||||
copyparty/pwhash.py,
|
||||
copyparty/qrkode.py,
|
||||
copyparty/res,
|
||||
copyparty/res/__init__.py,
|
||||
copyparty/res/COPYING.txt,
|
||||
|
@ -100,6 +101,7 @@ copyparty/web/mde.html,
|
|||
copyparty/web/mde.js,
|
||||
copyparty/web/msg.css,
|
||||
copyparty/web/msg.html,
|
||||
copyparty/web/opds.xml,
|
||||
copyparty/web/rups.css,
|
||||
copyparty/web/rups.html,
|
||||
copyparty/web/rups.js,
|
||||
|
|
|
@ -102,6 +102,7 @@ def tc1(vflags):
|
|||
"-p4321",
|
||||
"-e2dsa",
|
||||
"-e2tsr",
|
||||
"--wram",
|
||||
"--ban-403=no",
|
||||
"--dbd=yolo",
|
||||
"--no-mutagen",
|
||||
|
|
|
@ -143,10 +143,10 @@ class Cfg(Namespace):
|
|||
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||
ka = {}
|
||||
|
||||
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||
ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rm_partial rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs"
|
||||
ka.update(**{k: False for k in ex.split()})
|
||||
|
||||
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash see_dots plain_ip"
|
||||
ex = "dav_inf dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump wram re_dhash see_dots plain_ip"
|
||||
ka.update(**{k: True for k in ex.split()})
|
||||
|
||||
ex = "ah_cli ah_gen css_browser dbpath hist ipu js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua ua_nodoc ua_nozip"
|
||||
|
@ -155,7 +155,7 @@ class Cfg(Namespace):
|
|||
ex = "gid uid"
|
||||
ka.update(**{k: -1 for k in ex.split()})
|
||||
|
||||
ex = "hash_mt hsortn qdel safe_dedup srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz unp_who"
|
||||
ex = "hash_mt hsortn qdel safe_dedup scan_pr_r scan_pr_s scan_st_r srch_time tail_fd tail_rate th_spec_p u2abort u2j u2sz unp_who"
|
||||
ka.update(**{k: 1 for k in ex.split()})
|
||||
|
||||
ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt ups_who ver_iwho zip_who"
|
||||
|
@ -164,7 +164,7 @@ class Cfg(Namespace):
|
|||
ex = "ctl_re db_act forget_ip idp_cookie idp_store k304 loris no304 nosubtle qr_pin qr_wait re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo u2ow zipmaxn zipmaxs"
|
||||
ka.update(**{k: 0 for k in ex.split()})
|
||||
|
||||
ex = "ah_alg bname chdir chmod_f chpw_db doctitle df exit favico ipa html_head idp_login idp_logout lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles txt_eol unlist vname xff_src zipmaxt R RS SR"
|
||||
ex = "ah_alg bname chdir chmod_f chpw_db doctitle df exit favico ipa html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts shr tcolor textfiles txt_eol ufavico ufavico_h unlist vname xff_src zipmaxt R RS SR"
|
||||
ka.update(**{k: "" for k in ex.split()})
|
||||
|
||||
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner"
|
||||
|
@ -207,6 +207,7 @@ class Cfg(Namespace):
|
|||
put_name="put-{now.6f}-{cip}.bin",
|
||||
mv_retry="0/0",
|
||||
rm_retry="0/0",
|
||||
rotf_tz="UTC",
|
||||
s_rd_sz=256 * 1024,
|
||||
s_wr_sz=256 * 1024,
|
||||
shr_who="auth",
|
||||
|
@ -222,6 +223,7 @@ class Cfg(Namespace):
|
|||
th_x3="n",
|
||||
u2sort="s",
|
||||
u2ts="c",
|
||||
ui_filesz="1",
|
||||
unpost=600,
|
||||
ver_who="all",
|
||||
warksalt="hunter2",
|
||||
|
|
Loading…
Reference in a new issue