Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dabdaaee33 | ||
|
|
65e4d67c3e | ||
|
|
4b720f4150 | ||
|
|
2e85a25614 | ||
|
|
713fffcb8e | ||
|
|
8020b11ea0 | ||
|
|
2523d76756 | ||
|
|
7ede509973 | ||
|
|
7c1d97af3b | ||
|
|
95566e8388 | ||
|
|
76afb62b7b | ||
|
|
7dec922c70 | ||
|
|
c07e0110f8 | ||
|
|
2808734047 | ||
|
|
1f75314463 | ||
|
|
063fa3efde | ||
|
|
44693d79ec | ||
|
|
cea746377e | ||
|
|
59a98bd2b5 |
37
README.md
37
README.md
@@ -53,6 +53,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||||||
* [webdav server](#webdav-server) - with read-write support
|
* [webdav server](#webdav-server) - with read-write support
|
||||||
* [connecting to webdav from windows](#connecting-to-webdav-from-windows) - using the GUI
|
* [connecting to webdav from windows](#connecting-to-webdav-from-windows) - using the GUI
|
||||||
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
* [smb server](#smb-server) - unsafe, slow, not recommended for wan
|
||||||
|
* [browser ux](#browser-ux) - tweaking the ui
|
||||||
* [file indexing](#file-indexing) - enables dedup and music search ++
|
* [file indexing](#file-indexing) - enables dedup and music search ++
|
||||||
* [exclude-patterns](#exclude-patterns) - to save some time
|
* [exclude-patterns](#exclude-patterns) - to save some time
|
||||||
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
|
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
|
||||||
@@ -317,6 +318,8 @@ same order here too
|
|||||||
|
|
||||||
upgrade notes
|
upgrade notes
|
||||||
|
|
||||||
|
* `1.9.16` (2023-11-04):
|
||||||
|
* `--stats`/prometheus: `cpp_bans` renamed to `cpp_active_bans`, and that + `cpp_uptime` are gauges
|
||||||
* `1.6.0` (2023-01-29):
|
* `1.6.0` (2023-01-29):
|
||||||
* http-api: delete/move is now `POST` instead of `GET`
|
* http-api: delete/move is now `POST` instead of `GET`
|
||||||
* everything other than `GET` and `HEAD` must pass [cors validation](#cors)
|
* everything other than `GET` and `HEAD` must pass [cors validation](#cors)
|
||||||
@@ -789,6 +792,8 @@ other notes,
|
|||||||
|
|
||||||
* files named `README.md` / `readme.md` will be rendered after directory listings unless `--no-readme` (but `.epilogue.html` takes precedence)
|
* files named `README.md` / `readme.md` will be rendered after directory listings unless `--no-readme` (but `.epilogue.html` takes precedence)
|
||||||
|
|
||||||
|
* `README.md` and `*logue.html` can contain placeholder values which are replaced server-side before embedding into directory listings; see `--help-exp`
|
||||||
|
|
||||||
|
|
||||||
## searching
|
## searching
|
||||||
|
|
||||||
@@ -954,6 +959,16 @@ authenticate with one of the following:
|
|||||||
* username `$password`, password `k`
|
* username `$password`, password `k`
|
||||||
|
|
||||||
|
|
||||||
|
## browser ux
|
||||||
|
|
||||||
|
tweaking the ui
|
||||||
|
|
||||||
|
* set default sort order globally with `--sort` or per-volume with the `sort` volflag; specify one or more comma-separated columns to sort by, and prefix the column name with `-` for reverse sort
|
||||||
|
* the column names you can use are visible as tooltips when hovering over the column headers in the directory listing, for example `href ext sz ts tags/.up_at tags/Cirle tags/.tn tags/Artist tags/Title`
|
||||||
|
* to sort in music order (album, track, artist, title) with filename as fallback, you could `--sort tags/Cirle,tags/.tn,tags/Artist,tags/Title,href`
|
||||||
|
* to sort by upload date, first enable showing the upload date in the listing with `-e2d -mte +.up_at` and then `--sort tags/.up_at`
|
||||||
|
|
||||||
|
|
||||||
## file indexing
|
## file indexing
|
||||||
|
|
||||||
enables dedup and music search ++
|
enables dedup and music search ++
|
||||||
@@ -1292,8 +1307,23 @@ scrape_configs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
currently the following metrics are available,
|
currently the following metrics are available,
|
||||||
* `cpp_uptime_seconds`
|
* `cpp_uptime_seconds` time since last copyparty restart
|
||||||
* `cpp_bans` number of banned IPs
|
* `cpp_boot_unixtime_seconds` same but as an absolute timestamp
|
||||||
|
* `cpp_http_conns` number of open http(s) connections
|
||||||
|
* `cpp_http_reqs` number of http(s) requests handled
|
||||||
|
* `cpp_sus_reqs` number of 403/422/malicious requests
|
||||||
|
* `cpp_active_bans` number of currently banned IPs
|
||||||
|
* `cpp_total_bans` number of IPs banned since last restart
|
||||||
|
|
||||||
|
these are available unless `--nos-vst` is specified:
|
||||||
|
* `cpp_db_idle_seconds` time since last database activity (upload/rename/delete)
|
||||||
|
* `cpp_db_act_seconds` same but as an absolute timestamp
|
||||||
|
* `cpp_idle_vols` number of volumes which are idle / ready
|
||||||
|
* `cpp_busy_vols` number of volumes which are busy / indexing
|
||||||
|
* `cpp_offline_vols` number of volumes which are offline / unavailable
|
||||||
|
* `cpp_hashing_files` number of files queued for hashing / indexing
|
||||||
|
* `cpp_tagq_files` number of files queued for metadata scanning
|
||||||
|
* `cpp_mtpq_files` number of files queued for plugin-based analysis
|
||||||
|
|
||||||
and these are available per-volume only:
|
and these are available per-volume only:
|
||||||
* `cpp_disk_size_bytes` total HDD size
|
* `cpp_disk_size_bytes` total HDD size
|
||||||
@@ -1312,9 +1342,12 @@ some of the metrics have additional requirements to function correctly,
|
|||||||
the following options are available to disable some of the metrics:
|
the following options are available to disable some of the metrics:
|
||||||
* `--nos-hdd` disables `cpp_disk_*` which can prevent spinning up HDDs
|
* `--nos-hdd` disables `cpp_disk_*` which can prevent spinning up HDDs
|
||||||
* `--nos-vol` disables `cpp_vol_*` which reduces server startup time
|
* `--nos-vol` disables `cpp_vol_*` which reduces server startup time
|
||||||
|
* `--nos-vst` disables volume state, reducing the worst-case prometheus query time by 0.5 sec
|
||||||
* `--nos-dup` disables `cpp_dupe_*` which reduces the server load caused by prometheus queries
|
* `--nos-dup` disables `cpp_dupe_*` which reduces the server load caused by prometheus queries
|
||||||
* `--nos-unf` disables `cpp_unf_*` for no particular purpose
|
* `--nos-unf` disables `cpp_unf_*` for no particular purpose
|
||||||
|
|
||||||
|
note: the following metrics are counted incorrectly if multiprocessing is enabled with `-j`: `cpp_http_conns`, `cpp_http_reqs`, `cpp_sus_reqs`, `cpp_active_bans`, `cpp_total_bans`
|
||||||
|
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
# on fedora/rhel, remember to setsebool -P httpd_can_network_connect 1
|
||||||
|
|
||||||
upstream cpp {
|
upstream cpp {
|
||||||
server 127.0.0.1:3923;
|
server 127.0.0.1:3923 fail_timeout=1s;
|
||||||
keepalive 1;
|
keepalive 1;
|
||||||
}
|
}
|
||||||
server {
|
server {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.9.12"
|
pkgver="1.9.15"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Portable file sharing hub"
|
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, zeroconf, media indexer, thumbnails++"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
url="https://github.com/9001/${pkgname}"
|
url="https://github.com/9001/${pkgname}"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
@@ -20,7 +20,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("bf285725a70b3b201fa8927dd93b294dc9c8c29e00d6826accac8977fc72e1d4")
|
sha256sums=("ee569d664b22cb59ac0eb11850380648d9f8d42d1c26283d43dab350745c102e")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.9.12/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.9.15/copyparty-sfx.py",
|
||||||
"version": "1.9.12",
|
"version": "1.9.15",
|
||||||
"hash": "sha256-/ih867kYtyYcwM+jf5ciHmgTg8BVC+Ve6U8BnamN0kw="
|
"hash": "sha256-EUenh567NYj1klMpjVOWKqiBSqZbdEA0ZGidzzzpnsY="
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ from .authsrv import expand_config_file, re_vol, split_cfg_ln, upgrade_cfg_fmt
|
|||||||
from .cfg import flagcats, onedash
|
from .cfg import flagcats, onedash
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
from .util import (
|
from .util import (
|
||||||
|
DEF_EXP,
|
||||||
DEF_MTE,
|
DEF_MTE,
|
||||||
DEF_MTH,
|
DEF_MTH,
|
||||||
IMPLICATIONS,
|
IMPLICATIONS,
|
||||||
@@ -646,6 +647,47 @@ def get_sects():
|
|||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"exp",
|
||||||
|
"text expansion",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
specify --exp or the "exp" volflag to enable placeholder expansions
|
||||||
|
in README.md / .prologue.html / .epilogue.html
|
||||||
|
|
||||||
|
--exp-md (volflag exp_md) holds the list of placeholders which can be
|
||||||
|
expanded in READMEs, and --exp-lg (volflag exp_lg) likewise for logues;
|
||||||
|
any placeholder not given in those lists will be ignored and shown as-is
|
||||||
|
|
||||||
|
the default list will expand the following placeholders:
|
||||||
|
\033[36m{{self.ip}} \033[35mclient ip
|
||||||
|
\033[36m{{self.ua}} \033[35mclient user-agent
|
||||||
|
\033[36m{{self.uname}} \033[35mclient username
|
||||||
|
\033[36m{{self.host}} \033[35mthe "Host" header, or the server's external IP otherwise
|
||||||
|
\033[36m{{cfg.name}} \033[35mthe --name global-config
|
||||||
|
\033[36m{{cfg.logout}} \033[35mthe --logout global-config
|
||||||
|
\033[36m{{vf.scan}} \033[35mthe "scan" volflag
|
||||||
|
\033[36m{{vf.thsize}} \033[35mthumbnail size
|
||||||
|
\033[36m{{srv.itime}} \033[35mserver time in seconds
|
||||||
|
\033[36m{{srv.htime}} \033[35mserver time as YY-mm-dd, HH:MM:SS (UTC)
|
||||||
|
\033[36m{{hdr.cf_ipcountry}} \033[35mthe "CF-IPCountry" client header (probably blank)
|
||||||
|
\033[0m
|
||||||
|
so the following types of placeholders can be added to the lists:
|
||||||
|
* any client header can be accessed through {{hdr.*}}
|
||||||
|
* any variable in httpcli.py can be accessed through {{self.*}}
|
||||||
|
* any global server setting can be accessed through {{cfg.*}}
|
||||||
|
* any volflag can be accessed through {{vf.*}}
|
||||||
|
|
||||||
|
remove vf.scan from default list using --exp-md /vf.scan
|
||||||
|
add "accept" header to def. list using --exp-md +hdr.accept
|
||||||
|
|
||||||
|
for performance reasons, expansion only happens while embedding
|
||||||
|
documents into directory listings, and when accessing a ?doc=...
|
||||||
|
link, but never otherwise, so if you click a -txt- link you'll
|
||||||
|
have to refresh the page to apply expansion
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"ls",
|
"ls",
|
||||||
"volume inspection",
|
"volume inspection",
|
||||||
@@ -776,8 +818,6 @@ def add_general(ap, nc, srvname):
|
|||||||
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
|
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
|
||||||
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m]")
|
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, \033[33mSRC\033[0m:\033[33mDST\033[0m:\033[33mFLAG\033[0m; examples [\033[32m.::r\033[0m], [\033[32m/mnt/nas/music:/music:r:aed\033[0m]")
|
||||||
ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files")
|
ap2.add_argument("-ed", action="store_true", help="enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files")
|
||||||
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
|
|
||||||
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
|
||||||
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see --help-urlform")
|
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-form POSTs; see --help-urlform")
|
||||||
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
|
ap2.add_argument("--wintitle", metavar="TXT", type=u, default="cpp @ $pub", help="window title, for example [\033[32m$ip-10.1.2.\033[0m] or [\033[32m$ip-]")
|
||||||
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
||||||
@@ -974,6 +1014,7 @@ def add_stats(ap):
|
|||||||
ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts")
|
ap2.add_argument("--stats", action="store_true", help="enable openmetrics at /.cpr/metrics for admin accounts")
|
||||||
ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)")
|
ap2.add_argument("--nos-hdd", action="store_true", help="disable disk-space metrics (used/free space)")
|
||||||
ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)")
|
ap2.add_argument("--nos-vol", action="store_true", help="disable volume size metrics (num files, total bytes, vmaxb/vmaxn)")
|
||||||
|
ap2.add_argument("--nos-vst", action="store_true", help="disable volume state metrics (indexing, analyzing, activity)")
|
||||||
ap2.add_argument("--nos-dup", action="store_true", help="disable dupe-files metrics (good idea; very slow)")
|
ap2.add_argument("--nos-dup", action="store_true", help="disable dupe-files metrics (good idea; very slow)")
|
||||||
ap2.add_argument("--nos-unf", action="store_true", help="disable unfinished-uploads metrics")
|
ap2.add_argument("--nos-unf", action="store_true", help="disable unfinished-uploads metrics")
|
||||||
|
|
||||||
@@ -1054,7 +1095,7 @@ def add_logging(ap):
|
|||||||
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR")
|
||||||
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
ap2.add_argument("--no-voldump", action="store_true", help="do not list volumes and permissions on startup")
|
||||||
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
|
ap2.add_argument("--log-tdec", metavar="N", type=int, default=3, help="timestamp resolution / number of timestamp decimals")
|
||||||
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log passphrase of failed login attempts: 0=terse, 1=plaintext, 2=hashed")
|
ap2.add_argument("--log-badpwd", metavar="N", type=int, default=1, help="log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed")
|
||||||
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
ap2.add_argument("--log-conn", action="store_true", help="debug: print tcp-server msgs")
|
||||||
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
|
ap2.add_argument("--log-htp", action="store_true", help="debug: print http-server threadpool scaling")
|
||||||
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
||||||
@@ -1144,6 +1185,15 @@ def add_db_metadata(ap):
|
|||||||
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file")
|
||||||
|
|
||||||
|
|
||||||
|
def add_txt(ap):
|
||||||
|
ap2 = ap.add_argument_group('textfile options')
|
||||||
|
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="textfile editor checks for serverside changes every SEC seconds")
|
||||||
|
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
|
||||||
|
ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see --help-exp (volflag=exp)")
|
||||||
|
ap2.add_argument("--exp-md", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md)")
|
||||||
|
ap2.add_argument("--exp-lg", metavar="V,V,V", type=u, default=DEF_EXP, help="comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg)")
|
||||||
|
|
||||||
|
|
||||||
def add_ui(ap, retry):
|
def add_ui(ap, retry):
|
||||||
ap2 = ap.add_argument_group('ui options')
|
ap2 = ap.add_argument_group('ui options')
|
||||||
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
||||||
@@ -1237,6 +1287,7 @@ def run_argparse(
|
|||||||
add_handlers(ap)
|
add_handlers(ap)
|
||||||
add_hooks(ap)
|
add_hooks(ap)
|
||||||
add_stats(ap)
|
add_stats(ap)
|
||||||
|
add_txt(ap)
|
||||||
add_ui(ap, retry)
|
add_ui(ap, retry)
|
||||||
add_admin(ap)
|
add_admin(ap)
|
||||||
add_logging(ap)
|
add_logging(ap)
|
||||||
@@ -1264,7 +1315,7 @@ def run_argparse(
|
|||||||
for k, h, t in sects:
|
for k, h, t in sects:
|
||||||
k2 = "help_" + k.replace("-", "_")
|
k2 = "help_" + k.replace("-", "_")
|
||||||
if vars(ret)[k2]:
|
if vars(ret)[k2]:
|
||||||
lprint("# {} help page".format(k))
|
lprint("# %s help page (%s)" % (k, h))
|
||||||
lprint(t + "\033[0m")
|
lprint(t + "\033[0m")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 9, 13)
|
VERSION = (1, 9, 16)
|
||||||
CODENAME = "prometheable"
|
CODENAME = "prometheable"
|
||||||
BUILD_DT = (2023, 10, 21)
|
BUILD_DT = (2023, 11, 4)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -476,12 +476,10 @@ class VFS(object):
|
|||||||
err: int = 403,
|
err: int = 403,
|
||||||
) -> tuple["VFS", str]:
|
) -> tuple["VFS", str]:
|
||||||
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
"""returns [vfsnode,fs_remainder] if user has the requested permissions"""
|
||||||
if ANYWIN:
|
if relchk(vpath):
|
||||||
mod = relchk(vpath)
|
if self.log:
|
||||||
if mod:
|
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
||||||
if self.log:
|
raise Pebkac(422)
|
||||||
self.log("vfs", "invalid relpath [{}]".format(vpath))
|
|
||||||
raise Pebkac(404)
|
|
||||||
|
|
||||||
cvpath = undot(vpath)
|
cvpath = undot(vpath)
|
||||||
vn, rem = self._find(cvpath)
|
vn, rem = self._find(cvpath)
|
||||||
@@ -500,8 +498,8 @@ class VFS(object):
|
|||||||
t = "{} has no {} in [{}] => [{}] => [{}]"
|
t = "{} has no {} in [{}] => [{}] => [{}]"
|
||||||
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)
|
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)
|
||||||
|
|
||||||
t = "you don't have {}-access for this location"
|
t = 'you don\'t have %s-access in "/%s"'
|
||||||
raise Pebkac(err, t.format(msg))
|
raise Pebkac(err, t % (msg, cvpath))
|
||||||
|
|
||||||
return vn, rem
|
return vn, rem
|
||||||
|
|
||||||
@@ -1479,15 +1477,11 @@ class AuthSrv(object):
|
|||||||
raise Exception(t.format(dbd, dbds))
|
raise Exception(t.format(dbd, dbds))
|
||||||
|
|
||||||
# default tag cfgs if unset
|
# default tag cfgs if unset
|
||||||
if "mte" not in vol.flags:
|
for k in ("mte", "mth", "exp_md", "exp_lg"):
|
||||||
vol.flags["mte"] = self.args.mte.copy()
|
if k not in vol.flags:
|
||||||
else:
|
vol.flags[k] = getattr(self.args, k).copy()
|
||||||
vol.flags["mte"] = odfusion(self.args.mte, vol.flags["mte"])
|
else:
|
||||||
|
vol.flags[k] = odfusion(getattr(self.args, k), vol.flags[k])
|
||||||
if "mth" not in vol.flags:
|
|
||||||
vol.flags["mth"] = self.args.mth.copy()
|
|
||||||
else:
|
|
||||||
vol.flags["mth"] = odfusion(self.args.mth, vol.flags["mth"])
|
|
||||||
|
|
||||||
# append additive args from argv to volflags
|
# append additive args from argv to volflags
|
||||||
hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
|
hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
|
||||||
@@ -1727,6 +1721,9 @@ class AuthSrv(object):
|
|||||||
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
||||||
self.ah = PWHash(self.args)
|
self.ah = PWHash(self.args)
|
||||||
if not self.ah.on:
|
if not self.ah.on:
|
||||||
|
if self.args.ah_cli or self.args.ah_gen:
|
||||||
|
t = "\n BAD CONFIG:\n cannot --ah-cli or --ah-gen without --ah-alg"
|
||||||
|
raise Exception(t)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.args.ah_cli:
|
if self.args.ah_cli:
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"no_thumb": "dthumb",
|
"no_thumb": "dthumb",
|
||||||
"no_vthumb": "dvthumb",
|
"no_vthumb": "dvthumb",
|
||||||
"no_athumb": "dathumb",
|
"no_athumb": "dathumb",
|
||||||
"re_maxage": "scan",
|
|
||||||
"th_no_crop": "nocrop",
|
"th_no_crop": "nocrop",
|
||||||
"dav_auth": "davauth",
|
"dav_auth": "davauth",
|
||||||
"dav_rt": "davrt",
|
"dav_rt": "davrt",
|
||||||
@@ -33,6 +32,7 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"e2v",
|
"e2v",
|
||||||
"e2vu",
|
"e2vu",
|
||||||
"e2vp",
|
"e2vp",
|
||||||
|
"exp",
|
||||||
"grid",
|
"grid",
|
||||||
"hardlink",
|
"hardlink",
|
||||||
"magic",
|
"magic",
|
||||||
@@ -52,10 +52,19 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
ret = {
|
ret = {
|
||||||
"no_hash": "nohash",
|
"no_hash": "nohash",
|
||||||
"no_idx": "noidx",
|
"no_idx": "noidx",
|
||||||
|
"re_maxage": "scan",
|
||||||
"th_convt": "convt",
|
"th_convt": "convt",
|
||||||
"th_size": "thsize",
|
"th_size": "thsize",
|
||||||
}
|
}
|
||||||
for k in ("dbd", "lg_sbf", "md_sbf", "nrand", "sort", "unlist", "u2ts"):
|
for k in (
|
||||||
|
"dbd",
|
||||||
|
"lg_sbf",
|
||||||
|
"md_sbf",
|
||||||
|
"nrand",
|
||||||
|
"sort",
|
||||||
|
"unlist",
|
||||||
|
"u2ts",
|
||||||
|
):
|
||||||
ret[k] = k
|
ret[k] = k
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -64,6 +73,8 @@ def vf_cmap() -> dict[str, str]:
|
|||||||
"""argv-to-volflag: complex/lists"""
|
"""argv-to-volflag: complex/lists"""
|
||||||
ret = {}
|
ret = {}
|
||||||
for k in (
|
for k in (
|
||||||
|
"exp_lg",
|
||||||
|
"exp_md",
|
||||||
"html_head",
|
"html_head",
|
||||||
"mte",
|
"mte",
|
||||||
"mth",
|
"mth",
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ class FtpAuth(DummyAuthorizer):
|
|||||||
if bonk:
|
if bonk:
|
||||||
logging.warning("client banned: invalid passwords")
|
logging.warning("client banned: invalid passwords")
|
||||||
bans[ip] = bonk
|
bans[ip] = bonk
|
||||||
|
try:
|
||||||
|
# only possible if multiprocessing disabled
|
||||||
|
self.hub.broker.httpsrv.bans[ip] = bonk
|
||||||
|
self.hub.broker.httpsrv.nban += 1
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
raise AuthenticationFailed("Authentication failed.")
|
raise AuthenticationFailed("Authentication failed.")
|
||||||
|
|
||||||
@@ -148,7 +154,7 @@ class FtpFs(AbstractedFS):
|
|||||||
try:
|
try:
|
||||||
vpath = vpath.replace("\\", "/").strip("/")
|
vpath = vpath.replace("\\", "/").strip("/")
|
||||||
rd, fn = os.path.split(vpath)
|
rd, fn = os.path.split(vpath)
|
||||||
if ANYWIN and relchk(rd):
|
if relchk(rd):
|
||||||
logging.warning("malicious vpath: %s", vpath)
|
logging.warning("malicious vpath: %s", vpath)
|
||||||
t = "Unsupported characters in [{}]"
|
t = "Unsupported characters in [{}]"
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from .star import StreamTar
|
|||||||
from .sutil import StreamArc, gfilter
|
from .sutil import StreamArc, gfilter
|
||||||
from .szip import StreamZip
|
from .szip import StreamZip
|
||||||
from .util import (
|
from .util import (
|
||||||
|
Garda,
|
||||||
HTTPCODE,
|
HTTPCODE,
|
||||||
META_NOBOTS,
|
META_NOBOTS,
|
||||||
MultipartParser,
|
MultipartParser,
|
||||||
@@ -75,6 +76,7 @@ from .util import (
|
|||||||
runhook,
|
runhook,
|
||||||
s3enc,
|
s3enc,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
|
sanitize_vpath,
|
||||||
sendfile_kern,
|
sendfile_kern,
|
||||||
sendfile_py,
|
sendfile_py,
|
||||||
undot,
|
undot,
|
||||||
@@ -146,6 +148,7 @@ class HttpCli(object):
|
|||||||
self.rem = " "
|
self.rem = " "
|
||||||
self.vpath = " "
|
self.vpath = " "
|
||||||
self.vpaths = " "
|
self.vpaths = " "
|
||||||
|
self.gctx = " " # additional context for garda
|
||||||
self.trailing_slash = True
|
self.trailing_slash = True
|
||||||
self.uname = " "
|
self.uname = " "
|
||||||
self.pw = " "
|
self.pw = " "
|
||||||
@@ -254,8 +257,8 @@ class HttpCli(object):
|
|||||||
k, zs = header_line.split(":", 1)
|
k, zs = header_line.split(":", 1)
|
||||||
self.headers[k.lower()] = zs.strip()
|
self.headers[k.lower()] = zs.strip()
|
||||||
except:
|
except:
|
||||||
msg = " ]\n#[ ".join(headerlines)
|
msg = "#[ " + " ]\n#[ ".join(headerlines) + " ]"
|
||||||
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
raise Pebkac(400, "bad headers", log=msg)
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
self.mode = "GET"
|
self.mode = "GET"
|
||||||
@@ -268,8 +271,14 @@ class HttpCli(object):
|
|||||||
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
self.loud_reply(unicode(ex), status=ex.code, headers=h, volsan=True)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if ex.log:
|
||||||
|
self.log("additional error context:\n" + ex.log, 6)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.conn.hsrv.nreq += 1
|
||||||
|
|
||||||
self.ua = self.headers.get("user-agent", "")
|
self.ua = self.headers.get("user-agent", "")
|
||||||
self.is_rclone = self.ua.startswith("rclone/")
|
self.is_rclone = self.ua.startswith("rclone/")
|
||||||
|
|
||||||
@@ -411,12 +420,9 @@ class HttpCli(object):
|
|||||||
self.vpath + "/" if self.trailing_slash and self.vpath else self.vpath
|
self.vpath + "/" if self.trailing_slash and self.vpath else self.vpath
|
||||||
)
|
)
|
||||||
|
|
||||||
ok = "\x00" not in self.vpath
|
if relchk(self.vpath) and (self.vpath != "*" or self.mode != "OPTIONS"):
|
||||||
if ANYWIN:
|
|
||||||
ok = ok and not relchk(self.vpath)
|
|
||||||
|
|
||||||
if not ok and (self.vpath != "*" or self.mode != "OPTIONS"):
|
|
||||||
self.log("invalid relpath [{}]".format(self.vpath))
|
self.log("invalid relpath [{}]".format(self.vpath))
|
||||||
|
self.cbonk(self.conn.hsrv.g422, self.vpath, "bad_vp", "invalid relpaths")
|
||||||
return self.tx_404() and self.keepalive
|
return self.tx_404() and self.keepalive
|
||||||
|
|
||||||
zso = self.headers.get("authorization")
|
zso = self.headers.get("authorization")
|
||||||
@@ -549,6 +555,9 @@ class HttpCli(object):
|
|||||||
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
zb = b"<pre>" + html_escape(msg).encode("utf-8", "replace")
|
||||||
h = {"WWW-Authenticate": 'Basic realm="a"'} if pex.code == 401 else {}
|
h = {"WWW-Authenticate": 'Basic realm="a"'} if pex.code == 401 else {}
|
||||||
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
self.reply(zb, status=pex.code, headers=h, volsan=True)
|
||||||
|
if pex.log:
|
||||||
|
self.log("additional error context:\n" + pex.log, 6)
|
||||||
|
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
@@ -559,6 +568,36 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
return self.conn.iphash.s(self.ip)
|
return self.conn.iphash.s(self.ip)
|
||||||
|
|
||||||
|
def cbonk(self, g: Garda, v: str, reason: str, descr: str) -> bool:
|
||||||
|
self.conn.hsrv.nsus += 1
|
||||||
|
if not g.lim:
|
||||||
|
return False
|
||||||
|
|
||||||
|
bonk, ip = g.bonk(self.ip, v + self.gctx)
|
||||||
|
if not bonk:
|
||||||
|
return False
|
||||||
|
|
||||||
|
xban = self.vn.flags.get("xban")
|
||||||
|
if not xban or not runhook(
|
||||||
|
self.log,
|
||||||
|
xban,
|
||||||
|
self.vn.canonical(self.rem),
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
time.time(),
|
||||||
|
0,
|
||||||
|
self.ip,
|
||||||
|
time.time(),
|
||||||
|
reason,
|
||||||
|
):
|
||||||
|
self.log("client banned: %s" % (descr,), 1)
|
||||||
|
self.conn.hsrv.bans[ip] = bonk
|
||||||
|
self.conn.hsrv.nban += 1
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def is_banned(self) -> bool:
|
def is_banned(self) -> bool:
|
||||||
if not self.conn.bans:
|
if not self.conn.bans:
|
||||||
return False
|
return False
|
||||||
@@ -678,24 +717,7 @@ class HttpCli(object):
|
|||||||
or not self.args.nonsus_urls
|
or not self.args.nonsus_urls
|
||||||
or not self.args.nonsus_urls.search(self.vpath)
|
or not self.args.nonsus_urls.search(self.vpath)
|
||||||
):
|
):
|
||||||
bonk, ip = g.bonk(self.ip, self.vpath)
|
self.cbonk(g, self.vpath, str(status), "%ss" % (status,))
|
||||||
if bonk:
|
|
||||||
xban = self.vn.flags.get("xban")
|
|
||||||
if not xban or not runhook(
|
|
||||||
self.log,
|
|
||||||
xban,
|
|
||||||
self.vn.canonical(self.rem),
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
time.time(),
|
|
||||||
0,
|
|
||||||
self.ip,
|
|
||||||
time.time(),
|
|
||||||
str(status),
|
|
||||||
):
|
|
||||||
self.log("client banned: %ss" % (status,), 1)
|
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
|
||||||
|
|
||||||
if volsan:
|
if volsan:
|
||||||
vols = list(self.asrv.vfs.all_vols.values())
|
vols = list(self.asrv.vfs.all_vols.values())
|
||||||
@@ -2121,8 +2143,10 @@ class HttpCli(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_pwd_cookie(self, pwd: str) -> str:
|
def get_pwd_cookie(self, pwd: str) -> str:
|
||||||
if self.asrv.ah.hash(pwd) in self.asrv.iacct:
|
hpwd = self.asrv.ah.hash(pwd)
|
||||||
msg = "login ok"
|
uname = self.asrv.iacct.get(hpwd)
|
||||||
|
if uname:
|
||||||
|
msg = "hi " + uname
|
||||||
dur = int(60 * 60 * self.args.logout)
|
dur = int(60 * 60 * self.args.logout)
|
||||||
else:
|
else:
|
||||||
logpwd = pwd
|
logpwd = pwd
|
||||||
@@ -2133,27 +2157,7 @@ class HttpCli(object):
|
|||||||
logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
|
logpwd = "%" + base64.b64encode(zb[:12]).decode("utf-8")
|
||||||
|
|
||||||
self.log("invalid password: {}".format(logpwd), 3)
|
self.log("invalid password: {}".format(logpwd), 3)
|
||||||
|
self.cbonk(self.conn.hsrv.gpwd, pwd, "pw", "invalid passwords")
|
||||||
g = self.conn.hsrv.gpwd
|
|
||||||
if g.lim:
|
|
||||||
bonk, ip = g.bonk(self.ip, pwd)
|
|
||||||
if bonk:
|
|
||||||
xban = self.vn.flags.get("xban")
|
|
||||||
if not xban or not runhook(
|
|
||||||
self.log,
|
|
||||||
xban,
|
|
||||||
self.vn.canonical(self.rem),
|
|
||||||
self.vpath,
|
|
||||||
self.host,
|
|
||||||
self.uname,
|
|
||||||
time.time(),
|
|
||||||
0,
|
|
||||||
self.ip,
|
|
||||||
time.time(),
|
|
||||||
"pw",
|
|
||||||
):
|
|
||||||
self.log("client banned: invalid passwords", 1)
|
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
|
||||||
|
|
||||||
msg = "naw dude"
|
msg = "naw dude"
|
||||||
pwd = "x" # nosec
|
pwd = "x" # nosec
|
||||||
@@ -2177,26 +2181,30 @@ class HttpCli(object):
|
|||||||
new_dir = self.parser.require("name", 512)
|
new_dir = self.parser.require("name", 512)
|
||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
sanitized = sanitize_fn(new_dir, "", [])
|
return self._mkdir(vjoin(self.vpath, new_dir))
|
||||||
return self._mkdir(vjoin(self.vpath, sanitized))
|
|
||||||
|
|
||||||
def _mkdir(self, vpath: str, dav: bool = False) -> bool:
|
def _mkdir(self, vpath: str, dav: bool = False) -> bool:
|
||||||
nullwrite = self.args.nw
|
nullwrite = self.args.nw
|
||||||
|
self.gctx = vpath
|
||||||
|
vpath = undot(vpath)
|
||||||
vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
|
||||||
self._assert_safe_rem(rem)
|
rem = sanitize_vpath(rem, "/", [])
|
||||||
fn = vfs.canonical(rem)
|
fn = vfs.canonical(rem)
|
||||||
|
if not fn.startswith(vfs.realpath):
|
||||||
|
self.log("invalid mkdir [%s] [%s]" % (self.gctx, vpath), 1)
|
||||||
|
raise Pebkac(422)
|
||||||
|
|
||||||
if not nullwrite:
|
if not nullwrite:
|
||||||
fdir = os.path.dirname(fn)
|
fdir = os.path.dirname(fn)
|
||||||
|
|
||||||
if not bos.path.isdir(fdir):
|
if dav and not bos.path.isdir(fdir):
|
||||||
raise Pebkac(409, "parent folder does not exist")
|
raise Pebkac(409, "parent folder does not exist")
|
||||||
|
|
||||||
if bos.path.isdir(fn):
|
if bos.path.isdir(fn):
|
||||||
raise Pebkac(405, "that folder exists already")
|
raise Pebkac(405, 'folder "/%s" already exists' % (vpath,))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bos.mkdir(fn)
|
bos.makedirs(fn)
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
if ex.errno == errno.EACCES:
|
if ex.errno == errno.EACCES:
|
||||||
raise Pebkac(500, "the server OS denied write-access")
|
raise Pebkac(500, "the server OS denied write-access")
|
||||||
@@ -2205,7 +2213,7 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
self.out_headers["X-New-Dir"] = quotep(vpath.split("/")[-1])
|
self.out_headers["X-New-Dir"] = quotep(vpath)
|
||||||
|
|
||||||
if dav:
|
if dav:
|
||||||
self.reply(b"", 201)
|
self.reply(b"", 201)
|
||||||
@@ -2726,6 +2734,29 @@ class HttpCli(object):
|
|||||||
|
|
||||||
return file_lastmod, True
|
return file_lastmod, True
|
||||||
|
|
||||||
|
def _expand(self, txt: str, phs: list[str]) -> str:
|
||||||
|
for ph in phs:
|
||||||
|
if ph.startswith("hdr."):
|
||||||
|
sv = str(self.headers.get(ph[4:], ""))
|
||||||
|
elif ph.startswith("self."):
|
||||||
|
sv = str(getattr(self, ph[5:], ""))
|
||||||
|
elif ph.startswith("cfg."):
|
||||||
|
sv = str(getattr(self.args, ph[4:], ""))
|
||||||
|
elif ph.startswith("vf."):
|
||||||
|
sv = str(self.vn.flags.get(ph[3:]) or "")
|
||||||
|
elif ph == "srv.itime":
|
||||||
|
sv = str(int(time.time()))
|
||||||
|
elif ph == "srv.htime":
|
||||||
|
sv = datetime.now(UTC).strftime("%Y-%m-%d, %H:%M:%S")
|
||||||
|
else:
|
||||||
|
self.log("unknown placeholder in server config: [%s]" % (ph), 3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
sv = self.conn.hsrv.ptn_hsafe.sub("_", sv)
|
||||||
|
txt = txt.replace("{{%s}}" % (ph,), sv)
|
||||||
|
|
||||||
|
return txt
|
||||||
|
|
||||||
def tx_file(self, req_path: str) -> bool:
|
def tx_file(self, req_path: str) -> bool:
|
||||||
status = 200
|
status = 200
|
||||||
logmsg = "{:4} {} ".format("", self.req)
|
logmsg = "{:4} {} ".format("", self.req)
|
||||||
@@ -3052,7 +3083,7 @@ class HttpCli(object):
|
|||||||
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
self.reply(ico, mime=mime, headers={"Last-Modified": lm})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def tx_md(self, fs_path: str) -> bool:
|
def tx_md(self, vn: VFS, fs_path: str) -> bool:
|
||||||
logmsg = " %s @%s " % (self.req, self.uname)
|
logmsg = " %s @%s " % (self.req, self.uname)
|
||||||
|
|
||||||
if not self.can_write:
|
if not self.can_write:
|
||||||
@@ -3069,9 +3100,16 @@ class HttpCli(object):
|
|||||||
st = bos.stat(html_path)
|
st = bos.stat(html_path)
|
||||||
ts_html = st.st_mtime
|
ts_html = st.st_mtime
|
||||||
|
|
||||||
|
max_sz = 1024 * self.args.txt_max
|
||||||
sz_md = 0
|
sz_md = 0
|
||||||
lead = b""
|
lead = b""
|
||||||
|
fullfile = b""
|
||||||
for buf in yieldfile(fs_path):
|
for buf in yieldfile(fs_path):
|
||||||
|
if sz_md < max_sz:
|
||||||
|
fullfile += buf
|
||||||
|
else:
|
||||||
|
fullfile = b""
|
||||||
|
|
||||||
if not sz_md and b"\n" in buf[:2]:
|
if not sz_md and b"\n" in buf[:2]:
|
||||||
lead = buf[: buf.find(b"\n") + 1]
|
lead = buf[: buf.find(b"\n") + 1]
|
||||||
sz_md += len(lead)
|
sz_md += len(lead)
|
||||||
@@ -3080,6 +3118,21 @@ class HttpCli(object):
|
|||||||
for c, v in [(b"&", 4), (b"<", 3), (b">", 3)]:
|
for c, v in [(b"&", 4), (b"<", 3), (b">", 3)]:
|
||||||
sz_md += (len(buf) - len(buf.replace(c, b""))) * v
|
sz_md += (len(buf) - len(buf.replace(c, b""))) * v
|
||||||
|
|
||||||
|
if (
|
||||||
|
fullfile
|
||||||
|
and "exp" in vn.flags
|
||||||
|
and "edit" not in self.uparam
|
||||||
|
and "edit2" not in self.uparam
|
||||||
|
and vn.flags.get("exp_md")
|
||||||
|
):
|
||||||
|
fulltxt = fullfile.decode("utf-8", "replace")
|
||||||
|
fulltxt = self._expand(fulltxt, vn.flags.get("exp_md") or [])
|
||||||
|
fullfile = fulltxt.encode("utf-8", "replace")
|
||||||
|
|
||||||
|
if fullfile:
|
||||||
|
fullfile = html_bescape(fullfile)
|
||||||
|
sz_md = len(lead) + len(fullfile)
|
||||||
|
|
||||||
file_ts = int(max(ts_md, ts_html, self.E.t0))
|
file_ts = int(max(ts_md, ts_html, self.E.t0))
|
||||||
file_lastmod, do_send = self._chk_lastmod(file_ts)
|
file_lastmod, do_send = self._chk_lastmod(file_ts)
|
||||||
self.out_headers["Last-Modified"] = file_lastmod
|
self.out_headers["Last-Modified"] = file_lastmod
|
||||||
@@ -3121,8 +3174,11 @@ class HttpCli(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.s.sendall(html[0] + lead)
|
self.s.sendall(html[0] + lead)
|
||||||
for buf in yieldfile(fs_path):
|
if fullfile:
|
||||||
self.s.sendall(html_bescape(buf))
|
self.s.sendall(fullfile)
|
||||||
|
else:
|
||||||
|
for buf in yieldfile(fs_path):
|
||||||
|
self.s.sendall(html_bescape(buf))
|
||||||
|
|
||||||
self.s.sendall(html[1])
|
self.s.sendall(html[1])
|
||||||
|
|
||||||
@@ -3753,7 +3809,7 @@ class HttpCli(object):
|
|||||||
or "edit2" in self.uparam
|
or "edit2" in self.uparam
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
return self.tx_md(abspath)
|
return self.tx_md(vn, abspath)
|
||||||
|
|
||||||
return self.tx_file(abspath)
|
return self.tx_file(abspath)
|
||||||
|
|
||||||
@@ -3815,6 +3871,10 @@ class HttpCli(object):
|
|||||||
if bos.path.exists(fn):
|
if bos.path.exists(fn):
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
logues[n] = f.read().decode("utf-8")
|
logues[n] = f.read().decode("utf-8")
|
||||||
|
if "exp" in vn.flags:
|
||||||
|
logues[n] = self._expand(
|
||||||
|
logues[n], vn.flags.get("exp_lg") or []
|
||||||
|
)
|
||||||
|
|
||||||
readme = ""
|
readme = ""
|
||||||
if not self.args.no_readme and not logues[1]:
|
if not self.args.no_readme and not logues[1]:
|
||||||
@@ -3824,6 +3884,8 @@ class HttpCli(object):
|
|||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
readme = f.read().decode("utf-8")
|
readme = f.read().decode("utf-8")
|
||||||
break
|
break
|
||||||
|
if readme and "exp" in vn.flags:
|
||||||
|
readme = self._expand(readme, vn.flags.get("exp_md") or [])
|
||||||
|
|
||||||
vf = vn.flags
|
vf = vn.flags
|
||||||
unlist = vf.get("unlist", "")
|
unlist = vf.get("unlist", "")
|
||||||
@@ -4134,6 +4196,12 @@ class HttpCli(object):
|
|||||||
if sz < 1024 * self.args.txt_max:
|
if sz < 1024 * self.args.txt_max:
|
||||||
with open(fsenc(docpath), "rb") as f:
|
with open(fsenc(docpath), "rb") as f:
|
||||||
doctxt = f.read().decode("utf-8", "replace")
|
doctxt = f.read().decode("utf-8", "replace")
|
||||||
|
|
||||||
|
if doc.lower().endswith(".md") and "exp" in vn.flags:
|
||||||
|
doctxt = self._expand(doctxt, vn.flags.get("exp_md") or [])
|
||||||
|
else:
|
||||||
|
self.log("doc 2big: [{}]".format(doc), c=6)
|
||||||
|
doctxt = "( size of textfile exceeds serverside limit )"
|
||||||
else:
|
else:
|
||||||
self.log("doc 404: [{}]".format(doc), c=6)
|
self.log("doc 404: [{}]".format(doc), c=6)
|
||||||
doctxt = "( textfile not found )"
|
doctxt = "( textfile not found )"
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ class HttpSrv(object):
|
|||||||
|
|
||||||
self.u2fh = FHC()
|
self.u2fh = FHC()
|
||||||
self.metrics = Metrics(self)
|
self.metrics = Metrics(self)
|
||||||
|
self.nreq = 0
|
||||||
|
self.nsus = 0
|
||||||
|
self.nban = 0
|
||||||
self.srvs: list[socket.socket] = []
|
self.srvs: list[socket.socket] = []
|
||||||
self.ncli = 0 # exact
|
self.ncli = 0 # exact
|
||||||
self.clients: set[HttpConn] = set() # laggy
|
self.clients: set[HttpConn] = set() # laggy
|
||||||
@@ -149,6 +152,7 @@ class HttpSrv(object):
|
|||||||
self._build_statics()
|
self._build_statics()
|
||||||
|
|
||||||
self.ptn_cc = re.compile(r"[\x00-\x1f]")
|
self.ptn_cc = re.compile(r"[\x00-\x1f]")
|
||||||
|
self.ptn_hsafe = re.compile(r"[\x00-\x1f<>\"'&]")
|
||||||
|
|
||||||
self.mallow = "GET HEAD POST PUT DELETE OPTIONS".split()
|
self.mallow = "GET HEAD POST PUT DELETE OPTIONS".split()
|
||||||
if not self.args.no_dav:
|
if not self.args.no_dav:
|
||||||
|
|||||||
@@ -34,14 +34,23 @@ class Metrics(object):
|
|||||||
|
|
||||||
ret: list[str] = []
|
ret: list[str] = []
|
||||||
|
|
||||||
def addc(k: str, unit: str, v: str, desc: str) -> None:
|
def addc(k: str, v: str, desc: str) -> None:
|
||||||
if unit:
|
zs = "# TYPE %s counter\n# HELP %s %s\n%s_created %s\n%s_total %s"
|
||||||
k += "_" + unit
|
ret.append(zs % (k, k, desc, k, int(self.hsrv.t0), k, v))
|
||||||
zs = "# TYPE %s counter\n# UNIT %s %s\n# HELP %s %s\n%s_created %s\n%s_total %s"
|
|
||||||
ret.append(zs % (k, k, unit, k, desc, k, int(self.hsrv.t0), k, v))
|
def adduc(k: str, unit: str, v: str, desc: str) -> None:
|
||||||
else:
|
k += "_" + unit
|
||||||
zs = "# TYPE %s counter\n# HELP %s %s\n%s_created %s\n%s_total %s"
|
zs = "# TYPE %s counter\n# UNIT %s %s\n# HELP %s %s\n%s_created %s\n%s_total %s"
|
||||||
ret.append(zs % (k, k, desc, k, int(self.hsrv.t0), k, v))
|
ret.append(zs % (k, k, unit, k, desc, k, int(self.hsrv.t0), k, v))
|
||||||
|
|
||||||
|
def addg(k: str, v: str, desc: str) -> None:
|
||||||
|
zs = "# TYPE %s gauge\n# HELP %s %s\n%s %s"
|
||||||
|
ret.append(zs % (k, k, desc, k, v))
|
||||||
|
|
||||||
|
def addug(k: str, unit: str, v: str, desc: str) -> None:
|
||||||
|
k += "_" + unit
|
||||||
|
zs = "# TYPE %s gauge\n# UNIT %s %s\n# HELP %s %s\n%s %s"
|
||||||
|
ret.append(zs % (k, k, unit, k, desc, k, v))
|
||||||
|
|
||||||
def addh(k: str, typ: str, desc: str) -> None:
|
def addh(k: str, typ: str, desc: str) -> None:
|
||||||
zs = "# TYPE %s %s\n# HELP %s %s"
|
zs = "# TYPE %s %s\n# HELP %s %s"
|
||||||
@@ -54,17 +63,75 @@ class Metrics(object):
|
|||||||
def addv(k: str, v: str) -> None:
|
def addv(k: str, v: str) -> None:
|
||||||
ret.append("%s %s" % (k, v))
|
ret.append("%s %s" % (k, v))
|
||||||
|
|
||||||
|
t = "time since last copyparty restart"
|
||||||
v = "{:.3f}".format(time.time() - self.hsrv.t0)
|
v = "{:.3f}".format(time.time() - self.hsrv.t0)
|
||||||
addc("cpp_uptime", "seconds", v, "time since last server restart")
|
addug("cpp_uptime", "seconds", v, t)
|
||||||
|
|
||||||
|
# timestamps are gauges because initial value is not zero
|
||||||
|
t = "unixtime of last copyparty restart"
|
||||||
|
v = "{:.3f}".format(self.hsrv.t0)
|
||||||
|
addug("cpp_boot_unixtime", "seconds", v, t)
|
||||||
|
|
||||||
|
t = "number of open http(s) client connections"
|
||||||
|
addg("cpp_http_conns", str(self.hsrv.ncli), t)
|
||||||
|
|
||||||
|
t = "number of http(s) requests since last restart"
|
||||||
|
addc("cpp_http_reqs", str(self.hsrv.nreq), t)
|
||||||
|
|
||||||
|
t = "number of 403/422/malicious reqs since restart"
|
||||||
|
addc("cpp_sus_reqs", str(self.hsrv.nsus), t)
|
||||||
|
|
||||||
v = str(len(conn.bans or []))
|
v = str(len(conn.bans or []))
|
||||||
addc("cpp_bans", "", v, "number of banned IPs")
|
addg("cpp_active_bans", v, "number of currently banned IPs")
|
||||||
|
|
||||||
|
t = "number of IPs banned since last restart"
|
||||||
|
addg("cpp_total_bans", str(self.hsrv.nban), t)
|
||||||
|
|
||||||
|
if not args.nos_vst:
|
||||||
|
x = self.hsrv.broker.ask("up2k.get_state")
|
||||||
|
vs = json.loads(x.get())
|
||||||
|
|
||||||
|
nvidle = 0
|
||||||
|
nvbusy = 0
|
||||||
|
nvoffline = 0
|
||||||
|
for v in vs["volstate"].values():
|
||||||
|
if v == "online, idle":
|
||||||
|
nvidle += 1
|
||||||
|
elif "OFFLINE" in v:
|
||||||
|
nvoffline += 1
|
||||||
|
else:
|
||||||
|
nvbusy += 1
|
||||||
|
|
||||||
|
addg("cpp_idle_vols", str(nvidle), "number of idle/ready volumes")
|
||||||
|
addg("cpp_busy_vols", str(nvbusy), "number of busy/indexing volumes")
|
||||||
|
addg("cpp_offline_vols", str(nvoffline), "number of offline volumes")
|
||||||
|
|
||||||
|
t = "time since last database activity (upload/rename/delete)"
|
||||||
|
addug("cpp_db_idle", "seconds", str(vs["dbwt"]), t)
|
||||||
|
|
||||||
|
t = "unixtime of last database activity (upload/rename/delete)"
|
||||||
|
addug("cpp_db_act", "seconds", str(vs["dbwu"]), t)
|
||||||
|
|
||||||
|
t = "number of files queued for hashing/indexing"
|
||||||
|
addg("cpp_hashing_files", str(vs["hashq"]), t)
|
||||||
|
|
||||||
|
t = "number of files queued for metadata scanning"
|
||||||
|
addg("cpp_tagq_files", str(vs["tagq"]), t)
|
||||||
|
|
||||||
|
try:
|
||||||
|
t = "number of files queued for plugin-based analysis"
|
||||||
|
addg("cpp_mtpq_files", str(int(vs["mtpq"])), t)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if not args.nos_hdd:
|
if not args.nos_hdd:
|
||||||
addbh("cpp_disk_size_bytes", "total HDD size of volume")
|
addbh("cpp_disk_size_bytes", "total HDD size of volume")
|
||||||
addbh("cpp_disk_free_bytes", "free HDD space in volume")
|
addbh("cpp_disk_free_bytes", "free HDD space in volume")
|
||||||
for vpath, vol in allvols:
|
for vpath, vol in allvols:
|
||||||
free, total = get_df(vol.realpath)
|
free, total = get_df(vol.realpath)
|
||||||
|
if free is None or total is None:
|
||||||
|
continue
|
||||||
|
|
||||||
addv('cpp_disk_size_bytes{vol="/%s"}' % (vpath), str(total))
|
addv('cpp_disk_size_bytes{vol="/%s"}' % (vpath), str(total))
|
||||||
addv('cpp_disk_free_bytes{vol="/%s"}' % (vpath), str(free))
|
addv('cpp_disk_free_bytes{vol="/%s"}' % (vpath), str(free))
|
||||||
|
|
||||||
@@ -161,5 +228,6 @@ class Metrics(object):
|
|||||||
ret.append("# EOF")
|
ret.append("# EOF")
|
||||||
|
|
||||||
mime = "application/openmetrics-text; version=1.0.0; charset=utf-8"
|
mime = "application/openmetrics-text; version=1.0.0; charset=utf-8"
|
||||||
|
mime = cli.uparam.get("mime") or mime
|
||||||
cli.reply("\n".join(ret).encode("utf-8"), mime=mime)
|
cli.reply("\n".join(ret).encode("utf-8"), mime=mime)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -136,8 +136,12 @@ class PWHash(object):
|
|||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
p1 = getpass.getpass("password> ")
|
try:
|
||||||
p2 = getpass.getpass("again or just hit ENTER> ")
|
p1 = getpass.getpass("password> ")
|
||||||
|
p2 = getpass.getpass("again or just hit ENTER> ")
|
||||||
|
except EOFError:
|
||||||
|
return
|
||||||
|
|
||||||
if p2 and p1 != p2:
|
if p2 and p1 != p2:
|
||||||
print("\033[31minputs don't match; try again\033[0m", file=sys.stderr)
|
print("\033[31minputs don't match; try again\033[0m", file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from .util import (
|
|||||||
FFMPEG_URL,
|
FFMPEG_URL,
|
||||||
VERSIONS,
|
VERSIONS,
|
||||||
Daemon,
|
Daemon,
|
||||||
|
DEF_EXP,
|
||||||
DEF_MTE,
|
DEF_MTE,
|
||||||
DEF_MTH,
|
DEF_MTH,
|
||||||
Garda,
|
Garda,
|
||||||
@@ -442,6 +443,10 @@ class SvcHub(object):
|
|||||||
mth = ODict.fromkeys(DEF_MTH.split(","), True)
|
mth = ODict.fromkeys(DEF_MTH.split(","), True)
|
||||||
al.mth = odfusion(mth, al.mth)
|
al.mth = odfusion(mth, al.mth)
|
||||||
|
|
||||||
|
exp = ODict.fromkeys(DEF_EXP.split(" "), True)
|
||||||
|
al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
|
||||||
|
al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
|
||||||
|
|
||||||
for k in ["no_hash", "no_idx"]:
|
for k in ["no_hash", "no_idx"]:
|
||||||
ptn = getattr(self.args, k)
|
ptn = getattr(self.args, k)
|
||||||
if ptn:
|
if ptn:
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ from .util import (
|
|||||||
w8b64enc,
|
w8b64enc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pathlib import Path
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if HAVE_SQLITE3:
|
if HAVE_SQLITE3:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
@@ -261,6 +266,7 @@ class Up2k(object):
|
|||||||
"hashq": self.n_hashq,
|
"hashq": self.n_hashq,
|
||||||
"tagq": self.n_tagq,
|
"tagq": self.n_tagq,
|
||||||
"mtpq": mtpq,
|
"mtpq": mtpq,
|
||||||
|
"dbwu": "{:.2f}".format(self.db_act),
|
||||||
"dbwt": "{:.2f}".format(
|
"dbwt": "{:.2f}".format(
|
||||||
min(1000 * 24 * 60 * 60 - 1, time.time() - self.db_act)
|
min(1000 * 24 * 60 * 60 - 1, time.time() - self.db_act)
|
||||||
),
|
),
|
||||||
@@ -814,7 +820,7 @@ class Up2k(object):
|
|||||||
if str(fl[k1]) == str(getattr(self.args, k2)):
|
if str(fl[k1]) == str(getattr(self.args, k2)):
|
||||||
del fl[k1]
|
del fl[k1]
|
||||||
else:
|
else:
|
||||||
fl[k1] = ",".join(x for x in fl)
|
fl[k1] = ",".join(x for x in fl[k1])
|
||||||
a = [
|
a = [
|
||||||
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
||||||
for k, v in fl.items()
|
for k, v in fl.items()
|
||||||
@@ -2723,7 +2729,18 @@ class Up2k(object):
|
|||||||
raise Exception("symlink-fallback disabled in cfg")
|
raise Exception("symlink-fallback disabled in cfg")
|
||||||
|
|
||||||
if not linked:
|
if not linked:
|
||||||
os.symlink(fsenc(lsrc), fsenc(ldst))
|
if ANYWIN:
|
||||||
|
Path(ldst).symlink_to(lsrc)
|
||||||
|
if not bos.path.exists(dst):
|
||||||
|
try:
|
||||||
|
bos.unlink(dst)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
t = "the created symlink [%s] did not resolve to [%s]"
|
||||||
|
raise Exception(t % (ldst, lsrc))
|
||||||
|
else:
|
||||||
|
os.symlink(fsenc(lsrc), fsenc(ldst))
|
||||||
|
|
||||||
linked = True
|
linked = True
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("cannot link; creating copy: " + repr(ex))
|
self.log("cannot link; creating copy: " + repr(ex))
|
||||||
|
|||||||
@@ -289,6 +289,8 @@ EXTS["vnd.mozilla.apng"] = "png"
|
|||||||
MAGIC_MAP = {"jpeg": "jpg"}
|
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 = "circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
|
DEF_MTE = "circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash"
|
||||||
|
|
||||||
DEF_MTH = ".vq,.aq,vc,ac,fmt,res,.fps"
|
DEF_MTH = ".vq,.aq,vc,ac,fmt,res,.fps"
|
||||||
@@ -1561,8 +1563,8 @@ def read_header(sr: Unrecv, t_idle: int, t_tot: int) -> list[str]:
|
|||||||
|
|
||||||
raise Pebkac(
|
raise Pebkac(
|
||||||
400,
|
400,
|
||||||
"protocol error while reading headers:\n"
|
"protocol error while reading headers",
|
||||||
+ ret.decode("utf-8", "replace"),
|
log=ret.decode("utf-8", "replace"),
|
||||||
)
|
)
|
||||||
|
|
||||||
ofs = ret.find(b"\r\n\r\n")
|
ofs = ret.find(b"\r\n\r\n")
|
||||||
@@ -1771,7 +1773,16 @@ def sanitize_fn(fn: str, ok: str, bad: list[str]) -> str:
|
|||||||
return fn.strip()
|
return fn.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_vpath(vp: str, ok: str, bad: list[str]) -> str:
|
||||||
|
parts = vp.replace(os.sep, "/").split("/")
|
||||||
|
ret = [sanitize_fn(x, ok, bad) for x in parts]
|
||||||
|
return "/".join(ret)
|
||||||
|
|
||||||
|
|
||||||
def relchk(rp: str) -> str:
|
def relchk(rp: str) -> str:
|
||||||
|
if "\x00" in rp:
|
||||||
|
return "[nul]"
|
||||||
|
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
if "\n" in rp or "\r" in rp:
|
if "\n" in rp or "\r" in rp:
|
||||||
return "x\nx"
|
return "x\nx"
|
||||||
@@ -1809,15 +1820,18 @@ def exclude_dotfiles(filepaths: list[str]) -> list[str]:
|
|||||||
|
|
||||||
def odfusion(base: ODict[str, bool], oth: str) -> ODict[str, bool]:
|
def odfusion(base: ODict[str, bool], oth: str) -> ODict[str, bool]:
|
||||||
# merge an "ordered set" (just a dict really) with another list of keys
|
# merge an "ordered set" (just a dict really) with another list of keys
|
||||||
|
words0 = [x for x in oth.split(",") if x]
|
||||||
|
words1 = [x for x in oth[1:].split(",") if x]
|
||||||
|
|
||||||
ret = base.copy()
|
ret = base.copy()
|
||||||
if oth.startswith("+"):
|
if oth.startswith("+"):
|
||||||
for k in oth[1:].split(","):
|
for k in words1:
|
||||||
ret[k] = True
|
ret[k] = True
|
||||||
elif oth[:1] in ("-", "/"):
|
elif oth[:1] in ("-", "/"):
|
||||||
for k in oth[1:].split(","):
|
for k in words1:
|
||||||
ret.pop(k, None)
|
ret.pop(k, None)
|
||||||
else:
|
else:
|
||||||
ret = ODict.fromkeys(oth.split(","), True)
|
ret = ODict.fromkeys(words0, True)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -2971,9 +2985,12 @@ def hidedir(dp) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class Pebkac(Exception):
|
class Pebkac(Exception):
|
||||||
def __init__(self, code: int, msg: Optional[str] = None) -> None:
|
def __init__(
|
||||||
|
self, code: int, msg: Optional[str] = None, log: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
super(Pebkac, self).__init__(msg or HTTPCODE[code])
|
||||||
self.code = code
|
self.code = code
|
||||||
|
self.log = log
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "Pebkac({}, {})".format(self.code, repr(self.args))
|
return "Pebkac({}, {})".format(self.code, repr(self.args))
|
||||||
|
|||||||
@@ -1891,6 +1891,10 @@ html.y #doc {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
}
|
}
|
||||||
|
#docul li.bn span {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--fg-max);
|
||||||
|
}
|
||||||
#doc.prism {
|
#doc.prism {
|
||||||
padding-left: 3em;
|
padding-left: 3em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ var Ls = {
|
|||||||
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
"mt_coth": "convert all others (not mp3) to opus\">oth",
|
||||||
"mt_tint": "background level (0-100) on the seekbar$Nto make buffering less distracting",
|
"mt_tint": "background level (0-100) on the seekbar$Nto make buffering less distracting",
|
||||||
"mt_eq": "enables the equalizer and gain control;$N$Nboost <code>0</code> = standard 100% volume (unmodified)$N$Nwidth <code>1 </code> = standard stereo (unmodified)$Nwidth <code>0.5</code> = 50% left-right crossfeed$Nwidth <code>0 </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = vocal removal :^)$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero (except width = 1) if you care about that",
|
"mt_eq": "enables the equalizer and gain control;$N$Nboost <code>0</code> = standard 100% volume (unmodified)$N$Nwidth <code>1 </code> = standard stereo (unmodified)$Nwidth <code>0.5</code> = 50% left-right crossfeed$Nwidth <code>0 </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = vocal removal :^)$N$Nenabling the equalizer makes gapless albums fully gapless, so leave it on with all the values at zero (except width = 1) if you care about that",
|
||||||
"mt_drc": "enables the dynamic range compressor (volume flattener / brickwaller); will also enable EQ to balance the spaghetti, so set all EQ fields except for 'width' to 0 if you don't want it$N$Nlowers the volume of audio above THRESHOLD dB; for every RATIO dB past THRESHOLD there is 1 dB of output, so default values of tresh -24 and ratio 12 means it should never get louder than -22 dB and it is safe to increase the equalizer boost to 0.8, or even 1.8 with ATK 0 and a huge RLS like 90$N$Nplease see wikipedia instead, this is probably wrong",
|
"mt_drc": "enables the dynamic range compressor (volume flattener / brickwaller); will also enable EQ to balance the spaghetti, so set all EQ fields except for 'width' to 0 if you don't want it$N$Nlowers the volume of audio above THRESHOLD dB; for every RATIO dB past THRESHOLD there is 1 dB of output, so default values of tresh -24 and ratio 12 means it should never get louder than -22 dB and it is safe to increase the equalizer boost to 0.8, or even 1.8 with ATK 0 and a huge RLS like 90 (only works in firefox; RLS is max 1 in other browsers)$N$N(see wikipedia, they explain it much better)",
|
||||||
|
|
||||||
"mb_play": "play",
|
"mb_play": "play",
|
||||||
"mm_hashplay": "play this audio file?",
|
"mm_hashplay": "play this audio file?",
|
||||||
@@ -730,7 +730,7 @@ var Ls = {
|
|||||||
"mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre",
|
"mt_coth": "konverter alt annet (men ikke mp3) til opus\">andre",
|
||||||
"mt_tint": "nivå av bakgrunnsfarge på søkestripa (0-100),$Ngjør oppdateringer mindre distraherende",
|
"mt_tint": "nivå av bakgrunnsfarge på søkestripa (0-100),$Ngjør oppdateringer mindre distraherende",
|
||||||
"mt_eq": "aktiver tonekontroll og forsterker;$N$Nboost <code>0</code> = normal volumskala$N$Nwidth <code>1 </code> = normal stereo$Nwidth <code>0.5</code> = 50% blanding venstre-høyre$Nwidth <code>0 </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = instrumental :^)$N$Nreduserer også dødtid imellom sangfiler",
|
"mt_eq": "aktiver tonekontroll og forsterker;$N$Nboost <code>0</code> = normal volumskala$N$Nwidth <code>1 </code> = normal stereo$Nwidth <code>0.5</code> = 50% blanding venstre-høyre$Nwidth <code>0 </code> = mono$N$Nboost <code>-0.8</code> & width <code>10</code> = instrumental :^)$N$Nreduserer også dødtid imellom sangfiler",
|
||||||
"mt_drc": "aktiver volum-utjevning (dynamic range compressor); vil også aktivere tonejustering, så sett alle EQ-feltene bortsett fra 'width' til 0 hvis du ikke vil ha noe EQ$N$Nfilteret vil dempe volumet på alt som er høyere enn TRESH dB; for hver RATIO dB over grensen er det 1dB som treffer høyttalerne, så standardverdiene tresh -24 og ratio 12 skal bety at volumet ikke går høyere enn -22 dB, slik at man trygt kan øke boost-verdien i equalizer'n til rundt 0.8, eller 1.8 kombinert med ATK 0 og RLS 90$N$Ngodt mulig jeg har misforstått litt, så wikipedia forklarer nok bedre",
|
"mt_drc": "aktiver volum-utjevning (dynamic range compressor); vil også aktivere tonejustering, så sett alle EQ-feltene bortsett fra 'width' til 0 hvis du ikke vil ha noe EQ$N$Nfilteret vil dempe volumet på alt som er høyere enn TRESH dB; for hver RATIO dB over grensen er det 1dB som treffer høyttalerne, så standardverdiene tresh -24 og ratio 12 skal bety at volumet ikke går høyere enn -22 dB, slik at man trygt kan øke boost-verdien i equalizer'n til rundt 0.8, eller 1.8 kombinert med ATK 0 og RLS 90 (bare mulig i firefox; andre nettlesere tar ikke høyere RLS enn 1)$N$Nwikipedia forklarer dette mye bedre forresten",
|
||||||
|
|
||||||
"mb_play": "lytt",
|
"mb_play": "lytt",
|
||||||
"mm_hashplay": "spill denne sangen?",
|
"mm_hashplay": "spill denne sangen?",
|
||||||
@@ -2501,7 +2501,7 @@ var afilt = (function () {
|
|||||||
"drcen": false,
|
"drcen": false,
|
||||||
"bands": [31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
"bands": [31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||||
"gains": [4, 3, 2, 1, 0, 0, 1, 2, 3, 4],
|
"gains": [4, 3, 2, 1, 0, 0, 1, 2, 3, 4],
|
||||||
"drcv": [-24, 30, 12, 0.003, 0.25],
|
"drcv": [-24, 30, 12, 0.01, 0.25],
|
||||||
"drch": ['tresh', 'knee', 'ratio', 'atk', 'rls'],
|
"drch": ['tresh', 'knee', 'ratio', 'atk', 'rls'],
|
||||||
"drck": ['threshold', 'knee', 'ratio', 'attack', 'release'],
|
"drck": ['threshold', 'knee', 'ratio', 'attack', 'release'],
|
||||||
"drcn": null,
|
"drcn": null,
|
||||||
@@ -2576,6 +2576,8 @@ var afilt = (function () {
|
|||||||
var gains = jread('au_eq_gain', r.gains);
|
var gains = jread('au_eq_gain', r.gains);
|
||||||
if (r.gains.length == gains.length)
|
if (r.gains.length == gains.length)
|
||||||
r.gains = gains;
|
r.gains = gains;
|
||||||
|
|
||||||
|
r.drcv = jread('au_drcv', r.drcv);
|
||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
@@ -2692,6 +2694,17 @@ var afilt = (function () {
|
|||||||
for (var a = 0; a < r.drcv.length; a++)
|
for (var a = 0; a < r.drcv.length; a++)
|
||||||
fi[r.drck[a]].value = r.drcv[a];
|
fi[r.drck[a]].value = r.drcv[a];
|
||||||
|
|
||||||
|
if (r.drcv[3] < 0.02) {
|
||||||
|
// avoid static at decode start
|
||||||
|
fi.attack.value = 0.02;
|
||||||
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
|
fi.attack.value = r.drcv[3];
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
r.filters.push(fi);
|
r.filters.push(fi);
|
||||||
timer.add(showdrc);
|
timer.add(showdrc);
|
||||||
}
|
}
|
||||||
@@ -2783,7 +2796,7 @@ var afilt = (function () {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
r.drcv[n] = v;
|
r.drcv[n] = v;
|
||||||
jwrite('au_drc', r.drcv);
|
jwrite('au_drcv', r.drcv);
|
||||||
if (r.drcn)
|
if (r.drcn)
|
||||||
r.drcn[r.drck[n]].value = v;
|
r.drcn[r.drck[n]].value = v;
|
||||||
}
|
}
|
||||||
@@ -3784,7 +3797,7 @@ var fileman = (function () {
|
|||||||
|
|
||||||
function rename_cb() {
|
function rename_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fr_efail + msg);
|
toast.err(9, L.fr_efail + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3833,7 +3846,7 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
function delete_cb() {
|
function delete_cb() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fd_err + msg);
|
toast.err(9, L.fd_err + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3954,7 +3967,7 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
function paste_cb() {
|
function paste_cb() {
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.fp_err + msg);
|
toast.err(9, L.fp_err + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4287,7 +4300,7 @@ var showfile = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.mktree = function () {
|
r.mktree = function () {
|
||||||
var html = ['<li class="bn">' + L.tv_lst + '<br />' + linksplit(get_vpath()).join('') + '</li>'];
|
var html = ['<li class="bn">' + L.tv_lst + '<br />' + linksplit(get_vpath()).join('<span>/</span>') + '</li>'];
|
||||||
for (var a = 0; a < r.files.length; a++) {
|
for (var a = 0; a < r.files.length; a++) {
|
||||||
var file = r.files[a];
|
var file = r.files[a];
|
||||||
html.push('<li><a href="?doc=' +
|
html.push('<li><a href="?doc=' +
|
||||||
@@ -5287,10 +5300,7 @@ document.onkeydown = function (e) {
|
|||||||
|
|
||||||
function xhr_search_results() {
|
function xhr_search_results() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
if (msg.indexOf('<pre>') === 0)
|
|
||||||
msg = msg.slice(5);
|
|
||||||
|
|
||||||
srch_msg(true, "http " + this.status + ": " + msg);
|
srch_msg(true, "http " + this.status + ": " + msg);
|
||||||
search_in_progress = 0;
|
search_in_progress = 0;
|
||||||
return;
|
return;
|
||||||
@@ -5329,7 +5339,7 @@ document.onkeydown = function (e) {
|
|||||||
if (ext.length > 8)
|
if (ext.length > 8)
|
||||||
ext = '%';
|
ext = '%';
|
||||||
|
|
||||||
var links = linksplit(r.rp + '', id).join(''),
|
var links = linksplit(r.rp + '', id).join('<span>/</span>'),
|
||||||
nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
|
nodes = ['<tr><td>-</td><td><div>' + links + '</div>', sz];
|
||||||
|
|
||||||
for (var b = 0; b < tagord.length; b++) {
|
for (var b = 0; b < tagord.length; b++) {
|
||||||
@@ -7155,16 +7165,17 @@ var msel = (function () {
|
|||||||
form.onsubmit = function (e) {
|
form.onsubmit = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
clmod(sf, 'vis', 1);
|
clmod(sf, 'vis', 1);
|
||||||
sf.textContent = 'creating "' + tb.value + '"...';
|
var dn = tb.value;
|
||||||
|
sf.textContent = 'creating "' + dn + '"...';
|
||||||
|
|
||||||
var fd = new FormData();
|
var fd = new FormData();
|
||||||
fd.append("act", "mkdir");
|
fd.append("act", "mkdir");
|
||||||
fd.append("name", tb.value);
|
fd.append("name", dn);
|
||||||
|
|
||||||
var xhr = new XHR();
|
var xhr = new XHR();
|
||||||
xhr.vp = get_evpath();
|
xhr.vp = get_evpath();
|
||||||
xhr.dn = tb.value;
|
xhr.dn = dn;
|
||||||
xhr.open('POST', xhr.vp, true);
|
xhr.open('POST', dn.startsWith('/') ? (SR || '/') : xhr.vp, true);
|
||||||
xhr.onload = xhr.onerror = cb;
|
xhr.onload = xhr.onerror = cb;
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.send(fd);
|
xhr.send(fd);
|
||||||
@@ -7181,7 +7192,7 @@ var msel = (function () {
|
|||||||
xhrchk(this, L.fd_xe1, L.fd_xe2);
|
xhrchk(this, L.fd_xe1, L.fd_xe2);
|
||||||
|
|
||||||
if (this.status !== 201) {
|
if (this.status !== 201) {
|
||||||
sf.textContent = 'error: ' + this.responseText;
|
sf.textContent = 'error: ' + unpre(this.responseText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7190,8 +7201,9 @@ var msel = (function () {
|
|||||||
sf.textContent = '';
|
sf.textContent = '';
|
||||||
|
|
||||||
var dn = this.getResponseHeader('X-New-Dir');
|
var dn = this.getResponseHeader('X-New-Dir');
|
||||||
dn = dn || uricom_enc(this.dn);
|
dn = dn ? '/' + dn + '/' : uricom_enc(this.dn);
|
||||||
treectl.goto(this.vp + dn + '/', true);
|
treectl.goto(dn, true);
|
||||||
|
tree_scrollto();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -7228,7 +7240,7 @@ var msel = (function () {
|
|||||||
xhrchk(this, L.fsm_xe1, L.fsm_xe2);
|
xhrchk(this, L.fsm_xe1, L.fsm_xe2);
|
||||||
|
|
||||||
if (this.status < 200 || this.status > 201) {
|
if (this.status < 200 || this.status > 201) {
|
||||||
sf.textContent = 'error: ' + this.responseText;
|
sf.textContent = 'error: ' + unpre(this.responseText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7573,7 +7585,7 @@ var unpost = (function () {
|
|||||||
'<tr><td><a me="' + me + '" class="n' + a + '" href="#">' + L.un_del + '</a></td>' +
|
'<tr><td><a me="' + me + '" class="n' + a + '" href="#">' + L.un_del + '</a></td>' +
|
||||||
'<td>' + unix2iso(res[a].at) + '</td>' +
|
'<td>' + unix2iso(res[a].at) + '</td>' +
|
||||||
'<td>' + res[a].sz + '</td>' +
|
'<td>' + res[a].sz + '</td>' +
|
||||||
'<td>' + linksplit(res[a].vp).join(' ') + '</td></tr>');
|
'<td>' + linksplit(res[a].vp).join('<span> / </span>') + '</td></tr>');
|
||||||
}
|
}
|
||||||
|
|
||||||
html.push("</tbody></table>");
|
html.push("</tbody></table>");
|
||||||
@@ -7606,7 +7618,7 @@ var unpost = (function () {
|
|||||||
|
|
||||||
function unpost_delete_cb() {
|
function unpost_delete_cb() {
|
||||||
if (this.status !== 200) {
|
if (this.status !== 200) {
|
||||||
var msg = this.responseText;
|
var msg = unpre(this.responseText);
|
||||||
toast.err(9, L.un_derr + msg);
|
toast.err(9, L.un_derr + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
{{ html_head }}
|
{{ html_head }}
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/splash.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/ui.css?_={{ ts }}">
|
||||||
|
<style>ul{padding-left:1.3em}li{margin:.4em 0}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -48,9 +49,13 @@
|
|||||||
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
||||||
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>W:</b>
|
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>W:</b>
|
||||||
</pre>
|
</pre>
|
||||||
{% if s %}
|
<ul>
|
||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>--no-check-certificate</code> to the mount command</em><br />---</p>
|
{% if s %}
|
||||||
{% endif %}
|
<li>running <code>rclone mount</code> on LAN (or just dont have valid certificates)? add <code>--no-check-certificate</code></li>
|
||||||
|
{% endif %}
|
||||||
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
<p>if you want to use the native WebDAV client in windows instead (slow and buggy), first run <a href="{{ r }}/.cpr/a/webdav-cfg.bat">webdav-cfg.bat</a> to remove the 47 MiB filesize limit (also fixes latency and password login), then connect:</p>
|
||||||
<pre>
|
<pre>
|
||||||
@@ -73,10 +78,13 @@
|
|||||||
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=owncloud pacer_min_sleep=0.01ms{% if accs %} user=k pass=<b>{{ pw }}</b>{% endif %}
|
||||||
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>mp</b>
|
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ rvp }} <b>mp</b>
|
||||||
</pre>
|
</pre>
|
||||||
{% if s %}
|
<ul>
|
||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>--no-check-certificate</code> to the mount command</em><br />---</p>
|
{% if s %}
|
||||||
{% endif %}
|
<li>running <code>rclone mount</code> on LAN (or just dont have valid certificates)? add <code>--no-check-certificate</code></li>
|
||||||
|
{% endif %}
|
||||||
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
|
</ul>
|
||||||
<p>or the emergency alternative (gnome/gui-only):</p>
|
<p>or the emergency alternative (gnome/gui-only):</p>
|
||||||
<!-- gnome-bug: ignores vp -->
|
<!-- gnome-bug: ignores vp -->
|
||||||
<pre>
|
<pre>
|
||||||
@@ -123,8 +131,14 @@
|
|||||||
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
|
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
|
||||||
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>W:</b>
|
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>W:</b>
|
||||||
</pre>
|
</pre>
|
||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>no_check_certificate=true</code> to the config command</em><br />---</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<ul>
|
||||||
|
{% if args.ftps %}
|
||||||
|
<li>running on LAN (or just dont have valid certificates)? add <code>no_check_certificate=true</code> to the config command</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
|
</ul>
|
||||||
<p>if you want to use the native FTP client in windows instead (please dont), press <code>win+R</code> and run this command:</p>
|
<p>if you want to use the native FTP client in windows instead (please dont), press <code>win+R</code> and run this command:</p>
|
||||||
<pre>
|
<pre>
|
||||||
explorer {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}<b>{{ pw }}</b>:k@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
|
explorer {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}<b>{{ pw }}</b>:k@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ rvp }}
|
||||||
@@ -145,8 +159,14 @@
|
|||||||
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
|
rclone config create {{ aname }}-ftps ftp host={{ rip }} port={{ args.ftps }} pass=k user={% if accs %}<b>{{ pw }}</b>{% else %}anonymous{% endif %} tls=false explicit_tls=true
|
||||||
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>mp</b>
|
rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftps:{{ rvp }} <b>mp</b>
|
||||||
</pre>
|
</pre>
|
||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>no_check_certificate=true</code> to the config command</em><br />---</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<ul>
|
||||||
|
{% if args.ftps %}
|
||||||
|
<li>running on LAN (or just dont have valid certificates)? add <code>no_check_certificate=true</code> to the config command</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>running <code>rclone mount</code> as root? add <code>--allow-other</code></li>
|
||||||
|
<li>old version of rclone? replace all <code>=</code> with <code> </code> (space)</li>
|
||||||
|
</ul>
|
||||||
<p>emergency alternative (gnome/gui-only):</p>
|
<p>emergency alternative (gnome/gui-only):</p>
|
||||||
<!-- gnome-bug: ignores vp -->
|
<!-- gnome-bug: ignores vp -->
|
||||||
<pre>
|
<pre>
|
||||||
@@ -178,7 +198,7 @@
|
|||||||
partyfuse.py{% if accs %} -a <b>{{ pw }}</b>{% endif %} http{{ s }}://{{ ep }}/{{ rvp }} <b><span class="os win">W:</span><span class="os lin mac">mp</span></b>
|
partyfuse.py{% if accs %} -a <b>{{ pw }}</b>{% endif %} http{{ s }}://{{ ep }}/{{ rvp }} <b><span class="os win">W:</span><span class="os lin mac">mp</span></b>
|
||||||
</pre>
|
</pre>
|
||||||
{% if s %}
|
{% if s %}
|
||||||
<p><em>note: if you are on LAN (or just dont have valid certificates), add <code>-td</code></em></p>
|
<ul><li>if you are on LAN (or just dont have valid certificates), add <code>-td</code></li></ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
you can use <a href="{{ r }}/.cpr/a/u2c.py">u2c.py</a> to upload (sometimes faster than web-browsers)
|
you can use <a href="{{ r }}/.cpr/a/u2c.py">u2c.py</a> to upload (sometimes faster than web-browsers)
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--fg: #ccc;
|
||||||
|
--fg-max: #fff;
|
||||||
|
--bg-u2: #2b2b2b;
|
||||||
|
--bg-u5: #444;
|
||||||
|
}
|
||||||
|
html.y {
|
||||||
|
--fg: #222;
|
||||||
|
--fg-max: #000;
|
||||||
|
--bg-u2: #f7f7f7;
|
||||||
|
--bg-u5: #ccc;
|
||||||
|
}
|
||||||
|
html.bz {
|
||||||
|
--bg-u2: #202231;
|
||||||
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'scp';
|
font-family: 'scp';
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@@ -14,6 +29,7 @@ html {
|
|||||||
max-width: min(34em, 90%);
|
max-width: min(34em, 90%);
|
||||||
max-width: min(34em, calc(100% - 7em));
|
max-width: min(34em, calc(100% - 7em));
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
|
color: var(--fg);
|
||||||
background: #333;
|
background: #333;
|
||||||
background: var(--bg-u2);
|
background: var(--bg-u2);
|
||||||
border: 0 solid #777;
|
border: 0 solid #777;
|
||||||
@@ -171,24 +187,15 @@ html {
|
|||||||
color: #f6a;
|
color: #f6a;
|
||||||
}
|
}
|
||||||
html.y #tt {
|
html.y #tt {
|
||||||
color: #333;
|
|
||||||
background: #fff;
|
|
||||||
border-color: #888 #000 #777 #000;
|
border-color: #888 #000 #777 #000;
|
||||||
}
|
}
|
||||||
html.bz #tt {
|
html.bz #tt {
|
||||||
background: #202231;
|
|
||||||
border-color: #3b3f58;
|
border-color: #3b3f58;
|
||||||
}
|
}
|
||||||
html.y #tt,
|
html.y #tt,
|
||||||
html.y #toast {
|
html.y #toast {
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
html.y #tt code {
|
|
||||||
color: #fff;
|
|
||||||
color: var(--fg-max);
|
|
||||||
background: #060;
|
|
||||||
background: var(--bg-u5);
|
|
||||||
}
|
|
||||||
#modalc code {
|
#modalc code {
|
||||||
color: #060;
|
color: #060;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -326,6 +333,9 @@ html.y .btn:focus {
|
|||||||
box-shadow: 0 .1em .2em #037 inset;
|
box-shadow: 0 .1em .2em #037 inset;
|
||||||
outline: #037 solid .1em;
|
outline: #037 solid .1em;
|
||||||
}
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
input[type="text"]:focus,
|
input[type="text"]:focus,
|
||||||
input:not([type]):focus,
|
input:not([type]):focus,
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
|
|||||||
@@ -1407,7 +1407,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
pvis.addfile([
|
pvis.addfile([
|
||||||
uc.fsearch ? esc(entry.name) : linksplit(
|
uc.fsearch ? esc(entry.name) : linksplit(
|
||||||
entry.purl + uricom_enc(entry.name)).join(' '),
|
entry.purl + uricom_enc(entry.name)).join(' / '),
|
||||||
'📐 ' + L.u_hashing,
|
'📐 ' + L.u_hashing,
|
||||||
''
|
''
|
||||||
], entry.size, draw_each);
|
], entry.size, draw_each);
|
||||||
@@ -2284,7 +2284,7 @@ function up2k_init(subtle) {
|
|||||||
cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b',
|
cdiff = (Math.abs(diff) <= 2) ? '3c0' : 'f0b',
|
||||||
sdiff = '<span style="color:#' + cdiff + '">diff ' + diff;
|
sdiff = '<span style="color:#' + cdiff + '">diff ' + diff;
|
||||||
|
|
||||||
msg.push(linksplit(hit.rp).join('') + '<br /><small>' + tr + ' (srv), ' + tu + ' (You), ' + sdiff + '</small></span>');
|
msg.push(linksplit(hit.rp).join(' / ') + '<br /><small>' + tr + ' (srv), ' + tu + ' (You), ' + sdiff + '</small></span>');
|
||||||
}
|
}
|
||||||
msg = msg.join('<br />\n');
|
msg = msg.join('<br />\n');
|
||||||
}
|
}
|
||||||
@@ -2318,7 +2318,7 @@ function up2k_init(subtle) {
|
|||||||
url += '?k=' + fk;
|
url += '?k=' + fk;
|
||||||
}
|
}
|
||||||
|
|
||||||
pvis.seth(t.n, 0, linksplit(url).join(' '));
|
pvis.seth(t.n, 0, linksplit(url).join(' / '));
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunksize = get_chunksize(t.size),
|
var chunksize = get_chunksize(t.size),
|
||||||
@@ -2402,15 +2402,12 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
pvis.seth(t.n, 2, L.u_ehstmp, t);
|
||||||
|
|
||||||
var err = "",
|
var err = "",
|
||||||
rsp = (xhr.responseText + ''),
|
rsp = unpre(this.responseText),
|
||||||
ofs = rsp.lastIndexOf('\nURL: ');
|
ofs = rsp.lastIndexOf('\nURL: ');
|
||||||
|
|
||||||
if (ofs !== -1)
|
if (ofs !== -1)
|
||||||
rsp = rsp.slice(0, ofs);
|
rsp = rsp.slice(0, ofs);
|
||||||
|
|
||||||
if (rsp.indexOf('<pre>') === 0)
|
|
||||||
rsp = rsp.slice(5);
|
|
||||||
|
|
||||||
if (rsp.indexOf('rate-limit ') !== -1) {
|
if (rsp.indexOf('rate-limit ') !== -1) {
|
||||||
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
||||||
console.log("rate-limit: " + penalty);
|
console.log("rate-limit: " + penalty);
|
||||||
@@ -2429,7 +2426,7 @@ function up2k_init(subtle) {
|
|||||||
err = rsp;
|
err = rsp;
|
||||||
ofs = err.indexOf('\n/');
|
ofs = err.indexOf('\n/');
|
||||||
if (ofs !== -1) {
|
if (ofs !== -1) {
|
||||||
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' ');
|
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2).trimEnd()).join(' / ');
|
||||||
}
|
}
|
||||||
if (!t.rechecks && (err_pend || err_srcb)) {
|
if (!t.rechecks && (err_pend || err_srcb)) {
|
||||||
t.rechecks = 0;
|
t.rechecks = 0;
|
||||||
@@ -2536,7 +2533,7 @@ function up2k_init(subtle) {
|
|||||||
cdr = t.size;
|
cdr = t.size;
|
||||||
|
|
||||||
var orz = function (xhr) {
|
var orz = function (xhr) {
|
||||||
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
var txt = unpre((xhr.response && xhr.response.err) || xhr.responseText);
|
||||||
if (txt.indexOf('upload blocked by x') + 1) {
|
if (txt.indexOf('upload blocked by x') + 1) {
|
||||||
apop(st.busy.upload, upt);
|
apop(st.busy.upload, upt);
|
||||||
apop(t.postlist, npart);
|
apop(t.postlist, npart);
|
||||||
|
|||||||
@@ -622,9 +622,8 @@ function linksplit(rp, id) {
|
|||||||
}
|
}
|
||||||
var vlink = esc(uricom_dec(link));
|
var vlink = esc(uricom_dec(link));
|
||||||
|
|
||||||
if (link.indexOf('/') !== -1) {
|
if (link.indexOf('/') !== -1)
|
||||||
vlink = vlink.slice(0, -1) + '<span>/</span>';
|
vlink = vlink.slice(0, -1);
|
||||||
}
|
|
||||||
|
|
||||||
if (!rp) {
|
if (!rp) {
|
||||||
if (q)
|
if (q)
|
||||||
@@ -1357,6 +1356,11 @@ function lf2br(txt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function unpre(txt) {
|
||||||
|
return ('' + txt).replace(/^<pre>/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var toast = (function () {
|
var toast = (function () {
|
||||||
var r = {},
|
var r = {},
|
||||||
te = null,
|
te = null,
|
||||||
|
|||||||
@@ -1,3 +1,47 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2023-1024-1643 `v1.9.15` expand placeholder
|
||||||
|
|
||||||
|
[made it just in time!](https://a.ocv.me/pub/g/nerd-stuff/PXL_20231024_170348367.jpg) (EDIT: nevermind, three of the containers didn't finish uploading to ghcr before takeoff ;_; all up now)
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* #56 placeholder variables in markdown documents and prologue/epilogue html files
|
||||||
|
* default-disabled; must be enabled globally with `--exp` or per-volume with volflag `exp`
|
||||||
|
* `{{self.ip}}` becomes the client IP; see [/srv/expand/README.md](https://github.com/9001/copyparty/blob/hovudstraum/srv/expand/README.md) for more examples
|
||||||
|
* dynamic-range-compressor: reduced volume jumps between songs when enabled
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* v1.9.14 broke the `scan` volflag, causing volume rescans to happen every 10sec if enabled
|
||||||
|
* its global counterpart `--re-maxage` was not affected
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2023-1021-1443 `v1.9.14` uptime
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* search for files by upload time
|
||||||
|
* option to display upload time in directory listings
|
||||||
|
* enable globally with `-e2d -mte +.up_at` or per-volume with volflags `e2d,mte=+.up_at`
|
||||||
|
* has a ~17% performance impact on directory listings
|
||||||
|
* [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression) in the audioplayer settings
|
||||||
|
* `--ban-404` is now default-enabled
|
||||||
|
* the turbo-uploader will now un-turbo when necessary to avoid banning itself
|
||||||
|
* this only affects accounts with permissions `g`, `G`, or `h`
|
||||||
|
* accounts with read-access (which are able to see directory listings anyways) and accounts with write-only access are no longer affected by `--ban-404` or `--ban-url`
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* #55 clients could hit the `--url-ban` filter when uploading over webdav
|
||||||
|
* fixed by limiting `--ban-404` and `--ban-url` to accounts with permission `g`, `G`, or `h`
|
||||||
|
* fixed 20% performance drop in python 3.12 due to utcfromtimestamp deprecation
|
||||||
|
* but 3.12.0 is still 5% slower than 3.11.6 for some reason
|
||||||
|
* volume listing on startup would display some redundant info
|
||||||
|
|
||||||
|
## other changes
|
||||||
|
* timeout for unfinished uploads increased from 6 to 24 hours
|
||||||
|
* and is now configurable with `--snap-drop`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2023-1015-2006 `v1.9.12` more buttons
|
# 2023-1015-2006 `v1.9.12` more buttons
|
||||||
|
|
||||||
|
|||||||
@@ -106,20 +106,19 @@ def meichk():
|
|||||||
if filt not in sys.executable:
|
if filt not in sys.executable:
|
||||||
filt = os.path.basename(sys.executable)
|
filt = os.path.basename(sys.executable)
|
||||||
|
|
||||||
pids = []
|
hits = []
|
||||||
ptn = re.compile(r"^([^\s]+)\s+([0-9]+)")
|
|
||||||
try:
|
try:
|
||||||
procs = sp.check_output("tasklist").decode("utf-8", "replace")
|
cmd = "tasklist /fo csv".split(" ")
|
||||||
|
procs = sp.check_output(cmd).decode("utf-8", "replace")
|
||||||
except:
|
except:
|
||||||
procs = "" # winpe
|
procs = "" # winpe
|
||||||
|
|
||||||
for ln in procs.splitlines():
|
for ln in procs.split("\n"):
|
||||||
m = ptn.match(ln)
|
if filt in ln.split('"')[:2][-1]:
|
||||||
if m and filt in m.group(1).lower():
|
hits.append(ln)
|
||||||
pids.append(int(m.group(2)))
|
|
||||||
|
|
||||||
mod = os.path.dirname(os.path.realpath(__file__))
|
mod = os.path.dirname(os.path.realpath(__file__))
|
||||||
if os.path.basename(mod).startswith("_MEI") and len(pids) == 2:
|
if os.path.basename(mod).startswith("_MEI") and len(hits) == 2:
|
||||||
meicln(mod)
|
meicln(mod)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
srv/expand/README.md
Normal file
26
srv/expand/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
## text expansion
|
||||||
|
|
||||||
|
enable expansion of placeholder variables in `README.md` and prologue/epilogue files with `--exp` and customize the list of allowed placeholders to expand using `--exp-md` and `--exp-lg`
|
||||||
|
|
||||||
|
| explanation | placeholder |
|
||||||
|
| -------------------- | -------------------- |
|
||||||
|
| your ip address | {{self.ip}} |
|
||||||
|
| your user-agent | {{self.ua}} |
|
||||||
|
| your username | {{self.uname}} |
|
||||||
|
| the `Host` you see | {{self.host}} |
|
||||||
|
| server unix time | {{srv.itime}} |
|
||||||
|
| server datetime | {{srv.htime}} |
|
||||||
|
| server name | {{cfg.name}} |
|
||||||
|
| logout after | {{cfg.logout}} hours |
|
||||||
|
| vol reindex interval | {{vf.scan}} |
|
||||||
|
| thumbnail size | {{vf.thsize}} |
|
||||||
|
| your country | {{hdr.cf_ipcountry}} |
|
||||||
|
|
||||||
|
placeholders starting with...
|
||||||
|
* `self.` are grabbed from copyparty's internal state; anything in `httpcli.py` is fair game
|
||||||
|
* `cfg.` are the global server settings
|
||||||
|
* `vf.` are the volflags of the current volume
|
||||||
|
* `hdr.` are grabbed from the client headers; any header is supported, just add it (in lowercase) to the allowlist
|
||||||
|
* `srv.` are processed inside the `_expand` function in httpcli
|
||||||
|
|
||||||
|
for example (bad example), `hdr_cf_ipcountry` maps to the header `CF-IPCountry` (which is generated by cloudflare before the request is passed on to your server / copyparty)
|
||||||
@@ -109,7 +109,7 @@ class Cfg(Namespace):
|
|||||||
def __init__(self, a=None, v=None, c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vague_403 vc ver xdev xlink xvol"
|
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp exp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vague_403 vc ver xdev xlink xvol"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
|
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
|
||||||
@@ -130,6 +130,9 @@ class Cfg(Namespace):
|
|||||||
ex = "on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
ex = "on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
||||||
ka.update(**{k: [] for k in ex.split()})
|
ka.update(**{k: [] for k in ex.split()})
|
||||||
|
|
||||||
|
ex = "exp_lg exp_md"
|
||||||
|
ka.update(**{k: {} for k in ex.split()})
|
||||||
|
|
||||||
super(Cfg, self).__init__(
|
super(Cfg, self).__init__(
|
||||||
a=a or [],
|
a=a or [],
|
||||||
v=v or [],
|
v=v or [],
|
||||||
|
|||||||
Reference in New Issue
Block a user