Merge pull request #1 from 9001/hovudstraum

Merge upstream
This commit is contained in:
SelfishPig 2025-12-16 16:58:33 -06:00 committed by GitHub
commit 23e9c2c524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 1891 additions and 379 deletions

View file

@ -7,10 +7,9 @@ assignees: '9001'
---
NOTE:
<!-- NOTE:
**please use english, or include an english translation.** aside from that,
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md
all of the below are optional, consider them as inspiration, delete and rewrite at will, thx md -->
### Describe the bug
a description of what the bug is
@ -45,5 +44,11 @@ if the issue is possibly on the client-side, then mention some of the following:
* OS version:
* browser version:
### The rest of the stack
if you are connecting directly to copyparty then that's cool, otherwise please mention everything else between copyparty and the browser (reverseproxy, tunnels, etc.)
### Server log
if the issue might be server-related, include everything that appears in the copyparty log during startup, and also anything else you think might be relevant
### Additional context
any other context about the problem here

View file

@ -7,9 +7,9 @@ assignees: '9001'
---
NOTE:
<!-- NOTE:
**please use english, or include an english translation.** aside from that,
all of the below are optional, consider them as inspiration, delete and rewrite at will
all of the below are optional, consider them as inspiration, delete and rewrite at will -->
**is your feature request related to a problem? Please describe.**
a description of what the problem is, for example, `I'm always frustrated when [...]` or `Why is it not possible to [...]`

View file

@ -118,6 +118,7 @@ made in Norway 🇳🇴
* [nix package](#nix-package) - `nix profile install github:9001/copyparty`
* [nixos module](#nixos-module)
* [browser support](#browser-support) - TLDR: yes
* [server hall of fame](#server-hall-of-fame) - unexpected things that run copyparty
* [client examples](#client-examples) - interact with copyparty using non-browser clients
* [folder sync](#folder-sync) - sync folders to/from copyparty
* [mount as drive](#mount-as-drive) - a remote copyparty server as a local filesystem
@ -193,7 +194,7 @@ some recommended options:
* `-e2ts` enables audio metadata indexing (needs either FFprobe or Mutagen)
* `-v /mnt/music:/music:r:rw,foo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, and read-write for user `foo`, password `bar`
* replace `:r:rw,foo` with `:r,foo` to only make the folder readable by `foo` and nobody else
* see [accounts and volumes](#accounts-and-volumes) (or `--help-accounts`) for the syntax and other permissions
* see [accounts and volumes](#accounts-and-volumes) (or [`--help-accounts`](https://copyparty.eu/cli/#accounts-help-page)) for the syntax and other permissions
### mirrors
@ -450,6 +451,12 @@ upgrade notes
* CopyParty?
* nope! the name is either copyparty (all-lowercase) or Copyparty -- it's [one word](https://en.wiktionary.org/wiki/copyparty) after all :>
* what is a volflag?
* per-volume configuration; many (not all) global-options can be set as volflags, and most (not all) volflags can be set as global-options; [complete list of volflags](https://copyparty.eu/cli/#flags-help-page)
* what is a volume?
* a mapping from a URL (`/music/`) to a folder on your server's local filesystem (`C:\Users\ed\Music`) which can then be accessed through copyparty, depending on the permissions and options you set on it -- see [accounts and volumes](#accounts-and-volumes)
* can I change the 🌲 spinning pine-tree loading animation?
* [yeah...](https://github.com/9001/copyparty/tree/hovudstraum/docs/rice#boring-loader-spinner) :-(
@ -497,7 +504,7 @@ per-folder, per-user permissions - if your setup is getting complex, consider m
* much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if the user has `a`/admin in any volume)
* changes to the `[global]` config section requires a restart to take effect
a quick summary can be seen using `--help-accounts`
a quick summary can be seen using [`--help-accounts`](https://copyparty.eu/cli/#accounts-help-page)
configuring accounts/volumes with arguments:
* `-a usr:pwd` adds account `usr` with password `pwd`
@ -1262,7 +1269,7 @@ see [./srv/expand/](./srv/expand/) for usage and examples
* and `PREADME.md` / `preadme.md` is shown above directory listings unless `--no-readme` or `.prologue.html`
* `README.md` and `*logue.html` can contain placeholder values which are replaced server-side before embedding into directory listings; see `--help-exp`
* `README.md` and `*logue.html` can contain placeholder values which are replaced server-side before embedding into directory listings; see [`--help-exp`](https://copyparty.eu/cli/#exp-help-page)
## searching
@ -1292,10 +1299,9 @@ using arguments or config files, or a mix of both:
* or click the `[reload cfg]` button in the control-panel if the user has `a`/admin in any volume
* changes to the `[global]` config section requires a restart to take effect
**NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with `--help` to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in `--help-flags` can be used in volumes as volflags.
**NB:** as humongous as this readme is, there is also a lot of undocumented features. Run copyparty with [`--help`](https://copyparty.eu/cli/) (or click that link) to see all available global options; all of those can be used in the `[global]` section of config files, and everything listed in [`--help-flags`](https://copyparty.eu/cli/#flags-help-page) can be used in volumes as volflags (per-volume configuration).
* if running in docker/podman, try this: `docker run --rm -it copyparty/ac --help`
* or see this: https://ocv.me/copyparty/helptext.html
* or if you prefer plaintext, https://ocv.me/copyparty/helptext.txt
* or if you prefer plaintext, https://copyparty.eu/helptext.txt
## zeroconf
@ -1782,6 +1788,8 @@ notes:
* `:c,magic` enables filetype detection for nameless uploads, same as `--magic`
* needs https://pypi.org/project/python-magic/ `python3 -m pip install --user -U python-magic`
* on windows grab this instead `python3 -m pip install --user -U python-magic-bin`
* `cachectl` changes how webbrowser will cache responses (the `Cache-Control` response-header); default is `no-cache` which will prevent repeated downloading of the same file unless necessary (browser will ask copyparty if the file has changed)
* adding `?cache` to a link will override this with "fully cache this for 69 seconds"; `?cache=321` is 321 seconds, and `?cache=i` is 7 days
## database location
@ -1888,7 +1896,7 @@ trigger a program on uploads, renames etc ([examples](./bin/hooks/))
you can set hooks before and/or after an event happens, and currently you can hook uploads, moves/renames, and deletes
there's a bunch of flags and stuff, see `--help-hooks`
there's a bunch of flags and stuff, see [`--help-hooks`](https://copyparty.eu/cli/#hooks-help-page)
if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
@ -2411,6 +2419,8 @@ it comes with a [systemd service](./contrib/systemd/copyparty@.service) as well
after installing, start either the system service or the user service and navigate to http://127.0.0.1:3923 for further instructions (unless you already edited the config files, in which case you are good to go, probably)
> to start the systemd service, either do `systemctl start --user copyparty` to start it as your own user, or `systemctl start copyparty@bob` to use unix-user `bob`
## fedora package
@ -2623,6 +2633,15 @@ quick summary of more eccentric web-browsers trying to view a directory index:
<p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
# server hall of fame
unexpected things that run copyparty:
* an old [allwinner](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/aallwinner.jpg) android tv-box (ziptie-strapped to an HDD) running a firmware which flips the CPU into Big-Endian mode early during boot
* copyparty is [certified BE ready](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/be-ready.png)
* a [wristwatch](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/clockyparty.jpg)
# client examples
interact with copyparty using non-browser clients
@ -2810,7 +2829,7 @@ some notes on hardening
* cors doesn't work right otherwise
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
* this returns html documents as plaintext, and also disables markdown rendering
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or `--help-bind`
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or [`--help-bind`](https://copyparty.eu/cli/#bind-help-page)
safety profiles:
@ -2898,7 +2917,7 @@ dirkeys are generated based on another salt (`--dk-salt`) + filesystem-path and
## password hashing
you can hash passwords before putting them into config files / providing them as arguments; see `--help-pwhash` for all the details
you can hash passwords before putting them into config files / providing them as arguments; see [`--help-pwhash`](https://copyparty.eu/cli/#pwhash-help-page) for all the details
`--ah-alg argon2` enables it, and if you have any plaintext passwords then it'll print the hashed versions on startup so you can replace them

View file

@ -1,9 +1,7 @@
# Security Policy
if you hit something extra juicy pls let me know on either of the following
if you hit something extra juicy pls let me know on one of the following:
* email -- `copyparty@ocv.ze` except `ze` should be `me`
* [mastodon dm](https://layer8.space/@tripflag) -- `@tripflag@layer8.space`
* [github private vulnerability report](https://github.com/9001/copyparty/security/advisories/new), wow that form is complicated
* [twitter dm](https://twitter.com/tripflag) (if im somehow not banned yet)
no bug bounties sorry! all i can offer is greetz in the release notes

View file

@ -4,6 +4,11 @@ these programs either take zero arguments, or a filepath (the affected file), or
run copyparty with `--help-hooks` for usage details / hook type explanations (xm/xbu/xau/xiu/xbc/xac/xbr/xar/xbd/xad/xban)
in particular, if a hook is loaded into copyparty with the hook-flag `c` ("check") then its exit-code controls the action that launched the hook:
* exit-code `0` = allow the action, and/or continue running the next hook
* exit-code `100` = allow the action, and stop running any remaining consecutive hooks
* anything else = reject/prevent the original action, and don't run the remaining hooks
> **note:** in addition to event hooks (the stuff described here), copyparty has another api to run your programs/scripts while providing way more information such as audio tags / video codecs / etc and optionally daisychaining data between scripts in a processing pipeline; if that's what you want then see [mtp plugins](../mtag/) instead

View file

@ -4,13 +4,19 @@ import os
import sys
import tempfile
import subprocess as sp
import keyfinder
try:
import keyfinder
PKF = True
except:
PKF = False
from copyparty.util import fsenc
"""
dep: github/mixxxdj/libkeyfinder
dep: pypi/keyfinder
dep: pypi/keyfinder -OR- EvanPurkhiser/keyfinder-cli
dep: ffmpeg
"""
@ -35,7 +41,17 @@ def det(tf):
])
# fmt: on
print(keyfinder.key(tf).camelot())
if PKF:
print(keyfinder.key(tf).camelot())
else:
# fmt: off
sp.check_call([
b"keyfinder-cli",
b"-n",
b"camelot",
fsenc(tf)
])
# fmt: on
def main():

View file

@ -155,6 +155,11 @@ install_keyfinder() {
return
}
(cat /etc/alpine-release || echo a) 2>&1 | grep -E '3\.2[3-9]' && {
echo "alpine too new; ffmpeg8 is keyfinder-py incompat; giving up"
return
}
cd "$td"
github_tarball https://api.github.com/repos/mixxxdj/libkeyfinder/releases/latest
ls -al
@ -189,7 +194,7 @@ install_keyfinder() {
exit 1
}
x=${-//[^x]/}; set -x; cat /etc/alpine-release
x=${-//[^x]/}; set -x; cat /etc/alpine-release || true
# rm -rf /Users/ed/Library/Python/3.9/lib/python/site-packages/*keyfinder*
CFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \
CXXFLAGS="-I$h/pe/keyfinder/include -I/opt/local/include -I/usr/include/ffmpeg" \

View file

@ -6,8 +6,8 @@ __copyright__ = 2019
__license__ = "MIT"
__url__ = "https://github.com/9001/copyparty/"
S_VERSION = "2.1"
S_BUILD_DT = "2025-09-06"
S_VERSION = "2.2"
S_BUILD_DT = "2025-12-16"
"""
mount a copyparty server (local or remote) as a filesystem
@ -284,8 +284,8 @@ class Gateway(object):
if ar.td:
self.ssl_context = ssl._create_unverified_context()
elif ar.te:
self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
self.ssl_context.load_verify_locations(ar.te)
self.ssl_context = ssl.create_default_context(cafile=ar.te)
self.ssl_context.check_hostname = ar.teh
self.conns = {}
@ -1165,6 +1165,7 @@ NOTE: if server has --usernames enabled, then password is "username:password"
ap2 = ap.add_argument_group("https/TLS")
ap2.add_argument("-te", metavar="PEMFILE", help="certificate to expect/verify")
ap2.add_argument("-teh", action="store_true", help="require correct hostname in -te cert")
ap2.add_argument("-td", action="store_true", help="disable certificate check")
ap2 = ap.add_argument_group("cache/perf")

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
S_VERSION = "2.15"
S_BUILD_DT = "2025-10-25"
S_VERSION = "2.17"
S_BUILD_DT = "2025-12-16"
"""
u2c.py: upload to copyparty
@ -165,8 +165,8 @@ class HCli(object):
elif self.verify is True:
self.ctx = None
else:
self.ctx = ssl.SSLContext(ssl.PROTOCOL_TLS)
self.ctx.load_verify_locations(self.verify)
self.ctx = ssl.create_default_context(cafile=self.verify)
self.ctx.check_hostname = ar.teh
self.base_hdrs = {
"Accept": "*/*",
@ -492,6 +492,12 @@ print = safe_print if VT100 else flushing_print
def termsize():
try:
w, h = os.get_terminal_size()
return w, h
except:
pass
env = os.environ
def ioctl_GWINSZ(fd):
@ -1587,6 +1593,7 @@ NOTE: if server has --usernames enabled, then password is "username:password"
ap = app.add_argument_group("tls")
ap.add_argument("-te", metavar="PATH", help="path to ca.pem or cert.pem to expect/verify")
ap.add_argument("-teh", action="store_true", help="require correct hostname in -te cert")
ap.add_argument("-td", action="store_true", help="disable certificate check")
# fmt: on

View file

@ -61,6 +61,8 @@ def rep_server():
print("copyparty says %r" % (sck.recv_string(),))
reply = b"thx"
# reply = b"return 1" # non-zero to block an upload
# reply = b'{"rc":1}' # or as json, that's fine too
# reply = b'{"rejectmsg":"naw dude"}' # or custom message
sck.send(reply)

View file

@ -3,7 +3,7 @@
# NOTE: You generally shouldn't use this PKGBUILD on Arch, as it is mainly for testing purposes. Install copyparty using pacman instead.
pkgname=copyparty
pkgver="1.19.20"
pkgver="1.19.22"
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=("050ccc34554e59210aca7a67d87a186e69b3f4dbe013d5ee2f11a22c259a82a6")
sha256sums=("ba2e5c332b5481aa93f1bb9d5e79dfe1a6ed4329e470efac73e685fd3cc3a370")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -2,7 +2,7 @@
pkgname=copyparty
pkgver=1.19.20
pkgver=1.19.22
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=("050ccc34554e59210aca7a67d87a186e69b3f4dbe013d5ee2f11a22c259a82a6")
sha256sums=("ba2e5c332b5481aa93f1bb9d5e79dfe1a6ed4329e470efac73e685fd3cc3a370")
build() {
cd "${srcdir}/${pkgname}-${pkgver}/copyparty/web"

View file

@ -1,5 +1,5 @@
{
"url": "https://github.com/9001/copyparty/releases/download/v1.19.20/copyparty-1.19.20.tar.gz",
"version": "1.19.20",
"hash": "sha256-BQzMNFVOWSEKynpn2HoYbmmz9NvgE9XuLxGiLCWagqY="
"url": "https://github.com/9001/copyparty/releases/download/v1.19.22/copyparty-1.19.22.tar.gz",
"version": "1.19.22",
"hash": "sha256-ui5cMytUgaqT8budXnnf4abtQynkcO+sc+aF/TzDo3A="
}

View file

@ -39,3 +39,9 @@ point `--css-browser` to one of these by URL:
* turns copyparty into chromecast just more flexible (and probably way more buggy)
* usage: put the js somewhere in the webroot and `--js-browser /memes/meadup.js`
# junk
* [**rave.js**](./rave.js): april-fools joke, [demo (epilepsy warning)](https://cd.ocv.me/b/d2/d21/#af-9b927c42,sorthref), not maintained, very buggy

View file

@ -24,8 +24,8 @@ Note that you can select the owner and group of this volume by changing the `uid
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 mkdir -pv /etc/containers/systemd/ /etc/copyparty/
sudo cp -v copyparty.container /etc/containers/systemd/
sudo cp -v copyparty.conf /etc/copyparty/
sudo systemctl daemon-reload
sudo systemctl start copyparty

View file

@ -86,7 +86,6 @@ web/md2.js
web/mde.css
web/mde.html
web/mde.js
web/msg.css
web/msg.html
web/opds.xml
web/rups.css
@ -119,6 +118,7 @@ web/tl/spa.js
web/tl/swe.js
web/tl/tur.js
web/tl/ukr.js
web/tl/vie.js
web/ui.css
web/up2k.js
web/util.js

View file

@ -809,7 +809,8 @@ def get_sects():
\033[0m
hooks specified as commandline --args are appended to volflags;
each commandline --arg and volflag can be specified multiple times,
each hook will execute in order unless one returns non-zero
each hook will execute in order unless one returns non-zero, or
"100" which means "stop daisychaining and return 0 (success/OK)"
optionally prefix the command with comma-sep. flags similar to -mtp:
@ -1203,6 +1204,7 @@ def add_fs(ap):
ap2 = ap.add_argument_group("filesystem options")
rm_re_def = "15/0.1" if ANYWIN else "0/0"
ap2.add_argument("--casechk", metavar="N", type=u, default="auto", help="detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [\033[32my\033[0m] = detect and prevent, [\033[32mn\033[0m] = ignore and allow, [\033[32mauto\033[0m] = \033[32my\033[0m if CI fs detected. NOTE: \033[32my\033[0m is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk)")
ap2.add_argument("--fsnt", metavar="OS", type=u, default="auto", help="which characters to allow in file/folder names; [\033[32mwin\033[0m] = windows (not <>:|?*\"\\/), [\033[32mmac\033[0m] = macos (not :), [\033[32mlin\033[0m] = linux (anything goes) (volflag=fsnt)")
ap2.add_argument("--rm-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be deleted because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=rm_retry)")
ap2.add_argument("--mv-retry", metavar="T/R", type=u, default=rm_re_def, help="if a file cannot be renamed because it is busy, continue trying for \033[33mT\033[0m seconds, retry every \033[33mR\033[0m seconds; disable with 0/0 (volflag=mv_retry)")
ap2.add_argument("--iobuf", metavar="BYTES", type=int, default=256*1024, help="file I/O buffer-size; if your volumes are on a network drive, try increasing to \033[32m524288\033[0m or even \033[32m4194304\033[0m (and let me know if that improves your performance)")
@ -1274,9 +1276,14 @@ def add_network(ap):
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)")
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=9999999, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m-1\033[0m]=closest-proxy, [\033[32m-2\033[0m]=second-hop, [\033[32m-3\033[0m]=third-hop")
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from")
ap2.add_argument("--xf-host", metavar="NAME", type=u, default="x-forwarded-host", help="if reverse-proxied, which http header to read the correct Host value from; this header must contain the server's external domain name")
ap2.add_argument("--xf-proto", metavar="NAME", type=u, default="x-forwarded-proto", help="if reverse-proxied, which http header to read the correct protocol value from; this header must contain either 'http' or 'https'")
ap2.add_argument("--xf-proto-fb", metavar="T", type=u, default="", help="protocol to assume if the X-Forwarded-Proto header (\033[33m--xf-proto\033[0m) is not provided by the reverseproxy; either 'http' or 'https'")
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("--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]\n └─for performance and security, this only looks at the TCP/Network-level IP, and will NOT work behind a reverseproxy")
ap2.add_argument("--ipar", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated).\n └─this is reverseproxy-compatible; reads client-IP from 'X-Forwarded-For' if possible, with TCP/Network IP as fallback")
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("--cachectl", metavar="TXT", default="no-cache", help="default-value of the 'Cache-Control' response-header (controls caching in webbrowsers). Default prevents repeated downloading of the same file unless necessary (browser will ask copyparty if the file has changed). Examples: [\033[32mmax-age=604869\033[0m] will cache for 7 days, [\033[32mno-store, max-age=0\033[0m] will always redownload. (volflag=cachectl)")
ap2.add_argument("--http-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")
@ -1332,6 +1339,7 @@ def add_auth(ap):
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")
ap2.add_argument("--idp-chsub", metavar="TXT", type=u, default="", help="characters to replace in usernames/groupnames; a list of pairs of characters separated by | so for example | _| will replace spaces with _ to make configuration easier, or |%%_|^_|@_| will replace %%/^/@ with _")
ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)")
ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
@ -1417,7 +1425,7 @@ def add_ftp(ap):
ap2.add_argument("--ftps", metavar="PORT", type=int, default=0, help="enable FTPS server on \033[33mPORT\033[0m, for example \033[32m3990")
ap2.add_argument("--ftpv", action="store_true", help="verbose")
ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4")
ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m / \033[33m--ipar\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite")
ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, default="", help="the NAT address to use for passive connections")
@ -1431,7 +1439,8 @@ def add_webdav(ap):
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")
ap2.add_argument("--dav-rt", action="store_true", help="show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt)")
ap2.add_argument("--dav-auth", action="store_true", help="force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth)")
ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of tricky user-agents which expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank")
ap2.add_argument("--dav-ua1", metavar="PTN", type=u, default=r" kioworker/", help="regex of user-agents which ARE webdav-clients, and expect 401 from GET requests; disable with [\033[32mno\033[0m] or blank")
ap2.add_argument("--ua-nodav", metavar="PTN", type=u, default=r"^(Mozilla/|NetworkingExtension/|com\.apple\.WebKit)", help="regex of user-agents which are NOT webdav-clients")
def add_tftp(ap):
@ -1443,7 +1452,7 @@ def add_tftp(ap):
ap2.add_argument("--tftp-no-fast", action="store_true", help="debug: disable optimizations")
ap2.add_argument("--tftp-lsf", metavar="PTN", type=u, default="\\.?(dir|ls)(\\.txt)?", help="return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ...")
ap2.add_argument("--tftp-nols", action="store_true", help="if someone tries to download a directory, return an error instead of showing its directory listing")
ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--tftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m (comma-separated); specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m / \033[33m--ipar\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--tftp-pr", metavar="P-P", type=u, default="", help="the range of UDP ports to use for data transfer, for example \033[32m12000-13000")
@ -1554,6 +1563,7 @@ def add_safety(ap):
ap2.add_argument("--no-robots", action="store_true", help="adds http and html headers asking search engines to not index anything (volflag=norobots)")
ap2.add_argument("--logout", metavar="H", type=float, default=8086.0, help="logout clients after \033[33mH\033[0m hours of inactivity; [\033[32m0.0028\033[0m]=10sec, [\033[32m0.1\033[0m]=6min, [\033[32m24\033[0m]=day, [\033[32m168\033[0m]=week, [\033[32m720\033[0m]=month, [\033[32m8760\033[0m]=year)")
ap2.add_argument("--dont-ban", metavar="TXT", type=u, default="no", help="anyone at this accesslevel or above will not get banned: [\033[32mav\033[0m]=admin-in-volume, [\033[32maa\033[0m]=has-admin-anywhere, [\033[32mrw\033[0m]=read-write, [\033[32mauth\033[0m]=authenticated, [\033[32many\033[0m]=disable-all-bans, [\033[32mno\033[0m]=anyone-can-get-banned")
ap2.add_argument("--banmsg", metavar="TXT", type=u, default="thank you for playing \u00a0 (see serverlog and readme)", help="the response to send to banned users; can be @ban.html to send the contents of ban.html")
ap2.add_argument("--ban-pw", metavar="N,W,B", type=u, default="9,60,1440", help="more than \033[33mN\033[0m wrong passwords in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
ap2.add_argument("--ban-pwc", metavar="N,W,B", type=u, default="5,60,1440", help="more than \033[33mN\033[0m password-changes in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; disable with [\033[32mno\033[0m]")
ap2.add_argument("--ban-404", metavar="N,W,B", type=u, default="50,60,1440", help="hitting more than \033[33mN\033[0m 404's in \033[33mW\033[0m minutes = ban for \033[33mB\033[0m minutes; only affects users who cannot see directory listings because their access is either g/G/h")
@ -1602,6 +1612,7 @@ def add_logging(ap):
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
ap2.add_argument("--log-utc", action="store_true", help="do not use local timezone; assume the TZ env-var is UTC (tiny bit faster)")
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
ap2.add_argument("--log-date", metavar="TXT", type=u, default="", help="date-format, for example [\033[32m%%Y-%%m-%%d\033[0m] (default is disabled; no date, just HH:MM:SS)")
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=2, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
ap2.add_argument("--log-badxml", action="store_true", help="log any invalid XML received from a client")
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
@ -1642,6 +1653,7 @@ def add_thumbnail(ap):
ap2.add_argument("--th-ram-max", metavar="GB", type=float, default=th_ram, help="max memory usage (GiB) permitted by thumbnailer; not very accurate")
ap2.add_argument("--th-crop", metavar="TXT", type=u, default="y", help="crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [\033[32my\033[0m]=crop, [\033[32mn\033[0m]=nocrop, [\033[32mfy\033[0m]=force-y, [\033[32mfn\033[0m]=force-n (volflag=crop)")
ap2.add_argument("--th-x3", metavar="TXT", type=u, default="n", help="show thumbs at 3x resolution; client can override in UI unless force. [\033[32my\033[0m]=yes, [\033[32mn\033[0m]=no, [\033[32mfy\033[0m]=force-yes, [\033[32mfn\033[0m]=force-no (volflag=th3x)")
ap2.add_argument("--th-qv", metavar="N", type=int, default=40, help="thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qv)")
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,raw,ff", help="image decoders, in order of preference")
ap2.add_argument("--th-no-jpg", action="store_true", help="disable jpg output")
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
@ -1694,7 +1706,9 @@ def add_rss(ap):
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files")
ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files (volflag=rss_sort)")
ap2.add_argument("--rss-fmt-t", metavar="TXT", type=u, default="{fname}", help="title format (url-param 'rss_fmt_t') (volflag=rss_fmt_t)")
ap2.add_argument("--rss-fmt-d", metavar="TXT", type=u, default="{artist} - {title}", help="description format (url-param 'rss_fmt_d') (volflag=rss_fmt_d)")
def add_db_general(ap, hcores):
@ -1792,6 +1806,7 @@ def add_ui(ap, retry: int):
ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)")
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
ap2.add_argument("--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)")
@ -1807,6 +1822,10 @@ def add_ui(ap, retry: int):
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)")
ap2.add_argument("--prologues", metavar="T,T", type=u, default=".prologue.html", help="comma-sep. list of filenames to scan for and use as prologues (embed above/before directory listing) (volflag=prologues)")
ap2.add_argument("--epilogues", metavar="T,T", type=u, default=".epilogue.html", help="comma-sep. list of filenames to scan for and use as epilogues (embed below/after directory listing) (volflag=epilogues)")
ap2.add_argument("--preadmes", metavar="T,T", type=u, default="preadme.md,PREADME.md", help="comma-sep. list of filenames to scan for and use as preadmes (embed above/before directory listing) (volflag=preadmes)")
ap2.add_argument("--readmes", metavar="T,T", type=u, default="readme.md,README.md", help="comma-sep. list of filenames to scan for and use as readmes (embed below/after directory listing) (volflag=readmes)")
ap2.add_argument("--doctitle", metavar="TXT", type=u, default="copyparty @ --name", help="title / service-name to show in html documents")
ap2.add_argument("--bname", metavar="TXT", type=u, default="--name", help="server name (displayed in filebrowser document title)")
ap2.add_argument("--pb-url", metavar="URL", type=u, default=URL_PRJ, help="powered-by link; disable with \033[33m-nb\033[0m")

View file

@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (1, 19, 20)
VERSION = (1, 19, 22)
CODENAME = "usernames"
BUILD_DT = (2025, 11, 2)
BUILD_DT = (2025, 12, 14)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View file

@ -1054,6 +1054,7 @@ class AuthSrv(object):
self.is_lxc = args.c == ["/z/initcfg"]
self._vf0b = {
"cachectl": self.args.cachectl,
"tcolor": self.args.tcolor,
"du_iwho": self.args.du_iwho,
"shr_who": self.args.shr_who if self.args.shr else "no",
@ -1812,7 +1813,7 @@ class AuthSrv(object):
derive_args(self.args)
self.setup_auth_ord()
if self.args.ipu:
if self.args.ipu and not self.args.have_idp_hdrs:
# syntax (CIDR=UNAME) is verified in load_ipu
zsl = [x.split("=", 1)[1] for x in self.args.ipu]
zsl = [x for x in zsl if x not in acct]
@ -2384,7 +2385,7 @@ class AuthSrv(object):
if vf not in vol.flags:
vol.flags[vf] = getattr(self.args, ga)
zs = "forget_ip gid nrand tail_who th_spec_p u2abort u2ow uid unp_who ups_who zip_who"
zs = "forget_ip gid nrand tail_who th_qv th_spec_p u2abort u2ow uid unp_who ups_who zip_who"
for k in zs.split():
if k in vol.flags:
vol.flags[k] = int(vol.flags[k])
@ -2524,6 +2525,18 @@ class AuthSrv(object):
t = "WARNING: volume [/%s]: invalid value specified for ext-th: %s"
self.log(t % (vol.vpath, etv), 3)
zsl1 = [x for x in vol.flags["preadmes"].split(",") if x]
zsl2 = [x for x in vol.flags["readmes"].split(",") if x]
zsl3 = list(set([x.lower() for x in zsl1]))
zsl4 = list(set([x.lower() for x in zsl2]))
vol.flags["emb_mds"] = [[0, zsl1, zsl3], [1, zsl2, zsl4]]
zsl1 = [x for x in vol.flags["prologues"].split(",") if x]
zsl2 = [x for x in vol.flags["epilogues"].split(",") if x]
zsl3 = list(set([x.lower() for x in zsl1]))
zsl4 = list(set([x.lower() for x in zsl2]))
vol.flags["emb_lgs"] = [[0, zsl1, zsl3], [1, zsl2, zsl4]]
zs = str(vol.flags.get("html_head") or "")
if zs and zs[:1] in "%@":
vol.flags["html_head_d"] = zs
@ -2604,18 +2617,16 @@ class AuthSrv(object):
vol.flags[k] = int(vol.flags[k])
if "e2d" not in vol.flags:
if "lifetime" in vol.flags:
t = 'removing lifetime config from volume "/{}" because e2d is disabled'
self.log(t.format(vol.vpath), 1)
del vol.flags["lifetime"]
zs = "lifetime rss"
drop = [x for x in zs.split() if x in vol.flags]
needs_e2d = [x for x in hooks if x in ("xau", "xiu")]
drop = [x for x in needs_e2d if vol.flags.get(x)]
if drop:
t = 'removing [{}] from volume "/{}" because e2d is disabled'
self.log(t.format(", ".join(drop), vol.vpath), 1)
for x in drop:
vol.flags.pop(x)
zs = "xau xiu"
drop += [x for x in zs.split() if vol.flags.get(x)]
for k in drop:
t = 'cannot enable [%s] for volume "/%s" because this requires one of the following: e2d / e2ds / e2dsa (either as volflag or global-option)'
self.log(t % (k, vol.vpath), 1)
vol.flags.pop(k)
zi = vol.flags.get("lifetime") or 0
zi2 = time.time() // (86400 * 365)
@ -3049,6 +3060,8 @@ class AuthSrv(object):
vn.js_ls = {
"idx": "e2d" in vf,
"itag": "e2t" in vf,
"dlni": "dlni" in vf,
"dgrid": "grid" in vf,
"dnsort": "nsort" in vf,
"dhsortn": vf["hsortn"],
"dsort": vf["sort"],
@ -3091,7 +3104,8 @@ class AuthSrv(object):
"unlist0": vf.get("unlist") or "",
"see_dots": self.args.see_dots,
"dqdel": self.args.qdel,
"dgrid": "grid" in vf,
"dlni": vn.js_ls["dlni"],
"dgrid": vn.js_ls["dgrid"],
"dgsel": "gsel" in vf,
"dnsort": "nsort" in vf,
"dhsortn": vf["hsortn"],
@ -3131,7 +3145,10 @@ class AuthSrv(object):
self.log("BUG: /%s not in all_nodes" % (vol.vpath,), 1)
vols.append(vol)
if shr in vfs.all_nodes:
self.log("BUG: %s found in all_nodes" % (shr,), 1)
t = "invalid config: a volume is overlapping with the --shr global-option (/%s)"
t = t % (shr,)
self.log(t, 1)
raise Exception(t)
for vol in vols:
dbv = vol.get_dbv("")[0]
@ -3290,7 +3307,7 @@ class AuthSrv(object):
pwdb = {}
else:
jtxt = read_utf8(self.log, ap, True)
pwdb = json.loads(jtxt)
pwdb = json.loads(jtxt) if jtxt.strip() else {}
pwdb = [x for x in pwdb if x[0] != uname]
pwdb.append((uname, self.defpw[uname], hpw))
@ -3314,7 +3331,7 @@ class AuthSrv(object):
return
jtxt = read_utf8(self.log, ap, True)
pwdb = json.loads(jtxt)
pwdb = json.loads(jtxt) if jtxt.strip() else {}
useen = set()
urst = set()

View file

@ -40,7 +40,7 @@ def makedirs(name: str, vf: dict[str, Any] = MKD_755, exist_ok: bool = True) ->
todo = []
bname = fsenc(name)
while bname:
if os.path.isdir(bname):
if os.path.isdir(bname) or bname in todo:
break
todo.append(bname)
bname = os.path.dirname(bname)

View file

@ -30,6 +30,7 @@ def vf_bmap() -> dict[str, str]:
}
for k in (
"dedup",
"dlni",
"dotsrch",
"e2d",
"e2ds",
@ -94,13 +95,16 @@ def vf_vmap() -> dict[str, str]:
}
for k in (
"bup_ck",
"cachectl",
"casechk",
"chmod_d",
"chmod_f",
"dbd",
"du_who",
"epilogues",
"ufavico",
"forget_ip",
"fsnt",
"hsortn",
"html_head",
"html_head_s",
@ -121,10 +125,16 @@ def vf_vmap() -> dict[str, str]:
"og_tpl",
"og_ua",
"opds_exts",
"prologues",
"preadmes",
"put_ck",
"put_name",
"readmes",
"mv_retry",
"rm_retry",
"rss_sort",
"rss_fmt_t",
"rss_fmt_d",
"shr_who",
"sort",
"tail_fd",
@ -132,6 +142,7 @@ def vf_vmap() -> dict[str, str]:
"tail_tmax",
"tail_who",
"tcolor",
"th_qv",
"th_spec_p",
"txt_eol",
"unlist",
@ -200,10 +211,12 @@ flagcats = {
"noclone": "take dupe data from clients, even if available on HDD",
"nodupe": "rejects existing files (instead of linking/cloning them)",
"nodupem": "rejects existing files during moves as well",
"casechk=auto": "actively prevent case-insensitive filesystem? y/n",
"chmod_d=755": "unix-permission for new dirs/folders",
"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",
"fsnt=auto": "filesystem filename traits (lin/win/mac/auto)",
"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",
@ -265,7 +278,6 @@ flagcats = {
"no_db_ip": "never store uploader-IP in the db; disables unpost",
"fat32": "avoid excessive reindexing on android sdcardfs",
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
"casechk=auto": "actively prevent case-insensitive filesystem? y/n",
"xlink": "cross-volume dupe detection / linking (dangerous)",
"xdev": "do not descend into other filesystems",
"xvol": "do not follow symlinks leaving the volume root",
@ -288,6 +300,7 @@ flagcats = {
"thsize": "thumbnail res; WxH",
"crop": "center-cropping (y/n/fy/fn)",
"th3x": "3x resolution (y/n/fy/fn)",
"th_qv=40": "thumbnail quality (10~90)",
"convt": "convert-to-image timeout in seconds",
"aconvt": "convert-to-audio timeout in seconds",
"th_spec_p=1": "make spectrograms? 0=never 1=fallback 2=always",
@ -318,6 +331,7 @@ flagcats = {
"hsortn": "number of sort-rules to add to media URLs",
"ufavico=URL": "per-volume favicon (.ico/png/gif/svg)",
"unlist": "dont list files matching REGEX",
"dlni": "force-download (no-inline) files on click",
"html_head=TXT": "includes TXT in the <head>, or @PATH for file at PATH",
"html_head_s=TXT": "additional static text in the html <head>",
"tcolor=#fc0": "theme color (a hint for webbrowsers, discord, etc.)",
@ -327,6 +341,10 @@ flagcats = {
"norobots": "kindly asks search engines to leave",
"unlistcr": "don't list read-access in controlpanel",
"unlistcw": "don't list write-access in controlpanel",
"prologues=.prologue.html": "files to embed above/before files",
"epilogues=.epilogue.html": "files to embed below/after files",
"readmes=readme.md,README.md": "files to embed as readmes",
"preadmes=preadme.md,PREADME.md": "files to embed as preadmes",
"no_sb_md": "disable js sandbox for markdown files",
"no_sb_lg": "disable js sandbox for prologue/epilogue",
"sb_md": "enable js sandbox for markdown files (default)",
@ -379,6 +397,12 @@ flagcats = {
"tail_tmax=30": "kill connection after 30 sec",
"tail_who=2": "restrict ?tail access (1=admins,2=authed,3=everyone)",
},
"rss": {
"rss": "allow '?rss' URL suffix (experimental)",
"rss_sort=m": "default sort-order (m/u/n/s)",
"rss_fmt_t={fname}": "default title-format",
"rss_fmt_d={album},{.tn}": "default description-format",
},
"others": {
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
@ -386,7 +410,6 @@ flagcats = {
"dk=8": 'generates per-directory accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
"dks": "per-directory accesskeys allow browsing into subdirs",
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
"rss": "allow '?rss' URL suffix (experimental)",
"rmagic": "expensive analysis for mimetype accuracy",
"shr_who=auth": "who can create shares? no/auth/a",
"unp_who=2": "unpost only if same... 1=ip+name, 2=ip, 3=name",
@ -397,6 +420,7 @@ flagcats = {
"zipmaxt=no": "reply with 'no' if download-as-zip exceeds max",
"zipmaxu": "zip-size-limit does not apply to authenticated users",
"nopipe": "disable race-the-beam (download unfinished uploads)",
"cachectl=no-cache": "controls caching in webbrowsers",
"mv_retry": "ms-windows: timeout for renaming busy files",
"rm_retry": "ms-windows: timeout for deleting busy files",
"davauth": "ask webdav clients to login for all folders",

View file

@ -2,6 +2,7 @@
from __future__ import print_function, unicode_literals
import argparse
import json
import os
import re
import time
@ -9,7 +10,7 @@ import time
from .__init__ import ANYWIN, MACOS
from .authsrv import AXS, VFS, AuthSrv
from .bos import bos
from .util import chkcmd, min_ex, undot
from .util import chkcmd, json_hesc, min_ex, undot
if True: # pylint: disable=using-constant-test
from typing import Optional, Union
@ -127,6 +128,24 @@ class Fstab(object):
self.log("mtab has changed; reevaluating support for sparse files")
try:
fuses = [mp for mp, fs in dtab.items() if fs == "fuseblk"]
if not fuses or MACOS:
raise Exception()
try:
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINT"]) # centos6
except:
so, _ = chkcmd(["lsblk", "-nrfo", "FSTYPE,MOUNTPOINTS"]) # future
for ln in so.split("\n"):
zsl = ln.split(" ", 1)
if len(zsl) != 2:
continue
fs, mp = zsl
if mp in fuses:
dtab[mp] = fs
except:
pass
tab1 = list(dtab.items())
tab1.sort(key=lambda x: (len(x[0]), x[0]))
path1, fs1 = tab1[0]
@ -194,24 +213,53 @@ class Fstab(object):
return ret.realpath, ""
_fstab: Optional[Fstab] = None
winfs = set(("msdos", "vfat", "ntfs", "exfat"))
# "msdos" = vfat on macos
def ramdisk_chk(asrv: AuthSrv) -> None:
# should have been in authsrv but that's a circular import
global _fstab
mods = []
ramfs = ("tmpfs", "overlay")
log = asrv.log_func or print
fstab = Fstab(log, asrv.args, False)
if not _fstab:
_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)
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()
vn.axs.umove.clear()
for un, ztsp in list(vn.uaxs.items()):
zsl = list(ztsp)
zsl[1] = False
zsl[2] = False
vn.uaxs[un] = tuple(zsl)
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)
assume = "mac" if MACOS else "lin"
for vol in asrv.vfs.all_nodes.values():
if not vol.realpath or vol.flags.get("is_file"):
continue
zs = vol.flags["fsnt"].strip()[:3].lower()
if ANYWIN and not zs:
zs = "win"
if zs in ("lin", "win", "mac"):
vol.flags["fsnt"] = zs
continue
fs = _fstab.get(vol.realpath)[0]
fs = "win" if fs in winfs else assume
htm = json.loads(vol.js_htm)
vol.flags["fsnt"] = vol.js_ls["fsnt"] = htm["fsnt"] = fs
vol.js_htm = json_hesc(json.dumps(htm))

View file

@ -515,7 +515,7 @@ class FtpHandler(FTPHandler):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "Upload blocked by xbu server config: %r" % (vp,)
self.respond("550 %s" % (t,), logging.info)

View file

@ -49,6 +49,9 @@ from .util import (
HAVE_SQLITE3,
HTTPCODE,
UTC,
VPTL_MAC,
VPTL_OS,
VPTL_WIN,
Garda,
MultipartParser,
ODict,
@ -146,19 +149,16 @@ _ = (argparse, threading)
USED4SEC = {"usedforsecurity": False} if sys.version_info > (3, 9) else {}
NO_CACHE = {"Cache-Control": "no-cache"}
ALL_COOKIES = "k304 no304 js idxh dots cppwd cppws".split()
BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff-hdr was received from an untrusted reverse-proxy)"
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
BADXFP = ', or change the copyparty global-option "xf-proto" to another header-name to read this value from. Alternatively, if your reverseproxy is not able to provide a header similar to "X-Forwarded-Proto", then you must tell copyparty which protocol to assume by setting global-option --xf-proto-fb to either http or https'
BADXFFB = "<b>NOTE: serverlog has a message regarding your reverse-proxy config</b>"
H_CONN_KEEPALIVE = "Connection: Keep-Alive"
H_CONN_CLOSE = "Connection: Close"
LOGUES = [[0, ".prologue.html"], [1, ".epilogue.html"]]
READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
A_FILE = os.stat_result(
@ -166,12 +166,14 @@ A_FILE = os.stat_result(
)
RE_CC = re.compile(r"[\x00-\x1f]") # search always faster
RE_USAFE = re.compile(r'[\x00-\x1f<>"]') # search always faster
RE_HSAFE = re.compile(r"[\x00-\x1f<>\"'&]") # search always much faster
RE_HOST = re.compile(r"[^][0-9a-zA-Z.:_-]") # search faster <=17ch
RE_MHOST = re.compile(r"^[][0-9a-zA-Z.:_-]+$") # match faster >=18ch
RE_K = re.compile(r"[^0-9a-zA-Z_-]") # search faster <=17ch
RE_HR = re.compile(r"[<>\"'&]")
RE_MDV = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[Mm][Dd])$")
RE_RSS_KW = re.compile(r"(\{[^} ]+\})")
UPARAM_CC_OK = set("doc move tree".split())
@ -221,12 +223,11 @@ class HttpCli(object):
self.log_func = conn.log_func # mypy404
self.log_src = conn.log_src # mypy404
self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
self.tls: bool = hasattr(self.s, "cipher")
self.tls = self.is_https = hasattr(self.s, "cipher")
self.is_vproxied = bool(self.args.R)
# placeholders; assigned by run()
self.keepalive = False
self.is_https = False
self.in_hdr_recv = True
self.headers: dict[str, str] = {}
self.mode = " " # http verb
@ -247,8 +248,8 @@ class HttpCli(object):
self.dl_id = ""
self.gctx = " " # additional context for garda
self.trailing_slash = True
self.uname = " "
self.pw = " "
self.uname = "*"
self.pw = ""
self.rvol = self.wvol = self.avol = empty_stringlist
self.do_log = True
self.can_read = False
@ -390,9 +391,6 @@ class HttpCli(object):
self.keepalive = "close" not in zs and (
self.http_ver != "HTTP/1.0" or zs == "keep-alive"
)
self.is_https = (
self.headers.get("x-forwarded-proto", "").lower() == "https" or self.tls
)
self.host = self.headers.get("host") or ""
if not self.host:
if self.s.family == socket.AF_UNIX:
@ -417,7 +415,7 @@ class HttpCli(object):
self.bad_xff = True
if self.args.rproxy != 9999999:
t = "global-option --rproxy %d could not be used (out-of-bounds) for the received header [%s]"
self.log(t % (self.args.rproxy, zso), c=3)
self.log(t % (self.args.rproxy, zso) + BADXFF2, c=3)
else:
zsl = [
" rproxy: %d if this client's IP-address is [%s]"
@ -426,7 +424,24 @@ class HttpCli(object):
]
t = 'could not determine the client\'s IP-address because the global-option --rproxy has not been configured, so the request-header [%s] specified by global-option --xff-hdr cannot be used safely! The raw header value was [%s]. Please see the "reverse-proxy" section in the readme. The best approach is to configure your reverse-proxy to give copyparty the exact IP-address to assume (perhaps in another header), but you may also try the following:'
t = t % (self.args.xff_hdr, zso)
self.log("%s\n\n%s\n" % (t, "\n".join(zsl)), 3)
t = "%s\n\n%s\n" % (t, "\n".join(zsl))
zs = self.headers.get(self.args.xf_proto)
t2 = "\nFurthermore, the following request-headers are also relevant, and you should check that the values below are sensible:\n\n request-header [%s] (configured with global-option --xf-proto) has the value [%s]; this should be the protocol that the webbrowser is using, so either 'http' or 'https'"
t += t2 % (self.args.xf_proto, zs or "NOT-PROVIDED")
if not zs:
t += ". Because the header is not provided by the reverse-proxy, you must either fix the reverseproxy config"
t += BADXFP
zs = self.headers.get(self.args.xf_host)
t2 = "\n\n request-header [%s] (configured with global-option --xf-host) has the value [%s]; this should be the website domain or external IP-address which the webbrowser is accessing"
t += t2 % (self.args.xf_host, zs or "NOT-PROVIDED")
if not zs:
zs = self.headers.get("host")
t2 = ". Because the header is not provided by the reverse-proxy, copyparty is using the standard [Host] header which has the value [%s]"
t += t2 % (zs or "NOT-PROVIDED")
if zs:
t += ". If that is the address that visitors are supposed to use to access your server -- or, in other words, it is not some internal address you wish to keep secret -- then the current choice of using the [Host] header is fine (usually the case)"
self.log(t + "\n\n\n", 3)
pip = self.conn.addr[0]
xffs = self.conn.xff_nm
@ -436,6 +451,7 @@ class HttpCli(object):
t += ' Note: if you are behind cloudflare, then this default header is not a good choice; please first make sure your local reverse-proxy (if any) does not allow non-cloudflare IPs from providing cf-* headers, and then add this additional global setting: "--xff-hdr=cf-connecting-ip"'
else:
t += ' Note: depending on your reverse-proxy, and/or WAF, and/or other intermediates, you may want to read the true client IP from another header by also specifying "--xff-hdr=SomeOtherHeader"'
t += BADXFF2
if "." in pip:
zs = ".".join(pip.split(".")[:2]) + ".0.0/16"
@ -448,7 +464,22 @@ class HttpCli(object):
else:
self.ip = cli_ip
self.log_src = self.conn.set_rproxy(self.ip)
self.host = self.headers.get("x-forwarded-host") or self.host
self.host = self.headers.get(self.args.xf_host, self.host)
try:
self.is_https = len(self.headers[self.args.xf_proto]) == 5
except:
if self.args.xf_proto_fb:
self.is_https = len(self.args.xf_proto_fb) == 5
else:
self.bad_xff = True
self.host = "example.com"
t = 'got proxied request without header "%s" (global-option "xf-proto"). This header must contain either "http" or "https". Either fix your reverse-proxy config to include this header%s%s'
self.log(t % (self.args.xf_proto, BADXFP, BADXFF2), 3)
# the semantics of trusted_xff and bad_xff are different;
# trusted_xff is whether the connection came from a trusted reverseproxy,
# regardless of whether the client ip detection is correctly configured
# (the primary safeguard for idp is --idp-h-key)
trusted_xff = True
m = RE_HOST.search(self.host)
@ -463,6 +494,11 @@ class HttpCli(object):
if self.is_banned():
return False
if self.conn.ipar_nm and not self.conn.ipar_nm.map(self.ip):
self.log("client rejected (--ipar)", 3)
self.terse_reply(b"", 500)
return False
if self.conn.aclose:
nka = self.conn.aclose
ip = ipnorm(self.ip)
@ -505,8 +541,7 @@ class HttpCli(object):
self.loud_reply(t, status=400)
return False
ptn_cc = RE_CC
m = ptn_cc.search(self.req)
m = RE_USAFE.search(self.req)
if m:
zs = self.req
t = "malicious user; Cc in req0 %r => %r"
@ -528,6 +563,7 @@ class HttpCli(object):
vpath = undot(vpath)
re_k = RE_K
ptn_cc = RE_CC
k_safe = UPARAM_CC_OK
for k in arglist.split("&"):
if "=" in k:
@ -610,20 +646,21 @@ class HttpCli(object):
self.loud_reply("u wot m8", status=400)
return False
if VPTL_OS:
vpath = vpath.translate(VPTL_OS)
self.uparam = uparam
self.cookies = cookies
self.vpath = vpath
self.vpaths = (
self.vpath + "/" if self.trailing_slash and self.vpath else self.vpath
)
self.vpaths = vpath + "/" if self.trailing_slash and vpath else vpath
if "qr" in uparam:
return self.tx_qr()
if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
if "\x00" in vpath or (ANYWIN and ("\n" in vpath or "\r" in vpath)):
self.log("illegal relpath; req(%r) => %r" % (self.req, "/" + self.vpath))
self.cbonk(self.conn.hsrv.gmal, self.req, "bad_vp", "invalid relpaths")
return self.tx_404() and self.keepalive
return self.tx_404() and False
zso = self.headers.get("authorization")
bauth = ""
@ -676,6 +713,9 @@ class HttpCli(object):
if self.args.idp_h_grp
else ""
)
if self.args.idp_chsub:
idp_usr = idp_usr.translate(self.args.idp_chsub_tr)
idp_grp = idp_grp.translate(self.args.idp_chsub_tr)
if not trusted_xff:
pip = self.conn.addr[0]
@ -913,29 +953,31 @@ class HttpCli(object):
return False
xban = self.vn.flags.get("xban")
if not xban or not runhook(
self.log,
self.conn.hsrv.broker,
None,
"xban",
xban,
self.vn.canonical(self.rem),
self.vpath,
self.host,
self.uname,
"",
time.time(),
0,
self.ip,
time.time(),
[reason, reason],
):
self.log("client banned: %s" % (descr,), 1)
self.conn.hsrv.bans[ip] = bonk
self.conn.hsrv.nban += 1
return True
if xban:
hr = runhook(
self.log,
self.conn.hsrv.broker,
None,
"xban",
xban,
self.vn.canonical(self.rem),
self.vpath,
self.host,
self.uname,
"",
time.time(),
0,
self.ip,
time.time(),
[reason, reason],
)
if hr.get("rv") == 0:
return False
return False
self.log("client banned: %s" % (descr,), 1)
self.conn.hsrv.bans[ip] = bonk
self.conn.hsrv.nban += 1
return True
def is_banned(self) -> bool:
if not self.conn.bans:
@ -953,13 +995,13 @@ class HttpCli(object):
return False
self.log("banned for {:.0f} sec".format(rt), 6)
self.terse_reply(b"thank you for playing (see serverlog and readme)", 403)
self.terse_reply(self.args.banmsg_b, 403)
return True
def permit_caching(self) -> None:
cache = self.uparam.get("cache")
if cache is None:
self.out_headers.update(NO_CACHE)
self.out_headers["Cache-Control"] = self.vn.flags["cachectl"]
return
n = 69 if not cache else 604869 if cache == "i" else int(cache)
@ -1136,7 +1178,10 @@ class HttpCli(object):
]
if body:
lines.append("Content-Length: " + unicode(len(body)))
lines.append(
"Content-Type: text/html; charset=utf-8\r\nContent-Length: "
+ unicode(len(body))
)
lines.append("\r\n")
self.s.sendall("\r\n".join(lines).encode("utf-8") + body)
@ -1540,18 +1585,31 @@ class HttpCli(object):
ap = ""
use_magic = "rmagic" in self.vn.flags
tpl_t = self.uparam.get("fmt_t") or self.vn.flags["rss_fmt_t"]
tpl_d = self.uparam.get("fmt_d") or self.vn.flags["rss_fmt_d"]
kw_t = [[x, x[1:-1]] for x in RE_RSS_KW.findall(tpl_t)]
kw_d = [[x, x[1:-1]] for x in RE_RSS_KW.findall(tpl_d)]
for i in hits:
if use_magic:
ap = os.path.join(self.vn.realpath, i["rp"])
tags = i["tags"]
iurl = html_escape("%s%s" % (baseurl, i["rp"]), True, True)
title = unquotep(i["rp"].split("?")[0].split("/")[-1])
title = html_escape(title, True, True)
tag_t = str(i["tags"].get("title") or "")
tag_a = str(i["tags"].get("artist") or "")
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
desc = html_escape(desc, True, True) if desc else title
mime = html_escape(guess_mime(title, ap))
fname = tags["fname"] = unquotep(i["rp"].split("?")[0].split("/")[-1])
title = tpl_t
desc = tpl_d
for zs1, zs2 in kw_t:
title = title.replace(zs1, str(tags.get(zs2, "")))
for zs1, zs2 in kw_d:
desc = desc.replace(zs1, str(tags.get(zs2, "")))
title = html_escape(title.strip(), True, True)
if desc.strip(" -,"):
desc = html_escape(desc.strip(), True, True)
else:
desc = title
mime = html_escape(guess_mime(fname, ap))
lmod = formatdate(max(0, i["ts"]))
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
zs = (
@ -2386,7 +2444,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r" % (vp,)
self.log(t, 1)
@ -2521,7 +2579,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xau server config: %r" % (vp,)
self.log(t, 1)
@ -2792,6 +2850,11 @@ class HttpCli(object):
raise Pebkac(400, "your client is old; press CTRL-SHIFT-R and try again")
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
fsnt = vfs.flags["fsnt"]
if fsnt != "lin":
tl = VPTL_WIN if fsnt == "win" else VPTL_MAC
rem = rem.translate(tl)
name = name.translate(tl)
dbv, vrem = vfs.get_dbv(rem)
name = sanitize_fn(name, "")
@ -3359,7 +3422,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "new-md blocked by " + hn + " server config: %r"
t = t % (vjoin(vfs.vpath, rem),)
@ -3530,7 +3593,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r"
t = t % (vjoin(upload_vpath, fname),)
@ -3637,7 +3700,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xau server config: %r"
t = t % (vjoin(upload_vpath, fname),)
@ -3950,7 +4013,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "save blocked by xbu server config"
self.log(t, 1)
@ -3998,7 +4061,7 @@ class HttpCli(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "save blocked by xau server config"
self.log(t, 1)
@ -4108,40 +4171,36 @@ class HttpCli(object):
self, vn: VFS, abspath: str, lnames: Optional[dict[str, str]]
) -> tuple[list[str], list[str]]:
logues = ["", ""]
if not self.args.no_logues:
for n, fn in LOGUES:
if lnames is not None and fn not in lnames:
continue
for n, fns1, fns2 in [] if self.args.no_logues else vn.flags["emb_lgs"]:
for fn in fns1 if lnames is None else fns2:
if lnames is not None:
fn = lnames.get(fn)
if not fn:
continue
fn = "%s/%s" % (abspath, fn)
if bos.path.isfile(fn):
logues[n] = read_utf8(self.log, fsenc(fn), False)
if "exp" in vn.flags:
logues[n] = self._expand(
logues[n], vn.flags.get("exp_lg") or []
)
if not bos.path.isfile(fn):
continue
logues[n] = read_utf8(self.log, fsenc(fn), False)
if "exp" in vn.flags:
logues[n] = self._expand(logues[n], vn.flags.get("exp_lg") or [])
break
readmes = ["", ""]
for n, fns in [] if self.args.no_readme else READMES:
for n, fns1, fns2 in [] if self.args.no_readme else vn.flags["emb_mds"]:
if logues[n]:
continue
elif lnames is None:
pass
elif fns[0] in lnames:
fns = [lnames[fns[0]]]
else:
fns = []
txt = ""
for fn in fns:
for fn in fns1 if lnames is None else fns2:
if lnames is not None:
fn = lnames.get(fn.lower())
if not fn:
continue
fn = "%s/%s" % (abspath, fn)
if bos.path.isfile(fn):
txt = read_utf8(self.log, fsenc(fn), False)
break
if txt and "exp" in vn.flags:
txt = self._expand(txt, vn.flags.get("exp_md") or [])
readmes[n] = txt
if not bos.path.isfile(fn):
continue
readmes[n] = read_utf8(self.log, fsenc(fn), False)
if "exp" in vn.flags:
readmes[n] = self._expand(readmes[n], vn.flags.get("exp_md") or [])
break
return logues, readmes
@ -5128,7 +5187,7 @@ class HttpCli(object):
file_ts = int(max(ts_md, self.E.t0))
file_lastmod, do_send, _ = self._chk_lastmod(file_ts)
self.out_headers["Last-Modified"] = file_lastmod
self.out_headers.update(NO_CACHE)
self.out_headers["Cache-Control"] = "no-cache"
status = 200 if do_send else 304
arg_base = "?"
@ -5267,10 +5326,11 @@ class HttpCli(object):
fdone = max(0.001, 1 - rem)
td = max(0.1, now - t0)
rd, fn = vsplit(vp.replace(os.sep, "/"))
if not rd:
rd = "/"
erd = quotep(rd)
rds = rd.replace("/", " / ")
if rd:
rds = rd.replace("/", " / ")
erd = "/%s/" % (quotep(rd),)
else:
erd = rds = "/"
spd = humansize(sz * fdone / td, True) + "/s"
eta = s2hms((td / fdone) - td, True) if rem < 1 else "--"
idle = s2hms(now - poke, True)
@ -5297,10 +5357,11 @@ class HttpCli(object):
for t0, t1, sent, sz, vp, dl_id, uname in dl_list:
td = max(0.1, now - t0)
rd, fn = vsplit(vp)
if not rd:
rd = "/"
erd = quotep(rd)
rds = rd.replace("/", " / ")
if rd:
rds = rd.replace("/", " / ")
erd = "/%s/" % (quotep(rd),)
else:
erd = rds = "/"
spd = humansize(sent / td, True) + "/s"
hsent = humansize(sent, True)
idle = s2hms(now - t1, True)
@ -5394,6 +5455,7 @@ class HttpCli(object):
no304=self.no304(),
k304vis=self.args.k304 > 0,
no304vis=self.args.no304 > 0,
msg=BADXFFB if hasattr(self, "bad_xff") else "",
ver=S_VERSION if show_ver else "",
chpw=self.args.chpw and self.uname != "*",
ahttps="" if self.is_https else "https://" + self.host + self.req,
@ -5404,7 +5466,7 @@ class HttpCli(object):
def setck(self) -> bool:
k, v = self.uparam["setck"].split("=", 1)
t = 0 if v in ("", "x") else 86400 * 299
ck = gencookie(k, v, self.args.R, self.args.cookie_lax, False, t)
ck = gencookie(k, v, self.args.R, True, False, t)
self.out_headerlist.append(("Set-Cookie", ck))
if "cc" in self.ouparam:
self.redirect("", "?h#cc")
@ -5416,7 +5478,7 @@ class HttpCli(object):
for k in ALL_COOKIES:
if k not in self.cookies:
continue
cookie = gencookie(k, "x", self.args.R, self.args.cookie_lax, False)
cookie = gencookie(k, "x", self.args.R, True, False)
self.out_headerlist.append(("Set-Cookie", cookie))
self.redirect("", "?h#cc")
@ -5450,8 +5512,9 @@ class HttpCli(object):
rc == 403
and self.uname == "*"
and "sec-fetch-site" not in self.headers
and self.cookies.get("js") != "y"
and (
not self.ua.startswith("Mozilla/")
not self.args.ua_nodav.search(self.ua)
or (self.args.dav_ua1 and self.args.dav_ua1.search(self.ua))
)
):
@ -5713,17 +5776,18 @@ class HttpCli(object):
and (self.uname in vol.axs.uread or self.uname in vol.axs.upget)
}
bad_xff = hasattr(self, "bad_xff")
if bad_xff:
if hasattr(self, "bad_xff"):
allvols = []
t = "will not return list of recent uploads" + BADXFF
self.log(t, 1)
if self.avol:
raise Pebkac(500, t)
x = self.conn.hsrv.broker.ask(
"up2k.get_unfinished_by_user", self.uname, "" if bad_xff else self.ip
)
x = self.conn.hsrv.broker.ask("up2k.get_unfinished_by_user", self.uname, "")
else:
x = self.conn.hsrv.broker.ask(
"up2k.get_unfinished_by_user", self.uname, self.ip
)
zdsa: dict[str, Any] = x.get()
uret: list[dict[str, Any]] = []
if "timeout" in zdsa:
@ -6227,6 +6291,9 @@ class HttpCli(object):
self.log("unpost was denied" + BADXFF, 1)
raise Pebkac(403, "the delete feature is disabled in server config")
if not unpost and self.vn.shr_src:
raise Pebkac(403, "files in shares can only be deleted with unpost")
if not req:
req = [self.vpath]
elif self.is_vproxied:

View file

@ -62,6 +62,7 @@ class HttpConn(object):
self.ipu_iu: Optional[dict[str, str]] = hsrv.ipu_iu
self.ipu_nm: Optional[NetMap] = hsrv.ipu_nm
self.ipa_nm: Optional[NetMap] = hsrv.ipa_nm
self.ipar_nm: Optional[NetMap] = hsrv.ipar_nm
self.xff_nm: Optional[NetMap] = hsrv.xff_nm
self.xff_lan: NetMap = hsrv.xff_lan # type: ignore
self.iphash: HMaccas = hsrv.broker.iphash

View file

@ -201,6 +201,7 @@ class HttpSrv(object):
self.ipr = None
self.ipa_nm = build_netmap(self.args.ipa)
self.ipar_nm = build_netmap(self.args.ipar)
self.xff_nm = build_netmap(self.args.xff_src)
self.xff_lan = build_netmap("lan")

View file

@ -523,7 +523,7 @@ class MTag(object):
],
".tn": ["tracknumber", "trck", "trkn", "track"],
"genre": ["genre", "tcon", "\u00a9gen"],
"date": [
"tdate": [
"original-release-date",
"release-date",
"date",

View file

@ -265,7 +265,7 @@ class SMB(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "blocked by xbu server config: %r" % (vpath,)
yeet(t)

View file

@ -79,6 +79,7 @@ from .util import (
start_stackmon,
termsize,
ub64enc,
umktrans,
)
if HAVE_SQLITE3:
@ -1087,7 +1088,7 @@ class SvcHub(object):
vsa = [x.lower() for x in vsa if x]
setattr(al, k + "_set", set(vsa))
zs = "dav_ua1 sus_urls nonsus_urls ua_nodoc ua_nozip"
zs = "dav_ua1 sus_urls nonsus_urls ua_nodav ua_nodoc ua_nozip"
for k in zs.split(" "):
vs = getattr(al, k)
if not vs or vs == "no":
@ -1102,6 +1103,12 @@ class SvcHub(object):
else:
setattr(al, k, re.compile("^" + vs + "$"))
if al.banmsg.startswith("@"):
with open(al.banmsg[1:], "rb") as f:
al.banmsg_b = f.read()
else:
al.banmsg_b = al.banmsg.encode("utf-8") + b"\n"
if not al.sus_urls:
al.ban_url = "no"
elif al.ban_url == "no":
@ -1125,8 +1132,19 @@ class SvcHub(object):
except:
raise Exception("invalid --idp-hm-usr [%s]" % (zs0,))
al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa, True)
al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa, True)
zs1 = ""
zs2 = ""
zs = al.idp_chsub
while zs:
if zs[:1] != "|":
raise Exception("invalid --idp-chsub; expected another | but got " + zs)
zs1 += zs[1:2]
zs2 += zs[2:3]
zs = zs[3:]
al.idp_chsub_tr = umktrans(zs1, zs2)
al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa or al.ipar, True)
al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa or al.ipar, True)
mte = ODict.fromkeys(DEF_MTE.split(","), True)
al.mte = odfusion(mte, al.mte)
@ -1552,6 +1570,9 @@ class SvcHub(object):
with self.log_mutex:
dt = datetime.now(self.tz)
if dt.day != self.cday or dt.month != self.cmon:
if self.args.log_date:
zs = dt.strftime(self.args.log_date)
self.log_efmt = "%s %s" % (zs, self.log_efmt.split(" ")[-1])
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
zs = zs.format(dt.strftime("%Y-%m-%d"))
print(zs, end="")

View file

@ -382,7 +382,7 @@ class Tftpd(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r" % (vpath,)
yeet(t)

View file

@ -14,7 +14,7 @@ import time
from queue import Queue
from .__init__ import ANYWIN, PY2, TYPE_CHECKING
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, unicode
from .authsrv import VFS
from .bos import bos
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
@ -56,6 +56,56 @@ EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
# for n in {1..100}; do rm -rf /home/ed/Pictures/wp/.hist/th/ ; python3 -m copyparty -qv /home/ed/Pictures/wp/::r --th-no-webp --th-qv $n --th-dec pil >/dev/null 2>&1 & p=$!; printf '\033[A\033[J%3d ' $n; while true; do sleep 0.1; curl -s 127.1:3923 >/dev/null && break; done; curl -s '127.1:3923/?tar=j' >/dev/null ; cat /home/ed/Pictures/wp/.hist/th/1n/bs/1nBsjDetfie1iDq3y2D4YzF5/*.* | wc -c; kill $p; wait >/dev/null 2>&1; done
# filesize-equivalent, not quality (ff looks much shittier)
FF_JPG_Q = {
0: b"30", # 0
1: b"30", # 5
2: b"30", # 10
3: b"30", # 15
4: b"28", # 20
5: b"21", # 25
6: b"17", # 30
7: b"15", # 35
8: b"13", # 40
9: b"12", # 45
10: b"11", # 50
11: b"10", # 55
12: b"9", # 60
13: b"8", # 65
14: b"7", # 70
15: b"6", # 75
16: b"5", # 80
17: b"4", # 85
18: b"3", # 90
19: b"2", # 95
20: b"2", # 100
}
# FF_JPG_Q = {xn: ("%d" % (xn,)).encode("ascii") for xn in range(2, 33)}
VIPS_JPG_Q = {
0: 4, # 0
1: 7, # 5
2: 12, # 10
3: 17, # 15
4: 22, # 20
5: 27, # 25
6: 32, # 30
7: 37, # 35
8: 42, # 40
9: 47, # 45
10: 52, # 50
11: 56, # 55
12: 61, # 60
13: 66, # 65
14: 71, # 70
15: 75, # 75
16: 80, # 80
17: 85, # 85
18: 89, # 90 (vips explodes past this point)
19: 91, # 95
20: 97, # 100
}
try:
if os.environ.get("PRTY_NO_PIL"):
@ -308,6 +358,7 @@ class ThumbSrv(object):
if not bos.path.exists(inf_path):
with open(inf_path, "wb") as f:
f.write(afsenc(os.path.dirname(abspath)))
self.writevolcfg(histpath)
self.busy[tpath] = [cond]
do_conv = True
@ -351,6 +402,47 @@ class ThumbSrv(object):
"ffa": self.fmt_ffa,
}
def volcfgi(self, vn: VFS) -> str:
ret = []
zs = "th_dec th_no_webp th_no_jpg"
for zs in zs.split(" "):
ret.append("%s(%s)\n" % (zs, getattr(self.args, zs)))
zs = "th_qv thsize th_spec_p convt"
for zs in zs.split(" "):
ret.append("%s(%s)\n" % (zs, vn.flags.get(zs)))
return "".join(ret)
def volcfga(self, vn: VFS) -> str:
ret = []
zs = "q_opus q_mp3"
for zs in zs.split(" "):
ret.append("%s(%s)\n" % (zs, getattr(self.args, zs)))
zs = "aconvt"
for zs in zs.split(" "):
ret.append("%s(%s)\n" % (zs, vn.flags.get(zs)))
return "".join(ret)
def writevolcfg(self, histpath: str) -> None:
try:
bos.stat(os.path.join(histpath, "th", "cfg.txt"))
bos.stat(os.path.join(histpath, "ac", "cfg.txt"))
return
except:
pass
cfgi = cfga = ""
for vn in self.asrv.vfs.all_vols.values():
if vn.histpath == histpath:
cfgi = self.volcfgi(vn)
cfga = self.volcfga(vn)
break
t = "writing thumbnailer-config %d,%d to %s"
self.log(t % (len(cfgi), len(cfga), histpath))
chmod = bos.MKD_700 if self.args.free_umask else bos.MKD_755
for cfg, cat in ((cfgi, "th"), (cfga, "ac")):
bos.makedirs(os.path.join(histpath, cat), vf=chmod)
with open(os.path.join(histpath, cat, "cfg.txt"), "wb") as f:
f.write(cfg.encode("utf-8"))
def wait4ram(self, need: float, ttpath: str) -> None:
ram = self.args.th_ram_max
if need > ram * 0.99:
@ -529,7 +621,7 @@ class ThumbSrv(object):
im.thumbnail(self.getres(vn, fmt))
fmts = ["RGB", "L"]
args = {"quality": 40}
args = {"quality": vn.flags["th_qv"]}
if tpath.endswith(".webp"):
# quality 80 = pillow-default
@ -573,7 +665,12 @@ class ThumbSrv(object):
raise
assert img # type: ignore # !rm
img.write_to_file(tpath, Q=40)
args = {}
qv = vn.flags["th_qv"]
if tpath.endswith("jpg"):
qv = VIPS_JPG_Q[qv // 5]
args["optimize_coding"] = True
img.write_to_file(tpath, Q=qv, strip=True, **args)
def conv_raw(self, abspath: str, tpath: str, fmt: str, vn: VFS) -> None:
self.wait4ram(0.2, tpath)
@ -607,7 +704,12 @@ class ThumbSrv(object):
raise
assert img # type: ignore # !rm
img.write_to_file(tpath, Q=40)
args = {}
qv = vn.flags["th_qv"]
if tpath.endswith("jpg"):
qv = VIPS_JPG_Q[qv // 5]
args["optimize_coding"] = True
img.write_to_file(tpath, Q=qv, strip=True, **args)
elif HAVE_PIL:
if thumb.format == rawpy.ThumbFormat.BITMAP:
im = Image.fromarray(thumb.data, "RGB")
@ -671,12 +773,12 @@ class ThumbSrv(object):
if tpath.endswith(".jpg"):
cmd += [
b"-q:v",
b"6", # default=??
FF_JPG_Q[vn.flags["th_qv"] // 5], # default=??
]
else:
cmd += [
b"-q:v",
b"50", # default=75
unicode(vn.flags["th_qv"]).encode("ascii"), # default=75
b"-compression_level:v",
b"6", # default=4, 0=fast, 6=max
]
@ -722,7 +824,7 @@ class ThumbSrv(object):
if len(lines) > 50:
lines = lines[:25] + ["[...]"] + lines[-25:]
txt = "\n".join(["ff: " + str(x) for x in lines])
txt = "\n".join(["ff: " + unicode(x) for x in lines])
if len(txt) > 5000:
txt = txt[:2500] + "...\nff: [...]\nff: ..." + txt[-2500:]
@ -880,12 +982,12 @@ class ThumbSrv(object):
if tpath.endswith(".jpg"):
cmd += [
b"-q:v",
b"6", # default=??
FF_JPG_Q[vn.flags["th_qv"] // 5], # default=??
]
else:
cmd += [
b"-q:v",
b"50", # default=75
unicode(vn.flags["th_qv"]).encode("ascii"), # default=75
b"-compression_level:v",
b"6", # default=4, 0=fast, 6=max
]
@ -1143,7 +1245,7 @@ class ThumbSrv(object):
ret = []
for k, vs in raw_tags.items():
for v in vs:
if len(str(v)) >= 1024:
if len(unicode(v)) >= 1024:
bv = k.encode("utf-8", "replace")
ret += [b"-metadata", bv + b"="]
break
@ -1181,6 +1283,28 @@ class ThumbSrv(object):
time.sleep(interval)
def clean(self, histpath: str) -> int:
cfgi = cfga = ""
for vn in self.asrv.vfs.all_vols.values():
if vn.histpath == histpath:
cfgi = self.volcfgi(vn)
cfga = self.volcfga(vn)
break
for cfg, cat in ((cfgi, "th"), (cfga, "ac")):
if not cfg:
continue
try:
with open(os.path.join(histpath, cat, "cfg.txt"), "rb") as f:
oldcfg = f.read().decode("utf-8")
except:
oldcfg = ""
if cfg == oldcfg:
continue
zs = os.path.join(histpath, cat)
if not os.path.exists(zs):
continue
self.log("thumbnailer-config changed; deleting %s" % (zs,), 3)
shutil.rmtree(zs)
ret = 0
for cat in ["th", "ac"]:
top = os.path.join(histpath, cat)
@ -1239,7 +1363,7 @@ class ThumbSrv(object):
if len(b64) != 24 or len(ts) != 8 or ext not in exts:
raise Exception()
except:
if f != "dir.txt":
if f != "dir.txt" and f != "cfg.txt":
self.log("foreign file in thumbs dir: [{}]".format(fp), 1)
continue

View file

@ -1148,7 +1148,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 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"
zs = "bcasechk du_iwho emb_lgs emb_mds 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())
@ -3307,7 +3307,7 @@ class Up2k(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r"
t = t % (vp,)
@ -4003,7 +4003,7 @@ class Up2k(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xau server config: %r"
t = t % (djoin(vtop, rd, fn),)
@ -4221,7 +4221,7 @@ class Up2k(object):
_ = dbv.get(volpath, uname, *permsets[0])
if xbd:
if not runhook(
hr = runhook(
self.log,
None,
self,
@ -4237,9 +4237,12 @@ class Up2k(object):
ip,
time.time(),
None,
):
t = "delete blocked by xbd server config: %r"
self.log(t % (abspath,), 1)
)
t = hr.get("rejectmsg") or ""
if t or hr.get("rc") != 0:
if not t:
t = "delete blocked by xbd server config: %r" % (abspath,)
self.log(t, 1)
continue
n_files += 1
@ -4389,7 +4392,7 @@ class Up2k(object):
xbc = svn.flags.get("xbc")
xac = dvn.flags.get("xac")
if xbc:
if not runhook(
hr = runhook(
self.log,
None,
self,
@ -4405,8 +4408,11 @@ class Up2k(object):
ip,
time.time(),
None,
):
t = "copy blocked by xbr server config: %r" % (svp,)
)
t = hr.get("rejectmsg") or ""
if t or hr.get("rc") != 0:
if not t:
t = "copy blocked by xbr server config: %r" % (svp,)
self.log(t, 1)
raise Pebkac(405, t)
@ -4641,7 +4647,7 @@ class Up2k(object):
xbr = svn.flags.get("xbr")
xar = dvn.flags.get("xar")
if xbr:
if not runhook(
hr = runhook(
self.log,
None,
self,
@ -4657,8 +4663,11 @@ class Up2k(object):
ip,
time.time(),
None,
):
t = "move blocked by xbr server config: %r" % (svp,)
)
t = hr.get("rejectmsg") or ""
if t or hr.get("rc") != 0:
if not t:
t = "move blocked by xbr server config: %r" % (svp,)
self.log(t, 1)
raise Pebkac(405, t)
@ -5163,7 +5172,7 @@ class Up2k(object):
None,
)
t = hr.get("rejectmsg") or ""
if t or not hr:
if t or hr.get("rc") != 0:
if not t:
t = "upload blocked by xbu server config: %r" % (vp_chk,)
self.log(t, 1)

View file

@ -294,6 +294,23 @@ RE_MEMTOTAL = re.compile("^MemTotal:.* kB")
RE_MEMAVAIL = re.compile("^MemAvailable:.* kB")
if PY2:
def umktrans(s1, s2):
return {ord(c1): ord(c2) for c1, c2 in zip(s1, s2)}
else:
umktrans = str.maketrans
FNTL_WIN = umktrans('<>:|?*"\\/', "")
VPTL_WIN = umktrans('<>:|?*"\\', "")
APTL_WIN = umktrans('<>:|?*"/', "")
FNTL_MAC = VPTL_MAC = APTL_MAC = umktrans(":", "")
FNTL_OS = FNTL_WIN if ANYWIN else FNTL_MAC if MACOS else None
VPTL_OS = VPTL_WIN if ANYWIN else VPTL_MAC if MACOS else None
APTL_OS = APTL_WIN if ANYWIN else APTL_MAC if MACOS else None
BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
@ -470,9 +487,9 @@ 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_MTE = ".files,circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
DEF_MTE = ".files,circle,album,.tn,artist,title,tdate,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
DEF_MTH = ".vq,.aq,vc,ac,fmt,res,.fps"
DEF_MTH = "tdate,.vq,.aq,vc,ac,fmt,res,.fps"
REKOBO_KEY = {
@ -684,7 +701,7 @@ except Exception as ex:
ub64dec = base64.urlsafe_b64decode # type: ignore
b64enc = base64.b64encode # type: ignore
b64dec = base64.b64decode # type: ignore
if not PY36:
if PY36:
print("using fallback base64 codec due to %r" % (ex,))
@ -2232,32 +2249,22 @@ def sanitize_fn(fn: str, ok: str) -> str:
if "/" not in ok:
fn = fn.replace("\\", "/").split("/")[-1]
if ANYWIN:
remap = [
["<", ""],
[">", ""],
[":", ""],
['"', ""],
["/", ""],
["\\", ""],
["|", ""],
["?", ""],
["*", ""],
]
for a, b in [x for x in remap if x[0] not in ok]:
fn = fn.replace(a, b)
if APTL_OS:
fn = fn.translate(APTL_OS)
if ANYWIN:
bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10):
bad += ("com%s lpt%s" % (n, n)).split(" ")
bad = ["con", "prn", "aux", "nul"]
for n in range(1, 10):
bad += ("com%s lpt%s" % (n, n)).split(" ")
if fn.lower().split(".")[0] in bad:
fn = "_" + fn
if fn.lower().split(".")[0] in bad:
fn = "_" + fn
return fn.strip()
def sanitize_vpath(vp: str, ok: str) -> str:
if not FNTL_OS:
return vp
parts = vp.replace(os.sep, "/").split("/")
ret = [sanitize_fn(x, ok) for x in parts]
return "/".join(ret)
@ -3930,7 +3937,13 @@ def _runhook(
zi, zs = _zmq_hook(log, verbose, src, acmd[0][4:].lower(), arg, wait, sp_ka)
if zi:
raise Exception("zmq says %d" % (zi,))
return {"rc": 0, "stdout": zs}
try:
ret = json.loads(zs)
if "rc" not in ret:
ret["rc"] = 0
return ret
except:
return {"rc": 0, "stdout": zs}
if sin:
sp_ka["sin"] = (arg + "\n").encode("utf-8", "replace")
@ -3949,20 +3962,23 @@ def _runhook(
rc, v, err = runcmd(bcmd, **sp_ka) # type: ignore
if chk and rc:
ret["rc"] = rc
retchk(rc, bcmd, err, log, 5)
zi = 0 if rc == 100 else rc
retchk(zi, bcmd, err, log, 5)
else:
try:
ret = json.loads(v)
except:
ret = {}
pass
try:
if "stdout" not in ret:
ret["stdout"] = v
if "stderr" not in ret:
ret["stderr"] = err
if "rc" not in ret:
ret["rc"] = rc
except:
ret = {"rc": rc, "stdout": v}
ret = {"rc": rc, "stdout": v, "stderr": err}
if wait:
wait -= time.time() - t0
@ -3994,6 +4010,7 @@ def runhook(
verbose = args.hook_v
vp = vp.replace("\\", "/")
ret = {"rc": 0}
stop = False
for cmd in cmds:
try:
hr = _runhook(
@ -4001,8 +4018,6 @@ def runhook(
)
if verbose and log:
log("hook(%s) %r => \033[32m%s" % (src, cmd, hr), 6)
if not hr:
return {}
for k, v in hr.items():
if k in ("idx", "del") and v:
if broker:
@ -4013,17 +4028,20 @@ def runhook(
elif k == "reloc" and v:
# idk, just take the last one ig
ret["reloc"] = v
elif k == "rc" and v:
stop = True
ret[k] = 0 if v == 100 else v
elif k in ret:
if k == "rc" and v:
ret[k] = v
elif k == "stdout" and v and not ret[k]:
if k == "stdout" and v and not ret[k]:
ret[k] = v
else:
ret[k] = v
except Exception as ex:
(log or print)("hook: %r, %s" % (ex, ex))
if ",c," in "," + cmd:
return {}
return {"rc": 1}
break
if stop:
break
return ret
@ -4164,7 +4182,12 @@ def wrap(txt: str, maxlen: int, maxlen2: int) -> list[str]:
def termsize() -> tuple[int, int]:
# from hashwalk
try:
w, h = os.get_terminal_size()
return w, h
except:
pass
env = os.environ
def ioctl_GWINSZ(fd: int) -> Optional[tuple[int, int]]:

View file

@ -61,6 +61,15 @@ window.baguetteBox = (function () {
hideOverlay();
};
var vtouch = function (e) {
var v = vid(),
bv = v.getBoundingClientRect(),
tp = e.changedTouches[0];
if (bv.bottom - tp.clientY < 90)
touchFlag = true;
};
var touchstartHandler = function (e) {
touch.count = e.touches.length;
if (touch.count > 1)
@ -850,6 +859,7 @@ window.baguetteBox = (function () {
if (v == keep)
continue;
unbind(v, 'touchstart', vtouch, nonPassiveEvent);
unbind(v, 'error', lerr);
v.src = '';
v.load();
@ -1278,6 +1288,7 @@ window.baguetteBox = (function () {
setloop();
}
}
bind(v, 'touchstart', vtouch, nonPassiveEvent);
}
selbg();
mp_ctl();

View file

@ -3147,9 +3147,6 @@ html.bz .ghead {
html.b #files td {
padding: .5em .7em;
}
html.b #ggrid>a {
margin: .8em;
}
html.b .btn {
top: -.1em;
}
@ -3294,8 +3291,8 @@ html.d #treepar {
}
html.b #ggrid {
padding: 0 2em 2em 0;
gap: .5em 3em;
padding: 0 0 2em 0;
gap: 1em 1.5em;
}
#ggrid > a {

View file

@ -53,7 +53,7 @@
📝<input type="text" name="name" class="i" placeholder="weekend-plans">
<input type="submit" value="new file">
</form>
<span id="new_mdi"></p>
<span id="new_mdi"></span>
</div>
<div id="op_msg" class="opview opbox {% if not ls0 %}act{% endif %}">

View file

@ -87,6 +87,8 @@ if (1)
["M", "close textfile"],
["E", "edit textfile"],
["S", "select file (for cut/copy/rename)"],
["Y", "download textfile"],
["⇧ J", "beautify json"],
]
],
@ -226,6 +228,7 @@ if (1)
"ct_ttips": '◔ ◡ ◔"> tooltips',
"ct_thumb": 'in grid-view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
"ct_csel": 'use CTRL and SHIFT for file selection in grid-view">sel',
"ct_dl": 'force download (don\'t display inline) when a file is clicked">dl',
"ct_ihop": 'when the image viewer is closed, scroll down to the last viewed file">g⮯',
"ct_dots": 'show hidden files (if server permits)">dotfiles',
"ct_qdel": 'when deleting files, only ask for confirmation once">qdel',
@ -452,6 +455,7 @@ if (1)
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
"tvt_next": "show next document$NHotkey: K\">⬇ next",
"tvt_sel": "select file &nbsp; ( for cut / copy / delete / ... )$NHotkey: S\">sel",
"tvt_j": "beautify json$NHotkey: shift-J\">j",
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
"tvt_tail": "monitor file for changes; show new lines in real time\">📡 follow",
"tvt_wrap": "word-wrap\">↵",
@ -660,6 +664,7 @@ var LANGN = [
["swe", "Svenska"],
["tur", "Türkçe"],
["ukr", "Українська"],
["vie", "Tiếng Việt"],
];
if (window.langmod)
@ -878,6 +883,7 @@ ebi('op_cfg').innerHTML = (
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">' + L.ct_grid + '</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '</a>\n' +
' <a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '</a>\n' +
' <a id="dlni" class="tgl btn" href="#" tt="' + L.ct_dl + '</a>\n' +
' <a id="ihop" class="tgl btn" href="#" tt="' + L.ct_ihop + '</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '</a>\n' +
' <a id="qdel" class="tgl btn" href="#" tt="' + L.ct_qdel + '</a>\n' +
@ -1134,6 +1140,20 @@ var ACtx = !IPHONE && (window.AudioContext || window.webkitAudioContext),
dk, mp;
var x = '';
if (!fullui) {
if (window.ui_nombar || /[?&]nombar\b/.exec(sloc0)) x += '#ops,';
if (window.ui_noacci || /[?&]noacci\b/.exec(sloc0)) x += '#acc_info,';
if (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0)) x += '#srv_info,#srv_info2,';
if (window.ui_nocpla || /[?&]nocpla\b/.exec(sloc0)) x += '#goh,';
if (window.ui_nolbar || /[?&]nolbar\b/.exec(sloc0)) x += '#wfp,';
if (window.ui_noctxb || /[?&]noctxb\b/.exec(sloc0)) x += '#wtoggle,';
if (window.ui_norepl || /[?&]norepl\b/.exec(sloc0)) x += '#repl,';
}
if (x)
document.head.appendChild(mknod('style', '', x.slice(0, -1) + '{display:none!important}'));
if (location.pathname.indexOf('//') === 0)
hist_replace(location.pathname.replace(/^\/+/, '/'));
@ -1237,6 +1257,7 @@ var mpl = (function () {
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
'traversals': 0,
'm3ut': '#EXTM3U\n',
'np': [{'file': 'nothing'}, ['file']],
};
bcfg_bind(r, 'one', 'au_one', false, function (v) {
if (mp.au)
@ -1433,7 +1454,7 @@ var mpl = (function () {
if (!r.os_ctl || !mp.au)
return;
var np = get_np()[0],
var np = mpl.np[0],
fns = np.file.split(' - '),
artist = (np.circle && np.circle != np.artist ? np.circle + ' // ' : '') + (np.artist || (fns.length > 1 ? fns[0] : '')),
title = np.title || fns.pop(),
@ -1779,12 +1800,6 @@ function ft2dict(tr, skip) {
}
function get_np() {
var tr = QS('#files tr.play');
return ft2dict(tr, { 'up_ip': 1 });
};
// toggle player widget
var widget = (function () {
var r = {},
@ -1842,9 +1857,8 @@ var widget = (function () {
ck = irc ? '06' : '',
cv = irc ? '07' : '',
m = ck + 'np: ',
npr = get_np(),
npk = npr[1],
np = npr[0];
npk = mpl.np[1],
np = mpl.np[0];
for (var a = 0; a < npk.length; a++)
m += (npk[a] == 'file' ? '' : npk[a]).replace(/^\./, '') + '(' + cv + np[npk[a]] + ck + ') // ';
@ -2543,6 +2557,9 @@ var mpui = (function () {
if (mpl.prescan_evp == evp)
throw "evp match";
if (treectl.trunc)
return treectl.showmore(99999, repreload);
if (mpl.traversals++ > 4) {
mpl.prescan_evp = null;
toast.inf(10, L.mm_nof);
@ -3019,6 +3036,9 @@ function play(tid, is_ev, seek) {
}
if (tn >= mp.order.length) {
if (treectl.trunc)
return treectl.showmore(99999, next_song);
if (mpl.pb_mode == 'loop' || ebi('unsearch')) {
tn = 0;
}
@ -3092,9 +3112,12 @@ function play(tid, is_ev, seek) {
for (var a = 0, aa = trs.length; a < aa; a++)
clmod(trs[a], 'play');
var oid = 'a' + tid;
clmod(ebi(oid), 'act', 1);
clmod(ebi(oid).closest('tr'), 'play', 1);
var oid = 'a' + tid,
t_a = ebi(oid),
t_tr = t_a.closest('tr');
clmod(t_a, 'act', 1);
clmod(t_tr, 'play', 1);
clmod(ebi('wtoggle'), 'np', mpl.clip);
clmod(ebi('wtoggle'), 'm3u', mpl.m3uen);
if (thegrid)
@ -3116,12 +3139,12 @@ function play(tid, is_ev, seek) {
}
if (!seek && !ebi('unsearch')) {
var o = ebi(oid);
o.setAttribute('id', 'thx_js');
t_a.setAttribute('id', 'thx_js');
if (mpl.aplay)
sethash(oid + getsort());
o.setAttribute('id', oid);
t_a.setAttribute('id', oid);
}
mpl.np = ft2dict(t_tr, { 'up_ip': 1 });
pbar.unwave();
if (mpl.waves)
@ -3136,7 +3159,7 @@ function play(tid, is_ev, seek) {
catch (ex) {
toast.err(0, esc(L.mm_playerr + basenames(ex)));
}
clmod(ebi(oid), 'act');
clmod(t_a, 'act');
mpl.t_eplay = setTimeout(next_song, 5000);
}
@ -3825,7 +3848,7 @@ var fileman = (function () {
'<tr><td>perms</td><td class="sh_axs">',
];
for (var a = 0; a < perms.length; a++)
if (!has(['admin', 'move'], perms[a]))
if (!has(['admin', 'move', 'delete'], perms[a]))
html.push('<a href="#" class="tgl btn">' + perms[a] + '</a>');
if (has(perms, 'write'))
@ -4771,6 +4794,7 @@ var showfile = (function () {
'.log': 'ans',
'.m': 'matlab',
'.moon': 'moonscript',
'.nfo': 'ans',
'.patch': 'diff',
'.ps1': 'powershell',
'.psm1': 'powershell',
@ -4778,6 +4802,7 @@ var showfile = (function () {
'.rs': 'rust',
'.sh': 'bash',
'.service': 'systemd',
'.txt': 'ans',
'.vb': 'vbnet',
'.v': 'verilog',
'.vert': 'glsl',
@ -4791,7 +4816,8 @@ var showfile = (function () {
var x = txt_ext + ' ans c cfg conf cpp cs css diff glsl go html ini java js json jsx kt kts latex less lisp lua makefile md nim py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml zig';
x = x.split(/ +/g);
for (var a = 0; a < x.length; a++)
r.map["." + x[a]] = x[a];
if (!r.map["." + x[a]])
r.map["." + x[a]] = x[a];
r.sname = function (srch) {
return srch.split(/[?&]doc=/)[1].split('&')[0];
@ -5121,6 +5147,33 @@ var showfile = (function () {
return out.join('');
};
r.ppj = function (e) {
ebi(e);
try {
r.ppj2();
}
catch (ex) {
toast.err(10, '' + ex);
}
};
r.ppj2 = function () {
var btn = ebi('dldoc'),
el = ebi('doc'),
t = el.textContent.trim(),
jo = JSON.parse(t),
jt = JSON.stringify(jo, null, t.indexOf('\n') + 1 ? 0 : 2);
el.textContent = jt;
el.innerHTML = '<code>' + el.innerHTML + '</code>';
try {
el = QS('#doc>code');
el.className = 'language-json';
Prism.highlightElement(el);
}
catch (ex) { }
btn.setAttribute('download', ebi('docname').innerHTML);
btn.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(jt));
};
r.mktree = function () {
var top = get_evpath().slice(SR.length),
crumbs = linksplit(top).join('<span>/</span>'),
@ -5187,6 +5240,7 @@ var showfile = (function () {
'<a href="#" class="btn" id="prevdoc" tt="' + L.tvt_prev + '</a>\n' +
'<a href="#" class="btn" id="nextdoc" tt="' + L.tvt_next + '</a>\n' +
'<a href="#" class="btn" id="seldoc" tt="' + L.tvt_sel + '</a>\n' +
'<a href="#" class="btn" id="ppjdoc" tt="' + L.tvt_j + '</a>\n' +
'<a href="#" class="btn" id="editdoc" tt="' + L.tvt_edit + '</a>\n' +
'<a href="#" class="btn tgl" id="taildoc" tt="' + L.tvt_tail + '</a>\n' +
'<div id="tailbtns">\n' +
@ -5206,6 +5260,7 @@ var showfile = (function () {
ebi('prevdoc').onclick = function () { tree_neigh(-1); };
ebi('nextdoc').onclick = function () { tree_neigh(1); };
ebi('seldoc').onclick = r.tglsel;
ebi('ppjdoc').onclick = r.ppj;
bcfg_bind(r, 'wrap', 'wrapdoc', true, r.tglwrap);
bcfg_bind(r, 'taildoc', 'taildoc', false, r.tgltail);
bcfg_bind(r, 'tail2end', 'tail2end', true);
@ -5880,6 +5935,7 @@ var ahotkeys = function (e) {
return;
var k = (e.key || e.code) + '', pos = -1, n,
sh = e.shiftKey,
ae = document.activeElement,
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
@ -6002,7 +6058,7 @@ var ahotkeys = function (e) {
if (k == '?')
return hkhelp();
if (!e.shiftKey && ctrl(e)) {
if (!sh && ctrl(e)) {
var sel = window.getSelection && window.getSelection() || {};
sel = sel && !sel.isCollapsed && sel.direction != 'none';
@ -6021,7 +6077,16 @@ var ahotkeys = function (e) {
return;
}
if (e.shiftKey && kl != 'a' && kl != 'd')
if (showfile.active()) {
if (!sh && kl == 's')
return showfile.tglsel() || true;
if (!sh && kl == 'e' && ebi('editdoc').style.display != 'none')
return ebi('editdoc').click() || true;
if (sh && kl == 'j')
return showfile.ppj(e) || true;
}
if (sh && kl != 'a' && kl != 'd')
return;
if (/^[0-9]$/.test(k))
@ -6070,7 +6135,7 @@ var ahotkeys = function (e) {
if (k == 'F2')
return fileman.rename();
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
if (!treectl.hidden && (!sh || !thegrid.en)) {
if (kl == 'a')
return QS('#twig').click();
@ -6078,13 +6143,6 @@ var ahotkeys = function (e) {
return QS('#twobytwo').click();
}
if (showfile.active()) {
if (kl == 's')
showfile.tglsel();
if (kl == 'e' && ebi('editdoc').style.display != 'none')
ebi('editdoc').click();
}
if (mp && mp.au && !mp.au.paused) {
if (kl == 's')
return sel_song();
@ -6600,6 +6658,9 @@ var treectl = (function () {
mentered = null,
treesz = clamp(icfg_get('treesz', 16), 10, 50);
if (/[?&]dlni\b/.exec(sloc0))
swrite('dlni', /[?&]dlni=0\b/.exec(sloc0) ? 0 : 1);
var resort = function () {
ENATSORT = NATSORT && clgot(ebi('nsort'), 'on');
treectl.gentab(get_evpath(), treectl.lsc);
@ -6608,6 +6669,7 @@ var treectl = (function () {
bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
bcfg_bind(r, 'csel', 'csel', dgsel);
bcfg_bind(r, 'dlni', 'dlni', dlni, resort);
bcfg_bind(r, 'dots', 'dotfiles', see_dots, function (v) {
r.goto();
setck('dots=' + (v ? 'y' : ''));
@ -7157,6 +7219,14 @@ var treectl = (function () {
dth3x = res.dth3x;
dk = res.dk;
dlni = res.dlni;
if (!sread('dlni'))
clmod(ebi('dlni'), 'on', treectl.dlni = dlni);
dgrid = res.dgrid;
if (!sread('griden'))
clmod(ebi('griden'), 'on', thegrid.en = dgrid);
srvinf = res.srvinf;
if (rtt !== null)
srvinf += (srvinf ? '</span> // <span>rtt: ' : 'rtt: ') + rtt;
@ -7343,6 +7413,11 @@ var treectl = (function () {
html.push('</tbody>');
html = html.join('\n');
set_files_html(html);
if (r.dlni) {
var o = QSA('#files a[id]');
for (var a = 0, aa = o.length; a < aa; a++)
o[a].setAttribute('download', '');
}
if (r.trunc) {
r.setlazy(plain);
if (!r.ask) {
@ -7463,7 +7538,7 @@ var treectl = (function () {
catch (ex) { }
};
r.showmore = function (n) {
r.showmore = function (n, cb) {
window.removeEventListener('scroll', r.tscroll);
console.log('nvis {0} -> {1}'.format(r.nvis, n));
r.nvis = n;
@ -7473,6 +7548,8 @@ var treectl = (function () {
setTimeout(function () {
r.gentab(get_evpath(), r.lsc);
ebi('wrap').style.opacity = CLOSEST ? 'unset' : 1;
if (cb)
cb();
}, 1);
};
@ -8169,7 +8246,12 @@ var settheme = (function () {
freshen();
};
freshen();
var m = /[?&]theme=([0-9]+)/.exec(sloc0);
if (m)
r.go(parseInt(m[1]));
else
freshen();
return r;
})();
@ -9380,14 +9462,3 @@ function reload_browser() {
msel.render();
}
treectl.hydrate();
if (!fullui && (window.ui_nombar || /[?&]nombar\b/.exec(sloc0))) ebi('ops').style.display = 'none';
if (!fullui && (window.ui_noacci || /[?&]noacci\b/.exec(sloc0))) ebi('acc_info').style.display = 'none';
if (!fullui && (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0))) ebi('srv_info').style.display = 'none';
if (!fullui && (window.ui_nocpla || /[?&]nocpla\b/.exec(sloc0))) ebi('goh').style.display = 'none';
if (!fullui && (window.ui_nolbar || /[?&]nolbar\b/.exec(sloc0))) ebi('wfp').style.display = 'none';
if (!fullui && (window.ui_noctxb || /[?&]noctxb\b/.exec(sloc0))) ebi('wtoggle').style.display = 'none';
if (!fullui && (window.ui_norepl || /[?&]norepl\b/.exec(sloc0))) ebi('repl').style.display = 'none';
var m = /[?&]theme=([0-9]+)/.exec(sloc0);
if (m) settheme.go(parseInt(m[1]));

View file

@ -22,7 +22,8 @@
<a id="nsbs" href="#" tt="switch between editor and preview$NHotkey: ctrl-e">editor</a>
<div id="toolsbox">
<a id="tools" href="#">tools</a>
<a id="fmt_table" href="#">prettify table (ctrl-k)</a>
<a id="fmt_table" href="#">beautify table (ctrl-k)</a>
<a id="fmt_json" href="#">beautify json (ctrl-j)</a>
<a id="iter_uni" href="#">non-ascii: iterate (ctrl-u)</a>
<a id="mark_uni" href="#">non-ascii: markup</a>
<a id="cfg_uni" href="#">non-ascii: whitelist</a>

View file

@ -239,6 +239,12 @@ function convert_markdown(md_text, dest_dom) {
var href = nodes[a].getAttribute('href');
var txt = nodes[a].innerHTML;
if (/\.[Mm][Dd]$/.test(href)) {
var o = new URL(href, location.href).origin;
if (!o || o == location.origin)
nodes[a].href = href + '?v';
}
if (!txt)
nodes[a].textContent = href;
else if (href !== txt && !nodes[a].className)

View file

@ -9,10 +9,13 @@
width: calc(100% - 56em);
}
#mw {
left: max(0em, calc(100% - 55em));
overflow-y: auto;
position: fixed;
bottom: 0;
left: 0;
}
@media (min-width: 55em) {
#mw {left:calc(100% - 55em)}
}

View file

@ -334,7 +334,7 @@ window.onbeforeunload = function (e) {
// save handler
function save(e) {
if (e) e.preventDefault();
ev(e);
var save_btn = ebi("save"),
save_cls = save_btn.className + '';
@ -698,9 +698,42 @@ function reLastIndexOf(txt, ptn, end) {
}
// json formatter
function fmt_json(e) {
ev(e);
try {
fmt_json2();
}
catch (ex) {
return toast.err(7, 'json-format (CTRL-J) failed\n\n(hint: select the json you want to beautify/minify first)\n\n' + ex);
}
}
function fmt_json2() {
var txt = dom_src.value,
o0 = txt.lastIndexOf('\n', dom_src.selectionStart - 1),
o1 = txt.indexOf('\n', dom_src.selectionEnd);
o0 = o0 + 1 ? o0 + 1 : 0;
if (o1 < 0) o1 = txt.length;
for (var a = 0; a < 9; a++) {
if (has(['\r', '\n', ' '], txt.charAt(o0))) ++o0;
if (has(['\r', '\n', ' '], txt.charAt(o1))) --o1;
}
var jt0 = txt.slice(o0, ++o1),
jo = JSON.parse(jt0),
jt = JSON.stringify(jo, null, jt0.indexOf('\n') + 1 ? 0 : 2);
setsel({
"pre": txt.slice(0, o0),
"sel": jt,
"post": txt.slice(o1),
"car": o0,
"cdr": o0,
});
}
// table formatter
function fmt_table(e) {
if (e) e.preventDefault();
ev(e);
try {
fmt_table2();
}
@ -857,7 +890,7 @@ function fmt_table2() {
// show unicode
function mark_uni(e) {
if (e) e.preventDefault();
ev(e);
dom_tbox.className = '';
var txt = dom_src.value,
@ -873,7 +906,7 @@ function mark_uni(e) {
// iterate unicode
function iter_uni(e) {
if (e) e.preventDefault();
ev(e);
var txt = dom_src.value,
ofs = dom_src.selectionDirection == "forward" ? dom_src.selectionEnd : dom_src.selectionStart,
@ -895,7 +928,7 @@ function iter_uni(e) {
// configure whitelist
function cfg_uni(e) {
if (e) e.preventDefault();
ev(e);
modal.prompt("unicode whitelist", esc_uni_whitelist, function (reply) {
esc_uni_whitelist = reply;
@ -923,7 +956,9 @@ var set_lno = (function () {
if (i === pi)
return;
var v = 'L' + dom_src.value.slice(0, i).split('\n').length;
var lns = dom_src.value.slice(0, i).split('\n'),
v = lns.length + ' : ' + lns.pop().length;
if (v != pv)
lno.innerHTML = v;
@ -995,6 +1030,10 @@ var set_lno = (function () {
action_stack.redo();
return false;
}
if (kl == "j") {
fmt_json(e.shiftKey);
return false;
}
if (kl == "k") {
fmt_table();
return false;
@ -1038,14 +1077,14 @@ var set_lno = (function () {
ebi('tools').onclick = function (e) {
if (e) e.preventDefault();
ev(e);
var is_open = dom_tbox.className != 'open';
dom_tbox.className = is_open ? 'open' : '';
};
ebi('help').onclick = function (e) {
if (e) e.preventDefault();
ev(e);
dom_tbox.className = '';
var dom = ebi('helpbox');
@ -1065,6 +1104,7 @@ ebi('help').onclick = function (e) {
ebi('fmt_table').onclick = fmt_table;
ebi('fmt_json').onclick = fmt_json;
ebi('mark_uni').onclick = mark_uni;
ebi('iter_uni').onclick = iter_uni;
ebi('cfg_uni').onclick = cfg_uni;

View file

@ -1,36 +0,0 @@
:root {
--font-main: sans-serif;
--font-serif: serif;
--font-mono: 'scp';
}
html,body,tr,th,td,#files,a {
color: inherit;
background: none;
font-weight: inherit;
font-size: inherit;
padding: 0;
border: none;
}
html {
color: #ccc;
background: #333;
font-family: sans-serif;
font-family: var(--font-main), sans-serif;
text-shadow: 1px 1px 0px #000;
touch-action: manipulation;
}
html, body {
margin: 0;
padding: 0;
}
#box {
padding: .5em 1em;
background: #2c2c2c;
}
pre {
font-family: monospace, monospace;
font-family: var(--font-mono), monospace, monospace;
}
a {
color: #fc5;
}

View file

@ -7,7 +7,12 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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 }}">
<style>:root{--font-main:sans-serif;--font-mono:monospace}
html,body,a{margin:0;padding:0;border:none;color:#ccc;background:none;font-family:sans-serif;font-family:var(--font-main),sans-serif}
pre{font-family:monospace,monospace;font-family:var(--font-mono),monospace}
html{touch-action:manipulation;background:#333}
#box{padding:.5em 1em;background:#2c2c2c}
a{color:#fc5}</style>
{{ html_head }}
</head>
@ -43,7 +48,7 @@
<script>
setTimeout(function() {
location.replace("{{ redir }}");
}, 600);
}, 800);
</script>
{%- endif %}
{%- if js %}

View file

@ -42,7 +42,7 @@
<thead><tr><th>%</th><th>speed</th><th>eta</th><th>idle</th><th>dir</th><th>file</th></tr></thead>
<tbody>
{%- for u in ups %}
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ r }}/{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ r }}{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
{%- endfor %}
</tbody>
</table>
@ -54,7 +54,7 @@
<thead><tr><th>%</th><th>sent</th><th>speed</th><th>eta</th><th>idle</th><th></th><th>dir</th><th>file</th></tr></thead>
<tbody>
{%- for u in dls %}
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ r }}/{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ r }}{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
{%- endfor %}
</tbody>
</table>
@ -117,6 +117,7 @@
{%- if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{%- endif %}
<div id="lm"></div>
</form>
</div>
{%- else %}
@ -149,6 +150,7 @@
{%- if ahttps %}
<a id="w" href="{{ ahttps }}">switch to https</a>
{%- endif %}
<div id="lm"></div>
</form>
{%- endif %}
</div>

View file

@ -8,6 +8,8 @@ Ls.eng = {
"ta1": "fill in your new password first",
"ta2": "repeat to confirm new password:",
"ta3": "found a typo; please try again",
"nop": "ERROR: Password cannot be blank",
"nou": "ERROR: Username and/or password cannot be blank",
}
};
@ -15,6 +17,10 @@ if (window.langmod)
langmod();
var d = (Ls[lang] || Ls.eng).splash;
if (Ls.eng && d !== Ls.eng.splash)
for (var k in Ls.eng.splash)
if (d[k] === undefined)
d[k] = Ls.eng.splash[k];
d.wb = d.w;
@ -95,8 +101,23 @@ if (/\&re=/.test('' + location))
ebi('x').onclick = function (e) {
ev(e);
if (!pwi.value)
return redo(d.ta1);
return ebi('lm').innerHTML = d.ta1;
modal.prompt(d.ta2, "y", mok, null, stars);
};
})();
if (ebi('lf'))
ebi('lf').onsubmit = function() {
var un = ebi('lu');
if (ebi('lp').value && (!un || un.value))
return true;
ebi('lm').innerHTML = un ? d.nou : d.nop;
return false;
};
if (ebi('lp'))
ebi('lp').oninput = function() {
ebi('lm').innerHTML = this.value.length <= 64 ?
'' : 'ERROR: Password too long (max=64)';
};

View file

@ -83,7 +83,9 @@ Ls.chi = {
["I/K", "前一个/下一个文件"],
["M", "关闭文本文件"],
["E", "编辑文本文件"],
["S", "选择文件(用于剪切/重命名)"]
["S", "选择文件(用于剪切/重命名)"],
["Y", "下载文本文件"], //m
["⇧ J", "美化json"], //m
]
],
@ -223,6 +225,7 @@ Ls.chi = {
"ct_ttips": '◔ ◡ ◔"> 工具提示',
"ct_thumb": '在网格视图中,切换图标或缩略图$N快捷键: T">🖼️ 缩略图',
"ct_csel": '在网格视图中使用 CTRL 和 SHIFT 进行文件选择">CTRL',
"ct_dl": '点击文件时强制下载(不内联显示)">dl', //m
"ct_ihop": '当图像查看器关闭时,滚动到最后查看的文件">滚动',
"ct_dots": '显示隐藏文件(如果服务器允许)">隐藏文件',
"ct_qdel": '删除文件时,只需确认一次">快删', //m
@ -437,7 +440,7 @@ Ls.chi = {
"fcp_both_b": '<a href="#" id="modal-ok">复制</a><a href="#" id="modal-ng">上传</a>', //m
"mk_noname": "在左侧文本框中输入名称,然后再执行此操作 :p",
"nmd_i1": "还可以添加需要的文件扩展名,例如 <code>.txt</code>", //m
"nmd_i1": "还可以添加需要的文件扩展名,例如 <code>.md</code>", //m
"nmd_i2": "由于没有删除权限,你只能创建 <code>.md</code> 文件", //m
"tv_load": "加载文本文件:\n\n{0}\n\n{1}% ({2} 的 {3} MiB 已加载)",
@ -449,6 +452,7 @@ Ls.chi = {
"tvt_prev": "显示上一个文档$N快捷键: i\">⬆ 上一个",
"tvt_next": "显示下一个文档$N快捷键: K\">⬇ 下一个",
"tvt_sel": "选择文件&nbsp;(用于剪切/删除/...$N快捷键: S\">选择",
"tvt_j": "美化json$N快捷键: shift-J\">j", //m
"tvt_edit": "在文本编辑器中打开文件$N快捷键: E\">✏️ 编辑",
"tvt_tail": "监视文件更改,并实时显示新增的行\">📡 跟踪", //m
"tvt_wrap": "自动换行\">↵", //m
@ -674,6 +678,8 @@ Ls.chi = {
"ta1": "请先输入新密码",
"ta2": "重复以确认新密码:",
"ta3": "发现拼写错误;请重试",
"nop": "错误:密码不能为空", //m
"nou": "错误:用户名和/或密码不能为空", //m
"aa1": "正在接收的文件:", //m
"ab1": "关闭 k304",
"ac1": "开启 k304",

View file

@ -84,6 +84,8 @@ Ls.cze = {
["M", "zavřít textový soubor"],
["E", "upravit textový soubor"],
["S", "vybrat soubor (pro vyjmutí/kopírování/přejmenování)"],
["Y", "stáhnout textový soubor"], //m
["⇧ J", "zkrášlit json"], //m
]
],
@ -227,6 +229,7 @@ Ls.cze = {
"ct_ttips": '◔ ◡ ◔"> nápovědy',
"ct_thumb": 'v zobrazení mřížky přepnout ikony nebo náhledy$NKlávesová zkratka: T">🖼️ náhledy',
"ct_csel": 'použít CTRL a SHIFT pro výběr souborů v zobrazení mřížky">výběr',
"ct_dl": 'vynutit stažení (nezobrazovat inline) při kliknutí na soubor">dl', //m
"ct_ihop": 'když se zavře prohlížeč obrázků, posunout dolů k naposledy zobrazenému souboru">g⮯',
"ct_dots": 'zobrazit skryté soubory (pokud to server povoluje)">dotfiles',
"ct_qdel": 'při mazání souborů požádat o potvrzení jen jednou">rychlé mazání',
@ -441,7 +444,7 @@ Ls.cze = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopírovat</a><a href="#" id="modal-ng">Nahrát</a>',
"mk_noname": "napište název do textového pole vlevo předtím než to uděláte :p",
"nmd_i1": "můžeš také přidat příponu souboru, například <code>.txt</code>", //m
"nmd_i1": "můžeš také přidat příponu souboru, například <code>.md</code>", //m
"nmd_i2": "můžeš vytvářet pouze <code>.md</code> soubory, protože nemáš oprávnění mazat", //m
"tv_load": "Načítání textového dokumentu:\n\n{0}\n\n{1}% ({2} z {3} MiB načteno)",
@ -453,6 +456,7 @@ Ls.cze = {
"tvt_prev": "zobrazit předchozí dokument$NKlávesová zkratka: i\">⬆ předchozí",
"tvt_next": "zobrazit následující dokument$NKlávesová zkratka: K\">⬇ další",
"tvt_sel": "vybrat soubor &nbsp; ( pro vyjmutí / kopírování / mazání / ... )$NKlávesová zkratka: S\">výběr",
"tvt_j": "zkrášlit json$NKlávesová zkratka: shift-J\">j", //m
"tvt_edit": "otevřít soubor v textovém editoru$NKlávesová zkratka: E\">✏️ upravit",
"tvt_tail": "sledovat soubor pro změny; zobrazit nové řádky v reálném čase\">📡 sledovat",
"tvt_wrap": "zalamování slov\">↵",
@ -678,11 +682,14 @@ Ls.cze = {
"ta1": "nejprve vyplňte své nové heslo",
"ta2": "zopakujte pro potvrzení nového hesla:",
"ta3": "nalezen překlep; zkuste to prosím znovu",
"nop": "CHYBA: Heslo nesmí být prázdné", //m
"nou": "CHYBA: Uživatelské jméno a/nebo heslo nesmí být prázdné", //m
"aa1": "příchozí soubory:",
"ab1": "deaktivovat no304",
"ac1": "povolit no304",
"ad1": "povolení no304 deaktivuje veškeré mezipaměti; zkuste to, pokud k304 nestačilo. To ovšem zapříčíní obrovské množství síťového provozu!",
"ae1": "aktivní stahování:",
"af1": "zobrazit nedávné nahrávání",
"ag1": "zobrazit známé uživatele IdP", //m
}
};

View file

@ -84,6 +84,8 @@ Ls.deu = {
["M", "Textdatei schliessen"],
["E", "Textdatei bearbeiten"],
["S", "Textdatei auswählen (für Ausschneiden / Kopieren / Umbenennen)"],
["Y", "Textdatei herunterladen"], //m
["⇧ J", "json verschönern"], //m
]
],
@ -223,6 +225,7 @@ Ls.deu = {
"ct_ttips": '◔ ◡ ◔"> Tooltips',
"ct_thumb": 'In Raster-Ansicht, zwischen Icons und Vorschau wechseln$NHotkey: T">🖼️ Vorschaubilder',
"ct_csel": 'Benutze STRG und UMSCHALT für Dateiauswahl in Raster-Ansicht">sel',
"ct_dl": 'Herunterladen erzwingen (nicht inline anzeigen), wenn eine datei angeklickt wird">dl', //m
"ct_ihop": 'Wenn die Bildanzeige geschlossen ist, scrolle runter zu den zuletzt angesehenen Dateien">g⮯',
"ct_dots": 'Verstecke Dateien anzeigen (wenn erlaubt durch Server)">dotfiles',
"ct_qdel": 'Nur einmal fragen, wenn mehrere Dateien gelöscht werden">qdel',
@ -437,7 +440,7 @@ Ls.deu = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopieren</a><a href="#" id="modal-ng">Hochladen</a>',
"mk_noname": "Tipp' mal vorher lieber einen Namen in das Textfeld links, bevor du das machst :p",
"nmd_i1": "Fügen Sie auch die gewünschte Dateiendung hinzu, z. B. <code>.txt</code>", //m
"nmd_i1": "Fügen Sie auch die gewünschte Dateiendung hinzu, z. B. <code>.md</code>", //m
"nmd_i2": "Sie können nur <code>.md</code>-Dateien erstellen, da Ihnen die Löschberechtigung fehlt", //m
"tv_load": "Textdatei wird geladen:\n\n{0}\n\n{1}% ({2} von {3} MiB geladen)",
@ -449,6 +452,7 @@ Ls.deu = {
"tvt_prev": "Vorheriges Dokument zeigen$NHotkey: i\">⬆ vorh.",
"tvt_next": "Nächstes Dokument zeigen$NHotkey: K\">⬇ nächst.",
"tvt_sel": "Wählt diese Datei aus &nbsp; ( zum Ausschneiden / Kopieren / Löschen / ... )$NHotkey: S\">ausw.",
"tvt_j": "json verschönern$NHotkey: shift-J\">j", //m
"tvt_edit": "Datei im Texteditor zum Bearbeiten öffnen$NHotkey: E\">✏️ bearb.",
"tvt_tail": "Datei auf Veränderungen überwachen; Neue Zeilen werden in Echtzeit angezeigt\">📡 folgen",
"tvt_wrap": "Zeilenumbruch\">↵",
@ -674,11 +678,14 @@ Ls.deu = {
"ta1": "Trage zuerst dein Passwort ein",
"ta2": "Wiederhole dein Passwort zur Bestätigung:",
"ta3": "Da stimmt etwas nicht; probier's nochmal",
"nop": "FEHLER: Passwort darf nicht leer sein", //m
"nou": "FEHLER: Benutzername und/oder Passwort dürfen nicht leer sein", //m
"aa1": "Eingehende Dateien:",
"ab1": "no304 deaktivieren",
"ac1": "no304 aktivieren",
"ad1": "Das Aktivieren von no304 deaktiviert jegliche Form von Caching; probier dies, wenn k304 nicht genug war. Dies verschwendet eine grosse Menge Netzwerk-Traffic!",
"ae1": "Aktive Downloads:",
"af1": "Zeige neue Uploads",
"ag1": "Bekannte IdP-Benutzer anzeigen", //m
}
};

View file

@ -84,6 +84,8 @@ Ls.epo = {
["M", "fermi dosieron"],
["E", "redakti dosieron"],
["S", "elekti dosieron (por eltondado/kopiado/alinomado)"],
["Y", "elŝuti tekstodosieron"], //m
["⇧ J", "beligi json"], //m
]
],
@ -223,6 +225,7 @@ Ls.epo = {
"ct_ttips": '◔ ◡ ◔"> ŝpruchelpiloj',
"ct_thumb": 'dum krado-vido, baskuli montradon de simboloj aŭ bildetoj$NFulmoklavo: T">🖼️ bildetoj',
"ct_csel": 'uzi STIR kaj MAJ por elekti dosierojn en krado-vido">elekto',
"ct_dl": 'devigi elŝuton (ne montri enkadre) kiam dosiero estas alklakita">dl', //m
"ct_ihop": 'montri la lastan viditan bildo-dosieron post fermado de bildo-vidilo">g⮯',
"ct_dots": 'montri kaŝitajn dosierojn (se servilo permesas)">kaŝitaj',
"ct_qdel": 'peti konfirmon nur unufoje antaŭ forigado">rapid-forig.',
@ -437,7 +440,7 @@ Ls.epo = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopii</a><a href="#" id="modal-ng">Alŝuti</a>',
"mk_noname": "tajpu nomon en tekstokampo maldekstre antaŭ vi faras ĉi tion :p",
"nmd_i1": "vi povas aldoni la deziratan sufikson, ekzemple <code>.txt</code>", //m
"nmd_i1": "vi povas aldoni la deziratan sufikson, ekzemple <code>.md</code>", //m
"nmd_i2": "vi povas krei nur <code>.md</code>-dosierojn ĉar vi ne havas forigan permeson", //m
"tv_load": "Ŝargado de teksto-dokumento:\n\n{0}\n\n{1}% ({2} da {3} MiB ŝargita)",
@ -449,6 +452,7 @@ Ls.epo = {
"tvt_prev": "montri malsekvan dokumenton$NFulmoklavo: i\">⬆ malsekva",
"tvt_next": "montri sekvan dokumenton$NFulmoklavo: K\">⬇ sekva",
"tvt_sel": "elekti dosieron &nbsp; ( por eltondado / kopiado / forigado / ... )$NFulmoklavo: S\">elekti",
"tvt_j": "beligi json$NFulmoklavo: shift-J\">j", //m
"tvt_edit": "malfermi dosieron en teksto-redaktilo$NFulmoklavo: E\">✏️ redakti",
"tvt_tail": "observi ŝanĝojn en dosiero; novaj linioj estos tuje montritaj\">📡 gvati",
"tvt_wrap": "linifaldo\">↵",
@ -674,6 +678,8 @@ Ls.epo = {
"ta1": "entajpu novan pasvorton unue",
"ta2": "retajpu por konfirmi:",
"ta3": "tajpo-eraro; bonvolu provu denove",
"nop": "ERARO: Pasvorto ne povas esti malplena", //m
"nou": "ERARO: Uzantnomo kaj/aŭ pasvorto ne povas esti malplena", //m
"aa1": "aktivaj alŝutoj:",
"ab1": "malŝalti no304-on",
"ac1": "ŝalti no304-on",

View file

@ -84,6 +84,8 @@ Ls.fin = {
["M", "sulje tekstitiedosto"],
["E", "muokkaa tekstitiedostoa"],
["S", "valitse tiedosto (leikkausta/kopiointia/uudelleennimeämistä varten)"],
["Y", "lataa tekstitiedosto"], //m
["⇧ J", "kaunista json"], //m
]
],
@ -223,6 +225,7 @@ Ls.fin = {
"ct_ttips": '◔ ◡ ◔"> vihjelaatikot',
"ct_thumb": 'valitse kuvakkeiden / pienoiskuvien välillä kuvanäkymässä $NPikanäppäin: T">🖼️ pienoiskuvat',
"ct_csel": 'käytä CTRL ja SHIFT tiedostojen valintaan kuvanäkymässä">valitse',
"ct_dl": 'pakota lataus (älä näytä upotettuna), kun tiedostoa napsautetaan">dl', //m
"ct_ihop": 'kun kuvakatselin suljetaan, vieritä alas viimeksi katsottuun tiedostoon">g⮯',
"ct_dots": 'näytä piilotetut tiedostot (jos palvelin sallii)">piilotiedostot',
"ct_qdel": 'kysy vahvistusta vain kerran tiedostoja poistaessa">qdel',
@ -437,7 +440,7 @@ Ls.fin = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopioi</a><a href="#" id="modal-ng">Lähetä</a>',
"mk_noname": "kirjoita nimi vasemmalla olevaan tekstikenttään ennen kuin teet tuon :p",
"nmd_i1": "voit myös lisätä haluamasi tiedostopäätteen, esimerkiksi <code>.txt</code>", //m
"nmd_i1": "voit myös lisätä haluamasi tiedostopäätteen, esimerkiksi <code>.md</code>", //m
"nmd_i2": "voit luoda vain <code>.md</code>-tiedostoja, koska sinulla ei ole poistolupaa", //m
"tv_load": "Ladataan tekstidokumenttia:\n\n{0}\n\n{1}% ({2} / {3} Mt ladattu)",
@ -449,6 +452,7 @@ Ls.fin = {
"tvt_prev": "näytä edellinen dokumentti$NPikanäppäin: i\">⬆ edell",
"tvt_next": "näytä seuraava dokumentti$NPikanäppäin: K\">⬇ seur",
"tvt_sel": "valitse tiedosto &nbsp; ( leikkausta / kopiointia / poistoa / ... varten )$NPikanäppäin: S\">val",
"tvt_j": "kaunista json$NPikanäppäin: shift-J\">j", //m
"tvt_edit": "avaa tiedosto tekstieditorissa$NPikanäppäin: E\">✏️ muokkaa",
"tvt_tail": "seuraa tiedoston muutoksia; näytä uudet rivit reaaliaikaisesti\">📡 seuraa",
"tvt_wrap": "rivitys\">↵",
@ -674,6 +678,8 @@ Ls.fin = {
"ta1": "täytä ensin uusi salasana",
"ta2": "toista vahvistaaksesi uuden salasanan:",
"ta3": "löytyi kirjoitusvirhe; yritä uudelleen",
"nop": "VIRHE: Salasana ei voi olla tyhjä", //m
"nou": "VIRHE: Käyttäjänimi ja/tai salasana ei voi olla tyhjä", //m
"aa1": "saapuvat:",
"ab1": "poista no304 käytöstä",
"ac1": "ota no304 käyttöön",

View file

@ -84,6 +84,8 @@ Ls.fra = {
["M", "fermer le fichier texte"],
["E", "modifier le fichier texte"],
["S", "sélectioner le fichier (pour le couper/copier/renommer)"],
["Y", "télécharger le fichier texte"], //m
["⇧ J", "embellir json"], //m
]
],
@ -223,6 +225,7 @@ Ls.fra = {
"ct_ttips": '◔ ◡ ◔"> infobulles',
"ct_thumb": 'vue en grille, activer les icônes ou les miniatures$NHotkey: T">🖼️ minia',
"ct_csel": 'utiliser CTRL et MAJ pour selectioner des fichiers en vue en grille">sel',
"ct_dl": 'forcer le téléchargement (ne pas afficher en ligne) lorsquun fichier est cliqué">dl', //m
"ct_ihop": 'quand le visionneuse d\'image est fermé, faire defiller vers le bas jusqu\'au dernier fichier">g⮯',
"ct_dots": 'voir les fichiers caché (si le serveur le permet)">dotfiles',
"ct_qdel": 'ne demander qu\'une confirmation lors de la suppression de fichiers>qdel',
@ -437,7 +440,7 @@ Ls.fra = {
"fcp_both_b": '<a href="#" id="modal-ok">Copier</a><a href="#" id="modal-ng">Téléverser</a>',
"mk_noname": "entrez un nom dans le champ de texte à gauche avant de faire ça :p",
"nmd_i1": "ajoutez aussi lextension souhaitée, par exemple <code>.txt</code>", //m
"nmd_i1": "ajoutez aussi lextension souhaitée, par exemple <code>.md</code>", //m
"nmd_i2": "vous ne pouvez créer que des fichiers <code>.md</code> car vous navez pas la permission deffacer", //m
"tv_load": "Chargement du document texte:\n\n{0}\n\n{1}% ({2} de {3} MiB chargés)",
@ -449,6 +452,7 @@ Ls.fra = {
"tvt_prev": "montrer le document précédent$NHotkey: i\">⬆ précédent",
"tvt_next": "montrer le document suivant$NHotkey: K\">⬇ suivant",
"tvt_sel": "sélectionner le fichier &nbsp; ( pour couper / copier / supprimer / … )$NHotkey: S\">sel",
"tvt_j": "embellir json$NHotkey: shift-J\">j", //m
"tvt_edit": "ouvrir le fichier dans l'éditeur de texte$NHotkey: E\">✏️ modifier",
"tvt_tail": "surveiller le fichier pour les changements; montrer les nouvelles lignes en temps réel\">📡 suivre",
"tvt_wrap": "retour à la ligne\">↵",
@ -674,11 +678,14 @@ Ls.fra = {
"ta1": "entrez d'abord votre nouveau mot de passe",
"ta2": "répétez pour confirmer le nouveau mot de passe :",
"ta3": "une faute de frappe a été détectée ; veuillez réessayer.",
"nop": "ERREUR : Le mot de passe ne peut pas être vide", //m
"nou": "ERREUR : Le nom dutilisateur et/ou le mot de passe ne peut pas être vide", //m
"aa1": "fichiers entrants :",
"ab1": "désactiver no304",
"ac1": "activer no304",
"ad1": "l'activation de no304 désactivera toute mise en cache ; essayez ceci si k304 n'était pas suffisant. Cela va générer un trafic réseau considérable !",
"ae1": "téléchargements actifs :",
"af1": "afficher les derniers téléchargements",
"ag1": "afficher les utilisateurs IdP connus", //m
}
};

View file

@ -83,7 +83,9 @@ Ls.grc = {
["I/K", "προηγούμενο/επόμενο αρχείο"],
["M", "κλείσιμο αρχείου"],
["E", "επεξεργασία αρχείου"],
["S", "επιλογή αρχείου (για αποκοπή/αντιγραφή/μετονομασία)"]
["S", "επιλογή αρχείου (για αποκοπή/αντιγραφή/μετονομασία)"],
["Y", "λήψη αρχείου κειμένου"], //m
["⇧ J", "ομορφοποίηση json"], //m
]
],
@ -223,6 +225,7 @@ Ls.grc = {
"ct_ttips": '◔ ◡ ◔"> συμβουλές εργαλείων',
"ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες',
"ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή',
"ct_dl": 'εξαναγκασμός λήψης (να μην εμφανίζεται ενσωματωμένα) όταν γίνεται κλικ σε ένα αρχείο">dl', //m
"ct_ihop": 'όταν η προβολή εικόνων κλείνει, κάνε scroll στο τελευταίο προβαλλόμενο αρχείο">g⮯',
"ct_dots": 'εμφάνιση κρυφών αρχείων (αν το επιτρέπει ο server)">dotfiles',
"ct_qdel": 'όταν διαγράφεις αρχεία, ζήτα επιβεβαίωση μόνο μία φορά">γρήγορη διαγραφή',
@ -437,7 +440,7 @@ Ls.grc = {
"fcp_both_b": '<a href="#" id="modal-ok">Αντιγραφή</a><a href="#" id="modal-ng">Μεταφόρτωση</a>',
"mk_noname": "γράψε ένα όνομα στο πεδίο κειμένου αριστερά πριν το κάνεις :p",
"nmd_i1": "μπορείτε επίσης να προσθέσετε την κατάληξη που θέλετε, όπως <code>.txt</code>", //m
"nmd_i1": "μπορείτε επίσης να προσθέσετε την κατάληξη που θέλετε, όπως <code>.md</code>", //m
"nmd_i2": "μπορείτε να δημιουργήσετε μόνο αρχεία <code>.md</code> επειδή δεν έχετε δικαίωμα διαγραφής", //m
"tv_load": "Φόρτωση αρχείου κειμένου:\n\n{0}\n\n{1}% ({2} από {3} MiB φορτωμένα)",
@ -449,6 +452,7 @@ Ls.grc = {
"tvt_prev": "προβολή προηγούμενου εγγράφου$NΣυντόμευση: i\">⬆ προηγούμενο",
"tvt_next": "προβολή επόμενου εγγράφου$NΣυντόμευση: K\">⬇ επόμενο",
"tvt_sel": "επέλεξε αρχείο &nbsp; (για αποκοπή / αντιγραφή / διαγραφή / ...)$NΣυντόμευση: S\">επιλογή",
"tvt_j": "ομορφοποίηση json$NΣυντόμευση: shift-J\">j", //m
"tvt_edit": "άνοιγμα αρχείου στον επεξεργαστή κειμένου$NΣυντόμευση: E\">✏️ επεξεργασία",
"tvt_tail": "παρακολούθηση αρχείου για αλλαγές; εμφάνιση νέων γραμμών σε πραγματικό χρόνο\">📡 παρακολούθηση",
"tvt_wrap": "αναδίπλωση λέξεων\">↵",
@ -674,11 +678,14 @@ Ls.grc = {
"ta1": "συμπλήρωσε πρώτα το νέο σου κωδικό",
"ta2": "επανέλαβε για να επιβεβαιώσεις το νέο κωδικό:",
"ta3": "βρέθηκε τυπογραφικό λάθος· δοκίμασε ξανά",
"nop": "ΣΦΑΛΜΑ: Ο κωδικός πρόσβασης δεν μπορεί να είναι κενός", //m
"nou": "ΣΦΑΛΜΑ: Το όνομα χρήστη και/ή ο κωδικός πρόσβασης δεν μπορεί να είναι κενό", //m
"aa1": "εισερχόμενα αρχεία:",
"ab1": "απενεργοποίηση no304",
"ac1": "ενεργοποίηση no304",
"ad1": "η ενεργοποίηση του no304 θα απενεργοποιήσει όλη την προσωρινή αποθήκευση· δοκίμασέ το αν το k304 δεν ήταν αρκετό. Προσοχή, θα σπαταλήσει τεράστιο όγκο δικτυακής κίνησης!",
"ae1": "ενεργές μεταφορτώσεις:",
"af1": "προβολή πρόσφατων μεταφορτώσεων",
"ag1": "εμφάνιση γνωστών χρηστών IdP", //m
}
};

View file

@ -84,6 +84,8 @@ Ls.ita = {
["M", "chiudi file di testo"],
["E", "modifica file di testo"],
["S", "seleziona file (per taglia/copia/rinomina)"],
["Y", "scarica il file di testo"], //m
["⇧ J", "abbellire json"], //m
]
],
@ -223,6 +225,7 @@ Ls.ita = {
"ct_ttips": '◔ ◡ ◔"> tooltip',
"ct_thumb": 'nella vista griglia, alterna icone o miniature$NTasto rapido: T">🖼️ miniature',
"ct_csel": 'usa CTRL e SHIFT per la selezione file nella vista griglia">sel',
"ct_dl": 'forza il download (non visualizzare inline) quando si clicca su un file">dl', //m
"ct_ihop": 'quando il visualizzatore immagini è chiuso, scorri fino all\'ultimo file visualizzato">g⮯',
"ct_dots": 'mostra file nascosti (se il server lo permette)">dotfile',
"ct_qdel": 'quando elimini file, chiedi conferma solo una volta">qdel',
@ -437,7 +440,7 @@ Ls.ita = {
"fcp_both_b": '<a href="#" id="modal-ok">Copia</a><a href="#" id="modal-ng">Carica</a>',
"mk_noname": "scrivi un nome nel campo di testo a sinistra prima di farlo :p",
"nmd_i1": "puoi anche aggiungere lestensione che vuoi, per esempio <code>.txt</code>", //m
"nmd_i1": "puoi anche aggiungere lestensione che vuoi, per esempio <code>.md</code>", //m
"nmd_i2": "puoi creare solo file <code>.md</code> perché non hai il permesso di eliminare", //m
"tv_load": "Caricando documento di testo:\n\n{0}\n\n{1}% ({2} di {3} MiB caricati)",
@ -449,6 +452,7 @@ Ls.ita = {
"tvt_prev": "mostra documento precedente$NTasto rapido: i\">⬆ prec",
"tvt_next": "mostra documento successivo$NTasto rapido: K\">⬇ succ",
"tvt_sel": "seleziona file &nbsp; ( per taglia / copia / elimina / ... )$NTasto rapido: S\">sel",
"tvt_j": "abbellire json$NTasto rapido: shift-J\">j", //m
"tvt_edit": "apri file nell'editor di testo$NTasto rapido: E\">✏️ modifica",
"tvt_tail": "monitora file per cambiamenti; mostra nuove righe in tempo reale\">📡 segui",
"tvt_wrap": "a capo parola\">↵",
@ -674,6 +678,8 @@ Ls.ita = {
"ta1": "devi prima inserire una nuova password",
"ta2": "ripeti per confermare la nuova password:",
"ta3": "errore di digitazione; riprova",
"nop": "ERRORE: La password non può essere vuota", //m
"nou": "ERRORE: Il nome utente e/o la password non possono essere vuoti", //m
"aa1": "in arrivo:",
"ab1": "disattiva no304",
"ac1": "attiva no304",

View file

@ -84,6 +84,8 @@ Ls.kor = {
["M", "텍스트 파일 닫기"],
["E", "텍스트 파일 편집"],
["S", "파일 선택 (잘라내기/복사/이름 바꾸기용)"],
["Y", "텍스트 파일 다운로드"], //m
["⇧ J", "json 미화"], //m
]
],
@ -223,6 +225,7 @@ Ls.kor = {
"ct_ttips": '◔ ◡ ◔"> 도움말',
"ct_thumb": '그리드 보기에서 아이콘 또는 미리보기 이미지 전환$N단축키: T">🖼️ 미리보기',
"ct_csel": '그리드 보기에서 CTRL과 SHIFT를 사용하여 파일 선택">선택',
"ct_dl": '파일을 클릭하면 다운로드를 강제로 수행 (인라인으로 표시하지 않음)">dl', //m
"ct_ihop": '이미지 뷰어를 닫으면 마지막으로 본 파일로 스크롤">g⮯',
"ct_dots": '숨김 파일 표시 (서버가 허용하는 경우)">숨김파일',
"ct_qdel": '파일 삭제 시 한 번만 확인 요청">빠른삭제',
@ -437,7 +440,7 @@ Ls.kor = {
"fcp_both_b": '<a href="#" id="modal-ok">복사</a><a href="#" id="modal-ng">업로드</a>',
"mk_noname": "왼쪽 텍스트 필드에 이름을 먼저 입력해주세요 :p",
"nmd_i1": "원하는 파일 확장자를 추가할 수 있습니다. 예: <code>.txt</code>", //m
"nmd_i1": "원하는 파일 확장자를 추가할 수 있습니다. 예: <code>.md</code>", //m
"nmd_i2": "삭제 권한이 없어서 <code>.md</code> 파일만 만들 수 있습니다", //m
"tv_load": "텍스트 문서 불러오는 중:\n\n{0}\n\n{1}% ({3} MiB 중 {2} MiB 로드됨)",
@ -449,6 +452,7 @@ Ls.kor = {
"tvt_prev": "이전 문서 보기$N단축키: i\">⬆ 이전",
"tvt_next": "다음 문서 보기$N단축키: K\">⬇ 다음",
"tvt_sel": "파일 선택 &nbsp; (잘라내기/복사/삭제/...용)$N단축키: S\">선택",
"tvt_j": "json 미화$N단축키: shift-J\">j", //m
"tvt_edit": "텍스트 편집기에서 파일 열기$N단축키: E\">✏️ 편집",
"tvt_tail": "파일 변경 사항 모니터링; 실시간으로 새 줄 표시\">📡 팔로우",
"tvt_wrap": "자동 줄 바꿈\">↵",
@ -674,6 +678,8 @@ Ls.kor = {
"ta1": "새 비밀번호를 먼저 입력하세요",
"ta2": "새 비밀번호 확인을 위해 다시 입력하세요:",
"ta3": "오타가 있습니다. 다시 시도해주세요",
"nop": "오류: 비밀번호를 비워 둘 수 없습니다", //m
"nou": "오류: 사용자 이름 및/또는 비밀번호를 비워 둘 수 없습니다", //m
"aa1": "수신 중인 파일:",
"ab1": "no304 비활성화",
"ac1": "no304 활성화",

View file

@ -84,6 +84,8 @@ Ls.nld = {
["M", "sluit tekst bestand"],
["E", "bewerk tekst bestand"],
["S", "selecteer bestand (voor knip/kopie/hernoem)"],
["Y", "tekst bestand downloaden"], //m
["⇧ J", "json verfraaien"], //m
]
],
@ -223,6 +225,7 @@ Ls.nld = {
"ct_ttips": '◔ ◡ ◔"> tooltips',
"ct_thumb": 'In grid-overzicht, wissel tussen iconen of thumbnails$NHotkey: T">🖼️ thumbs',
"ct_csel": 'Gebruik CTRL en SHIFT voor de bestand selectie in grid-overzicht>sel',
"ct_dl": 'download afdwingen (niet inline weergeven) wanneer op een bestand wordt geklikt">dl', //m
"ct_ihop": 'Als je afbeeldingviewer afsluit, scroll omlaag naar de laatst bekeken bestand">g⮯',
"ct_dots": 'Laat verborgen bestanden zien (als de server dat toestaat)">dotfiles',
"ct_qdel": 'Waneeer je een bestand verwijderd, vraag eenmalig om bevestiging">qdel',
@ -437,7 +440,7 @@ Ls.nld = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopieer</a><a href="#" id="modal-ng">Upload</a>',
"mk_noname": "Voer een naam in het tekstveld aan de linkerkant voordat je verder gaat :p",
"nmd_i1": "Voeg ook de gewenste extensie toe, bijvoorbeeld <code>.txt</code>", //m
"nmd_i1": "Voeg ook de gewenste extensie toe, bijvoorbeeld <code>.md</code>", //m
"nmd_i2": "Je kunt alleen <code>.md</code>-bestanden maken omdat je geen verwijderrechten hebt", //m
"tv_load": "Tekstdocument laden:\n\n{0}\n\n{1}% ({2} van de {3} MiB geladen)",
@ -449,6 +452,7 @@ Ls.nld = {
"tvt_prev": "Vorig document tonen$NHotkey: i\">⬆ prev",
"tvt_next": "Volgende document tonen$NHotkey: K\">⬇ next",
"tvt_sel": "Selecteer bestand &nbsp; ( voor knip / verplaats / verwijder / ... )$NHotkey: S\">sel",
"tvt_j": "json verfraaien$NHotkey: shift-J\">j", //m
"tvt_edit": "Bestand openen in teksteditor$NHotkey: E\">✏️ bewerk",
"tvt_tail": "Bestand controleren op wijzigingen; nieuwe regels in realtime weergeven\">📡 volgen",
"tvt_wrap": "Automatische terugloop\">↵",
@ -674,6 +678,8 @@ Ls.nld = {
"ta1": "Je moet eerst een nieuw wachtwoord invoeren",
"ta2": "Herhaal om nieuw wachtwoord te bevestigen:",
"ta3": "Typefout gevonden; probeer het opnieuw",
"nop": "FOUT: Wachtwoord mag niet leeg zijn", //m
"nou": "FOUT: Gebruikersnaam en/of wachtwoord mag niet leeg zijn", //m
"aa1": "Inkomend:",
"ab1": "Schakel nr. 304 uit",
"ac1": "Schakel nr. 304 in",

View file

@ -82,6 +82,7 @@ Ls.nno = {
["E", "redigér tekstdokument"],
["S", "markér fil (for F2/ctrl-x/...)"],
["Y", "last ned tekstfil"],
["⇧ J", "formattér json"],
]
],
@ -221,6 +222,7 @@ Ls.nno = {
"ct_ttips": 'vis hjelpetekst ved å holde musa over ting"> tips',
"ct_thumb": 'vis miniatyrbilder i staden for ikon$NSnarvei: T">🖼️ bilder',
"ct_csel": 'bruk tastane CTRL og SHIFT for markering av filer i ikonvising">merk',
"ct_dl": 'last ned filer (ikkje vis i nettleseren)">dl',
"ct_ihop": 'bla ned åt sist viste bilde når bildevisaren lukkast">g⮯',
"ct_dots": 'vis skjulte filer (gitt at serveren tillèt det)">.synlig',
"ct_qdel": 'sletteknappen spør berre éin gong om stadfesting">hurtig🗑',
@ -435,7 +437,7 @@ Ls.nno = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopiér</a><a href="#" id="modal-ng">Last opp</a>',
"mk_noname": "skriv inn eit namn i tekstboksa åt venstre først :p",
"nmd_i1": "leggja også til filendinga du vil, til dømes <code>.txt</code>", //m
"nmd_i1": "leggja også til filendinga du vil, til dømes <code>.md</code>", //m
"nmd_i2": "du kan berre laga <code>.md</code>-filer fordi du ikkje har delete-tilgang", //m
"tv_load": "Lastar inn tekstfil:\n\n{0}\n\n{1}% ({2} av {3} MiB lasta ned)",
@ -447,6 +449,7 @@ Ls.nno = {
"tvt_prev": "vis førre dokument$NSnarvei: i\">⬆ forr.",
"tvt_next": "vis neste dokument$NSnarvei: K\">⬇ neste",
"tvt_sel": "markér fila &nbsp; ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
"tvt_j": "formattér json$NSnarvei: shift-J\">j",
"tvt_edit": "redigér fila$NSnarvei: E\">✏️ endre",
"tvt_tail": "overvak fila for endringar og vis nye linjer i sanntid\">📡 følg",
"tvt_wrap": "tekstbryting\">↵",
@ -672,6 +675,8 @@ Ls.nno = {
"ta1": "du må skrive eit nytt passord først",
"ta2": "gjenta for å stadfeste nytt passord:",
"ta3": "fant ein skrivefeil; vennligst prøv igjen",
"nop": "FEIL: Passord kan ikkje vere tomt",
"nou": "FEIL: Brukarnamn og passord må fyllast ut",
"aa1": "innkommande:",
"ab1": "skru av no304",
"ac1": "skru på no304",

View file

@ -82,6 +82,7 @@ Ls.nor = {
["E", "rediger tekstdokument"],
["S", "marker fil (for F2/ctrl-x/...)"],
["Y", "last ned tekstfil"],
["⇧ J", "formattér json"],
]
],
@ -221,6 +222,7 @@ Ls.nor = {
"ct_ttips": 'vis hjelpetekst ved å holde musen over ting"> tips',
"ct_thumb": 'vis miniatyrbilder istedenfor ikoner$NSnarvei: T">🖼️ bilder',
"ct_csel": 'bruk tastene CTRL og SHIFT for markering av filer i ikonvisning">merk',
"ct_dl": 'last ned filer (ikke vis i nettleseren)">dl',
"ct_ihop": 'bla ned til sist viste bilde når bildeviseren lukkes">g⮯',
"ct_dots": 'vis skjulte filer (gitt at serveren tillater det)">.synlig',
"ct_qdel": 'sletteknappen spør bare én gang om bekreftelse">hurtig🗑',
@ -435,7 +437,7 @@ Ls.nor = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopiér</a><a href="#" id="modal-ng">Last opp</a>',
"mk_noname": "skriv inn et navn i tekstboksen til venstre først :p",
"nmd_i1": "legg også til ønsket filtype, for eksempel <code>.txt</code>", //m
"nmd_i1": "legg også til ønsket filtype, for eksempel <code>.md</code>", //m
"nmd_i2": "du kan bare lage <code>.md</code>-filer fordi du ikke har delete-tilgang", //m
"tv_load": "Laster inn tekstfil:\n\n{0}\n\n{1}% ({2} av {3} MiB lastet ned)",
@ -447,6 +449,7 @@ Ls.nor = {
"tvt_prev": "vis forrige dokument$NSnarvei: i\">⬆ forr.",
"tvt_next": "vis neste dokument$NSnarvei: K\">⬇ neste",
"tvt_sel": "markér filen &nbsp; ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
"tvt_j": "formattér json$NSnarvei: shift-J\">j",
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
"tvt_tail": "overvåk filen for endringer og vis nye linjer i sanntid\">📡 følg",
"tvt_wrap": "tekstbryting\">↵",
@ -672,6 +675,8 @@ Ls.nor = {
"ta1": "du må skrive et nytt passord først",
"ta2": "gjenta for å bekrefte nytt passord:",
"ta3": "fant en skrivefeil; vennligst prøv igjen",
"nop": "FEIL: Passord kan ikke være blankt",
"nou": "FEIL: Både brukernavn og passord må angis",
"aa1": "innkommende:",
"ab1": "skru av no304",
"ac1": "skru på no304",

View file

@ -84,6 +84,8 @@ Ls.pol = {
["M", "zamknij plik"],
["E", "edytuj plik"],
["S", "wybierz plik (do wycięcia/skopiowania/zmiany nazwy)"],
["Y", "pobierz plik tekstowy"], //m
["⇧ J", "upiększ json"], //m
]
],
@ -226,6 +228,7 @@ Ls.pol = {
"ct_ttips": '◔ ◡ ◔"> podpowiedzi',
"ct_thumb": 'w widoku siatki, przełącz ikony i miniaturki$NSkrót: T">🖼️ miniaturki',
"ct_csel": 'użyj CTRL i SHIFT do wybierania plików w widoku siatki">wybierz',
"ct_dl": 'wymuś pobieranie (nie wyświetlaj inline) po kliknięciu pliku">dl', //m
"ct_ihop": 'przejdź do ostatniego pliku po zamknięciu przeglądarki obrazów">g⮯',
"ct_dots": 'pokaż ukryte pliki (jeśli pozwala serwer)">ukryte',
"ct_qdel": 'pytaj o potwierdzenie przy usuwaniu tylko raz">pyt. us.',
@ -440,7 +443,7 @@ Ls.pol = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopiuj</a><a href="#" id="modal-ng">Prześlij</a>',
"mk_noname": "wpisz nazwę do pola po lewej zanim to zrobisz :p",
"nmd_i1": "możesz też dodać wybrane rozszerzenie, np. <code>.txt</code>", //m
"nmd_i1": "możesz też dodać wybrane rozszerzenie, np. <code>.md</code>", //m
"nmd_i2": "możesz tworzyć tylko pliki <code>.md</code>, ponieważ nie masz uprawnień do usuwania", //m
"tv_load": "Wczytywanie pliku tekstowego:\n\n{0}\n\n{1}% (wczytano {2} z {3} MiB)",
@ -452,6 +455,7 @@ Ls.pol = {
"tvt_prev": "pokaż poprzedni dokument$NSkrót: i\">⬆ poprzedni",
"tvt_next": "pokaż następny dokument$NSkrót: K\">⬇ następny",
"tvt_sel": "wybierz plik &nbsp; ( do wycięcia / skopiowania / usunięcia / itp. )$NSkrót: S\">wyb",
"tvt_j": "upiększ json$NSkrót: shift-J\">j", //m
"tvt_edit": "otwórz plik w edytorze tekstu$NSkrót: E\">✏️ edytuj",
"tvt_tail": "śledź zmiany w pliku; pokazuj nowe linie w czasie rzeczywistym\">📡 śledź",
"tvt_wrap": "zawijaj tekst\">↵",
@ -677,6 +681,8 @@ Ls.pol = {
"ta1": "najpierw wprowadź nowe hasło",
"ta2": "powtórz hasło dla potwierdzenia:",
"ta3": "znaleziono literówkę, spróbuj ponownie",
"nop": "BŁĄD: Hasło nie może być puste", //m
"nou": "BŁĄD: Nazwa użytkownika i/lub hasło nie może być puste", //m
"aa1": "pliki przychodzące:",
"ab1": "wyłącz no304",
"ac1": "włącz no304",

View file

@ -84,6 +84,8 @@ Ls.por = {
["M", "fechar arquivo de texto"],
["E", "editar arquivo de texto"],
["S", "selecionar arquivo (para recortar/copiar/renomear)"],
["Y", "baixar arquivo de texto"], //m
["⇧ J", "embelezar json"], //m
]
],
@ -223,6 +225,7 @@ Ls.por = {
"ct_ttips": '◔ ◡ ◔"> dicas de ferramentas',
"ct_thumb": 'na visualização de grade, alternar entre ícones ou miniaturas$NHotkey: T">🖼️ miniaturas',
"ct_csel": 'usar CTRL e SHIFT para seleção de arquivo na visualização de grade">sel',
"ct_dl": 'forçar download (não exibir inline) ao clicar em um arquivo">dl', //m
"ct_ihop": 'quando o visualizador de imagens for fechado, rolar para o último arquivo visualizado">g⮯',
"ct_dots": 'mostrar arquivos ocultos (se o servidor permitir)">dotfiles',
"ct_qdel": 'ao excluir arquivos, pedir confirmação apenas uma vez">qdel',
@ -437,7 +440,7 @@ Ls.por = {
"fcp_both_b": '<a href="#" id="modal-ok">Copiar</a><a href="#" id="modal-ng">Enviar</a>',
"mk_noname": "digite um nome no campo de texto à esquerda antes de fazer isso :p",
"nmd_i1": "também pode adicionar a extensão desejada, por exemplo <code>.txt</code>", //m
"nmd_i1": "também pode adicionar a extensão desejada, por exemplo <code>.md</code>", //m
"nmd_i2": "só pode criar ficheiros <code>.md</code> porque não tem permissão para apagar", //m
"tv_load": "Carregando documento de texto:\n\n{0}\n\n{1}% ({2} de {3} MiB carregados)",
@ -449,6 +452,7 @@ Ls.por = {
"tvt_prev": "mostrar documento anterior$NHotkey: i\">⬆ anterior",
"tvt_next": "mostrar próximo documento$NHotkey: K\">⬇ próximo",
"tvt_sel": "selecionar arquivo &nbsp; ( para recortar / copiar / excluir / ... )$NHotkey: S\">sel",
"tvt_j": "embelezar json$NHotkey: shift-J\">j", //m
"tvt_edit": "abrir arquivo no editor de texto$NHotkey: E\">✏️ editar",
"tvt_tail": "monitorar arquivo para alterações; mostrar novas linhas em tempo real\">📡 seguir",
"tvt_wrap": "quebra de linha\">↵",
@ -674,6 +678,8 @@ Ls.por = {
"ta1": "primeiro digite sua nova senha",
"ta2": "repita para confirmar a nova senha:",
"ta3": "há um erro; por favor, tente novamente",
"nop": "ERRO: A senha não pode estar em branco", //m
"nou": "ERRO: O nome de usuário e/ou a senha não podem estar em branco", //m
"aa1": "arquivos de entrada:",
"ab1": "desativar no304",
"ac1": "ativar no304",

View file

@ -84,6 +84,8 @@ Ls.rus = {
["M", "закрыть файл"],
["E", "отредактировать файл"],
["S", "выделить файл"],
["Y", "скачать текстовый файл"], //m
["⇧ J", "приукрасить json"], //m
]
],
@ -223,6 +225,7 @@ Ls.rus = {
"ct_ttips": '◔ ◡ ◔"> подсказки',
"ct_thumb": 'переключение между иконками и миниатюрами в режиме сетки$NГорячая клавиша: T">🖼️ миниат.',
"ct_csel": 'держите CTRL или SHIFT для выделения файлов в режиме сетки">выбор',
"ct_dl": 'принудительная загрузка (не показывать встроенно) при щелчке по файлу">dl', //m
"ct_ihop": 'показывать последний открытый файл после закрытия просмотрщика изображений">g⮯',
"ct_dots": 'показывать скрытые файлы (если есть доступ)">скрыт.',
"ct_qdel": 'спрашивать подтверждение только один раз перед удалением файлов">быстр. удал.',
@ -437,7 +440,7 @@ Ls.rus = {
"fcp_both_b": '<a href="#" id="modal-ok">Скопировать</a><a href="#" id="modal-ng">Загрузить</a>',
"mk_noname": "введите имя в текстовое поле слева перед тем, как это делать :p",
"nmd_i1": "вы также можете указать нужное расширение, например <code>.txt</code>", //m
"nmd_i1": "вы также можете указать нужное расширение, например <code>.md</code>", //m
"nmd_i2": "вы можете создавать только файлы <code>.md</code>, так как у вас нет разрешения на удаление", //m
"tv_load": "Загружаю текстовый документ:\n\n{0}\n\n{1}% ({2} из {3} МиБ загружено)",
@ -449,6 +452,7 @@ Ls.rus = {
"tvt_prev": "показать предыдущий документ$NГорячая клавиша: i\">⬆ пред",
"tvt_next": "показать следующий документ$NГорячая клавиша: K\">⬇ след",
"tvt_sel": "выбрать документ &nbsp; ( для вырезания / копирования / удаления / ... )$NГорячая клавиша: S\">выд",
"tvt_j": "приукрасить json$NГорячая клавиша: shift-J\">j", //m
"tvt_edit": "открыть документ в текстовом редакторе$NГорячая клавиша: E\">✏️ изменить",
"tvt_tail": "проверять файл на изменения; показывать новые строки в реальном времени\">📡 обновлять",
"tvt_wrap": "перенос слов\">↵",
@ -674,6 +678,8 @@ Ls.rus = {
"ta1": "сначала введите свой новый пароль",
"ta2": "повторите новый пароль:",
"ta3": "опечатка; попробуйте снова",
"nop": "ОШИБКА: Пароль не может быть пустым", //m
"nou": "ОШИБКА: Имя пользователя и/или пароль не могут быть пустыми", //m
"aa1": "входящие файлы:",
"ab1": "отключить no304",
"ac1": "включить no304",

View file

@ -83,7 +83,9 @@ Ls.spa = {
["I/K", "anterior/siguiente archivo"],
["M", "cerrar archivo"],
["E", "editar archivo"],
["S", "seleccionar archivo (para cortar/copiar/renombrar)"]
["S", "seleccionar archivo (para cortar/copiar/renombrar)"],
["Y", "descargar archivo de texto"], //m
["⇧ J", "embellecer json"], //m
]
],
@ -222,6 +224,7 @@ Ls.spa = {
"ct_ttips": '◔ ◡ ◔"> tooltips',
"ct_thumb": 'en vista de cuadrícula, alternar iconos o miniaturas$NAtajo: T">🖼️ miniaturas',
"ct_csel": 'usa CTRL y SHIFT para seleccionar archivos en la vista de cuadrícula">sel',
"ct_dl": 'forzar descarga (no mostrar en línea) al hacer clic en un archivo">dl', //m
"ct_ihop": 'al cerrar el visor de imágenes, desplazarse hasta el último archivo visto">g⮯',
"ct_dots": 'mostrar archivos ocultos (si el servidor lo permite)">archivos ocultos',
"ct_qdel": 'al eliminar archivos, pedir confirmación solo una vez">elim. rápida',
@ -436,7 +439,7 @@ Ls.spa = {
"fcp_both_b": "<a href=\"#\" id=\"modal-ok\">Copiar</a><a href=\"#\" id=\"modal-ng\">Subir</a>",
"mk_noname": "escribe un nombre en el campo de texto de la izquierda antes de hacer eso :p",
"nmd_i1": "también puedes añadir la extensión que quieras, por ejemplo <code>.txt</code>", //m
"nmd_i1": "también puedes añadir la extensión que quieras, por ejemplo <code>.md</code>", //m
"nmd_i2": "solo puedes crear archivos <code>.md</code> porque no tienes permiso para borrar", //m
"tv_load": "Cargando documento de texto:\n\n{0}\n\n{1}% ({2} de {3} MiB cargados)",
@ -448,6 +451,7 @@ Ls.spa = {
"tvt_prev": "mostrar documento anterior$NAtajo: i\">⬆ ant",
"tvt_next": "mostrar siguiente documento$NAtajo: K\">⬇ sig",
"tvt_sel": "seleccionar archivo &nbsp; ( para cortar / copiar / eliminar / ... )$NAtajo: S\">sel",
"tvt_j": "embellecer json$NAtajo: shift-J\">j", //m
"tvt_edit": "abrir archivo en editor de texto$NAtajo: E\">✏️ editar",
"tvt_tail": "monitorizar cambios en el archivo; mostrar nuevas líneas en tiempo real\">📡 seguir",
"tvt_wrap": "ajuste de línea\">↵",
@ -673,6 +677,8 @@ Ls.spa = {
"ta1": "primero escribe tu nueva contraseña",
"ta2": "repite para confirmar la nueva contraseña:",
"ta3": "hay un error; por favor, inténtalo de nuevo",
"nop": "ERROR: La contraseña no puede estar vacía", //m
"nou": "ERROR: El nombre de usuario y/o la contraseña no pueden estar vacíos", //m
"aa1": "archivos entrantes:",
"ab1": "desactivar no304",
"ac1": "activar no304",

View file

@ -84,6 +84,8 @@ Ls.swe = {
["M", "stäng textfil"],
["E", "redigera textfil"],
["S", "välj fil"],
["Y", "ladda ner textfil"], //m
["⇧ J", "försköna json"], //m
]
],
@ -223,6 +225,7 @@ Ls.swe = {
"ct_ttips": '◔ ◡ ◔"> tips',
"ct_thumb": 'växla mellan miniatyrer och ikoner i rutnätsvyn$NSnabbtangent: T">🖼️ miniatyrer',
"ct_csel": 'använd CTRL och SKIFT för urval av filer i rutnätsvyn">val',
"ct_dl": 'tvinga nedladdning (visa inte inline) när en fil klickas">dl', //m
"ct_ihop": 'skrolla till den senast visade filen när bildvisaren stängs">g⮯',
"ct_dots": 'visa dolda filer (om servern tillåter detta)">dolda',
"ct_qdel": 'bekräfta endast en gång när filer raderas">srad',
@ -437,7 +440,7 @@ Ls.swe = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopiera</a><a href="#" id="modal-ng">Ladda upp</a>',
"mk_noname": "skriv ett namn i fältet till vänster först :p",
"nmd_i1": "lägg också till filändelsen du vill ha, till exempel <code>.txt</code>", //m
"nmd_i1": "lägg också till filändelsen du vill ha, till exempel <code>.md</code>", //m
"nmd_i2": "du kan bara skapa <code>.md</code>-filer eftersom du inte har borttagningsbehörighet", //m
"tv_load": "Laddar textfil:\n\n{0}\n\n{1}% ({2} av {3} MiB laddat)",
@ -449,6 +452,7 @@ Ls.swe = {
"tvt_prev": "visa föregående fil$NSnabbtangent: i\">⬆ föreg.",
"tvt_next": "visa nästa fil$NSnabbtangent: K\">⬇ nästa",
"tvt_sel": "välj fil &nbsp; ( för klipp / kopiera / radera / ... )$NSnabbtangent: S\">välj",
"tvt_j": "försköna json$NSnabbtangent: shift-J\">j", //m
"tvt_edit": "öppna fil i textredigerare$NSnabbtangent: E\">✏️ redigera",
"tvt_tail": "övervaka filen; visa nya rader i realtid\">📡 övervaka",
"tvt_wrap": "automatisk radbrytning\">↵",
@ -674,6 +678,8 @@ Ls.swe = {
"ta1": "fyll i ditt nya lösenord",
"ta2": "upprepa det nya lösenordet:",
"ta3": "det blev fel; vänligen försök igen",
"nop": "FEL: Lösenordet får inte vara tomt", //m
"nou": "FEL: Användarnamn och/eller lösenord får inte vara tomt", //m
"aa1": "inkommande filer:",
"ab1": "avaktivera no304",
"ac1": "aktivera no304",

View file

@ -84,6 +84,8 @@ Ls.tur = {
["M", "metin dosyasını kapat"],
["E", "metin dosyasını düzenle"],
["S", "dosyayı seç (kes/kopyala/yeniden adlandır)"],
["Y", "metin dosyasını indir"], //m
["⇧ J", "json güzelleştir"], //m
]
],
@ -223,6 +225,7 @@ Ls.tur = {
"ct_ttips": '◔ ◡ ◔"> ipuçları',
"ct_thumb": 'ızgara görünümünde, simgeler ve küçük resimler arasında geçiş yapın$NKısayol: T">🖼️ küçük resimler',
"ct_csel": 'ızgara görünümünde dosya seçimi için CTRL ve SHIFT tuşlarını kullanın">seç',
"ct_dl": 'dosyaya tıklandığında indirmeyi zorla (satır içinde görüntüleme)">dl', //m
"ct_ihop": 'resim görüntüleyici kapatıldığında, en son görüntülenen dosyaya kaydırın">g⮯',
"ct_dots": 'gizli dosyaları göster (sunucu izin veriyorsa)">nokta dosyaları',
"ct_qdel": 'dosyaları silerken yalnız bir kez onay isteyin">qdel',
@ -437,7 +440,7 @@ Ls.tur = {
"fcp_both_b": '<a href="#" id="modal-ok">Kopyala</a><a href="#" id="modal-ng">Yükle</a>',
"mk_noname": "bunu yapmadan önce soldaki boşluğa bir şeyler yazsana :p",
"nmd_i1": "ayrıca istediğin dosya uzantısını ekleyebilirsin, örneğin <code>.txt</code>", //m
"nmd_i1": "ayrıca istediğin dosya uzantısını ekleyebilirsin, örneğin <code>.md</code>", //m
"nmd_i2": "silme iznin olmadığı için yalnızca <code>.md</code> dosyaları oluşturabilirsin", //m
"tv_load": "Metin belgesi yükleniyor:\n\n{0}\n\n{1}% ({2} of {3} MiB yüklendi)",
@ -449,6 +452,7 @@ Ls.tur = {
"tvt_prev": "önceki belgeyi göster$NKısayol: i\">⬆ önceki",
"tvt_next": "sonraki belgeyi göster$NKısayol: K\">⬇ sonraki",
"tvt_sel": "dosyayı seç$NKısayol: S\">seç",
"tvt_j": "json güzelleştir$NKısayol: shift-J\">j", //m
"tvt_edit": "dosyayı metin düzenleyicisinde aç$NKısayol: E\">✏️ düzenle",
"tvt_tail": "dosyalardaki değişiklikleri izle; yeni satırları gerçek zamanlı göster\">📡 takip",
"tvt_wrap": "kelime sarma\">↵",
@ -669,11 +673,14 @@ Ls.tur = {
"ta1": "ilk önce yeni şifreyi doldur",
"ta2": "yeni şifreyi onaylamak için tekrar girin:",
"ta3": "bir yazım hatası bulundu; lütfen tekrar deneyin",
"nop": "HATA: Parola boş olamaz", //m
"nou": "HATA: Kullanıcı adı ve/veya parola boş olamaz", //m
"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",
"ag1": "bilinen IdP kullanıcılarını göster", //m
}
};

View file

@ -84,6 +84,8 @@ Ls.ukr = {
["M", "закрити текстовий файл"],
["E", "редагувати текстовий файл"],
["S", "вибрати файл (для вирізання/копіювання/перейменування)"],
["Y", "завантажити текстовий файл"], //m
["⇧ J", "прикрасити json"], //m
]
],
@ -223,6 +225,7 @@ Ls.ukr = {
"ct_ttips": '◔ ◡ ◔"> підказки',
"ct_thumb": 'у режимі сітки, перемкнути іконки або мініатюри$NГаряча клавіша: T">🖼️ мініатюри',
"ct_csel": 'використовувати CTRL і SHIFT для вибору файлів у режимі сітки">вибір',
"ct_dl": 'примусове завантаження (не показувати вбудовано) під час натискання на файл">dl', //m
"ct_ihop": 'коли переглядач зображень закрито, прокрутити вниз до останнього переглянутого файлу">g⮯',
"ct_dots": 'показати приховані файли (якщо сервер дозволяє)">приховані файли',
"ct_qdel": 'при видаленні файлів, запитати підтвердження лише один раз">швидке видалення',
@ -437,7 +440,7 @@ Ls.ukr = {
"fcp_both_b": '<a href="#" id="modal-ok">Скопіювати</a><a href="#" id="modal-ng">Завантажити</a>',
"mk_noname": "введіть ім'я в текстове поле зліва перед тим, як робити це :p",
"nmd_i1": "ви також можете додати потрібне розширення, наприклад <code>.txt</code>", //m
"nmd_i1": "ви також можете додати потрібне розширення, наприклад <code>.md</code>", //m
"nmd_i2": "ви можете створювати тільки файли <code>.md</code>, оскільки не маєте дозволу на видалення", //m
"tv_load": "Завантаження текстового документа:\n\n{0}\n\n{1}% ({2} з {3} MiB завантажено)",
@ -449,6 +452,7 @@ Ls.ukr = {
"tvt_prev": "показати попередній документ$NГаряча клавіша: i\">⬆ попер",
"tvt_next": "показати наступний документ$NГаряча клавіша: K\">⬇ наст",
"tvt_sel": "вибрати файл &nbsp; ( для вирізання / копіювання / видалення / ... )$NГаряча клавіша: S\">вибр",
"tvt_j": "прикрасити json$NГаряча клавіша: shift-J\">j", //m
"tvt_edit": "відкрити файл в текстовому редакторі$NГаряча клавіша: E\">✏️ редагувати",
"tvt_tail": "моніторити файл на зміни; показувати нові рядки в реальному часі\">📡 слідкувати",
"tvt_wrap": "перенесення слів\">↵",
@ -674,6 +678,8 @@ Ls.ukr = {
"ta1": "спочатку заповніть ваш новий пароль",
"ta2": "повторіть для підтвердження нового пароля:",
"ta3": "описка; спробуйте знову",
"nop": "ПОМИЛКА: Пароль не може бути порожнім", //m
"nou": "ПОМИЛКА: Ім’я користувача та/або пароль не можуть бути порожніми", //m
"aa1": "вхідні файли:",
"ab1": "вимкнути no304",
"ac1": "увімкнути no304",

730
copyparty/web/tl/vie.js Normal file
View file

@ -0,0 +1,730 @@
Ls.vie = {
"tt": "Tiếng Việt",
"cols": {
"c": "nút hành động",
"dur": "thời lượng",
"q": "chất lượng / bitrate",
"Ac": "codec âm thanh",
"Vc": "codec video",
"Fmt": "định dạng / container",
"Ahash": "checksum âm thanh",
"Vhash": "checksum video",
"Res": "độ phân giải",
"T": "loại tệp",
"aq": "chất lượng âm thanh / bitrate",
"vq": "chất lượng video / bitrate",
"pixfmt": "subsampling / pixel structure",
"resw": "độ phân giải ngang",
"resh": "độ phân giải dọc",
"chs": "kênh âm thanh",
"hz": "tốc độ lấy mẫu",
},
"hks": [
[
"misc",
["ESC", "đóng nhiều mục"],
"file-manager",
["G", "chuyển đổi chế độ xem danh sách / lưới"],
["T", "chuyển đổi ảnh thu nhỏ / biểu tượng"],
["⇧ A/D", "kích thước ảnh thu nhỏ"],
["ctrl-K", "xoá mục đã chọn"],
["ctrl-X", "cắt mục đã chọn vào bảng nhớ tạm"],
["ctrl-C", "sao chép mục đã chọn vào bảng nhớ tạm"],
["ctrl-V", "dán (di chuyển/sao chép) tại đây"],
["Y", "tải xuống mục đã chọn"],
["F2", "đổi tên mục đã chọn"],
"file-list-sel",
["space", "chuyển đổi chọn tệp"],
["↑/↓", "di chuyển con trỏ chọn"],
["ctrl ↑/↓", "di chuyển con trỏ và khung nhìn"],
["⇧ ↑/↓", "chọn tệp trước / sau"],
["ctrl-A", "chọn tất cả tệp / thư mục"],
], [
"navigation",
["B", "chuyển đổi đường dẫn / thanh điều hướng"],
["I/K", "thư mục trước / sau"],
["M", "thư mục cha (hoặc thu gọn hiện tại)"],
["V", "chuyển đổi thư mục / tệp văn bản trong thanh điều hướng"],
["A/D", "kích thước thanh điều hướng"],
], [
"audio-player",
["J/L", "bài trước / sau"],
["U/O", "lùi / tiến 10 giây"],
["0..9", "nhảy đến 0%..90%"],
["P", "phát/tạm dừng (cũng khởi động)"],
["S", "chọn bài đang phát"],
["Y", "tải xuống bài hát"],
], [
"image-viewer",
["J/L, ←/→", "ảnh trước / sau"],
["Home/End", "ảnh đầu / cuối"],
["F", "toàn màn hình"],
["R", "xoay theo chiều kim đồng hồ"],
["⇧ R", "xoay ngược chiều kim đồng hồ"],
["S", "chọn ảnh"],
["Y", "tải xuống ảnh"],
], [
"video-player",
["U/O", "lùi / tiến 10 giây"],
["P/K/Space", "phát/tạm dừng"],
["C", "tiếp tục phát bài tiếp theo"],
["V", "vòng lặp"],
["M", "tắt tiếng"],
["[ and ]", "đặt khoảng lặp"],
], [
"textfile-viewer",
["I/K", "tệp trước / sau"],
["M", "đóng tệp văn bản"],
["E", "chỉnh sửa tệp văn bản"],
["S", "chọn tệp (để cắt/sao chép/đổi tên)"],
]
],
"m_ok": "OK",
"m_ng": "Hủy",
"enable": "Bật",
"danger": "NGUY HIỂM",
"clipped": "đã sao chép vào bảng nhớ tạm",
"ht_s1": "giây",
"ht_s2": "giây",
"ht_m1": "phút",
"ht_m2": "phút",
"ht_h1": "giờ",
"ht_h2": "giờ",
"ht_d1": "ngày",
"ht_d2": "ngày",
"ht_and": " và ",
"goh": "bảng điều khiển",
"gop": 'thư mục trước">trước',
"gou": 'thư mục cha">lên',
"gon": 'thư mục sau">tiếp',
"logout": "Đăng xuất ",
"login": "Đăng nhập",
"access": "quyền truy cập",
"ot_close": "đóng menu con",
"ot_search": "tìm kiếm các tệp theo thuộc tính, đường dẫn / tên, tag nhạc hoặc bất kỳ sự kết hợp nào của chúng$N$N&lt;code&gt;foo bar&lt;/code&gt; = phải chứa cả «foo» và «bar»,$N&lt;code&gt;foo -bar&lt;/code&gt; = phải chứa «foo» nhưng không chứa «bar»,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = bắt đầu bằng «yana» và là tệp «opus»$N&lt;code&gt;&quot;try unite&quot;&lt;/code&gt; = chứa chính xác «try unite»$N$Nđịnh dạng ngày là iso-8601, như$N&lt;code&gt;2009-12-31&lt;/code&gt; hoặc &lt;code&gt;2020-09-12 23:30:00&lt;/code&gt;",
"ot_unpost": "unpost: xoá các tệp đã tải lên gần đây hoặc huỷ những tệp đang tải dở",
"ot_bup": "bup: trình tải lên cơ bản, hỗ trợ cả Netscape 4.0",
"ot_mkdir": "mkdir: tạo thư mục mới",
"ot_md": "new-file: tạo tệp văn bản mới",
"ot_msg": "msg: gửi tin nhắn đến nhật ký máy chủ",
"ot_mp": "tuỳ chọn trình phát phương tiện",
"ot_cfg": "tuỳ chọn cấu hình",
"ot_u2i": 'up2k: tải tệp lên (nếu bạn có quyền ghi) hoặc chuyển sang chế độ tìm kiếm để xem chúng có tồn tại ở đâu đó trên máy chủ không$N$Ntải lên có thế tiếp tục nếu bị gián đoạn, chạy đa luồng và giữ nguyên dấu thời gian tệp, nhưng tiêu tốn nhiều CPU hơn [🎈]&nbsp; (trình tải lên cơ bản)<br /><br />trong quá trình tải, biểu tượng này sẽ trở thành chỉ thị tiến trình!',
"ot_u2w": 'up2k: tải tệp lên với hỗ trợ tiếp tục (đóng trình duyệt và thả lại tệp đó lên sau)$N$Nchạy đa luồng và giữ nguyên dấu thời gian tệp, nhưng tiêu tốn nhiều CPU hơn [🎈]&nbsp; (trình tải lên cơ bản)<br /><br />trong quá trình tải, biểu tượng này sẽ trở thành chỉ thị tiến trình!',
"ot_noie": 'Vui lòng sử dụng Chrome / Firefox / Edge',
"ab_mkdir": "tạo thư mục",
"ab_mkdoc": "tạo tệp văn bản",
"ab_msg": "gửi tin nhắn đến nhật ký máy chủ",
"ay_path": "bỏ qua đến thư mục",
"ay_files": "bỏ qua đến tệp",
"wt_ren": "đổi tên các mục đã chọn$NPhím tắt: F2",
"wt_del": "xóa các mục đã chọn$NPhím tắt: ctrl-K",
"wt_cut": "cắt các mục đã chọn &lt;small&gt;(sau đó dán ở nơi khác)&lt;/small&gt;$NPhím tắt: ctrl-X",
"wt_cpy": "sao chép các mục đã chọn vào bảng nhớ tạm$N(để dán ở nơi khác)$NPhím tắt: ctrl-C",
"wt_pst": "dán một lựa chọn đã cắt / sao chép trước đó$NPhím tắt: ctrl-V",
"wt_selall": "chọn tất cả các tệp$NPhím tắt: ctrl-A (khi tệp được chọn)",
"wt_selinv": "đảo ngược lựa chọn",
"wt_zip1": "tải thư mục này dưới định dạng nén",
"wt_selzip": "tải lựa chọn dưới định dạng nén",
"wt_seldl": "tải lựa chọn dưới dạng các tệp riêng biệt$NPhím tắt: Y",
"wt_npirc": "sao chép thông tin bản nhạc theo định dạng irc",
"wt_nptxt": "sao chép thông tin bản nhạc dưới dạng văn bản thuần túy",
"wt_m3ua": "thêm vào danh sách phát m3u (bấm <code>📻copy</code> sau)",
"wt_m3uc": "sao chép danh sách phát m3u vào bảng nhớ tạm",
"wt_grid": "chuyển đổi chế độ xem danh sách / lưới $NPhím tắt: G",
"wt_prev": "bài trước$NPhím tắt: J",
"wt_play": "phát / tạm dừng$NPhím tắt: P",
"wt_next": "bài sau$NPhím tắt: L",
"ul_par": "tải lên song song:",
"ut_rand": "ngẫu nhiên hoá tên tệp",
"ut_u2ts": "sao chép dấu thời gian chỉnh sửa cuối$Ntừ hệ thống tệp của bạn lên máy chủ\">📅",
"ut_ow": "ghi đè các tệp đã có trên máy chủ?$N🛡: không bao giờ (sẽ tạo tên tệp mới)$N🕒: ghi đè nếu tệp trên máy chủ cũ hơn$N♻: luôn ghi đè nếu hai tệp khác nhau",
"ut_mt": "tiếp tục hash các tệp khác trong khi tải lên$N$NCó thể tắt nếu CPU hoặc HDD của bạn bị nghẽn",
"ut_ask": 'yêu cầu xác nhận trước khi bắt đầu tải lên">💭',
"ut_pot": "cải thiện tốc độ tải lên trên các thiết bị chậm$Nbằng cách đơn giản hoá giao diện người dùng",
"ut_srch": "không tải lên, chỉ kiểm tra xem tệp$Nđã tồn tại trên máy chủ hay chưa (sẽ quét toàn bộ thư mục bạn có quyền đọc)",
"ut_par": "tạm dừng tải lên bằng cách đặt thành 0$N$NTăng lên nếu kết nối chậm hoặc độ trễ cao$N$NGiữ ở mức 1 khi dùng LAN hoặc nếu ổ cứng máy chủ bị nghẽn",
"ul_btn": "thả tệp / thư mục<br>ở đây (hoặc nhấn vào tôi)",
"ul_btnu": "T Ả I L Ê N",
"ul_btns": "T Ì M K I Ế M",
"ul_hash": "hash",
"ul_send": "gửi",
"ul_done": "hoàn tất",
"ul_idle1": "chưa có mục nào trong hàng chờ tải lên",
"ut_etah": "tốc độ &lt;em&gt;hash&lt;/em&gt; trung bình và thời gian dự kiến để hoàn tất",
"ut_etau": "tốc độ &lt;em&gt;tải lên&lt;/em&gt; trung bình và thời gian dự kiến để hoàn tất",
"ut_etat": "tốc độ &lt;em&gt;tổng&lt;/em&gt; trung bình và thời gian dự kiến để hoàn tất",
"uct_ok": "hoàn tất thành công",
"uct_ng": "không hợp lệ: lỗi / bị từ chối / không tìm thấy",
"uct_done": "đã xử lý: gồm cả thành công và không hợp lệ",
"uct_bz": "đang hash hoặc tải lên",
"uct_q": "nhàn rỗi, đang chờ",
"utl_name": "tên tệp",
"utl_ulist": "danh sách",
"utl_ucopy": "sao chép",
"utl_links": "đường dẫn",
"utl_stat": "trạng thái",
"utl_prog": "tiến trình",
// keep short:
// phần up2k
"utl_404": "404",
"utl_err": "LỖI",
"utl_oserr": "Lỗi hệ thống",
"utl_found": "tìm thấy",
"utl_defer": "hoãn",
"utl_yolo": "YOLO",
"utl_done": "hoàn tất",
"ul_flagblk": "tệp đã được thêm vào hàng chờ</b><br>tuy vậy đang có một tiến trình up2k đang chạy ở một tab khác<br>vui lòng đợi cho đến khi tiến trình đó hoàn tất hoặc bị hủy",
"ul_btnlk": "cài đặt của máy chủ đã khóa tùy chọn ở trạng thái này",
"udt_up": "Tải lên",
"udt_srch": "Tìm kiếm",
"udt_drop": "thả vào đây",
"u_nav_m": '<h6>chọn phương thức tải lên</h6><code>Enter</code> = Tệp (một hoặc nhiều)\n<code>ESC</code> = Một thư mục (kèm thư mục con)',
"u_nav_b": '<a href="#" id="modal-ok">Tệp</a><a href="#" id="modal-ng">Một thư mục</a>',
// settings / config:
"cl_opts": "tuỳ chọn",
"cl_hfsz": "kích thước tệp",
"cl_themes": "giao diện",
"cl_langs": "ngôn ngữ",
"cl_ziptype": "định dạng nén",
"cl_uopts": "tuỳ chọn up2k",
"cl_favico": "favicon",
"cl_bigdir": "thư mục lớn",
"cl_hsort": "#sắp xếp",
"cl_keytype": "ghi chú bàn phím",
"cl_hiddenc": "cột đã ẩn",
"cl_hidec": "ẩn",
"cl_reset": "đặt lại",
"cl_hpick": "chạm vào tiêu đề cột để ẩn trong bảng bên dưới",
"cl_hcancel": "đã hủy việc ẩn cột",
// settings / tuỳ chọn
"ct_grid": '田 chế độ lưới',
"ct_ttips": '༼ ◕_◕ ༽"> tooltips',
"ct_thumb": 'ở chế độ lưới, chuyển biểu tượng hoặc hình thu nhỏ$NPhím tắt: T">🖼️ ảnh thu nhỏ',
"ct_csel": 'dùng CTRL và SHIFT để chọn tệp trong chế độ lưới">sel',
"ct_dl": 'cưỡng chế tải xuống (không hiện thị trong dòng) khi nhấp vào tệp">dl',
"ct_ihop": 'khi đóng trình xem ảnh, cuộn xuống tệp đã xem gần nhất">g⮯',
"ct_dots": 'hiển thị tệp ẩn (nếu máy chủ cho phép)">dotfiles',
"ct_qdel": 'khi xóa tệp, chỉ hỏi xác nhận một lần">qdel',
"ct_dir1st": 'sắp xếp thư mục trước tệp">📁 first',
"ct_nsort": 'sắp xếp tự nhiên (cho tên tệp có số ở đầu)">nsort',
"ct_utc": 'hiển thị mọi thời gian theo UTC">UTC',
"ct_readme": 'hiển thị README.md trong danh sách thư mục">📜 readme',
"ct_idxh": 'hiển thị index.html thay cho danh sách thư mục">htm',
"ct_sbars": 'hiển thị thanh cuộn">⟊',
// tuỳ chọn up2k
"cut_umod": "nếu tệp đã tồn tại trên máy chủ, cập nhật dấu thời gian chỉnh sửa cuối của máy chủ cho khớp với tệp cục bộ của bạn (yêu cầu quyền ghi và xóa)\">re📅",
"cut_turbo": "nút YOLO, bạn gần như KHÔNG nên bật tuỳ chọn này:$N$Ndùng khi bạn đang tải lên một lượng tệp rất lớn và phải khởi động lại vì lý do nào đó, và muốn tiếp tục tải càng sớm sàng tốt$N$Ntuỳ chọn này thay thế kiểm tra hash bằng kiểm tra <em>&quot;kích thước tệp ở trên máy chủ có giống nhau không&quot;</em> nên nếu nội dung tệp khác nhau thì sẽ KHÔNG được tải lên$N$Nbạn nên tắt tuỳ chọn này sau khi tải lên xong, và &quot;tải lên&quot; lại các tệp đó để xác minh\">turbo",
"cut_datechk": "không có tác dụng trừ khi nút turbo được bật$N$Ngiảm mức độ yolo một chút; kiểm tra xem dấu thời gian tệp trên máy chủ có khớp với của bạn không$N$Nnên <em>về lý thuyết</em> có thể phát hiện phần lớn tệp chưa xong hoặc bị lỗi, nhưng không thể thay thế cho việc chạy xác minh sau khi tắt turbo\">date-chk",
"cut_u2sz": "kích thước (tính theo MiB) của mỗi khối tải lên; dùng kích thước khối lớn khi truyền ở khoảng cách lục địa. Hãy thử giá trị nhỏ hơn với kết nối không ổn định",
"cut_flag": "đảm bảo chỉ một tab được tải lên tại một thời điểm $N -- tab khác cũng cần bật tùy chọn này $N -- tác dụng trong các tab cùng tên miền",
"cut_az": "tải tệp theo thứ tự bảng chữ cái thay vì tệp nhỏ trước$N$Ntải lên theo thứ tự bảng chữ cái giúp dễ quan sát nếu có vấn đề trên máy chủ, nhưng làm tốc độ tải chậm hơn trên mạng cáp quang hoặc LAN",
"cut_nag": "thông báo của hệ điều hành khi tải lên hoàn tất$N(chỉ khi trình duyệt hoặc tab không hoạt động)",
"cut_sfx": "âm báo khi tải lên hoàn tất$N(chỉ khi trình duyệt hoặc tab không hoạt động)",
"cut_mt": "dùng đa luồng để tăng tốc hash tệp$N$dùng web workers và cần $nhiều RAM hơn (tối đa thêm 512 MiB)$N$làm cho https nhanh hơn 30%, http nhanh hơn 4.5 lần\">mt",
"cut_wasm": "dùng wasm thay vì bộ hash tích hợp của trình duyệt; nhanh hơn trên trình duyệt chromium nhưng làm tăng tải CPU, và nhiều bản chrome cũ có lỗi khiến trình duyệt dùng hết RAM và treo nếu bật tuỳ chọn này\">wasm",
// favicon
"cft_text": "chuỗi favicon (để trống và làm mới trang để tắt)",
"cft_fg": "màu chữ",
"cft_bg": "màu nền",
// big dirs
"cdt_lim": "số tệp tối đa hiển thị trong thư mục",
"cdt_ask": "khi cuộn xuống cuối,$Nthay vì tải thêm tệp,$Nhỏi người dùng muốn làm gì",
"cdt_hsort": "số lượng luật sắp xếp(&lt;code&gt;,sorthref&lt;/code&gt;) được đưa vào URL media. Đặt bằng 0 cũng sẽ bỏ qua các quy tắc sắp xếp trong liên kết media khi nhấp vào chúng",
"tt_entree": "hiển thị thanh điều hướng (cây thư mục)$NPhím tắt: B",
"tt_detree": "hiển thị đường dẫn$NPhím tắt: B",
"tt_visdir": "cuộn đến thư mục đã chọn",
"tt_ftree": "chuyển đổi cây thư mục / tệp văn bản$NPhím tắt: V",
"tt_pdock": "hiển thị thư mục cha trong thanh ghim trên cùng",
"tt_dynt": "tự mở rộng khi cây mở rộng",
"tt_wrap": "ngắt dòng",
"tt_hover": "hiện thị dòng tràn khi rê chuột$N( không cuộn được nếu $N&nbsp; con trỏ chuột nằm ngoài cột trái )",
"ml_pmode": "ở cuối thư mục...",
"ml_btns": "lệnh",
"ml_tcode": "mã hoá lại",
// chắc là phần nhạc
"ml_tcode2": "mã hoá lại thành",
"ml_tint": "tô màu",
"ml_eq": "bộ cân bằng âm thanh",
"ml_drc": "bộ nén dải động",
// nhạc
"mt_loop": "lặp lại một bài\">🔁",
"mt_one": "dừng sau một bài\">1⃣",
"mt_shuf": "trộn các bài trong thư mụcr\">🔀",
"mt_aplay": "tự động phát nếu có ID bài trong link bạn nhấp để truy cập máy chủ$N$Ntắt tuỳ chọn sẽ ngăn URL của trang cập nhật theo ID bài khi phát nhạc, tránh tự động phát nếu cài đặt mất nhưng URL còn\">a▶",
"mt_preload": "bắt đầu tải bài hát tiếp theo khi gần hết bài để phát liền mạch\">preload",
"mt_prescan": "chuyển đến thư mục tiếp theo trước khi bài cuối cùng $Nkết thúc, giúp giữ trình duyệt hoạt động $N và không dừng phát nhạc\">nav",
"mt_fullpre": "cố gắng tải trước toàn bộ bài;$N✅ bật với kết nối <b>không ổn định</b>,$N❌ tắt <b>với kết nối chậm</b>\">full",
"mt_fau": "trên điện thoại, ngăn nhạc dừng nếu bài tiếp theo tải chậm (có thể gây lỗi hiển thị tag nhạc)\">☕️",
"mt_waves": "thanh tiến trình bài hát dạng sóng:$Nhiển thị biên độ âm thanh trong thanh tiến trình\">~s",
"mt_npclip": "hiển thị nút để sao chép bài đang phát\">/np",
"mt_m3u_c": "hiển thị nút để sao chép $Nnhững bài đã chọn dưới dạng danh sách phát m3u8\">📻",
"mt_octl": "tích hợp hệ điều hành (phím tắt media / OSD)\">os-ctl",
"mt_oseek": "cho phép tìm kiếm qua tích hợp hệ điều hành$N$Nlưu ý: trên một số thiết bị (iphone), $Nthao tác này sẽ thay thế nút bài hát tiếp theo\">seek",
"mt_oscv": "hiển thị bài album trên OSD\">art",
"mt_follow": "giữ bài đang phát trong tầm nhìn\">🎯",
"mt_compact": "giao diện điều khiển thu gọn\">⟎",
"mt_uncache": "xoá bộ nhớ đệm &nbsp;(thử nếu trình duyệt lưu trữ đệm $Nmột bản nhạc bị lỗi và không thể phát)\">uncache",
"mt_mloop": "lặp trong thư mục đang mở\">🔁 loop",
"mt_mnext": "tải thư mục tiếp theo và tiếp tục\">📂 next",
"mt_mstop": "dừng phát\">⏸ stop",
"mt_cflac": "chuyển flac / wav sang {0}\">flac",
"mt_caac": "chuyển aac / m4a sang {0}\">aac",
"mt_coth": "chuyển mọi loại khác (trừ mp3) thành {0}\">oth",
"mt_c2opus": "lựa chọn tốt nhất cho máy tính, laptop, android\">opus",
"mt_c2owa": "opus-weba, cho iOS 17.5 trở lên\">owa",
"mt_c2caf": "opus-caf, cho iOS 11 đến 17\">caf",
"mt_c2mp3": "dùng trên thiết bị cũ\">mp3",
"mt_c2flac": "chất lượng âm thanh tốt nhất, nhưng tệp tải xuống lớn\">flac",
"mt_c2wav": "phát không nén (tệp còn lớn hơn nữa)\">wav",
"mt_c2ok": "lựa chọn hợp lý",
"mt_c2nd": "không phải định dạng khuyến nghị cho thiết bị, nhưng hãy thử nếu bạn muốn",
"mt_c2ng": "thiết bị dường như không hỗ trợ định dạng này, nhưng hãy thử nếu bạn muốn",
"mt_xowa": "có một vài lỗi trên iOS ngăn phát nền với định dạng này; vui lòng dùng caf hoặc mp3",
"mt_tint": "mức nền (0-100) trên thanh tiến trình",
"mt_eq": "bật bộ cân bằng âm thanh và bộ tăng ích;$N$Nboost &lt;code&gt;0 &nbsp;&lt;/code&gt; = âm lượng chuẩn 100% (không chỉnh)$N$Nwidth &lt;code&gt;1 &nbsp;&lt;/code&gt; = stereo chuẩn (không chỉnh)$Nwidth &lt;code&gt;0.5&lt;/code&gt; = 50% pha trái-phải$Nwidth &lt;code&gt;0 &nbsp;&lt;/code&gt; = mono$N$Nboost &lt;code&gt;-0.8&lt;/code&gt; &amp; width &lt;code&gt;10&lt;/code&gt; = loại bỏ lời hát :^)$N$Nbật EQ giúp cho album được phát liền mạch không ngắt quãng, nên giữ các giá trị bằng 0 (trừ width = 1) nếu bạn không muốn thay đổi âm thanh gốc",
"mt_drc": "bật bộ nén dải động (làm phẳng âm lượng / brickwaller); cũng bật EQ để cân bằng, nên đặt tất cả EQ trừ 'width' = 0 nếu không muốn$N$Ngiảm âm thanh trên THRESHOLD dB; với mỗi RATIO dB vượt THRESHOLD thì có 1 dB đầu ra, ví dụ tresh -24 và ratio 12 => âm lượng không vượt -22 dB, có thể tăng EQ boost lên 0.8 hoặc 1.8 với ATK 0 và RLS lớn 90 (chỉ Firefox; RLS max 1 trên browser khác)$N$NXem Wikipedia để hiểu chi tiết hơn",
"mb_play": "phát",
"mm_hashplay": "phát bản nhạc này?",
"mm_m3u": "bấm <code>Enter/OK</code> để phát\nbấm <code>ESC/Cancel</code> để chỉnh sửa",
"mp_breq": "cần firefox 82+ hoặc chrome 73+ hoặc iOS 15+",
"mm_bload": "đang tải...",
"mm_bconv": "đang chuyển đổi sang {0}, vui lòng chờ...",
"mm_opusen": "trình duyệt không hỗ trợ tệp aac / m4a;\nchuyển sang định dạng opus hiện đã được bật",
"mm_playerr": "phát lỗi: ",
"mm_eabrt": "Việc phát nhạc đã bị huỷ",
"mm_enet": "Kết nối Internet không ổn định",
"mm_edec": "Tệp này dường như đã bị hỏng??",
"mm_esupp": "Trình duyệt của bạn không nhận dạng được định dạng tệp này.",
"mm_eunk": "Lỗi không xác định",
"mm_e404": "Không thể phát âm thanh; lỗi 404: Không tìm thấy tệp.",
"mm_e403": "Không thể phát âm thanh; lỗi 403: Từ chối truy cập.\n\nThử nhấn F5 để tải lại, có thể bạn đã đăng xuất",
"mm_e500": "Không thể phát âm thanh; lỗi 500: Kiểm tra nhật ký máy chủ.",
"mm_e5xx": "Không thể phát âm thanh; lỗi máy chủ ",
"mm_nof": "không tìm thấy thêm tệp âm thanh nào gần đó",
"mm_prescan": "Đang tìm bài nhạc tiếp theo để phát...",
"mm_scank": "Đã tìm thấy bài nhạc tiếp theo:",
"mm_uncache": "đã xoá bộ nhớ đệm; tất cả bài nhạc sẽ được tải lại khi phát tiếp",
"mm_hnf": "bài nhạc này không còn tồn tại nữa",
"im_hnf": "hình ảnh này không còn tồn tại nữa",
"f_empty": 'thư mục này trống',
"f_chide": 'ẩn cột «{0}»\n\bạn có thế hiện lại nó trong tuỳ chọn cấu hình',
"f_bigtxt": "tệp này nặng {0} MiB -- xác nhận xem dưới dạng văn bản?",
"f_bigtxt2": "chỉ xem phần cuối của tệp? điều này cũng sẽ bật theo dõi, hiển thị các dòng văn bản mới được thêm vào theo thời gian thực",
"fbd_more": '<div id="blazy">hiện <code>{0}</code> của <code>{1}</code> tệp; <a href="#" id="bd_more">hiện {2}</a> hoặc <a href="#" id="bd_all">hiện tất cả</a></div>',
"fbd_all": '<div id="blazy">đang hiện <code>{0}</code> của <code>{1}</code> tệp; <a href="#" id="bd_all">hiện tất cả</a></div>',
"f_anota": "chỉ {0} trong {1} tệp được chọn;\nđể chọn toàn bộ thư mục, trước tiên hãy kéo xuống cuối",
"f_dls": 'những đường dẫn đến tệp trong thư mục này\nđã được chuyển thành đường dẫn tải trực tiếp',
"f_partial": "Để tải an toàn một tệp đang được tải lên, hãy bấm vào tệp có cùng tên nhưng *không* có phần mở rộng <code>.PARTIAL</code>. Hãy nhấn CANCEL hoặc Escape để thực hiện.\n\nNếu nhấn OK / Enter, cảnh báo sẽ bị bỏ qua và bạn sẽ tải tệp tạm <code>.PARTIAL</code> thay vào đó, gần như chắc chắn dẫn đến dữ liệu bị hỏng.",
"ft_paste": "dán {0} mục$NPhím tắt: ctrl-V",
"fr_eperm": "không thể đổi tên:\nbạn không có quyền “move” trong thư mục này",
"fd_eperm": "không thể xóa:\nbạn không có quyền “delete” trong thư mục này",
"fc_eperm": "không thể cắt:\nbạn không có quyền “move” trong thư mục này",
"fp_eperm": "không thể dán:\nbạn không có quyền “write” trong thư mục này",
"fr_emore": "hãy chọn ít nhất một mục để đổi tên",
"fd_emore": "hãy chọn ít nhất một mục để xóa",
"fc_emore": "hãy chọn ít nhất một mục để cắt",
"fcp_emore": "hãy chọn ít nhất một mục để sao chép vào bảng nhớ tạm",
"fs_sc": "chia sẻ thư mục hiện tại",
"fs_ss": "chia sẻ các tệp đã chọn",
"fs_just1d": "bạn không thể chọn nhiều hơn một thư mục,\nhoặc trộn tệp và thư mục trong cùng một lựa chọn",
"fs_abrt": "❌ hủy",
"fs_rand": "🎲 tên ngẫu nhiên",
"fs_go": "✅ tạo liên kết chia sẻ",
"fs_name": "tên",
"fs_src": "nguồn",
"fs_pwd": "mật khẩu",
"fs_exp": "hết hạn",
"fs_tmin": "phút",
"fs_thrs": "giờ",
"fs_tdays": "ngày",
"fs_never": "vĩnh viễn",
"fs_pname": "tên liên kết tùy chọn; sẽ dùng tên ngẫu nhiên nếu để trống",
"fs_tsrc": "tệp hoặc thư mục cần chia sẻ",
"fs_ppwd": "mật khẩu tùy chọn",
"fs_w8": "đang tạo liên kết chia sẻ...",
"fs_ok": "nhấn <code>Enter/OK</code> để chép vào bảng nhớ tạm\nnhấn <code>ESC/Cancel</code> để đóng",
"frt_dec": "có thể sửa một số trường hợp tên tệp bị lỗi\">url-decode",
"frt_rst": "khôi phục tên gốc\">↺ reset",
"frt_abrt": "hủy và đóng cửa sổ này\">❌ cancel",
"frb_apply": "ÁP DỤNG ĐỔI TÊN",
"fr_adv": "đổi tên theo lô / metadata / pattern\">advanced",
"fr_case": "regex phân biệt hoa thường\">case",
"fr_win": "tên tương thích Windows; thay <code>&lt;&gt;:&quot;\\|?*</code> bằng ký tự fullwidth tiếng Nhật\">win",
"fr_slash": "thay <code>/</code> bằng ký tự khác để tránh tạo thư mục mới\">no /",
"fr_re": "regex áp dụng lên tên gốc; các nhóm bắt có thể được tham chiếu trong trường định dạng bên dưới như &lt;code&gt;(1)&lt;/code&gt;, &lt;code&gt;(2)&lt;/code&gt; ...",
"fr_fmt": "lấy cảm hứng từ foobar2000:$N&lt;code&gt;(title)&lt;/code&gt; được thay bằng tên bài hát,$N&lt;code&gt;[(artist) - ](title)&lt;/code&gt; bỏ qua phần trong ngoặc nếu artist trống,$N&lt;code&gt;$lpad((tn),2,0)&lt;/code&gt; thêm số 0 để tracknumber đủ 2 chữ số",
"fr_pdel": "xóa",
"fr_pnew": "lưu dưới tên mới",
"fr_pname": "nhập tên cho preset mới",
"fr_aborted": "đã hủy",
"fr_lold": "tên cũ",
"fr_lnew": "tên mới",
"fr_tags": "tag của các tệp đã chọn (chỉ xem, không chỉnh sửa):",
"fr_busy": "đang đổi tên {0} mục...\n\n{1}",
"fr_efail": "đổi tên thất bại:\n",
"fr_nchg": "{0} tên mới đã bị chỉnh sửa do <code>win</code> và/hoặc <code>no /</code>\n\nTiếp tục với các tên đã chỉnh sửa?",
"fd_ok": "hoàn tất xoá",
"fd_err": "xoá gặp lỗi:\n",
"fd_none": "không xóa được mục nào; có thể bị chặn bởi cấu hình máy chủ (xbd)?",
"fd_busy": "đang xóa {0} mục...\n\n{1}",
"fd_warn1": "XÓA {0} mục này?",
"fd_warn2": "<b>Cảnh báo cuối!</b> Không thể hoàn tác. Xóa?",
"fc_ok": "đã cắt {0} mục",
"fc_warn": "đã cắt {0} mục\n\nnhưng: chỉ <b>tab trình duyệt này</b> có thể dán\n(vì lựa chọn quá lớn)",
"fcc_ok": "đã sao chép {0} mục vào bảng nhớ tạm",
"fcc_warn": "đã sao chép {0} mục vào bảng nhớ tạm\n\nnhưng: chỉ <b>tab trình duyệt này</b> có thể dán\n(vì lựa chọn quá lớn)",
"fp_apply": "dùng các tên này",
"fp_ecut": "hãy cắt hoặc sao chép một số tệp / thư mục trước khi dán / di chuyển\n\nlưu ý: bạn có thể cắt / dán giữa các tab trình duyệt khác nhau",
"fp_ename": "{0} mục không thể được di chuyển vào đây vì tên đã tồn tại. Hãy đặt tên mới bên dưới để tiếp tục, hoặc để trống để bỏ qua:",
"fcp_ename": "{0} mục không thể được sao chép vào đây vì tên đã tồn tại. Hãy đặt tên mới bên dưới để tiếp tục, hoặc để trống để bỏ qua:",
"fp_emore": "vẫn còn xung đột tên tệp cần xử lý",
"fp_ok": "di chuyển OK",
"fcp_ok": "sao chép OK",
"fp_busy": "đang di chuyển {0} mục...\n\n{1}",
"fcp_busy": "đang sao chép {0} mục...\n\n{1}",
"fp_abrt": "đang hủy...",
"fp_err": "di chuyển thất bại:\n",
"fcp_err": "sao chép thất bại:\n",
"fp_confirm": "di chuyển {0} mục này vào đây?",
"fcp_confirm": "sao chép {0} mục này vào đây?",
"fp_etab": "không đọc được bảng nhớ tạm từ tab trình duyệt khác",
"fp_name": "đang tải lên tệp từ thiết bị. Hãy đặt tên cho tệp:",
"fp_both_m": "<h6>chọn thao tác để dán</h6><code>Enter</code> = Di chuyển {0} tệp từ «{1}»\n<code>ESC</code> = Tải lên {2} tệp từ thiết bị",
"fcp_both_m": "<h6>chọn thao tác để dán</h6><code>Enter</code> = Sao chép {0} tệp từ «{1}»\n<code>ESC</code> = Tải lên {2} tệp từ thiết bị",
"fp_both_b": "<a href=\"#\" id=\"modal-ok\">Di chuyển</a><a href=\"#\" id=\"modal-ng\">Tải lên</a>",
"fcp_both_b": "<a href=\"#\" id=\"modal-ok\">Sao chép</a><a href=\"#\" id=\"modal-ng\">Tải lên</a>",
"mk_noname": "hãy nhập tên vào ô bên trái trước khi thực hiện :p",
"nmd_i1": "hãy thêm cả phần mở rộng tệp bạn muốn, ví dụ <code>.md</code>",
"nmd_i2": "bạn chỉ có thể tạo tệp <code>.md</code> vì bạn không có quyền xóa",
"tv_load": "Đang tải tài liệu văn bản:\n\n{0}\n\n{1}% ({2} / {3} MiB)",
"tv_xe1": "không thể tải tệp văn bản:\n\nlỗi ",
"tv_xe2": "404, không tìm thấy tệp",
"tv_lst": "danh sách các tệp văn bản trong",
"tvt_close": "quay lại chế độ xem thư mục$NPhím tắt: M (hoặc Esc)\">❌ close",
"tvt_dl": "tải xuống tệp này$NPhím tắt: Y\">💾 download",
"tvt_prev": "hiển thị tài liệu trước đó$NPhím tắt: I\">⬆ prev",
"tvt_next": "hiển thị tài liệu kế tiếp$NPhím tắt: K\">⬇ next",
"tvt_sel": "chọn tệp &nbsp; (để cắt / sao chép / xóa / ...)$NPhím tắt: S\">sel",
"tvt_j": "chuẩn hóa json$NPhím tắt: shift-J\">j",
"tvt_edit": "mở tệp trong trình soạn thảo văn bản$NPhím tắt: E\">✏️ edit",
"tvt_tail": "theo dõi thay đổi của tệp; hiển thị dòng mới theo thời gian thực\">📡 follow",
"tvt_wrap": "ngắt dòng\">↵",
"tvt_atail": "khóa cuộn ở cuối trang\">⚓",
"tvt_ctail": "giải mã màu terminal (ansi escape codes)\">🌈",
"tvt_ntail": "giới hạn scrollback (số byte văn bản được giữ trong bộ nhớ)",
"m3u_add1": "đã thêm 1 bài vào danh sách phát m3u",
"m3u_addn": "đã thêm {0} bài vào danh sách phát m3u",
"m3u_clip": "danh sách phát m3u đã được chép vào bảng nhớ tạm\n\nbạn nên tạo một tệp văn bản mới tên bất kỳ.m3u rồi dán nội dung danh sách phát vào đó để có thể phát được",
"gt_vau": "không hiện video, chỉ phát âm thanh\">🎧",
"gt_msel": "bật chọn nhiều; ctrl-click để ghi đè$N$N&lt;em&gt;khi bật: nhấn đúp để mở tệp / thư mục&lt;/em&gt;$N$NPhím tắt: S\">multiselect",
"gt_crop": "cắt chính giữa ảnh\">crop",
"gt_3x": "ảnh độ nét cao\">3x",
"gt_zoom": "zoom",
"gt_chop": "chop",
"gt_sort": "sắp xếp theo",
"gt_name": "tên",
"gt_sz": "dung lượng",
"gt_ts": "ngày",
"gt_ext": "loại",
"gt_c1": "rút ngắn tên tệp hơn (hiện ít hơn)",
"gt_c2": "rút ngắn tên tệp ít hơn (hiện nhiều hơn)",
"sm_w8": "đang tìm...",
"sm_prev": "kết quả bên dưới là từ lần tìm trước:\n ",
"sl_close": "đóng kết quả tìm kiếm",
"sl_hits": "hiển thị {0} kết quả",
"sl_moar": "tải thêm",
"s_sz": "dung lượng",
"s_dt": "ngày",
"s_rd": "đường dẫn",
"s_fn": "tên",
"s_ta": "tag",
"s_ua": "up@",
"s_ad": "nâng cao",
"s_s1": "tối thiểu MiB",
"s_s2": "tối đa MiB",
"s_d1": "ngày tối thiểu (iso8601)",
"s_d2": "ngày tối đa (iso8601)",
"s_u1": "tải lên sau",
"s_u2": "và/hoặc trước",
"s_r1": "đường dẫn chứa &nbsp; (cách nhau bằng dấu cách)",
"s_f1": "tên chứa &nbsp; (thêm -nope để phủ định)",
"s_t1": "tag chứa &nbsp; (^=bắt đầu, kết thúc=$)",
"s_a1": "thuộc tính metadata cụ thể",
"md_eshow": "không thể tải",
"md_off": "[📜<em>readme</em>] đã tắt trong [⚙️] -- tài liệu bị ẩn",
"badreply": "Không thể phân tích phản hồi từ máy chủ",
"xhr403": "403: Access denied\n\nhãy thử nhấn F5, có thể bạn đã bị đăng xuất",
"xhr0": "không rõ (có thể mất kết nối với máy chủ hoặc máy chủ đang offline)",
"cf_ok": "rất tiếc, lớp bảo vệ DD" + wah + "oS đã kích hoạt\n\nmọi thứ sẽ tiếp tục sau khoảng 30 giây\n\nnếu không có gì xảy ra, hãy nhấn F5 để tải lại trang",
"tl_xe1": "không thể liệt kê thư mục con:\n\nlỗi ",
"tl_xe2": "404: Không tìm thấy thư mục",
"fl_xe1": "không thể liệt kê tệp trong thư mục:\n\nlỗi ",
"fl_xe2": "404: Không tìm thấy thư mục",
"fd_xe1": "không thể tạo thư mục con:\n\nlỗi ",
"fd_xe2": "404: Không tìm thấy thư mục cha",
"fsm_xe1": "không thể gửi tin nhắn:\n\nlỗi ",
"fsm_xe2": "404: Không tìm thấy thư mục cha",
"fu_xe1": "không tải được danh sách unpost từ máy chủ:\n\nlỗi ",
"fu_xe2": "404: Không tìm thấy tệp??",
"fz_tar": "file gnu-tar không nén (linux / mac)",
"fz_pax": "file tar định dạng pax không nén (chậm hơn)",
"fz_targz": "gnu-tar với nén gzip mức 3$N$Nthường rất chậm nên$Nhãy dùng tar không nén",
"fz_tarxz": "gnu-tar với nén xz mức 1$N$Nthường rất chậm nên$Nhãy dùng tar không nén",
"fz_zip8": "zip với tên tệp utf8 (có thể lỗi trên windows 7 và cũ hơn)",
"fz_zipd": "zip với tên tệp cp437 truyền thống, dành cho phần mềm rất cũ",
"fz_zipc": "cp437 với crc32 tính sớm,$Ndành cho MS-DOS PKZIP v2.04g (tháng 10/1993)$N(mất thời gian xử lý lâu hơn trước khi tải xuống bắt đầu)",
"un_m1": "bạn có thể xóa các lần upload gần đây (hoặc hủy những mục chưa hoàn tất) bên dưới",
"un_upd": "làm mới",
"un_m4": "hoặc chia sẻ các tệp hiển thị bên dưới:",
"un_ulist": "hiện",
"un_ucopy": "chép",
"un_flt": "bộ lọc tùy chọn:&nbsp; đường dẫn phải chứa",
"un_fclr": "xóa bộ lọc",
"un_derr": "xóa unpost thất bại:\n",
"un_f5": "có gì đó lỗi rồi, hãy thử tải lại trang hoặc nhấn F5",
"un_uf5": "xin lỗi nhưng bạn cần tải lại trang (ví dụ nhấn F5 hoặc CTRL-R) trước khi có thể hủy upload này",
"un_nou": "<b>cảnh báo:</b> máy chủ đang bận nên không thể hiển thị các upload chưa hoàn tất; hãy bấm “làm mới” sau một lúc",
"un_noc": "<b>cảnh báo:</b> unpost cho các tệp đã upload xong không được bật hoặc không được phép trong cấu hình máy chủ",
"un_max": "đang hiển thị 2000 tệp đầu tiên (hãy dùng bộ lọc)",
"un_avail": "có thể xóa {0} tải lên gần đây<br />{1} mục chưa hoàn tất có thể hủy",
"un_m2": "sắp xếp theo thời gian tải lên; mới nhất ở trên:",
"un_no1": "không có tải lên nào đủ mới",
"un_no2": "không có tải lên nào đủ mới khớp với bộ lọc đó",
"un_next": "xóa {0} tệp kế bên dưới",
"un_abrt": "hủy",
"un_del": "xóa",
"un_m3": "đang tải danh sách tải lên gần đây...",
"un_busy": "đang xóa {0} tệp...",
"un_clip": "{0} liên kết đã chép vào bảng nhớ tạm",
"u_https1": "bạn nên",
"u_https2": "chuyển sang https",
"u_https3": "để có hiệu suất tốt hơn",
"u_ancient": "trình duyệt của bạn quá cũ; bạn có thể <a href=\"#\" onclick=\"goto('bup')\">dùng bup</a> thay thế",
"u_nowork": "cần Firefox 53+, Chrome 57+ hoặc iOS 11+",
"tail_2old": "cần Firefox 105+, Chrome 71+ hoặc iOS 14.5+",
"u_nodrop": "trình duyệt của bạn quá cũ để dùng kéo thả khi tải lên",
"u_notdir": "đây không phải thư mục\n\ntrình duyệt của bạn quá cũ,\nvui lòng thử dùng dragdrop",
"u_uri": "để kéo thả ảnh từ cửa sổ trình duyệt khác,\nvui lòng thả lên nút tải lên lớn",
"u_enpot": "chuyển sang <a href=\"#\">giao diện đơn giản</a> (có thể tăng tốc độ tải lên)",
"u_depot": "chuyển sang <a href=\"#\">giao diện đầy đủ</a> (có thể giảm tốc độ tải lên)",
"u_gotpot": "đang chuyển sang giao diện đơn giản để cải thiện tốc độ tải lên\n\nbạn có thể đổi lại bất kỳ lúc nào",
"u_pott": "<p>tệp: &nbsp; <b>{0}</b> hoàn tất, &nbsp; <b>{1}</b> lỗi, &nbsp; <b>{2}</b> đang chạy, &nbsp; <b>{3}</b> chờ</p>",
"u_ever": "đây là trình tải lên cơ bản; up2k yêu cầu<br>Chrome 21 // Firefox 13 // Edge 12 // Opera 12 // Safari 5.1 trở lên",
"u_su2k": "đây là trình tải lên cơ bản; <a href=\"#\" id=\"u2yea\">up2k</a> tốt hơn",
"u_uput": "tối ưu tốc độ (bỏ qua checksum)",
"u_ewrite": "bạn không có quyền ghi vào thư mục này",
"u_eread": "bạn không có quyền đọc thư mục này",
"u_enoi": "tìm kiếm tệp chưa được bật trong cấu hình máy chủ",
"u_enoow": "ghi đè không khả dụng; cần quyền Xóa",
"u_badf": "Đã bỏ qua {0} tệp (trong tổng {1}) có thể do quyền hệ thống tệp:\n\n",
"u_blankf": "{0} tệp (trong tổng {1}) trống; vẫn tải lên?\n\n",
"u_applef": "{0} tệp (trong tổng {1}) có thể không cần thiết;\nNhấn <code>OK/Enter</code> để BỎ QUA,\nNhấn <code>Cancel/ESC</code> để giữ lại và tải lên:\n\n",
"u_just1": "\nCó thể sẽ hoạt động tốt hơn nếu bạn chỉ chọn một tệp",
"u_ff_many": "nếu bạn dùng <b>Linux / macOS / Android</b> thì số lượng tệp này <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\"><em>có thể</em> làm Firefox crash</a>\nnếu xảy ra, vui lòng thử lại hoặc dùng Chrome",
"u_up_life": "Tệp tải lên sẽ bị xóa khỏi máy chủ\n{0} sau khi hoàn tất",
"u_asku": "tải {0} tệp này lên <code>{1}</code>",
"u_unpt": "bạn có thể hoàn tác hoặc xóa lần tải lên này bằng biểu tượng ở góc trên bên trái 🧯",
"u_bigtab": "sắp hiển thị {0} tệp\n\nviệc này có thể làm trình duyệt treo, bạn có chắc không?",
"u_scan": "Đang quét tệp...",
"u_dirstuck": "bộ lặp thư mục bị treo khi truy cập {0} mục sau; sẽ bỏ qua:",
"u_etadone": "Hoàn tất ({0}, {1} tệp)",
"u_etaprep": "(đang chuẩn bị tải lên)",
"u_hashdone": "hash hoàn tất",
"u_hashing": "hash",
"u_hs": "đang bắt tay...",
"u_started": "tệp đang được tải lên; xem biểu tượng [🚀]",
"u_dupdefer": "bị trùng; sẽ xử lý sau khi hoàn tất các tệp khác",
"u_actx": "nhấn vào dòng này để tránh giảm hiệu suất khi chuyển cửa sổ hoặc tab",
"u_fixed": "OK;&nbsp; đã sửa 👍",
"u_cuerr": "không tải được phần {0}/{1};\ncó thể không nghiêm trọng, tiếp tục\n\ntệp: {2}",
"u_cuerr2": "máy chủ từ chối phần tải {0}/{1};\nsẽ thử lại sau\n\ntệp: {2}\n\nlỗi ",
"u_ehstmp": "sẽ thử lại; xem góc dưới bên phải",
"u_ehsfin": "máy chủ từ chối yêu cầu hoàn tất tải lên; đang thử lại...",
"u_ehssrch": "máy chủ từ chối yêu cầu tìm kiếm; đang thử lại...",
"u_ehsinit": "máy chủ từ chối yêu cầu bắt đầu tải lên; đang thử lại...",
"u_eneths": "lỗi mạng khi thực hiện bắt tay; đang thử lại...",
"u_enethd": "lỗi mạng khi kiểm tra tồn tại mục tiêu; đang thử lại...",
"u_cbusy": "chờ máy chủ cho phép tiếp tục sau sự cố mạng...",
"u_ehsdf": "máy chủ hết dung lượng!\n\nsẽ tiếp tục thử trong trường hợp có ai đó giải phóng dung lượng",
"u_emtleak1": "có dấu hiệu cho thấy trình duyệt có thể bị rò rỉ bộ nhớ;\nvui lòng",
"u_emtleak2": " <a href=\"{0}\">chuyển sang https (khuyến nghị)</a> hoặc ",
"u_emtleak3": " ",
"u_emtleakc": "thử cách sau:\n<ul><li>nhấn <code>F5</code> để tải lại trang</li><li>sau đó tắt nút &nbsp;<code>mt</code>&nbsp; trong &nbsp;<code>⚙️ cài đặt</code></li><li>và thử tải lại</li></ul>Tốc độ có thể chậm hơn một chút\nXin lỗi vì sự bất tiện\n\nPS: chrome v107 <a href=\"https://bugs.chromium.org/p/chromium/issues/detail?id=1354816\" target=\"_blank\">đã có bản sửa</a>",
"u_emtleakf": "thử cách sau:\n<ul><li>nhấn <code>F5</code> để tải lại trang</li><li>sau đó bật <code>🥔</code> (potato) trong giao diện tải lên<li>và thử lại</li></ul>\nPS: firefox <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1790500\" target=\"_blank\">hy vọng sẽ có bản sửa</a>",
"u_s404": "không tìm thấy trên máy chủ",
"u_expl": "giải thích",
"u_maxconn": "đa số trình duyệt giới hạn giá trị này ở mức 6, nhưng Firefox cho phép tăng bằng <code>connections-per-server</code> trong <code>about:config</code>",
"u_tu": "<p class=\"warn\">CẢNH BÁO: turbo đang bật <span>&nbsp;client có thể không nhận diện và tiếp tục tải lại tệp chưa hoàn tất; xem tooltip nút turbo</span></p>",
"u_ts": "<p class=\"warn\">CẢNH BÁO: turbo đang bật <span>&nbsp;kết quả tìm kiếm có thể không chính xác; xem tooltip nút turbo</span></p>",
"u_turbo_c": "turbo bị tắt trong cấu hình máy chủ",
"u_turbo_g": "đang tắt turbo vì bạn không có quyền liệt kê thư mục trong phân vùng này",
"u_life_cfg": "tự xóa sau <input id=\"lifem\" p=\"60\" /> phút (hoặc <input id=\"lifeh\" p=\"3600\" /> giờ)",
"u_life_est": "tệp sẽ bị xóa <span id=\"lifew\" tt=\"local time\">---</span>",
"u_life_max": "thư mục này áp dụng\nthời gian tồn tại tối đa {0}",
"u_unp_ok": "cho phép unpost trong {0}",
"u_unp_ng": "không cho phép unpost",
"ue_ro": "bạn chỉ có quyền đọc thư mục này\n\n",
"ue_nl": "bạn chưa đăng nhập",
"ue_la": "bạn đang đăng nhập với tài khoản \"{0}\"",
"ue_sr": "bạn đang ở chế độ tìm kiếm tệp\n\nhãy chuyển sang chế độ tải lên bằng cách nhấn biểu tượng kính lúp 🔎 (bên cạnh nút SEARCH), rồi thử lại\n\nxin lỗi",
"ue_ta": "hãy thử tải lên lại; giờ có thể được",
"ue_ab": "tệp này đang được tải lên ở thư mục khác và cần hoàn tất trước khi có thể tải lên nơi khác.\n\nBạn có thể hủy và quên tiến trình ban đầu bằng biểu tượng ở góc trên bên trái 🧯",
"ur_1uo": "OK: tệp đã tải lên thành công",
"ur_auo": "OK: toàn bộ {0} tệp đã tải lên thành công",
"ur_1so": "OK: đã tìm thấy tệp trên máy chủ",
"ur_aso": "OK: đã tìm thấy toàn bộ {0} tệp trên máy chủ",
"ur_1un": "Tải lên thất bại",
"ur_aun": "{0} lần tải lên đều thất bại",
"ur_1sn": "KHÔNG tìm thấy tệp trên máy chủ",
"ur_asn": "{0} tệp KHÔNG tìm thấy trên máy chủ",
"ur_um": "Hoàn tất\n{0} tải lên thành công,\n{1} tải lên thất bại",
"ur_sm": "Hoàn tất\n{0} tệp tìm thấy trên máy chủ,\n{1} tệp KHÔNG tìm thấy",
"lang_set": "tải lại trang để áp dụng thay đổi ngôn ngữ",
"splash": {
"a1": "tải lại",
"b1": "xin chào khách &nbsp; <small>(bạn chưa đăng nhập)</small>",
"c1": "đăng xuất",
"d1": "ghi lại ngăn xếp",
"d2": "hiển thị trạng thái của tất cả các luồng đang hoạt động",
"e1": "tải lại cấu hình",
"e2": "tải lại các tệp cấu hình (tài khoản/ổ đĩa/cờ ổ đĩa),$Nvà quét lại tất cả các ổ đĩa e2ds$N$Nchú ý: bất kỳ thay đổi nào đối với cài đặt toàn cục$Ncần khởi động lại hoàn toàn để có hiệu lực",
"f1": "bạn có thể duyệt:",
"g1": "bạn có thể tải lên:",
"cc1": "thứ khác:",
"h1": "vô hiệu hoá k304",
"i1": "bật k304",
"j1": "bật k304 sẽ ngắt kết nối client của bạn trên mỗi HTTP 304, tùy chọn này có thể ngăn một số proxy bị lỗi kẹt (đột ngột không tải được trang), <em>nhưng</em> nó cũng sẽ làm mọi thứ chậm hơn",
"k1": "đặt lại cài đặt client",
"l1": "đăng nhập để có thêm:",
"m1": "chào mừng trở lại,",
"n1": "404 không tìm thấy &nbsp;┐( ´ -`)┌",
"o1": 'hoặc có thể bạn không có quyền truy cập -- thử mật khẩu hoặc <a href="' + SR + '/?h">về trang chủ</a>',
"p1": "403 bị cấm &nbsp;~┻━┻",
"q1": 'sử dụng mật khẩu hoặc <a href="' + SR + '/?h">về trang chủ</a>',
"r1": "về trang chủ",
".s1": "quét lại",
"t1": "hành động",
"u2": "thời gian kể từ lần ghi máy chủ cuối cùng$N( tải lên / đổi tên / ... )$N$N17d = 17 ngày$N1h23 = 1 giờ 23 phút$N4m56 = 4 phút 56 giây",
"v1": "kết nối",
"v2": "sử dụng máy chủ này như một ổ cứng cục bộ",
"w1": "chuyển sang https",
"x1": "đổi mật khẩu",
"y1": "chỉnh sửa chia sẻ",
"z1": "mở khóa chia sẻ này:",
"ta1": "điền mật khẩu mới:",
"ta2": "nhập lại mật khẩu mới:",
"ta3": "mật khẩu không khớp, xin hãy thử lại",
"nop": "LỖI: Mật khẩu không được để trống", //m
"nou": "LỖI: Tên người dùng và/hoặc mật khẩu không được để trống", //m
"aa1": "tệp đến",
"ab1": "vô hiệu hóa no304",
"ac1": "bật no304",
"ad1": "bật no304 sẽ vô hiệu hóa tất cả bộ nhớ đệm; hãy thử tùy chọn này nếu k304 không đủ. Tùy chọn này sẽ làm lãng phí một lượng lớn lưu lượng mạng!",
"ae1": "tải xuống đang hoạt động:",
"af1": "hiển thị các tệp đã tải lên gần đây",
"ag1": "hiển thị người dùng IdP đã biết", //m
}
};

View file

@ -1,3 +1,84 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-1202-2047 `v1.19.21` tadaimback
## 🧪 new features
* [hooks](https://github.com/9001/copyparty/tree/hovudstraum/bin/hooks#readme) now behave more usefully/predictably; 889bd324
* hooks returning `0` will run the next hook (if any), and let the initiating action proceed if no other hooks object
* hooks returning `100` will stop processing successive hooks, but return success, letting the initiating action proceed
* hooks returning anything else will stop processing successive hooks (like the documentation always said) and also fail the initiating action (if hook is checked)
* zmq hooks can now respond with json, doing relocations and all that stuff
* new mtag plugin, [geotag.py](https://github.com/9001/copyparty/blob/hovudstraum/bin/mtag/geotag.py): read image geotags with exiftool ([demo](https://a.ocv.me/pub/blog/j8/11/)) 1c15c0d5 ac085b81
* #972 markdown-links are rewritten to open in the markdown-viewer 278a0d85
* #794 add json beautifier / minifier
* ...in the textfile-editor fd8c5bfc
* ...in the textfile-viewer 89cab5b5
* #1058 ui-option and server-config to force download instead of showing files inline a9174e5d
* option `stats-u` to grant access to prometheus-metrics based on username, not just permissions b427d780
## 🩹 bugfixes
* #1003 u2c.py (commandline uploader) did not install correctly on archlinux and/or pypi 9385daea
* #1035 uploader could fail to initialize if: 98701b78
* the `mt` button (webworkers) was enabled in the settings tab
* **and** the network was severely strained during intial page load
* possible deadlock on shutdown if thumbnailer queue was hella busy fb9f0441
* #971 windows: fix deadlock on startup if trying to use a nonexistant driveletter as a volume 945b2276
* #1022 js-panic if audio playback is set to stay-in-folder a28503e8
* links to ongoing file transfers in the controlpanel could 404 (thx @Habetdin!) 77f74ddb f4d67ff0
* video scrubbing on iOS dba7c5d4
* #1054 audio volume slider could skip one percent (thx @shermanhlc!) ca6d3a5c
* detect invalid config:
* #959 panic if `ipu` user doesn't exist 79e10786
* panic if share config overlaps with a volume cedfc444
* #943
## 🔧 other changes
* the "new-markdown" feature was repurposed into "new-file", accepting any file extension 7d62335c
* #1023 the option to grant delete-access when creating a share was removed due to never having been implemented in the backend 04ac7fbd
* #1012 rephrased the controlpanel login-text when logged in to avoid confusion 7a291403
* add hints that the serverlog is a good place to look in some situations c424a55d
* all thumbnail types and combinations can now be pregenerated a359b89e
* #1030 add debug if cfssl is misbehaving ec00dc18
* #871 `grid` volflag is applied during navigation if user has not set a preference a9378a8e
* cosmetic:
* show column number in markdown editor b9aacba1
* reduced grid margins in theme2 e469bc94
* reduced redirect delay after logging in f7e7b03f
* controlpanel greeting in some fail-early responses acde21d4
* update hooks to ignore the new upload-queue-empty message 3f4b79ff
* docs:
* #1032 fix typo in example docker idp config (thx @tuetenk0pp!) 867237d0
* warn that using/changing `-j` is usually a bad idea cad15fbf
* add hotlink anchors to https://copyparty.eu/cli/ 7f9c139e
* nixos:
* #868 option to install from git-head (thx @shelvacu!) c7345308
* #962 support idp volumes (thx @nicomem!) 904c984b
* #963 use configured chmod-d when creating volumes (thx @nicomem!) 3242145e
* copyparty.exe: update to python 3.13.10, pillow 12.0 cdffde78
## 🌠 fun facts
* copyparty has been observed running [on a wristwatch](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/clockyparty.jpg) and on an [android tv-box](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/aallwinner.jpg) running in big-endian mode, so copyparty is [BE-certified](https://a.ocv.me/pub/g/nerd-stuff/cpp/servers/be-ready.png)
* also... **it's december!** [you know what that means](https://a.ocv.me/pub/demo/music/.bonus/#af-55d4554d) :^)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-1102-0109 `v1.19.20` november
## 🧪 new features
* #961 the `/?shares` listing now shows the list of filenames for each share 2cc53ea15181f750b4367e6cd20dfebd0bcb3bee
## 🩹 bugfixes
* #967 per-volume md/lg sandbox rules are now applied during navigation db60951d9fa5b17c8190e8b3ab4ceb422a9d2701
* if a volume has `no-sb-lg` or `no-sb-md` set then it'll apply when navigating into that volume, and vice-versa
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
# 2025-1025-1918 `v1.19.19` copyparty.eu マークII

View file

@ -40,6 +40,7 @@ open the Package Center and install `Text Editor` (by Synology Inc.) to create a
rss, daw, ver # some other nice-to-have features
#dedup # you may want this, or maybe not
hist: /cfg/hist # don't pollute the shared-folder
unlist: ^@eaDir # hide the synology "@eaDir" folders
name: synology # shows in the browser, can be anything
[accounts]
@ -49,10 +50,6 @@ open the Package Center and install `Text Editor` (by Synology Inc.) to create a
/w # the "/w" docker-volume (the shared-folder)
accs:
A: ed # give Admin to username ed
# hide the synology system files by creating a hidden volume
[/@eaDir]
/w/@eaDir
```
if you ever change the copyparty config file, then [restart the container](https://ocv.me/copyparty/doc/pics/dsm71-02.png) to make the changes take effect

View file

@ -3,7 +3,7 @@ WORKDIR /z
ENV ver_asmcrypto=c72492f4a66e17a0e5dd8ad7874de354f3ccdaa5 \
ver_hashwasm=4.12.0 \
ver_marked=4.3.0 \
ver_dompf=3.2.7 \
ver_dompf=3.3.1 \
ver_mde=2.18.0 \
ver_codemirror=5.65.18 \
ver_fontawesome=5.13.0 \

View file

@ -18,7 +18,7 @@ RUN apk add -U !pyc \
py3-magic \
vips-jxl vips-heif vips-poppler vips-magick \
py3-numpy fftw libsndfile \
vamp-sdk vamp-sdk-libs \
vamp-sdk vamp-sdk-libs keyfinder-cli \
libraw py3-numpy cython \
&& apk add -t .bd \
bash wget gcc g++ make cmake patchelf \

View file

@ -28,8 +28,8 @@ f4b4e330995ebe96c0bd06e16e5b26062ece9473f06d369775aa68eab261dedcf32dfdd159acaa22
00731cfdd9d5c12efef04a7161c90c1e5ed1dc4677aa88a1d4054aff836f3430df4da5262ed4289c21637358a9e10e5df16f76743cbf5a29bb3a44b146c19cf3 MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
a726fb46cce24f781fc8b55a3e6dea0a884ebc3b2b400ea74aa02333699f4955a5dc1e2ec5927ac72f35a624401f3f3b442882ba1cc4cadaf9c88558b5b8bdae packaging-25.0-py3-none-any.whl
3e39ea6e16b502d99a2e6544579095d0f7c6097761cd85135d5e929b9dec1b32e80669a846f94ee8c2cca9be2f5fe728625d09453988864c04e16bb8445c3f91 pillow-11.3.0-cp313-cp313-win_amd64.whl
fa5d24c51e39760fc5121e56e9948384e03f62b66907ba313a6a803dd601832df62fb5066f3019620664d7cc6b0482e13000cd2d3d1553b709a56a347919565e pillow-12.0.0-cp313-cp313-win_amd64.whl
b9b98714dfca6fa80b0b3f222965724d63be9c54d19435d1fe768e07016913d6db8d6e043fcb185b55a9bd6fe370a80cf961814fc096046a5f4640d99ed575ef pyinstaller-6.15.0-py3-none-win_amd64.whl
cad0f7cf39de691813b1d4abc7d33f8bda99a87d9c5886039b814752e8690364150da26fb61b3e28d5698ff57a90e6dcd619ed2b64b04f72b5aadb75e201bdb0 pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
419f499560f09b770060ef336926f5bf2776b5c33937969ce75d1e3263735de1ed6eb2199ae88797cba0e4cb17de4a235beec4d7985f993ecb3de7320c482917 python-3.13.9-amd64.exe
1735728ae50e003badc5266638e41a73358f2151405e7888b6dc45697c074a60e6e58c8507b49a3f42d8f4fe4005fbc225cd766ab6582cbf85aa79bab699c08f python-3.13.11-amd64.exe
2a0420f7faaa33d2132b82895a8282688030e939db0225ad8abb95a47bdb87b45318f10985fc3cee271a9121441c1526caa363d7f2e4a4b18b1a674068766e87 setuptools-80.9.0-py3-none-any.whl

View file

@ -39,10 +39,10 @@ fns=(
MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl
mutagen-1.47.0-py3-none-any.whl
packaging-25.0-py3-none-any.whl
pillow-11.3.0-cp313-cp313-win_amd64.whl
pillow-12.0.0-cp313-cp313-win_amd64.whl
pyinstaller-6.15.0-py3-none-win_amd64.whl
pyinstaller_hooks_contrib-2025.8-py3-none-any.whl
python-3.13.9-amd64.exe
python-3.13.11-amd64.exe
setuptools-80.9.0-py3-none-any.whl
)
[ $w7 ] && fns+=(

View file

@ -99,7 +99,6 @@ copyparty/web/md2.js,
copyparty/web/mde.css,
copyparty/web/mde.html,
copyparty/web/mde.js,
copyparty/web/msg.css,
copyparty/web/msg.html,
copyparty/web/opds.xml,
copyparty/web/rups.css,
@ -133,6 +132,7 @@ copyparty/web/tl/spa.js,
copyparty/web/tl/swe.js,
copyparty/web/tl/tur.js,
copyparty/web/tl/ukr.js,
copyparty/web/tl/vie.js,
copyparty/web/ui.css,
copyparty/web/up2k.js,
copyparty/web/util.js,

View file

@ -113,6 +113,8 @@ Ls.hmn = {
["M", "close textfile"],
["E", "edit textfile"],
["S", "select file (for cut/copy/rename)"],
["Y", "download textfile"],
["⇧ J", "beautify json"],
]
],
@ -252,6 +254,7 @@ Ls.hmn = {
"ct_ttips": '◔ ◡ ◔"> tooltips',
"ct_thumb": 'in grid-view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
"ct_csel": 'use CTRL and SHIFT for file selection in grid-view">sel',
"ct_dl": 'force download (don\'t display inline) when a file is clicked">dl',
"ct_ihop": 'when the image viewer is closed, scroll down to the last viewed file">g⮯',
"ct_dots": 'show hidden files (if server permits)">dotfiles',
"ct_qdel": 'when deleting files, only ask for confirmation once">qdel',
@ -478,6 +481,7 @@ Ls.hmn = {
"tvt_prev": "show previous document$NHotkey: i\">⬆ prev",
"tvt_next": "show next document$NHotkey: K\">⬇ next",
"tvt_sel": "select file &nbsp; ( for cut / copy / delete / ... )$NHotkey: S\">sel",
"tvt_j": "beautify json$NHotkey: shift-J\">j",
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
"tvt_tail": "monitor file for changes; show new lines in real time\">📡 follow",
"tvt_wrap": "word-wrap\">↵",
@ -698,11 +702,14 @@ Ls.hmn = {
"ta1": "fill in your new password first",
"ta2": "repeat to confirm new password:",
"ta3": "found a typo; please try again",
"nop": "ERROR: Password cannot be blank",
"nou": "ERROR: Username and/or password cannot be blank",
"aa1": "incoming files:",
"ab1": "disable no304",
"ac1": "enable no304",
"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
"ae1": "active downloads:",
"af1": "show recent uploads",
"ag1": "view idp cache", // TLNote: is a link to a page where IdP users can be managed
}
};

View file

@ -105,12 +105,15 @@ Ls.{lang3} = {{
"ta1": "fill in your new password first",
"ta2": "repeat to confirm new password:",
"ta3": "found a typo; please try again",
"nop": "ERROR: Password cannot be blank",
"nou": "ERROR: Username and/or password cannot be blank",
"aa1": "incoming files:",
"ab1": "disable no304",
"ac1": "enable no304",
"ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!",
"ae1": "active downloads:",
"af1": "show recent uploads",
"ag1": "view idp cache", // TLNote: is a link to a page where IdP users can be managed
}}
}};
"""

View file

@ -78,6 +78,41 @@ class TestHooks(tu.TC):
h, b = self.curl(url_dl)
self.assertEqual(b, "ok %s\n" % (url_up))
def test2(self):
hooktxt = "import sys\nopen('h%d','wb').close()\nsys.exit(%d)\n"
for hooktype in ("xbu", "xau"):
for upfun in (self.put, self.bup):
self.reset()
for n in [0, 1, 100]:
with open("h%d.py" % (n,), "wb") as f:
f.write((hooktxt % (n, n)).encode("utf-8"))
vcfg = [
"012:012:A:c,H=c,h0.py:c,H=c,h1.py:c,H=c,h100.py",
"021:021:A:c,H=c,h0.py:c,H=c,h100.py:c,H=c,h1.py",
"120:120:A:c,H=c,h1.py:c,H=c,h100.py:c,H=c,h0.py",
"30:30:A:c,H=c,enoent.py:c,H=c,h100.py", # not-exist
]
vcfg = [x.replace("H", hooktype) for x in vcfg]
self.args = Cfg(v=vcfg, a=["o:o"], e2d=True)
self.asrv = AuthSrv(self.args, self.log)
self.cinit()
scenarios = (
("012", False, True, True, False),
("021", True, True, False, True),
("120", False, False, True, False),
("30", False, False, False, False),
)
for (vp, ok, h0, h1, h2) in scenarios:
for zs in ("h0", "h1", "h100"):
if os.path.exists(zs):
os.unlink(zs)
vp = "%s/f" % (vp,)
h, b = upfun(vp)
self.assertEqual(ok, os.path.exists(vp))
self.assertEqual(h0, os.path.exists("h0"))
self.assertEqual(h1, os.path.exists("h1"))
self.assertEqual(h2, os.path.exists("h100"))
def makehook(self, hs):
with open("h.py", "wb") as f:
f.write(hs.encode("utf-8"))

View file

@ -143,7 +143,7 @@ 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 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_dupe_m 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 no_zls 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 ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi 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 dlni 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_dupe_m 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 no_zls 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 ui_noacci ui_nocpla ui_noctxb ui_nolbar ui_nombar ui_nonav ui_notree ui_norepl ui_nosrvi 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 wram re_dhash see_dots plain_ip"
@ -158,16 +158,16 @@ class Cfg(Namespace):
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"
ex = "ac_convt au_vol dl_list du_iwho mtab_age reg_cap s_thead s_tbody tail_tmax tail_who th_convt th_qv ups_who ver_iwho zip_who"
ka.update(**{k: 9 for k in ex.split()})
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 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"
ex = "ah_alg bname chdir chmod_f chpw_db doctitle df epilogues exit favico ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date 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 preadmes prologues readmes 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 dont_ban spinner"
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url dont_ban cachectl rss_fmt_d rss_fmt_t spinner"
ka.update(**{k: "no" for k in ex.split()})
ex = "ext_th grp idp_h_usr idp_hm_usr ipr on403 on404 qr_file xac xad xar xau xban xbc xbd xbr xbu xiu xm"
@ -193,6 +193,7 @@ class Cfg(Namespace):
du_who="all",
dk_salt="b" * 16,
fk_salt="a" * 16,
fsnt="lin",
grp_all="acct",
idp_gsep=re.compile("[|:;+,]"),
iobuf=256 * 1024,
@ -208,6 +209,7 @@ class Cfg(Namespace):
mv_retry="0/0",
rm_retry="0/0",
rotf_tz="UTC",
rss_sort="m",
s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024,
shr_who="auth",
@ -353,6 +355,7 @@ class VHttpConn(object):
self.ico = Ico(args)
self.ipr = None
self.ipa_nm = None
self.ipar_nm = None
self.lf_url = None
self.log_func = log
self.log_src = "a"