Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1441ccee4f | ||
|
|
491803d8b7 | ||
|
|
3dcc386b6f | ||
|
|
5aa54d1217 | ||
|
|
88b876027c | ||
|
|
fcc3aa98fd | ||
|
|
f2f5e266b4 | ||
|
|
e17bf8f325 | ||
|
|
d19cb32bf3 | ||
|
|
85a637af09 | ||
|
|
043e3c7dd6 | ||
|
|
8f59afb159 | ||
|
|
77f1e51444 | ||
|
|
22fc4bb938 | ||
|
|
50c7bba6ea | ||
|
|
551d99b71b | ||
|
|
b54b7213a7 | ||
|
|
a14943c8de |
10
README.md
10
README.md
@@ -327,7 +327,7 @@ upgrade notes
|
|||||||
# accounts and volumes
|
# accounts and volumes
|
||||||
|
|
||||||
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
|
per-folder, per-user permissions - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
|
||||||
* 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 logged in as admin)
|
* 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
|
* 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`
|
||||||
@@ -346,6 +346,7 @@ permissions:
|
|||||||
* `d` (delete): delete files/folders
|
* `d` (delete): delete files/folders
|
||||||
* `g` (get): only download files, cannot see folder contents or zip/tar
|
* `g` (get): only download files, cannot see folder contents or zip/tar
|
||||||
* `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below)
|
* `G` (upget): same as `g` except uploaders get to see their own filekeys (see `fk` in examples below)
|
||||||
|
* `a` (admin): can see uploader IPs, config-reload
|
||||||
|
|
||||||
examples:
|
examples:
|
||||||
* add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
|
* add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
|
||||||
@@ -490,6 +491,9 @@ images with the following names (see `--th-covers`) become the thumbnail of the
|
|||||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
||||||
* indicated by the audio files having the ▶ icon instead of 💾
|
* indicated by the audio files having the ▶ icon instead of 💾
|
||||||
|
|
||||||
|
enabling `multiselect` lets you click files to select them, and then shift-click another file for range-select
|
||||||
|
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
||||||
|
|
||||||
|
|
||||||
## zip downloads
|
## zip downloads
|
||||||
|
|
||||||
@@ -612,6 +616,7 @@ file selection: click somewhere on the line (not the link itsef), then:
|
|||||||
* `up/down` to move
|
* `up/down` to move
|
||||||
* `shift-up/down` to move-and-select
|
* `shift-up/down` to move-and-select
|
||||||
* `ctrl-shift-up/down` to also scroll
|
* `ctrl-shift-up/down` to also scroll
|
||||||
|
* shift-click another line for range-select
|
||||||
|
|
||||||
* cut: select some files and `ctrl-x`
|
* cut: select some files and `ctrl-x`
|
||||||
* paste: `ctrl-v` in another folder
|
* paste: `ctrl-v` in another folder
|
||||||
@@ -773,7 +778,7 @@ for the above example to work, add the commandline argument `-e2ts` to also scan
|
|||||||
using arguments or config files, or a mix of both:
|
using arguments or config files, or a mix of both:
|
||||||
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf)
|
* config files (`-c some.conf`) can set additional commandline arguments; see [./docs/example.conf](docs/example.conf) and [./docs/example2.conf](docs/example2.conf)
|
||||||
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
|
* `kill -s USR1` (same as `systemctl reload copyparty`) to reload accounts and volumes from config files without restarting
|
||||||
* or click the `[reload cfg]` button in the control-panel when logged in as admin
|
* 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
|
* changes to the `[global]` config section requires a restart to take effect
|
||||||
|
|
||||||
|
|
||||||
@@ -1536,6 +1541,7 @@ some notes on hardening
|
|||||||
|
|
||||||
* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy)
|
* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy)
|
||||||
* cors doesn't work right otherwise
|
* 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`
|
||||||
|
|
||||||
safety profiles:
|
safety profiles:
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ in {
|
|||||||
"d" (delete): permanently delete files and folders
|
"d" (delete): permanently delete files and folders
|
||||||
"g" (get): download files, but cannot see folder contents
|
"g" (get): download files, but cannot see folder contents
|
||||||
"G" (upget): "get", but can see filekeys of their own uploads
|
"G" (upget): "get", but can see filekeys of their own uploads
|
||||||
|
"a" (upget): can see uploader IPs, config-reload
|
||||||
|
|
||||||
For example: "rwmd"
|
For example: "rwmd"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: icxes <dev.null@need.moe>
|
# Maintainer: icxes <dev.null@need.moe>
|
||||||
pkgname=copyparty
|
pkgname=copyparty
|
||||||
pkgver="1.8.0"
|
pkgver="1.8.3"
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Portable file sharing hub"
|
pkgdesc="Portable file sharing hub"
|
||||||
arch=("any")
|
arch=("any")
|
||||||
@@ -20,7 +20,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
|||||||
)
|
)
|
||||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
backup=("etc/${pkgname}.d/init" )
|
backup=("etc/${pkgname}.d/init" )
|
||||||
sha256sums=("e8ac0442abbf4c95428db0ad35a6965ad73038e0f384795907cde08af9e76612")
|
sha256sums=("6903106cab52536e5273f385813884b9c6dc734ee971ddddacfef8af6b7fec9b")
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/9001/copyparty/releases/download/v1.8.0/copyparty-sfx.py",
|
"url": "https://github.com/9001/copyparty/releases/download/v1.8.3/copyparty-sfx.py",
|
||||||
"version": "1.8.0",
|
"version": "1.8.3",
|
||||||
"hash": "sha256-7e0XL1r+m7e7scOsvbG9I7jk/Z24OGBuWJHxST8Ko7Y="
|
"hash": "sha256-jV9DUp2+lxhLP4QlIYtMoE0Woum9W4i6U/oLDyYyoRE="
|
||||||
}
|
}
|
||||||
@@ -492,6 +492,7 @@ def get_sects():
|
|||||||
"d" (delete): permanently delete files and folders
|
"d" (delete): permanently delete files and folders
|
||||||
"g" (get): download files, but cannot see folder contents
|
"g" (get): download files, but cannot see folder contents
|
||||||
"G" (upget): "get", but can see filekeys of their own uploads
|
"G" (upget): "get", but can see filekeys of their own uploads
|
||||||
|
"a" (admin): can see uploader IPs, config-reload
|
||||||
|
|
||||||
too many volflags to list here, see --help-flags
|
too many volflags to list here, see --help-flags
|
||||||
|
|
||||||
@@ -586,6 +587,7 @@ def get_sects():
|
|||||||
\033[36mxbd\033[35m executes CMD before a file delete
|
\033[36mxbd\033[35m executes CMD before a file delete
|
||||||
\033[36mxad\033[35m executes CMD after a file delete
|
\033[36mxad\033[35m executes CMD after a file delete
|
||||||
\033[36mxm\033[35m executes CMD on message
|
\033[36mxm\033[35m executes CMD on message
|
||||||
|
\033[36mxban\033[35m executes CMD if someone gets banned
|
||||||
\033[0m
|
\033[0m
|
||||||
can be defined as --args or volflags; for example \033[36m
|
can be defined as --args or volflags; for example \033[36m
|
||||||
--xau notify-send
|
--xau notify-send
|
||||||
@@ -621,6 +623,9 @@ def get_sects():
|
|||||||
executed program on STDIN instead of as argv arguments, and
|
executed program on STDIN instead of as argv arguments, and
|
||||||
it also includes the wark (file-id/hash) as a json property
|
it also includes the wark (file-id/hash) as a json property
|
||||||
|
|
||||||
|
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
|
||||||
|
if the program returns 0 (true/OK) then the ban will NOT happen
|
||||||
|
|
||||||
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
|
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
|
||||||
so it's recommended to use the \033[36mf\033[0m flag unless you really need
|
so it's recommended to use the \033[36mf\033[0m flag unless you really need
|
||||||
to wait for the hook to finish before continuing (without \033[36mf\033[0m
|
to wait for the hook to finish before continuing (without \033[36mf\033[0m
|
||||||
@@ -919,6 +924,7 @@ def add_hooks(ap):
|
|||||||
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute CMD before a file delete")
|
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute CMD before a file delete")
|
||||||
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute CMD after a file delete")
|
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute CMD after a file delete")
|
||||||
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute CMD on message")
|
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute CMD on message")
|
||||||
|
ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute CMD if someone gets banned (pw/404)")
|
||||||
|
|
||||||
|
|
||||||
def add_yolo(ap):
|
def add_yolo(ap):
|
||||||
@@ -1007,10 +1013,10 @@ def add_thumbnail(ap):
|
|||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails (volflag=dthumb)")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails (volflag=dvthumb)")
|
||||||
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
|
ap2.add_argument("--no-athumb", action="store_true", help="disable audio thumbnails (spectrograms) (volflag=dathumb)")
|
||||||
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res")
|
ap2.add_argument("--th-size", metavar="WxH", default="320x256", help="thumbnail res (volflag=thsize)")
|
||||||
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
ap2.add_argument("--th-mt", metavar="CORES", type=int, default=CORES, help="num cpu cores to use for generating thumbnails")
|
||||||
ap2.add_argument("--th-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
|
ap2.add_argument("--th-convt", metavar="SEC", type=float, default=60, help="conversion timeout in seconds (volflag=convt)")
|
||||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image (volflag=nocrop)")
|
||||||
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,ff", help="image decoders, in order of preference")
|
ap2.add_argument("--th-dec", metavar="LIBS", default="vips,pil,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-jpg", action="store_true", help="disable jpg output")
|
||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
@@ -1073,7 +1079,7 @@ def add_db_metadata(ap):
|
|||||||
ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
|
ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers")
|
||||||
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
||||||
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
||||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash,up_ip,.up_at")
|
||||||
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||||
default=".vq,.aq,vc,ac,fmt,res,.fps")
|
default=".vq,.aq,vc,ac,fmt,res,.fps")
|
||||||
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")
|
||||||
@@ -1337,11 +1343,9 @@ def main(argv: Optional[list[str]] = None) -> None:
|
|||||||
if re.match("c[^,]", opt):
|
if re.match("c[^,]", opt):
|
||||||
mod = True
|
mod = True
|
||||||
na.append("c," + opt[1:])
|
na.append("c," + opt[1:])
|
||||||
elif re.sub("^[rwmdgG]*", "", opt) and "," not in opt:
|
elif re.sub("^[rwmdgGa]*", "", opt) and "," not in opt:
|
||||||
mod = True
|
mod = True
|
||||||
perm = opt[0]
|
perm = opt[0]
|
||||||
if perm == "a":
|
|
||||||
perm = "rw"
|
|
||||||
na.append(perm + "," + opt[1:])
|
na.append(perm + "," + opt[1:])
|
||||||
else:
|
else:
|
||||||
na.append(opt)
|
na.append(opt)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (1, 8, 1)
|
VERSION = (1, 8, 4)
|
||||||
CODENAME = "argon"
|
CODENAME = "argon"
|
||||||
BUILD_DT = (2023, 7, 7)
|
BUILD_DT = (2023, 7, 18)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class AXS(object):
|
|||||||
udel: Optional[Union[list[str], set[str]]] = None,
|
udel: Optional[Union[list[str], set[str]]] = None,
|
||||||
uget: Optional[Union[list[str], set[str]]] = None,
|
uget: Optional[Union[list[str], set[str]]] = None,
|
||||||
upget: Optional[Union[list[str], set[str]]] = None,
|
upget: Optional[Union[list[str], set[str]]] = None,
|
||||||
|
uadmin: Optional[Union[list[str], set[str]]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.uread: set[str] = set(uread or [])
|
self.uread: set[str] = set(uread or [])
|
||||||
self.uwrite: set[str] = set(uwrite or [])
|
self.uwrite: set[str] = set(uwrite or [])
|
||||||
@@ -69,14 +70,11 @@ class AXS(object):
|
|||||||
self.udel: set[str] = set(udel or [])
|
self.udel: set[str] = set(udel or [])
|
||||||
self.uget: set[str] = set(uget or [])
|
self.uget: set[str] = set(uget or [])
|
||||||
self.upget: set[str] = set(upget or [])
|
self.upget: set[str] = set(upget or [])
|
||||||
|
self.uadmin: set[str] = set(uadmin or [])
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "AXS(%s)" % (
|
ks = "uread uwrite umove udel uget upget uadmin".split()
|
||||||
", ".join(
|
return "AXS(%s)" % (", ".join("%s=%r" % (k, self.__dict__[k]) for k in ks),)
|
||||||
"%s=%r" % (k, self.__dict__[k])
|
|
||||||
for k in "uread uwrite umove udel uget upget".split()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Lim(object):
|
class Lim(object):
|
||||||
@@ -326,6 +324,7 @@ class VFS(object):
|
|||||||
self.adel: dict[str, list[str]] = {}
|
self.adel: dict[str, list[str]] = {}
|
||||||
self.aget: dict[str, list[str]] = {}
|
self.aget: dict[str, list[str]] = {}
|
||||||
self.apget: dict[str, list[str]] = {}
|
self.apget: dict[str, list[str]] = {}
|
||||||
|
self.aadmin: dict[str, list[str]] = {}
|
||||||
|
|
||||||
if realpath:
|
if realpath:
|
||||||
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
|
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
|
||||||
@@ -435,8 +434,8 @@ class VFS(object):
|
|||||||
|
|
||||||
def can_access(
|
def can_access(
|
||||||
self, vpath: str, uname: str
|
self, vpath: str, uname: str
|
||||||
) -> tuple[bool, bool, bool, bool, bool, bool]:
|
) -> tuple[bool, bool, bool, bool, bool, bool, bool]:
|
||||||
"""can Read,Write,Move,Delete,Get,Upget"""
|
"""can Read,Write,Move,Delete,Get,Upget,Admin"""
|
||||||
if vpath:
|
if vpath:
|
||||||
vn, _ = self._find(undot(vpath))
|
vn, _ = self._find(undot(vpath))
|
||||||
else:
|
else:
|
||||||
@@ -450,6 +449,7 @@ class VFS(object):
|
|||||||
uname in c.udel or "*" in c.udel,
|
uname in c.udel or "*" in c.udel,
|
||||||
uname in c.uget or "*" in c.uget,
|
uname in c.uget or "*" in c.uget,
|
||||||
uname in c.upget or "*" in c.upget,
|
uname in c.upget or "*" in c.upget,
|
||||||
|
uname in c.uadmin or "*" in c.uadmin,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
@@ -944,7 +944,7 @@ class AuthSrv(object):
|
|||||||
try:
|
try:
|
||||||
self._l(ln, 5, "volume access config:")
|
self._l(ln, 5, "volume access config:")
|
||||||
sk, sv = ln.split(":")
|
sk, sv = ln.split(":")
|
||||||
if re.sub("[rwmdgG]", "", sk) or not sk:
|
if re.sub("[rwmdgGa]", "", sk) or not sk:
|
||||||
err = "invalid accs permissions list; "
|
err = "invalid accs permissions list; "
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
if " " in re.sub(", *", "", sv).strip():
|
if " " in re.sub(", *", "", sv).strip():
|
||||||
@@ -953,7 +953,7 @@ class AuthSrv(object):
|
|||||||
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
|
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
|
||||||
continue
|
continue
|
||||||
except:
|
except:
|
||||||
err += "accs entries must be 'rwmdgG: user1, user2, ...'"
|
err += "accs entries must be 'rwmdgGa: user1, user2, ...'"
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
if cat == catf:
|
if cat == catf:
|
||||||
@@ -989,7 +989,7 @@ class AuthSrv(object):
|
|||||||
def _read_vol_str(
|
def _read_vol_str(
|
||||||
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
|
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
if lvl.strip("crwmdgG"):
|
if lvl.strip("crwmdgGa"):
|
||||||
raise Exception("invalid volflag: {},{}".format(lvl, uname))
|
raise Exception("invalid volflag: {},{}".format(lvl, uname))
|
||||||
|
|
||||||
if lvl == "c":
|
if lvl == "c":
|
||||||
@@ -1021,6 +1021,7 @@ class AuthSrv(object):
|
|||||||
("g", axs.uget),
|
("g", axs.uget),
|
||||||
("G", axs.uget),
|
("G", axs.uget),
|
||||||
("G", axs.upget),
|
("G", axs.upget),
|
||||||
|
("a", axs.uadmin),
|
||||||
]: # b bb bbb
|
]: # b bb bbb
|
||||||
if ch in lvl:
|
if ch in lvl:
|
||||||
if un == "*":
|
if un == "*":
|
||||||
@@ -1047,7 +1048,8 @@ class AuthSrv(object):
|
|||||||
flags[name] = True
|
flags[name] = True
|
||||||
return
|
return
|
||||||
|
|
||||||
if name not in "mtp xbu xau xiu xbr xar xbd xad xm on404 on403".split():
|
zs = "mtp on403 on404 xbu xau xiu xbr xar xbd xad xm xban"
|
||||||
|
if name not in zs.split():
|
||||||
if value is True:
|
if value is True:
|
||||||
t = "└─add volflag [{}] = {} ({})"
|
t = "└─add volflag [{}] = {} ({})"
|
||||||
else:
|
else:
|
||||||
@@ -1092,7 +1094,7 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
if self.args.v:
|
if self.args.v:
|
||||||
# list of src:dst:permset:permset:...
|
# list of src:dst:permset:permset:...
|
||||||
# permset is <rwmdgG>[,username][,username] or <c>,<flag>[=args]
|
# permset is <rwmdgGa>[,username][,username] or <c>,<flag>[=args]
|
||||||
for v_str in self.args.v:
|
for v_str in self.args.v:
|
||||||
m = re_vol.match(v_str)
|
m = re_vol.match(v_str)
|
||||||
if not m:
|
if not m:
|
||||||
@@ -1181,7 +1183,7 @@ class AuthSrv(object):
|
|||||||
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
||||||
vol.root = vfs
|
vol.root = vfs
|
||||||
|
|
||||||
for perm in "read write move del get pget".split():
|
for perm in "read write move del get pget admin".split():
|
||||||
axs_key = "u" + perm
|
axs_key = "u" + perm
|
||||||
unames = ["*"] + list(acct.keys())
|
unames = ["*"] + list(acct.keys())
|
||||||
umap: dict[str, list[str]] = {x: [] for x in unames}
|
umap: dict[str, list[str]] = {x: [] for x in unames}
|
||||||
@@ -1196,7 +1198,15 @@ class AuthSrv(object):
|
|||||||
all_users = {}
|
all_users = {}
|
||||||
missing_users = {}
|
missing_users = {}
|
||||||
for axs in daxs.values():
|
for axs in daxs.values():
|
||||||
for d in [axs.uread, axs.uwrite, axs.umove, axs.udel, axs.uget, axs.upget]:
|
for d in [
|
||||||
|
axs.uread,
|
||||||
|
axs.uwrite,
|
||||||
|
axs.umove,
|
||||||
|
axs.udel,
|
||||||
|
axs.uget,
|
||||||
|
axs.upget,
|
||||||
|
axs.uadmin,
|
||||||
|
]:
|
||||||
for usr in d:
|
for usr in d:
|
||||||
all_users[usr] = 1
|
all_users[usr] = 1
|
||||||
if usr != "*" and usr not in acct:
|
if usr != "*" and usr not in acct:
|
||||||
@@ -1420,6 +1430,10 @@ class AuthSrv(object):
|
|||||||
if k in vol.flags:
|
if k in vol.flags:
|
||||||
vol.flags[k] = int(vol.flags[k])
|
vol.flags[k] = int(vol.flags[k])
|
||||||
|
|
||||||
|
for k in ("convt",):
|
||||||
|
if k in vol.flags:
|
||||||
|
vol.flags[k] = float(vol.flags[k])
|
||||||
|
|
||||||
for k1, k2 in IMPLICATIONS:
|
for k1, k2 in IMPLICATIONS:
|
||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = True
|
vol.flags[k2] = True
|
||||||
@@ -1445,7 +1459,7 @@ class AuthSrv(object):
|
|||||||
vol.flags["mth"] = self.args.mth
|
vol.flags["mth"] = self.args.mth
|
||||||
|
|
||||||
# append additive args from argv to volflags
|
# append additive args from argv to volflags
|
||||||
hooks = "xbu xau xiu xbr xar xbd xad xm".split()
|
hooks = "xbu xau xiu xbr xar xbd xad xm xban".split()
|
||||||
for name in "mtp on404 on403".split() + hooks:
|
for name in "mtp on404 on403".split() + hooks:
|
||||||
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
||||||
|
|
||||||
@@ -1468,6 +1482,10 @@ class AuthSrv(object):
|
|||||||
hfs = [x for x in hfs if x != "f"]
|
hfs = [x for x in hfs if x != "f"]
|
||||||
ocmd = ",".join(hfs + [cmd])
|
ocmd = ",".join(hfs + [cmd])
|
||||||
|
|
||||||
|
if "c" not in hfs and "f" not in hfs and hn == "xban":
|
||||||
|
hfs = ["c"] + hfs
|
||||||
|
ocmd = ",".join(hfs + [cmd])
|
||||||
|
|
||||||
ncmds.append(ocmd)
|
ncmds.append(ocmd)
|
||||||
vol.flags[hn] = ncmds
|
vol.flags[hn] = ncmds
|
||||||
|
|
||||||
@@ -1607,6 +1625,7 @@ class AuthSrv(object):
|
|||||||
["delete", "udel"],
|
["delete", "udel"],
|
||||||
[" get", "uget"],
|
[" get", "uget"],
|
||||||
[" upget", "upget"],
|
[" upget", "upget"],
|
||||||
|
["uadmin", "uadmin"],
|
||||||
]:
|
]:
|
||||||
u = list(sorted(getattr(zv.axs, attr)))
|
u = list(sorted(getattr(zv.axs, attr)))
|
||||||
u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
|
u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
|
||||||
@@ -1752,10 +1771,19 @@ class AuthSrv(object):
|
|||||||
raise Exception("volume not found: " + zs)
|
raise Exception("volume not found: " + zs)
|
||||||
|
|
||||||
self.log(str({"users": users, "vols": vols, "flags": flags}))
|
self.log(str({"users": users, "vols": vols, "flags": flags}))
|
||||||
t = "/{}: read({}) write({}) move({}) del({}) get({}) upget({})"
|
t = "/{}: read({}) write({}) move({}) del({}) get({}) upget({}) uadmin({})"
|
||||||
for k, zv in self.vfs.all_vols.items():
|
for k, zv in self.vfs.all_vols.items():
|
||||||
vc = zv.axs
|
vc = zv.axs
|
||||||
vs = [k, vc.uread, vc.uwrite, vc.umove, vc.udel, vc.uget, vc.upget]
|
vs = [
|
||||||
|
k,
|
||||||
|
vc.uread,
|
||||||
|
vc.uwrite,
|
||||||
|
vc.umove,
|
||||||
|
vc.udel,
|
||||||
|
vc.uget,
|
||||||
|
vc.upget,
|
||||||
|
vc.uadmin,
|
||||||
|
]
|
||||||
self.log(t.format(*vs))
|
self.log(t.format(*vs))
|
||||||
|
|
||||||
flag_v = "v" in flags
|
flag_v = "v" in flags
|
||||||
@@ -1835,7 +1863,8 @@ class AuthSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
csv = set("i p".split())
|
csv = set("i p".split())
|
||||||
lst = set("c ihead mtm mtp xad xar xau xiu xbd xbr xbu xm on404 on403".split())
|
zs = "c ihead mtm mtp on403 on404 xad xar xau xiu xban xbd xbr xbu xm"
|
||||||
|
lst = set(zs.split())
|
||||||
askip = set("a v c vc cgen theme".split())
|
askip = set("a v c vc cgen theme".split())
|
||||||
|
|
||||||
# keymap from argv to vflag
|
# keymap from argv to vflag
|
||||||
@@ -1894,6 +1923,7 @@ class AuthSrv(object):
|
|||||||
"d": "udel",
|
"d": "udel",
|
||||||
"g": "uget",
|
"g": "uget",
|
||||||
"G": "upget",
|
"G": "upget",
|
||||||
|
"a": "uadmin",
|
||||||
}
|
}
|
||||||
users = {}
|
users = {}
|
||||||
for pkey in perms.values():
|
for pkey in perms.values():
|
||||||
@@ -2090,7 +2120,7 @@ def upgrade_cfg_fmt(
|
|||||||
else:
|
else:
|
||||||
sn = sn.replace(",", ", ")
|
sn = sn.replace(",", ", ")
|
||||||
ret.append(" " + sn)
|
ret.append(" " + sn)
|
||||||
elif sn[:1] in "rwmdgG":
|
elif sn[:1] in "rwmdgGa":
|
||||||
if cat != catx:
|
if cat != catx:
|
||||||
cat = catx
|
cat = catx
|
||||||
ret.append(cat)
|
ret.append(cat)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
"no_dedup": "copydupes",
|
"no_dedup": "copydupes",
|
||||||
"no_dupe": "nodupe",
|
"no_dupe": "nodupe",
|
||||||
"no_forget": "noforget",
|
"no_forget": "noforget",
|
||||||
|
"th_no_crop": "nocrop",
|
||||||
"dav_auth": "davauth",
|
"dav_auth": "davauth",
|
||||||
"dav_rt": "davrt",
|
"dav_rt": "davrt",
|
||||||
}
|
}
|
||||||
@@ -40,8 +41,8 @@ def vf_bmap() -> dict[str, str]:
|
|||||||
|
|
||||||
def vf_vmap() -> dict[str, str]:
|
def vf_vmap() -> dict[str, str]:
|
||||||
"""argv-to-volflag: simple values"""
|
"""argv-to-volflag: simple values"""
|
||||||
ret = {}
|
ret = {"th_convt": "convt", "th_size": "thsize"}
|
||||||
for k in ("lg_sbf", "md_sbf", "unlist"):
|
for k in ("dbd", "lg_sbf", "md_sbf", "nrand", "unlist"):
|
||||||
ret[k] = k
|
ret[k] = k
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ def vf_vmap() -> dict[str, str]:
|
|||||||
def vf_cmap() -> dict[str, str]:
|
def vf_cmap() -> dict[str, str]:
|
||||||
"""argv-to-volflag: complex/lists"""
|
"""argv-to-volflag: complex/lists"""
|
||||||
ret = {}
|
ret = {}
|
||||||
for k in ("dbd", "html_head", "mte", "mth", "nrand"):
|
for k in ("html_head", "mte", "mth"):
|
||||||
ret[k] = k
|
ret[k] = k
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ flagcats = {
|
|||||||
"nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
|
"nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
|
||||||
"noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
|
"noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
|
||||||
"noforget": "don't forget files when deleted from disk",
|
"noforget": "don't forget files when deleted from disk",
|
||||||
|
"fat32": "avoid excessive reindexing on android sdcardfs",
|
||||||
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
"dbd=[acid|swal|wal|yolo]": "database speed-durability tradeoff",
|
||||||
"xlink": "cross-volume dupe detection / linking",
|
"xlink": "cross-volume dupe detection / linking",
|
||||||
"xdev": "do not descend into other filesystems",
|
"xdev": "do not descend into other filesystems",
|
||||||
@@ -124,6 +126,9 @@ flagcats = {
|
|||||||
"dvthumb": "disables video thumbnails",
|
"dvthumb": "disables video thumbnails",
|
||||||
"dathumb": "disables audio thumbnails (spectrograms)",
|
"dathumb": "disables audio thumbnails (spectrograms)",
|
||||||
"dithumb": "disables image thumbnails",
|
"dithumb": "disables image thumbnails",
|
||||||
|
"thsize": "thumbnail res; WxH",
|
||||||
|
"nocrop": "disable center-cropping",
|
||||||
|
"convt": "conversion timeout in seconds",
|
||||||
},
|
},
|
||||||
"handlers\n(better explained in --help-handlers)": {
|
"handlers\n(better explained in --help-handlers)": {
|
||||||
"on404=PY": "handle 404s by executing PY file",
|
"on404=PY": "handle 404s by executing PY file",
|
||||||
@@ -138,6 +143,7 @@ flagcats = {
|
|||||||
"xbd=CMD": "execute CMD before a file delete",
|
"xbd=CMD": "execute CMD before a file delete",
|
||||||
"xad=CMD": "execute CMD after a file delete",
|
"xad=CMD": "execute CMD after a file delete",
|
||||||
"xm=CMD": "execute CMD on message",
|
"xm=CMD": "execute CMD on message",
|
||||||
|
"xban=CMD": "execute CMD if someone gets banned",
|
||||||
},
|
},
|
||||||
"client and ux": {
|
"client and ux": {
|
||||||
"grid": "show grid/thumbnails by default",
|
"grid": "show grid/thumbnails by default",
|
||||||
@@ -151,6 +157,7 @@ flagcats = {
|
|||||||
"sb_lg": "enable js sandbox for prologue/epilogue (default)",
|
"sb_lg": "enable js sandbox for prologue/epilogue (default)",
|
||||||
"md_sbf": "list of markdown-sandbox safeguards to disable",
|
"md_sbf": "list of markdown-sandbox safeguards to disable",
|
||||||
"lg_sbf": "list of *logue-sandbox safeguards to disable",
|
"lg_sbf": "list of *logue-sandbox safeguards to disable",
|
||||||
|
"nohtml": "return html and markdown as text/html",
|
||||||
},
|
},
|
||||||
"others": {
|
"others": {
|
||||||
"fk=8": 'generates per-file accesskeys,\nwhich will then be required at the "g" permission',
|
"fk=8": 'generates per-file accesskeys,\nwhich will then be required at the "g" permission',
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class FtpFs(AbstractedFS):
|
|||||||
|
|
||||||
self.can_read = self.can_write = self.can_move = False
|
self.can_read = self.can_write = self.can_move = False
|
||||||
self.can_delete = self.can_get = self.can_upget = False
|
self.can_delete = self.can_get = self.can_upget = False
|
||||||
|
self.can_admin = False
|
||||||
|
|
||||||
self.listdirinfo = self.listdir
|
self.listdirinfo = self.listdir
|
||||||
self.chdir(".")
|
self.chdir(".")
|
||||||
@@ -168,7 +169,7 @@ class FtpFs(AbstractedFS):
|
|||||||
if not avfs:
|
if not avfs:
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|
||||||
cr, cw, cm, cd, _, _ = avfs.can_access("", self.h.uname)
|
cr, cw, cm, cd, _, _, _ = avfs.can_access("", self.h.uname)
|
||||||
if r and not cr or w and not cw or m and not cm or d and not cd:
|
if r and not cr or w and not cw or m and not cm or d and not cd:
|
||||||
raise FSE(t.format(vpath), 1)
|
raise FSE(t.format(vpath), 1)
|
||||||
|
|
||||||
@@ -243,6 +244,7 @@ class FtpFs(AbstractedFS):
|
|||||||
self.can_delete,
|
self.can_delete,
|
||||||
self.can_get,
|
self.can_get,
|
||||||
self.can_upget,
|
self.can_upget,
|
||||||
|
self.can_admin,
|
||||||
) = avfs.can_access("", self.h.uname)
|
) = avfs.can_access("", self.h.uname)
|
||||||
|
|
||||||
def mkdir(self, path: str) -> None:
|
def mkdir(self, path: str) -> None:
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from .util import (
|
|||||||
Pebkac,
|
Pebkac,
|
||||||
UnrecvEOF,
|
UnrecvEOF,
|
||||||
alltrace,
|
alltrace,
|
||||||
|
absreal,
|
||||||
atomic_move,
|
atomic_move,
|
||||||
exclude_dotfiles,
|
exclude_dotfiles,
|
||||||
fsenc,
|
fsenc,
|
||||||
@@ -137,6 +138,8 @@ class HttpCli(object):
|
|||||||
self.uparam: dict[str, str] = {}
|
self.uparam: dict[str, str] = {}
|
||||||
self.cookies: dict[str, str] = {}
|
self.cookies: dict[str, str] = {}
|
||||||
self.avn: Optional[VFS] = None
|
self.avn: Optional[VFS] = None
|
||||||
|
self.vn = self.asrv.vfs
|
||||||
|
self.rem = " "
|
||||||
self.vpath = " "
|
self.vpath = " "
|
||||||
self.uname = " "
|
self.uname = " "
|
||||||
self.pw = " "
|
self.pw = " "
|
||||||
@@ -146,6 +149,7 @@ class HttpCli(object):
|
|||||||
self.dvol = [" "]
|
self.dvol = [" "]
|
||||||
self.gvol = [" "]
|
self.gvol = [" "]
|
||||||
self.upvol = [" "]
|
self.upvol = [" "]
|
||||||
|
self.avol = [" "]
|
||||||
self.do_log = True
|
self.do_log = True
|
||||||
self.can_read = False
|
self.can_read = False
|
||||||
self.can_write = False
|
self.can_write = False
|
||||||
@@ -153,6 +157,7 @@ class HttpCli(object):
|
|||||||
self.can_delete = False
|
self.can_delete = False
|
||||||
self.can_get = False
|
self.can_get = False
|
||||||
self.can_upget = False
|
self.can_upget = False
|
||||||
|
self.can_admin = False
|
||||||
# post
|
# post
|
||||||
self.parser: Optional[MultipartParser] = None
|
self.parser: Optional[MultipartParser] = None
|
||||||
# end placeholders
|
# end placeholders
|
||||||
@@ -401,6 +406,7 @@ class HttpCli(object):
|
|||||||
self.dvol = self.asrv.vfs.adel[self.uname]
|
self.dvol = self.asrv.vfs.adel[self.uname]
|
||||||
self.gvol = self.asrv.vfs.aget[self.uname]
|
self.gvol = self.asrv.vfs.aget[self.uname]
|
||||||
self.upvol = self.asrv.vfs.apget[self.uname]
|
self.upvol = self.asrv.vfs.apget[self.uname]
|
||||||
|
self.avol = self.asrv.vfs.aadmin[self.uname]
|
||||||
|
|
||||||
if self.pw and (
|
if self.pw and (
|
||||||
self.pw != cookie_pw or self.conn.freshen_pwd + 30 < time.time()
|
self.pw != cookie_pw or self.conn.freshen_pwd + 30 < time.time()
|
||||||
@@ -431,10 +437,13 @@ class HttpCli(object):
|
|||||||
self.can_delete,
|
self.can_delete,
|
||||||
self.can_get,
|
self.can_get,
|
||||||
self.can_upget,
|
self.can_upget,
|
||||||
|
self.can_admin,
|
||||||
) = (
|
) = (
|
||||||
avn.can_access("", self.uname) if avn else [False] * 6
|
avn.can_access("", self.uname) if avn else [False] * 6
|
||||||
)
|
)
|
||||||
self.avn = avn
|
self.avn = avn
|
||||||
|
self.vn = vn
|
||||||
|
self.rem = rem
|
||||||
|
|
||||||
self.s.settimeout(self.args.s_tbody or None)
|
self.s.settimeout(self.args.s_tbody or None)
|
||||||
|
|
||||||
@@ -570,9 +579,8 @@ class HttpCli(object):
|
|||||||
|
|
||||||
# default to utf8 html if no content-type is set
|
# default to utf8 html if no content-type is set
|
||||||
if not mime:
|
if not mime:
|
||||||
mime = self.out_headers.get("Content-Type", "text/html; charset=utf-8")
|
mime = self.out_headers.get("Content-Type") or "text/html; charset=utf-8"
|
||||||
|
|
||||||
assert mime
|
|
||||||
self.out_headers["Content-Type"] = mime
|
self.out_headers["Content-Type"] = mime
|
||||||
|
|
||||||
for k, zs in list(self.out_headers.items()) + self.out_headerlist:
|
for k, zs in list(self.out_headers.items()) + self.out_headerlist:
|
||||||
@@ -597,6 +605,20 @@ class HttpCli(object):
|
|||||||
if g.lim:
|
if g.lim:
|
||||||
bonk, ip = g.bonk(self.ip, self.vpath)
|
bonk, ip = g.bonk(self.ip, self.vpath)
|
||||||
if bonk:
|
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(),
|
||||||
|
"404",
|
||||||
|
):
|
||||||
self.log("client banned: 404s", 1)
|
self.log("client banned: 404s", 1)
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
self.conn.hsrv.bans[ip] = bonk
|
||||||
|
|
||||||
@@ -760,7 +782,14 @@ class HttpCli(object):
|
|||||||
self.reply(b"", 301, headers=h)
|
self.reply(b"", 301, headers=h)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
|
path_base = os.path.join(self.E.mod, "web")
|
||||||
|
static_path = absreal(os.path.join(path_base, self.vpath[5:]))
|
||||||
|
if not static_path.startswith(path_base):
|
||||||
|
t = "attempted path traversal [{}] => [{}]"
|
||||||
|
self.log(t.format(self.vpath, static_path), 1)
|
||||||
|
self.tx_404()
|
||||||
|
return False
|
||||||
|
|
||||||
return self.tx_file(static_path)
|
return self.tx_file(static_path)
|
||||||
|
|
||||||
if "cf_challenge" in self.uparam:
|
if "cf_challenge" in self.uparam:
|
||||||
@@ -771,9 +800,8 @@ class HttpCli(object):
|
|||||||
t = "@{} has no access to [{}]"
|
t = "@{} has no access to [{}]"
|
||||||
self.log(t.format(self.uname, self.vpath))
|
self.log(t.format(self.uname, self.vpath))
|
||||||
|
|
||||||
if self.avn and "on403" in self.avn.flags:
|
if "on403" in self.vn.flags:
|
||||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
|
||||||
ret = self.on40x(vn.flags["on403"], vn, rem)
|
|
||||||
if ret == "true":
|
if ret == "true":
|
||||||
return True
|
return True
|
||||||
elif ret == "false":
|
elif ret == "false":
|
||||||
@@ -782,6 +810,7 @@ class HttpCli(object):
|
|||||||
self.log("plugin override; access permitted")
|
self.log("plugin override; access permitted")
|
||||||
self.can_read = self.can_write = self.can_move = True
|
self.can_read = self.can_write = self.can_move = True
|
||||||
self.can_delete = self.can_get = self.can_upget = True
|
self.can_delete = self.can_get = self.can_upget = True
|
||||||
|
self.can_admin = True
|
||||||
else:
|
else:
|
||||||
return self.tx_404(True)
|
return self.tx_404(True)
|
||||||
else:
|
else:
|
||||||
@@ -1039,9 +1068,6 @@ class HttpCli(object):
|
|||||||
|
|
||||||
from .dxml import mkenod, mktnod, parse_xml
|
from .dxml import mkenod, mktnod, parse_xml
|
||||||
|
|
||||||
self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
|
||||||
# abspath = vn.dcanonical(rem)
|
|
||||||
|
|
||||||
buf = b""
|
buf = b""
|
||||||
for rbuf in self.get_body_reader()[0]:
|
for rbuf in self.get_body_reader()[0]:
|
||||||
buf += rbuf
|
buf += rbuf
|
||||||
@@ -1098,8 +1124,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
from .dxml import mkenod, mktnod, parse_xml
|
from .dxml import mkenod, mktnod, parse_xml
|
||||||
|
|
||||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
abspath = self.vn.dcanonical(self.rem)
|
||||||
abspath = vn.dcanonical(rem)
|
|
||||||
|
|
||||||
buf = b""
|
buf = b""
|
||||||
for rbuf in self.get_body_reader()[0]:
|
for rbuf in self.get_body_reader()[0]:
|
||||||
@@ -1313,20 +1338,17 @@ class HttpCli(object):
|
|||||||
plain = zb.decode("utf-8", "replace")
|
plain = zb.decode("utf-8", "replace")
|
||||||
if buf.startswith(b"msg="):
|
if buf.startswith(b"msg="):
|
||||||
plain = plain[4:]
|
plain = plain[4:]
|
||||||
vfs, rem = self.asrv.vfs.get(
|
xm = self.vn.flags.get("xm")
|
||||||
self.vpath, self.uname, False, False
|
|
||||||
)
|
|
||||||
xm = vfs.flags.get("xm")
|
|
||||||
if xm:
|
if xm:
|
||||||
runhook(
|
runhook(
|
||||||
self.log,
|
self.log,
|
||||||
xm,
|
xm,
|
||||||
vfs.canonical(rem),
|
self.vn.canonical(self.rem),
|
||||||
self.vpath,
|
self.vpath,
|
||||||
self.host,
|
self.host,
|
||||||
self.uname,
|
self.uname,
|
||||||
time.time(),
|
time.time(),
|
||||||
len(xm),
|
len(buf),
|
||||||
self.ip,
|
self.ip,
|
||||||
time.time(),
|
time.time(),
|
||||||
plain,
|
plain,
|
||||||
@@ -1998,6 +2020,20 @@ class HttpCli(object):
|
|||||||
if g.lim:
|
if g.lim:
|
||||||
bonk, ip = g.bonk(self.ip, pwd)
|
bonk, ip = g.bonk(self.ip, pwd)
|
||||||
if bonk:
|
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.log("client banned: invalid passwords", 1)
|
||||||
self.conn.hsrv.bans[ip] = bonk
|
self.conn.hsrv.bans[ip] = bonk
|
||||||
|
|
||||||
@@ -2728,6 +2764,9 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
mime = guess_mime(req_path)
|
mime = guess_mime(req_path)
|
||||||
|
|
||||||
|
if "nohtml" in self.vn.flags and "html" in mime:
|
||||||
|
mime = "text/plain; charset=utf-8"
|
||||||
|
|
||||||
self.out_headers["Accept-Ranges"] = "bytes"
|
self.out_headers["Accept-Ranges"] = "bytes"
|
||||||
self.send_headers(length=upper - lower, status=status, mime=mime)
|
self.send_headers(length=upper - lower, status=status, mime=mime)
|
||||||
|
|
||||||
@@ -2966,13 +3005,12 @@ class HttpCli(object):
|
|||||||
|
|
||||||
def tx_mounts(self) -> bool:
|
def tx_mounts(self) -> bool:
|
||||||
suf = self.urlq({}, ["h"])
|
suf = self.urlq({}, ["h"])
|
||||||
avol = [x for x in self.wvol if x in self.rvol]
|
|
||||||
rvol, wvol, avol = [
|
rvol, wvol, avol = [
|
||||||
[("/" + x).rstrip("/") + "/" for x in y]
|
[("/" + x).rstrip("/") + "/" for x in y]
|
||||||
for y in [self.rvol, self.wvol, avol]
|
for y in [self.rvol, self.wvol, self.avol]
|
||||||
]
|
]
|
||||||
|
|
||||||
if avol and not self.args.no_rescan:
|
if self.avol and not self.args.no_rescan:
|
||||||
x = self.conn.hsrv.broker.ask("up2k.get_state")
|
x = self.conn.hsrv.broker.ask("up2k.get_state")
|
||||||
vs = json.loads(x.get())
|
vs = json.loads(x.get())
|
||||||
vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
|
vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
|
||||||
@@ -3088,7 +3126,7 @@ class HttpCli(object):
|
|||||||
return "" # unhandled / fallthrough
|
return "" # unhandled / fallthrough
|
||||||
|
|
||||||
def scanvol(self) -> bool:
|
def scanvol(self) -> bool:
|
||||||
if not self.can_read or not self.can_write:
|
if not self.can_admin:
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
|
|
||||||
if self.args.no_rescan:
|
if self.args.no_rescan:
|
||||||
@@ -3111,7 +3149,7 @@ class HttpCli(object):
|
|||||||
if act != "cfg":
|
if act != "cfg":
|
||||||
raise Pebkac(400, "only config files ('cfg') can be reloaded rn")
|
raise Pebkac(400, "only config files ('cfg') can be reloaded rn")
|
||||||
|
|
||||||
if not [x for x in self.wvol if x in self.rvol]:
|
if not self.avol:
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
|
|
||||||
if self.args.no_reload:
|
if self.args.no_reload:
|
||||||
@@ -3121,7 +3159,7 @@ class HttpCli(object):
|
|||||||
return self.redirect("", "?h", x.get(), "return to", False)
|
return self.redirect("", "?h", x.get(), "return to", False)
|
||||||
|
|
||||||
def tx_stack(self) -> bool:
|
def tx_stack(self) -> bool:
|
||||||
if not [x for x in self.wvol if x in self.rvol]:
|
if not self.avol and not [x for x in self.wvol if x in self.rvol]:
|
||||||
raise Pebkac(403, "not allowed for user " + self.uname)
|
raise Pebkac(403, "not allowed for user " + self.uname)
|
||||||
|
|
||||||
if self.args.no_stack:
|
if self.args.no_stack:
|
||||||
@@ -3397,7 +3435,8 @@ class HttpCli(object):
|
|||||||
|
|
||||||
vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
|
vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
|
||||||
|
|
||||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
vn = self.vn
|
||||||
|
rem = self.rem
|
||||||
abspath = vn.dcanonical(rem)
|
abspath = vn.dcanonical(rem)
|
||||||
dbv, vrem = vn.get_dbv(rem)
|
dbv, vrem = vn.get_dbv(rem)
|
||||||
|
|
||||||
@@ -3493,8 +3532,14 @@ class HttpCli(object):
|
|||||||
self.log("wrong filekey, want {}, got {}".format(correct, got))
|
self.log("wrong filekey, want {}, got {}".format(correct, got))
|
||||||
return self.tx_404()
|
return self.tx_404()
|
||||||
|
|
||||||
if abspath.endswith(".md") and (
|
if (
|
||||||
"v" in self.uparam or "edit" in self.uparam or "edit2" in self.uparam
|
abspath.endswith(".md")
|
||||||
|
and "nohtml" not in vn.flags
|
||||||
|
and (
|
||||||
|
"v" in self.uparam
|
||||||
|
or "edit" in self.uparam
|
||||||
|
or "edit2" in self.uparam
|
||||||
|
)
|
||||||
):
|
):
|
||||||
return self.tx_md(abspath)
|
return self.tx_md(abspath)
|
||||||
|
|
||||||
@@ -3535,6 +3580,8 @@ class HttpCli(object):
|
|||||||
perms.append("get")
|
perms.append("get")
|
||||||
if self.can_upget:
|
if self.can_upget:
|
||||||
perms.append("upget")
|
perms.append("upget")
|
||||||
|
if self.can_admin:
|
||||||
|
perms.append("admin")
|
||||||
|
|
||||||
url_suf = self.urlq({}, ["k"])
|
url_suf = self.urlq({}, ["k"])
|
||||||
is_ls = "ls" in self.uparam
|
is_ls = "ls" in self.uparam
|
||||||
@@ -3786,26 +3833,38 @@ class HttpCli(object):
|
|||||||
if vn != dbv:
|
if vn != dbv:
|
||||||
_, rd = vn.get_dbv(rd)
|
_, rd = vn.get_dbv(rd)
|
||||||
|
|
||||||
|
erd_efn = (rd, fn)
|
||||||
q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'"
|
q = "select mt.k, mt.v from up inner join mt on mt.w = substr(up.w,1,16) where up.rd = ? and up.fn = ? and +mt.k != 'x'"
|
||||||
try:
|
try:
|
||||||
r = icur.execute(q, (rd, fn))
|
r = icur.execute(q, erd_efn)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if "database is locked" in str(ex):
|
if "database is locked" in str(ex):
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
args = s3enc(idx.mem_cur, rd, fn)
|
erd_efn = s3enc(idx.mem_cur, rd, fn)
|
||||||
r = icur.execute(q, args)
|
r = icur.execute(q, erd_efn)
|
||||||
except:
|
except:
|
||||||
t = "tag read error, {}/{}\n{}"
|
t = "tag read error, {}/{}\n{}"
|
||||||
self.log(t.format(rd, fn, min_ex()))
|
self.log(t.format(rd, fn, min_ex()))
|
||||||
break
|
break
|
||||||
|
|
||||||
fe["tags"] = {k: v for k, v in r}
|
fe["tags"] = {k: v for k, v in r}
|
||||||
|
|
||||||
|
if self.can_admin:
|
||||||
|
q = "select ip, at from up where rd=? and fn=?"
|
||||||
|
try:
|
||||||
|
zs1, zs2 = icur.execute(q, erd_efn).fetchone()
|
||||||
|
fe["tags"]["up_ip"] = zs1
|
||||||
|
fe["tags"][".up_at"] = zs2
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
_ = [tagset.add(k) for k in fe["tags"]]
|
_ = [tagset.add(k) for k in fe["tags"]]
|
||||||
|
|
||||||
if icur:
|
if icur:
|
||||||
taglist = [k for k in vn.flags.get("mte", "").split(",") if k in tagset]
|
mte = vn.flags.get("mte") or "up_ip,.up_at"
|
||||||
|
taglist = [k for k in mte.split(",") if k in tagset]
|
||||||
for fe in dirs:
|
for fe in dirs:
|
||||||
fe["tags"] = {}
|
fe["tags"] = {}
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import time
|
|||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from .__init__ import ANYWIN, TYPE_CHECKING
|
from .__init__ import ANYWIN, TYPE_CHECKING
|
||||||
|
from .authsrv import VFS
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
||||||
from .util import (
|
from .util import (
|
||||||
@@ -110,8 +111,6 @@ class ThumbSrv(object):
|
|||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log_func = hub.log
|
self.log_func = hub.log
|
||||||
|
|
||||||
res = hub.args.th_size.split("x")
|
|
||||||
self.res = tuple([int(x) for x in res])
|
|
||||||
self.poke_cd = Cooldown(self.args.th_poke)
|
self.poke_cd = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
@@ -119,7 +118,7 @@ class ThumbSrv(object):
|
|||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.nthr = max(1, self.args.th_mt)
|
self.nthr = max(1, self.args.th_mt)
|
||||||
|
|
||||||
self.q: Queue[Optional[tuple[str, str]]] = Queue(self.nthr * 4)
|
self.q: Queue[Optional[tuple[str, str, VFS]]] = Queue(self.nthr * 4)
|
||||||
for n in range(self.nthr):
|
for n in range(self.nthr):
|
||||||
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
||||||
|
|
||||||
@@ -184,6 +183,10 @@ class ThumbSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
return not self.nthr
|
return not self.nthr
|
||||||
|
|
||||||
|
def getres(self, vn: VFS) -> tuple[int, int]:
|
||||||
|
w, h = vn.flags["thsize"].split("x")
|
||||||
|
return int(w), int(h)
|
||||||
|
|
||||||
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
|
||||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||||
if not histpath:
|
if not histpath:
|
||||||
@@ -211,7 +214,13 @@ class ThumbSrv(object):
|
|||||||
do_conv = True
|
do_conv = True
|
||||||
|
|
||||||
if do_conv:
|
if do_conv:
|
||||||
self.q.put((abspath, tpath))
|
allvols = list(self.asrv.vfs.all_vols.values())
|
||||||
|
vn = next((x for x in allvols if x.realpath == ptop), None)
|
||||||
|
if not vn:
|
||||||
|
self.log("ptop [{}] not in {}".format(ptop, allvols), 3)
|
||||||
|
vn = self.asrv.vfs.all_aps[0][1]
|
||||||
|
|
||||||
|
self.q.put((abspath, tpath, vn))
|
||||||
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
|
self.log("conv {} \033[0m{}".format(tpath, abspath), c=6)
|
||||||
|
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
@@ -248,7 +257,7 @@ class ThumbSrv(object):
|
|||||||
if not task:
|
if not task:
|
||||||
break
|
break
|
||||||
|
|
||||||
abspath, tpath = task
|
abspath, tpath, vn = task
|
||||||
ext = abspath.split(".")[-1].lower()
|
ext = abspath.split(".")[-1].lower()
|
||||||
png_ok = False
|
png_ok = False
|
||||||
funs = []
|
funs = []
|
||||||
@@ -281,7 +290,7 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
for fun in funs:
|
for fun in funs:
|
||||||
try:
|
try:
|
||||||
fun(abspath, ttpath)
|
fun(abspath, ttpath, vn)
|
||||||
break
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "{} could not create thumbnail of {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
@@ -315,9 +324,10 @@ class ThumbSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.nthr -= 1
|
self.nthr -= 1
|
||||||
|
|
||||||
def fancy_pillow(self, im: "Image.Image") -> "Image.Image":
|
def fancy_pillow(self, im: "Image.Image", vn: VFS) -> "Image.Image":
|
||||||
# exif_transpose is expensive (loads full image + unconditional copy)
|
# exif_transpose is expensive (loads full image + unconditional copy)
|
||||||
r = max(*self.res) * 2
|
res = self.getres(vn)
|
||||||
|
r = max(*res) * 2
|
||||||
im.thumbnail((r, r), resample=Image.LANCZOS)
|
im.thumbnail((r, r), resample=Image.LANCZOS)
|
||||||
try:
|
try:
|
||||||
k = next(k for k, v in ExifTags.TAGS.items() if v == "Orientation")
|
k = next(k for k, v in ExifTags.TAGS.items() if v == "Orientation")
|
||||||
@@ -331,23 +341,23 @@ class ThumbSrv(object):
|
|||||||
if rot in rots:
|
if rot in rots:
|
||||||
im = im.transpose(rots[rot])
|
im = im.transpose(rots[rot])
|
||||||
|
|
||||||
if self.args.th_no_crop:
|
if "nocrop" in vn.flags:
|
||||||
im.thumbnail(self.res, resample=Image.LANCZOS)
|
im.thumbnail(res, resample=Image.LANCZOS)
|
||||||
else:
|
else:
|
||||||
iw, ih = im.size
|
iw, ih = im.size
|
||||||
dw, dh = self.res
|
dw, dh = res
|
||||||
res = (min(iw, dw), min(ih, dh))
|
res = (min(iw, dw), min(ih, dh))
|
||||||
im = ImageOps.fit(im, res, method=Image.LANCZOS)
|
im = ImageOps.fit(im, res, method=Image.LANCZOS)
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def conv_pil(self, abspath: str, tpath: str) -> None:
|
def conv_pil(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
with Image.open(fsenc(abspath)) as im:
|
with Image.open(fsenc(abspath)) as im:
|
||||||
try:
|
try:
|
||||||
im = self.fancy_pillow(im)
|
im = self.fancy_pillow(im, vn)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log("fancy_pillow {}".format(ex), "90")
|
self.log("fancy_pillow {}".format(ex), "90")
|
||||||
im.thumbnail(self.res)
|
im.thumbnail(self.getres(vn))
|
||||||
|
|
||||||
fmts = ["RGB", "L"]
|
fmts = ["RGB", "L"]
|
||||||
args = {"quality": 40}
|
args = {"quality": 40}
|
||||||
@@ -370,12 +380,12 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
im.save(tpath, **args)
|
im.save(tpath, **args)
|
||||||
|
|
||||||
def conv_vips(self, abspath: str, tpath: str) -> None:
|
def conv_vips(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
crops = ["centre", "none"]
|
crops = ["centre", "none"]
|
||||||
if self.args.th_no_crop:
|
if "nocrop" in vn.flags:
|
||||||
crops = ["none"]
|
crops = ["none"]
|
||||||
|
|
||||||
w, h = self.res
|
w, h = self.getres(vn)
|
||||||
kw = {"height": h, "size": "down", "intent": "relative"}
|
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||||
|
|
||||||
for c in crops:
|
for c in crops:
|
||||||
@@ -389,8 +399,8 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
img.write_to_file(tpath, Q=40)
|
img.write_to_file(tpath, Q=40)
|
||||||
|
|
||||||
def conv_ffmpeg(self, abspath: str, tpath: str) -> None:
|
def conv_ffmpeg(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if not ret:
|
if not ret:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -402,12 +412,13 @@ class ThumbSrv(object):
|
|||||||
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
||||||
|
|
||||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||||
if self.args.th_no_crop:
|
if "nocrop" in vn.flags:
|
||||||
scale += "decrease,setsar=1:1"
|
scale += "decrease,setsar=1:1"
|
||||||
else:
|
else:
|
||||||
scale += "increase,crop={0}:{1},setsar=1:1"
|
scale += "increase,crop={0}:{1},setsar=1:1"
|
||||||
|
|
||||||
bscale = scale.format(*list(self.res)).encode("utf-8")
|
res = self.getres(vn)
|
||||||
|
bscale = scale.format(*list(res)).encode("utf-8")
|
||||||
# fmt: off
|
# fmt: off
|
||||||
cmd = [
|
cmd = [
|
||||||
b"ffmpeg",
|
b"ffmpeg",
|
||||||
@@ -439,11 +450,11 @@ class ThumbSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
def _run_ff(self, cmd: list[bytes]) -> None:
|
def _run_ff(self, cmd: list[bytes], vn: VFS) -> None:
|
||||||
# self.log((b" ".join(cmd)).decode("utf-8"))
|
# self.log((b" ".join(cmd)).decode("utf-8"))
|
||||||
ret, _, serr = runcmd(cmd, timeout=self.args.th_convt)
|
ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"])
|
||||||
if not ret:
|
if not ret:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -486,8 +497,8 @@ class ThumbSrv(object):
|
|||||||
self.log(t + txt, c=c)
|
self.log(t + txt, c=c)
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def conv_waves(self, abspath: str, tpath: str) -> None:
|
def conv_waves(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
@@ -512,10 +523,10 @@ class ThumbSrv(object):
|
|||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
def conv_spec(self, abspath: str, tpath: str) -> None:
|
def conv_spec(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
@@ -555,13 +566,13 @@ class ThumbSrv(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
cmd += [fsenc(tpath)]
|
cmd += [fsenc(tpath)]
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
def conv_opus(self, abspath: str, tpath: str) -> None:
|
def conv_opus(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||||
if self.args.no_acode:
|
if self.args.no_acode:
|
||||||
raise Exception("disabled in server config")
|
raise Exception("disabled in server config")
|
||||||
|
|
||||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||||
if "ac" not in ret:
|
if "ac" not in ret:
|
||||||
raise Exception("not audio")
|
raise Exception("not audio")
|
||||||
|
|
||||||
@@ -597,7 +608,7 @@ class ThumbSrv(object):
|
|||||||
fsenc(tmp_opus)
|
fsenc(tmp_opus)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
# iOS fails to play some "insufficiently complex" files
|
# iOS fails to play some "insufficiently complex" files
|
||||||
# (average file shorter than 8 seconds), so of course we
|
# (average file shorter than 8 seconds), so of course we
|
||||||
@@ -621,7 +632,7 @@ class ThumbSrv(object):
|
|||||||
fsenc(tpath)
|
fsenc(tpath)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
elif want_caf:
|
elif want_caf:
|
||||||
# simple remux should be safe
|
# simple remux should be safe
|
||||||
@@ -639,7 +650,7 @@ class ThumbSrv(object):
|
|||||||
fsenc(tpath)
|
fsenc(tpath)
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self._run_ff(cmd)
|
self._run_ff(cmd, vn)
|
||||||
|
|
||||||
if tmp_opus != tpath:
|
if tmp_opus != tpath:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from queue import Queue
|
|||||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS
|
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS
|
||||||
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
|
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
from .cfg import vf_bmap, vf_vmap
|
||||||
from .fsutil import Fstab
|
from .fsutil import Fstab
|
||||||
from .mtag import MParser, MTag
|
from .mtag import MParser, MTag
|
||||||
from .util import (
|
from .util import (
|
||||||
@@ -199,6 +200,7 @@ class Up2k(object):
|
|||||||
if self.stop:
|
if self.stop:
|
||||||
# up-mt consistency not guaranteed if init is interrupted;
|
# up-mt consistency not guaranteed if init is interrupted;
|
||||||
# drop caches for a full scan on next boot
|
# drop caches for a full scan on next boot
|
||||||
|
with self.mutex:
|
||||||
self._drop_caches()
|
self._drop_caches()
|
||||||
|
|
||||||
if self.pp:
|
if self.pp:
|
||||||
@@ -593,6 +595,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
if self.args.re_dhash or [zv for zv in vols if "e2tsr" in zv.flags]:
|
if self.args.re_dhash or [zv for zv in vols if "e2tsr" in zv.flags]:
|
||||||
self.args.re_dhash = False
|
self.args.re_dhash = False
|
||||||
|
with self.mutex:
|
||||||
self._drop_caches()
|
self._drop_caches()
|
||||||
|
|
||||||
for vol in vols:
|
for vol in vols:
|
||||||
@@ -757,8 +760,9 @@ class Up2k(object):
|
|||||||
ff = "\033[0;35m{}{:.0}"
|
ff = "\033[0;35m{}{:.0}"
|
||||||
fv = "\033[0;36m{}:\033[90m{}"
|
fv = "\033[0;36m{}:\033[90m{}"
|
||||||
fx = set(("html_head",))
|
fx = set(("html_head",))
|
||||||
fdl = ("dbd", "lg_sbf", "md_sbf", "mte", "mth", "mtp", "nrand", "rand")
|
fd = vf_bmap()
|
||||||
fd = {x: x for x in fdl}
|
fd.update(vf_vmap())
|
||||||
|
fd = {v: k for k, v in fd.items()}
|
||||||
fl = {
|
fl = {
|
||||||
k: v
|
k: v
|
||||||
for k, v in flags.items()
|
for k, v in flags.items()
|
||||||
@@ -769,6 +773,9 @@ class Up2k(object):
|
|||||||
for k, v in fl.items()
|
for k, v in fl.items()
|
||||||
if k not in fx
|
if k not in fx
|
||||||
]
|
]
|
||||||
|
if not a:
|
||||||
|
a = ["\033[90mall-default"]
|
||||||
|
|
||||||
if a:
|
if a:
|
||||||
vpath = "?"
|
vpath = "?"
|
||||||
for k, v in self.asrv.vfs.all_vols.items():
|
for k, v in self.asrv.vfs.all_vols.items():
|
||||||
@@ -876,6 +883,7 @@ class Up2k(object):
|
|||||||
rei = vol.flags.get("noidx")
|
rei = vol.flags.get("noidx")
|
||||||
reh = vol.flags.get("nohash")
|
reh = vol.flags.get("nohash")
|
||||||
n4g = bool(vol.flags.get("noforget"))
|
n4g = bool(vol.flags.get("noforget"))
|
||||||
|
ffat = "fat32" in vol.flags
|
||||||
cst = bos.stat(top)
|
cst = bos.stat(top)
|
||||||
dev = cst.st_dev if vol.flags.get("xdev") else 0
|
dev = cst.st_dev if vol.flags.get("xdev") else 0
|
||||||
|
|
||||||
@@ -912,6 +920,7 @@ class Up2k(object):
|
|||||||
rei,
|
rei,
|
||||||
reh,
|
reh,
|
||||||
n4g,
|
n4g,
|
||||||
|
ffat,
|
||||||
[],
|
[],
|
||||||
cst,
|
cst,
|
||||||
dev,
|
dev,
|
||||||
@@ -967,6 +976,7 @@ class Up2k(object):
|
|||||||
rei: Optional[Pattern[str]],
|
rei: Optional[Pattern[str]],
|
||||||
reh: Optional[Pattern[str]],
|
reh: Optional[Pattern[str]],
|
||||||
n4g: bool,
|
n4g: bool,
|
||||||
|
ffat: bool,
|
||||||
seen: list[str],
|
seen: list[str],
|
||||||
cst: os.stat_result,
|
cst: os.stat_result,
|
||||||
dev: int,
|
dev: int,
|
||||||
@@ -1011,7 +1021,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
lmod = int(inf.st_mtime)
|
lmod = int(inf.st_mtime)
|
||||||
sz = inf.st_size
|
sz = inf.st_size
|
||||||
if fat32 and inf.st_mtime % 2:
|
if fat32 and not ffat and inf.st_mtime % 2:
|
||||||
fat32 = False
|
fat32 = False
|
||||||
|
|
||||||
if stat.S_ISDIR(inf.st_mode):
|
if stat.S_ISDIR(inf.st_mode):
|
||||||
@@ -1028,7 +1038,19 @@ class Up2k(object):
|
|||||||
# self.log(" dir: {}".format(abspath))
|
# self.log(" dir: {}".format(abspath))
|
||||||
try:
|
try:
|
||||||
ret += self._build_dir(
|
ret += self._build_dir(
|
||||||
db, top, excl, abspath, rap, rei, reh, n4g, seen, inf, dev, xvol
|
db,
|
||||||
|
top,
|
||||||
|
excl,
|
||||||
|
abspath,
|
||||||
|
rap,
|
||||||
|
rei,
|
||||||
|
reh,
|
||||||
|
n4g,
|
||||||
|
fat32,
|
||||||
|
seen,
|
||||||
|
inf,
|
||||||
|
dev,
|
||||||
|
xvol,
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
t = "failed to index subdir [{}]:\n{}"
|
t = "failed to index subdir [{}]:\n{}"
|
||||||
@@ -2993,6 +3015,7 @@ class Up2k(object):
|
|||||||
permsets = [[False, True]]
|
permsets = [[False, True]]
|
||||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
||||||
vn, rem = vn.get_dbv(rem)
|
vn, rem = vn.get_dbv(rem)
|
||||||
|
with self.mutex:
|
||||||
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
||||||
|
|
||||||
t = "you cannot delete this: "
|
t = "you cannot delete this: "
|
||||||
|
|||||||
@@ -2427,7 +2427,7 @@ def killtree(root: int) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def runcmd(
|
def runcmd(
|
||||||
argv: Union[list[bytes], list[str]], timeout: Optional[int] = None, **ka: Any
|
argv: Union[list[bytes], list[str]], timeout: Optional[float] = None, **ka: Any
|
||||||
) -> tuple[int, str, str]:
|
) -> tuple[int, str, str]:
|
||||||
kill = ka.pop("kill", "t") # [t]ree [m]ain [n]one
|
kill = ka.pop("kill", "t") # [t]ree [m]ain [n]one
|
||||||
capture = ka.pop("capture", 3) # 0=none 1=stdout 2=stderr 3=both
|
capture = ka.pop("capture", 3) # 0=none 1=stdout 2=stderr 3=both
|
||||||
@@ -2480,7 +2480,7 @@ def chkcmd(argv: Union[list[bytes], list[str]], **ka: Any) -> tuple[str, str]:
|
|||||||
return sout, serr
|
return sout, serr
|
||||||
|
|
||||||
|
|
||||||
def mchkcmd(argv: Union[list[bytes], list[str]], timeout: int = 10) -> None:
|
def mchkcmd(argv: Union[list[bytes], list[str]], timeout: float = 10) -> None:
|
||||||
if PY2:
|
if PY2:
|
||||||
with open(os.devnull, "wb") as f:
|
with open(os.devnull, "wb") as f:
|
||||||
rv = sp.call(argv, stdout=f, stderr=f)
|
rv = sp.call(argv, stdout=f, stderr=f)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ window.baguetteBox = (function () {
|
|||||||
var gallery = [];
|
var gallery = [];
|
||||||
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
||||||
var imageElementClickHandler = function (e) {
|
var imageElementClickHandler = function (e) {
|
||||||
if (ctrl(e))
|
if (ctrl(e) || e && e.shiftKey)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
e.preventDefault ? e.preventDefault() : e.returnValue = false;
|
e.preventDefault ? e.preventDefault() : e.returnValue = false;
|
||||||
|
|||||||
@@ -1230,7 +1230,8 @@ html.y #widget.open {
|
|||||||
#wfm a.hide {
|
#wfm a.hide {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#files tbody tr.fcut td {
|
#files tbody tr.fcut td,
|
||||||
|
#ggrid>a.fcut {
|
||||||
animation: fcut .5s ease-out;
|
animation: fcut .5s ease-out;
|
||||||
}
|
}
|
||||||
@keyframes fcut {
|
@keyframes fcut {
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ var Ls = {
|
|||||||
"cl_hpick": "click one column header to hide in the table below",
|
"cl_hpick": "click one column header to hide in the table below",
|
||||||
"cl_hcancel": "column hiding aborted",
|
"cl_hcancel": "column hiding aborted",
|
||||||
|
|
||||||
"ct_thumb": "in icon view, toggle icons or thumbnails$NHotkey: T",
|
"ct_thumb": "in grid-view, toggle icons or thumbnails$NHotkey: T",
|
||||||
|
"ct_csel": "use CTRL and SHIFT for file selection in grid-view",
|
||||||
"ct_dots": "show hidden files (if server permits)",
|
"ct_dots": "show hidden files (if server permits)",
|
||||||
"ct_dir1st": "sort folders before files",
|
"ct_dir1st": "sort folders before files",
|
||||||
"ct_readme": "show README.md in folder listings",
|
"ct_readme": "show README.md in folder listings",
|
||||||
@@ -651,6 +652,7 @@ var Ls = {
|
|||||||
"cl_hcancel": "kolonne-skjuling avbrutt",
|
"cl_hcancel": "kolonne-skjuling avbrutt",
|
||||||
|
|
||||||
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
|
"ct_thumb": "vis miniatyrbilder istedenfor ikoner$NSnarvei: T",
|
||||||
|
"ct_csel": "bruk tastene CTRL og SHIFT for markering av filer i ikonvisning",
|
||||||
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
|
"ct_dots": "vis skjulte filer (gitt at serveren tillater det)",
|
||||||
"ct_dir1st": "sorter slik at mapper kommer foran filer",
|
"ct_dir1st": "sorter slik at mapper kommer foran filer",
|
||||||
"ct_readme": "vis README.md nedenfor filene",
|
"ct_readme": "vis README.md nedenfor filene",
|
||||||
@@ -1096,6 +1098,7 @@ ebi('op_cfg').innerHTML = (
|
|||||||
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
||||||
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
|
' <a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">田 the grid</a>\n' +
|
||||||
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
|
' <a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '">🖼️ thumbs</a>\n' +
|
||||||
|
' <a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '">sel</a>\n' +
|
||||||
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
|
' <a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '">dotfiles</a>\n' +
|
||||||
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
|
' <a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '">📁 first</a>\n' +
|
||||||
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
|
' <a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '">📜 readme</a>\n' +
|
||||||
@@ -3689,18 +3692,27 @@ var fileman = (function () {
|
|||||||
if (!sel.length)
|
if (!sel.length)
|
||||||
toast.err(3, L.fc_emore);
|
toast.err(3, L.fc_emore);
|
||||||
|
|
||||||
var els = [];
|
var els = [], griden = thegrid.en;
|
||||||
for (var a = 0; a < sel.length; a++) {
|
for (var a = 0; a < sel.length; a++) {
|
||||||
vps.push(sel[a].vp);
|
vps.push(sel[a].vp);
|
||||||
if (sel.length < 100) {
|
if (sel.length < 100)
|
||||||
|
try {
|
||||||
|
if (griden)
|
||||||
|
els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
|
||||||
|
else
|
||||||
els.push(ebi(sel[a].id).closest('tr'));
|
els.push(ebi(sel[a].id).closest('tr'));
|
||||||
|
|
||||||
clmod(els[a], 'fcut');
|
clmod(els[a], 'fcut');
|
||||||
}
|
}
|
||||||
|
catch (ex) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
try {
|
||||||
for (var a = 0; a < els.length; a++)
|
for (var a = 0; a < els.length; a++)
|
||||||
clmod(els[a], 'fcut', 1);
|
clmod(els[a], 'fcut', 1);
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -4288,7 +4300,7 @@ var thegrid = (function () {
|
|||||||
setsz();
|
setsz();
|
||||||
|
|
||||||
function gclick1(e) {
|
function gclick1(e) {
|
||||||
if (ctrl(e))
|
if (ctrl(e) && !treectl.csel && !r.sel)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return gclick.bind(this)(e, false);
|
return gclick.bind(this)(e, false);
|
||||||
@@ -4312,8 +4324,10 @@ var thegrid = (function () {
|
|||||||
td = oth.closest('td').nextSibling,
|
td = oth.closest('td').nextSibling,
|
||||||
tr = td.parentNode;
|
tr = td.parentNode;
|
||||||
|
|
||||||
if (r.sel && !dbl) {
|
if ((r.sel && !dbl && !ctrl(e)) || (treectl.csel && (e.shiftKey || ctrl(e)))) {
|
||||||
td.click();
|
td.onclick.bind(td)(e);
|
||||||
|
if (e.shiftKey)
|
||||||
|
return r.loadsel();
|
||||||
clmod(this, 'sel', clgot(tr, 'sel'));
|
clmod(this, 'sel', clgot(tr, 'sel'));
|
||||||
}
|
}
|
||||||
else if (widget.is_open && aplay)
|
else if (widget.is_open && aplay)
|
||||||
@@ -4706,6 +4720,7 @@ document.onkeydown = function (e) {
|
|||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
clmod(el, 'sel', 't');
|
clmod(el, 'sel', 't');
|
||||||
|
msel.origin_tr(el);
|
||||||
msel.selui();
|
msel.selui();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4714,6 +4729,7 @@ document.onkeydown = function (e) {
|
|||||||
}
|
}
|
||||||
if (k == 'Space') {
|
if (k == 'Space') {
|
||||||
clmod(ae, 'sel', 't');
|
clmod(ae, 'sel', 't');
|
||||||
|
msel.origin_tr(ae);
|
||||||
msel.selui();
|
msel.selui();
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
@@ -4722,6 +4738,7 @@ document.onkeydown = function (e) {
|
|||||||
all = msel.getall();
|
all = msel.getall();
|
||||||
|
|
||||||
msel.evsel(e, sel.length < all.length);
|
msel.evsel(e, sel.length < all.length);
|
||||||
|
msel.origin_id(null);
|
||||||
return ev(e);
|
return ev(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5198,6 +5215,7 @@ var treectl = (function () {
|
|||||||
bcfg_bind(r, 'ireadme', 'ireadme', true);
|
bcfg_bind(r, 'ireadme', 'ireadme', true);
|
||||||
bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
|
bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
|
||||||
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
|
bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
|
||||||
|
bcfg_bind(r, 'csel', 'csel', false);
|
||||||
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
|
bcfg_bind(r, 'dots', 'dotfiles', false, function (v) {
|
||||||
r.goto(get_evpath());
|
r.goto(get_evpath());
|
||||||
});
|
});
|
||||||
@@ -5780,14 +5798,18 @@ var treectl = (function () {
|
|||||||
|
|
||||||
for (var b = 0; b < res.taglist.length; b++) {
|
for (var b = 0; b < res.taglist.length; b++) {
|
||||||
var k = res.taglist[b],
|
var k = res.taglist[b],
|
||||||
v = (tn.tags || {})[k] || "";
|
v = (tn.tags || {})[k] || "",
|
||||||
|
sv = null;
|
||||||
|
|
||||||
if (k == ".dur") {
|
if (k == ".dur")
|
||||||
var sv = v ? s2ms(v) : "";
|
sv = v ? s2ms(v) : "";
|
||||||
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
else if (k == ".up_at")
|
||||||
|
sv = v ? unix2iso(v) : "";
|
||||||
|
else {
|
||||||
|
ln.push(v);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ln.push(v);
|
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||||
}
|
}
|
||||||
ln = ln.concat([tn.ext, unix2iso(tn.ts)]).join('</td><td>');
|
ln = ln.concat([tn.ext, unix2iso(tn.ts)]).join('</td><td>');
|
||||||
html.push(ln + '</td></tr>');
|
html.push(ln + '</td></tr>');
|
||||||
@@ -6066,7 +6088,7 @@ function apply_perms(res) {
|
|||||||
|
|
||||||
var axs = [],
|
var axs = [],
|
||||||
aclass = '>',
|
aclass = '>',
|
||||||
chk = ['read', 'write', 'move', 'delete', 'get'];
|
chk = ['read', 'write', 'move', 'delete', 'get', 'admin'];
|
||||||
|
|
||||||
for (var a = 0; a < chk.length; a++)
|
for (var a = 0; a < chk.length; a++)
|
||||||
if (has(perms, chk[a]))
|
if (has(perms, chk[a]))
|
||||||
@@ -6135,6 +6157,16 @@ function apply_perms(res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function tr2id(tr) {
|
||||||
|
try {
|
||||||
|
return tr.cells[1].querySelector('a[id]').getAttribute('id');
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function find_file_col(txt) {
|
function find_file_col(txt) {
|
||||||
var i = -1,
|
var i = -1,
|
||||||
min = false,
|
min = false,
|
||||||
@@ -6637,9 +6669,11 @@ var msel = (function () {
|
|||||||
var r = {};
|
var r = {};
|
||||||
r.sel = null;
|
r.sel = null;
|
||||||
r.all = null;
|
r.all = null;
|
||||||
|
r.so = null; // selection origin
|
||||||
|
r.pr = null; // previous range
|
||||||
|
|
||||||
r.load = function () {
|
r.load = function (reset) {
|
||||||
if (r.sel)
|
if (r.sel && !reset)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
r.sel = [];
|
r.sel = [];
|
||||||
@@ -6650,6 +6684,7 @@ var msel = (function () {
|
|||||||
if (ao.sel)
|
if (ao.sel)
|
||||||
r.sel.push(ao);
|
r.sel.push(ao);
|
||||||
}
|
}
|
||||||
|
if (!reset)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6676,6 +6711,7 @@ var msel = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.loadsel = function (sel) {
|
r.loadsel = function (sel) {
|
||||||
|
r.so = r.pr = null;
|
||||||
r.sel = [];
|
r.sel = [];
|
||||||
r.load();
|
r.load();
|
||||||
|
|
||||||
@@ -6707,15 +6743,60 @@ var msel = (function () {
|
|||||||
thegrid.loadsel();
|
thegrid.loadsel();
|
||||||
fileman.render();
|
fileman.render();
|
||||||
showfile.updtree();
|
showfile.updtree();
|
||||||
}
|
};
|
||||||
r.seltgl = function (e) {
|
r.seltgl = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
var tr = this.parentNode;
|
var tr = this.parentNode,
|
||||||
clmod(tr, 'sel', 't');
|
id = tr2id(tr);
|
||||||
r.selui();
|
|
||||||
|
if ((treectl.csel || !thegrid.en || thegrid.sel) && e.shiftKey && r.so && id && r.so != id) {
|
||||||
|
var o1 = -1, o2 = -1;
|
||||||
|
for (a = 0; a < r.all.length; a++) {
|
||||||
|
var ai = r.all[a].id;
|
||||||
|
if (ai == r.so)
|
||||||
|
o1 = a;
|
||||||
|
if (ai == id)
|
||||||
|
o2 = a;
|
||||||
}
|
}
|
||||||
|
var st = r.all[o1].sel;
|
||||||
|
if (o1 > o2)
|
||||||
|
o2 = [o1, o1 = o2][0];
|
||||||
|
|
||||||
|
if (r.pr) {
|
||||||
|
// invert previous range, in case it was narrowed
|
||||||
|
for (var a = r.pr[0]; a <= r.pr[1]; a++)
|
||||||
|
clmod(ebi(r.all[a].id).closest('tr'), 'sel', !st);
|
||||||
|
|
||||||
|
// and invert current selection if repeated
|
||||||
|
if (r.pr[0] === o1 && r.pr[1] === o2)
|
||||||
|
st = !st;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = o1; a <= o2; a++)
|
||||||
|
clmod(ebi(r.all[a].id).closest('tr'), 'sel', st);
|
||||||
|
|
||||||
|
r.pr = [o1, o2];
|
||||||
|
|
||||||
|
if (window.getSelection)
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clmod(tr, 'sel', 't');
|
||||||
|
r.origin_tr(tr);
|
||||||
|
}
|
||||||
|
r.selui();
|
||||||
|
};
|
||||||
|
r.origin_tr = function (tr) {
|
||||||
|
r.so = tr2id(tr);
|
||||||
|
r.pr = null;
|
||||||
|
};
|
||||||
|
r.origin_id = function (id) {
|
||||||
|
r.so = id;
|
||||||
|
r.pr = null;
|
||||||
|
};
|
||||||
r.evsel = function (e, fun) {
|
r.evsel = function (e, fun) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
r.so = r.pr = null;
|
||||||
var trs = QSA('#files tbody tr');
|
var trs = QSA('#files tbody tr');
|
||||||
for (var a = 0, aa = trs.length; a < aa; a++)
|
for (var a = 0, aa = trs.length; a < aa; a++)
|
||||||
clmod(trs[a], 'sel', fun);
|
clmod(trs[a], 'sel', fun);
|
||||||
@@ -7340,7 +7421,7 @@ ebi('path').onclick = function (e) {
|
|||||||
|
|
||||||
|
|
||||||
ebi('files').onclick = ebi('docul').onclick = function (e) {
|
ebi('files').onclick = ebi('docul').onclick = function (e) {
|
||||||
if (ctrl(e))
|
if (!treectl.csel && e && (ctrl(e) || e.shiftKey))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var tgt = e.target.closest('a[id]');
|
var tgt = e.target.closest('a[id]');
|
||||||
@@ -7437,6 +7518,8 @@ function reload_browser() {
|
|||||||
reload_mp();
|
reload_mp();
|
||||||
try { showsort(ftab); } catch (ex) { }
|
try { showsort(ftab); } catch (ex) { }
|
||||||
makeSortable(ftab, function () {
|
makeSortable(ftab, function () {
|
||||||
|
msel.origin_id(null);
|
||||||
|
msel.load(true);
|
||||||
thegrid.setdirty();
|
thegrid.setdirty();
|
||||||
mp.read_order();
|
mp.read_order();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,86 @@
|
|||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2023-0714-1558 `v1.8.2` URGENT: fix path traversal vulnerability
|
||||||
|
|
||||||
|
* read-only demo server at https://a.ocv.me/pub/demo/
|
||||||
|
* [docker image](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) ╱ [similar software](https://github.com/9001/copyparty/blob/hovudstraum/docs/versus.md) ╱ [client testbed](https://cd.ocv.me/b/)
|
||||||
|
|
||||||
|
Starting with the bad and important news; this release fixes https://github.com/9001/copyparty/security/advisories/GHSA-pxfv-7rr3-2qjg / [CVE-2023-37474](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-37474) -- so please upgrade!
|
||||||
|
|
||||||
|
Every version until now had a [path traversal vulnerability](https://owasp.org/www-community/attacks/Path_Traversal) which allowed read-access to any file on the server's filesystem. To summarize,
|
||||||
|
* Every file that the copyparty process had the OS-level permissions to read, could be retrieved over HTTP without password authentication
|
||||||
|
* However, an attacker would need to know the full (or copyparty-module-relative) path to the file; it was luckily impossible to list directory contents to discover files on the server
|
||||||
|
* You may have been running copyparty with some mitigations against this:
|
||||||
|
* [prisonparty](https://github.com/9001/copyparty/tree/hovudstraum/bin#prisonpartysh) limited the scope of access to files which were intentionally given to copyparty for sharing; meaning all volumes, as well as the following read-only filesystem locations: `/bin`, `/lib`, `/lib32`, `/lib64`, `/sbin`, `/usr`, `/etc/alternatives`
|
||||||
|
* the [nix package](https://github.com/9001/copyparty#nix-package) has a similar mitigation implemented using systemd concepts
|
||||||
|
* [docker containers](https://github.com/9001/copyparty/tree/hovudstraum/scripts/docker) would only expose the files which were intentionally mounted into the container, so even better
|
||||||
|
* More conventional setups, such as just running the sfx (python or exe editions), would unfortunately expose all files readable by the current user
|
||||||
|
* The following configurations would have made the impact much worse:
|
||||||
|
* running copyparty as root
|
||||||
|
|
||||||
|
So, three years, and finally a CVE -- which has been there since day one... Not great huh. There is a list of all the copyparty alternatives that I know of in the `similar software` link above.
|
||||||
|
|
||||||
|
Thanks for flying copyparty! And especially if you decide to continue doing so :-)
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* #43 volflags to specify thumbnailer behavior per-volume;
|
||||||
|
* `--th-no-crop` / volflag `nocrop` to specify whether autocrop should be disabled
|
||||||
|
* `--th-size` / volflag `thsize` to set a custom thumbnail resolution
|
||||||
|
* `--th-convt` / volflag `convt` to specify conversion timeout
|
||||||
|
* #45 resulted in a handful of opportunities to tighten security in intentionally-dangerous setups (public folders with anonymous uploads enabled):
|
||||||
|
* a new permission, `a` (in addition to the existing `rwmdgG`), to show the uploader-IP and upload-time for each file in the file listing
|
||||||
|
* accidentally incompatible with the `d2t` volflag (will be fixed in the next ver)
|
||||||
|
* volflag `nohtml` is a good defense against (un)intentional XSS; it returns HTML-files and markdown-files as plaintext instead of rendering them, meaning any malicious `<script>` won't run -- bad idea for regular use since it breaks fundamental functionality, but good when you really need it
|
||||||
|
* the README-previews below the file-listing still renders as usual, as this is fine thanks to the sandbox
|
||||||
|
* a new eventhook `--xban` to run a plugin when copyparty decides to ban someone (for password bruteforcing or excessive 404's), for example to blackhole the IP using fail2ban or similar
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* **fixes a path traversal vulnerability,** https://github.com/9001/copyparty/security/advisories/GHSA-pxfv-7rr3-2qjg / [CVE-2023-37474](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-37474)
|
||||||
|
* HUGE thanks to @TheHackyDog for reporting this !!
|
||||||
|
* if you use a reverse proxy, you can check if you have been exploited like so:
|
||||||
|
* nginx: grep your logs for URLs containing both `.cpr/` and `%2[^0]`, for example using the following command:
|
||||||
|
```bash
|
||||||
|
(gzip -dc access.log.*.gz; cat access.log) | sed -r 's/" [0-9]+ .*//' | grep -E 'cpr/.*%2[^0]' | grep -vF data:image/svg
|
||||||
|
```
|
||||||
|
* 77f1e5144455eb946db7368792ea11c934f0f6da fixes an extremely unlikely race-condition (see the commit for details)
|
||||||
|
* 8f59afb1593a75b8ce8c91ceee304097a07aea6e fixes another race-condition which is a bit worse:
|
||||||
|
* the unpost feature could collide with other database activity, with the worst-case outcome being aborted batch operations, for example a directory move or a batch-rename which stops halfways
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
# 💾 what to download?
|
||||||
|
| download link | is it good? | description |
|
||||||
|
| -- | -- | -- |
|
||||||
|
| **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** | ✅ the best 👍 | runs anywhere! only needs python |
|
||||||
|
| [a docker image](https://github.com/9001/copyparty/blob/hovudstraum/scripts/docker/README.md) | it's ok | good if you prefer docker 🐋 |
|
||||||
|
| [copyparty.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty.exe) | ⚠️ [acceptable](https://github.com/9001/copyparty#copypartyexe) | for [win8](https://user-images.githubusercontent.com/241032/221445946-1e328e56-8c5b-44a9-8b9f-dee84d942535.png) or later; built-in thumbnailer |
|
||||||
|
| [u2c.exe](https://github.com/9001/copyparty/releases/download/v1.7.1/u2c.exe) | ⚠️ acceptable | [CLI uploader](https://github.com/9001/copyparty/blob/hovudstraum/bin/u2c.py) as a win7+ exe ([video](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm)) |
|
||||||
|
| [copyparty32.exe](https://github.com/9001/copyparty/releases/latest/download/copyparty32.exe) | ⛔️ [dangerous](https://github.com/9001/copyparty#copypartyexe) | for [win7](https://user-images.githubusercontent.com/241032/221445944-ae85d1f4-d351-4837-b130-82cab57d6cca.png) -- never expose to the internet! |
|
||||||
|
| [cpp-winpe64.exe](https://github.com/9001/copyparty/releases/download/v1.8.2/copyparty-winpe64.exe) | ⛔️ dangerous | runs on [64bit WinPE](https://user-images.githubusercontent.com/241032/205454984-e6b550df-3c49-486d-9267-1614078dd0dd.png), otherwise useless |
|
||||||
|
|
||||||
|
* except for [u2c.exe](https://github.com/9001/copyparty/releases/download/v1.7.1/u2c.exe), all of the options above are equivalent
|
||||||
|
* the zip and tar.gz files below are just source code
|
||||||
|
* python packages are available at [PyPI](https://pypi.org/project/copyparty/#files)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
# 2023-0707-2220 `v1.8.1` in case of 404
|
||||||
|
|
||||||
|
## new features
|
||||||
|
* [handlers](https://github.com/9001/copyparty/tree/hovudstraum/bin/handlers); change the behavior of 404 / 403 with plugins
|
||||||
|
* makes it possible to use copyparty as a [caching proxy](https://github.com/9001/copyparty/blob/hovudstraum/bin/handlers/caching-proxy.py)
|
||||||
|
* #42 add mpv + streamlink support to [very-bad-idea](https://github.com/9001/copyparty/tree/hovudstraum/bin/mtag#dangerous-plugins)
|
||||||
|
* add support for Pillow 10
|
||||||
|
* also improved text rendering in icons
|
||||||
|
* mention the [fedora package](https://github.com/9001/copyparty#fedora-package) in the readme
|
||||||
|
|
||||||
|
## bugfixes
|
||||||
|
* theme 6 (hacker) didn't show the state of some toggle-switches
|
||||||
|
* windows: keep quickedit enabled when hashing passwords interactively
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
# 2023-0626-0005 `v1.8.0` argon
|
# 2023-0626-0005 `v1.8.0` argon
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,3 @@ or using commandline arguments,
|
|||||||
# build the images yourself
|
# build the images yourself
|
||||||
|
|
||||||
basically `./make.sh hclean pull img push` but see [devnotes.md](./devnotes.md)
|
basically `./make.sh hclean pull img push` but see [devnotes.md](./devnotes.md)
|
||||||
|
|
||||||
|
|
||||||
# notes
|
|
||||||
|
|
||||||
* currently unable to play [tracker music](https://en.wikipedia.org/wiki/Module_file) (mod/s3m/xm/it/...) -- will be fixed in june 2023 (Alpine 3.18)
|
|
||||||
|
|||||||
73
scripts/test/ptrav.py
Executable file
73
scripts/test/ptrav.py
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import itertools
|
||||||
|
import requests
|
||||||
|
|
||||||
|
atlas = ["%", "25", "2e", "2f", ".", "/"]
|
||||||
|
|
||||||
|
|
||||||
|
def genlen(ubase, port, ntot, nth, wlen):
|
||||||
|
n = 0
|
||||||
|
t0 = time.time()
|
||||||
|
print("genlen %s nth %s port %s" % (wlen, nth, port))
|
||||||
|
rsession = requests.Session()
|
||||||
|
ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/")
|
||||||
|
for path in itertools.product(atlas, repeat=wlen):
|
||||||
|
if "%" not in path:
|
||||||
|
continue
|
||||||
|
path = "".join(path)
|
||||||
|
if ptn.search(path):
|
||||||
|
continue
|
||||||
|
n += 1
|
||||||
|
if n % ntot != nth:
|
||||||
|
continue
|
||||||
|
url = ubase % (port, path)
|
||||||
|
if n % 500 == nth:
|
||||||
|
spd = n / (time.time() - t0)
|
||||||
|
print(wlen, n, int(spd), url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = rsession.get(url)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
print("\n[=== RETRY ===]", url)
|
||||||
|
try:
|
||||||
|
r = rsession.get(url)
|
||||||
|
except:
|
||||||
|
r = rsession.get(url)
|
||||||
|
|
||||||
|
if "fgsfds" in r.text:
|
||||||
|
with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f:
|
||||||
|
f.write(url)
|
||||||
|
raise Exception("HIT! {}".format(url))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ubase = sys.argv[1]
|
||||||
|
port = int(sys.argv[2])
|
||||||
|
ntot = int(sys.argv[3])
|
||||||
|
nth = int(sys.argv[4])
|
||||||
|
for wlen in range(20):
|
||||||
|
genlen(ubase, port, ntot, nth, wlen)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
"""
|
||||||
|
python3 -m copyparty -v srv::r -p 3931 -q -j4
|
||||||
|
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 0
|
||||||
|
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 1
|
||||||
|
nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 2
|
||||||
|
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 0
|
||||||
|
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 1
|
||||||
|
nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 2
|
||||||
|
(13x slower than /tests/ptrav.py)
|
||||||
|
"""
|
||||||
@@ -62,7 +62,16 @@ class Cpp(object):
|
|||||||
|
|
||||||
def tc1(vflags):
|
def tc1(vflags):
|
||||||
ub = "http://127.0.0.1:4321/"
|
ub = "http://127.0.0.1:4321/"
|
||||||
|
try:
|
||||||
|
if not os.path.exists("/dev/shm"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
td = "/dev/shm/cppsmoketst"
|
||||||
|
ntd = 4
|
||||||
|
except:
|
||||||
td = os.path.join("srv", "smoketest")
|
td = os.path.join("srv", "smoketest")
|
||||||
|
ntd = 2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(td)
|
shutil.rmtree(td)
|
||||||
except:
|
except:
|
||||||
@@ -91,6 +100,7 @@ def tc1(vflags):
|
|||||||
"-p4321",
|
"-p4321",
|
||||||
"-e2dsa",
|
"-e2dsa",
|
||||||
"-e2tsr",
|
"-e2tsr",
|
||||||
|
"--dbd=yolo",
|
||||||
"--no-mutagen",
|
"--no-mutagen",
|
||||||
"--th-ff-jpg",
|
"--th-ff-jpg",
|
||||||
"--hist",
|
"--hist",
|
||||||
@@ -99,38 +109,38 @@ def tc1(vflags):
|
|||||||
pdirs = []
|
pdirs = []
|
||||||
hpaths = {}
|
hpaths = {}
|
||||||
|
|
||||||
for d1 in ["r", "w", "a"]:
|
for d1 in ["r", "w", "rw"]:
|
||||||
pdirs.append("{}/{}".format(td, d1))
|
pdirs.append("{}/{}".format(td, d1))
|
||||||
pdirs.append("{}/{}/j".format(td, d1))
|
pdirs.append("{}/{}/j".format(td, d1))
|
||||||
for d2 in ["r", "w", "a", "c"]:
|
for d2 in ["r", "w", "rw", "c"]:
|
||||||
d = os.path.join(td, d1, "j", d2)
|
d = os.path.join(td, d1, "j", d2)
|
||||||
pdirs.append(d)
|
pdirs.append(d)
|
||||||
os.makedirs(d)
|
os.makedirs(d)
|
||||||
|
|
||||||
pdirs = [x.replace("\\", "/") for x in pdirs]
|
pdirs = [x.replace("\\", "/") for x in pdirs]
|
||||||
udirs = [x.split("/", 2)[2] for x in pdirs]
|
udirs = [x.split("/", ntd)[ntd] for x in pdirs]
|
||||||
perms = [x.rstrip("cj/")[-1] for x in pdirs]
|
perms = [x.rstrip("cj/")[-1] for x in pdirs]
|
||||||
perms = ["rw" if x == "a" else x for x in perms]
|
|
||||||
for pd, ud, p in zip(pdirs, udirs, perms):
|
for pd, ud, p in zip(pdirs, udirs, perms):
|
||||||
if ud[-1] == "j" or ud[-1] == "c":
|
if ud[-1] == "j" or ud[-1] == "c":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hp = None
|
hp = None
|
||||||
if pd.endswith("st/a"):
|
if pd.endswith("st/rw"):
|
||||||
hp = hpaths[ud] = os.path.join(td, "db1")
|
hp = hpaths[ud] = os.path.join(td, "db1")
|
||||||
elif pd[:-1].endswith("a/j/"):
|
elif pd[:-1].endswith("rw/j/"):
|
||||||
hpaths[ud] = os.path.join(td, "dbm")
|
hpaths[ud] = os.path.join(td, "dbm")
|
||||||
hp = None
|
hp = None
|
||||||
else:
|
else:
|
||||||
hp = "-"
|
hp = "-"
|
||||||
hpaths[ud] = os.path.join(pd, ".hist")
|
hpaths[ud] = os.path.join(pd, ".hist")
|
||||||
|
|
||||||
arg = "{}:{}:{}".format(pd, ud, p)
|
arg = "{}:{}:a{}".format(pd, ud, p)
|
||||||
if hp:
|
if hp:
|
||||||
arg += ":c,hist=" + hp
|
arg += ":c,hist=" + hp
|
||||||
|
|
||||||
args += ["-v", arg + vflags]
|
args += ["-v", arg + vflags]
|
||||||
|
|
||||||
|
# print("\n".join(args))
|
||||||
# return
|
# return
|
||||||
cpp = Cpp(args)
|
cpp = Cpp(args)
|
||||||
CPP.append(cpp)
|
CPP.append(cpp)
|
||||||
@@ -163,7 +173,7 @@ def tc1(vflags):
|
|||||||
|
|
||||||
# stat filesystem
|
# stat filesystem
|
||||||
for d, p in zip(pdirs, perms):
|
for d, p in zip(pdirs, perms):
|
||||||
u = "{}/{}.h264".format(d, d.split("test/")[-1].replace("/", ""))
|
u = "{}/{}.h264".format(d, d[len(td) :].replace("/", ""))
|
||||||
ok = os.path.exists(u)
|
ok = os.path.exists(u)
|
||||||
if ok != (p in ["rw", "w"]):
|
if ok != (p in ["rw", "w"]):
|
||||||
raise Exception("stat {} with perm {} at {}".format(ok, p, u))
|
raise Exception("stat {} with perm {} at {}".format(ok, p, u))
|
||||||
|
|||||||
87
tests/ptrav.py
Normal file
87
tests/ptrav.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from . import util as tu
|
||||||
|
from .util import Cfg
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
|
||||||
|
atlas = ["%", "25", "2e", "2f", ".", "/"]
|
||||||
|
|
||||||
|
|
||||||
|
def nolog(*a, **ka):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def hdr(query):
|
||||||
|
h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n"
|
||||||
|
return h.format(query).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def curl(args, asrv, url, binary=False):
|
||||||
|
conn = tu.VHttpConn(args, asrv, nolog, hdr(url))
|
||||||
|
HttpCli(conn).run()
|
||||||
|
if binary:
|
||||||
|
h, b = conn.s._reply.split(b"\r\n\r\n", 1)
|
||||||
|
return [h.decode("utf-8"), b]
|
||||||
|
|
||||||
|
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||||
|
|
||||||
|
|
||||||
|
def genlen(ubase, ntot, nth, wlen):
|
||||||
|
args = Cfg(v=["s2::r"], a=["o:o", "x:x"])
|
||||||
|
asrv = AuthSrv(args, print)
|
||||||
|
# h, ret = curl(args, asrv, "hey")
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
t0 = time.time()
|
||||||
|
print("genlen %s nth %s" % (wlen, nth))
|
||||||
|
ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/")
|
||||||
|
for path in itertools.product(atlas, repeat=wlen):
|
||||||
|
if "%" not in path:
|
||||||
|
continue
|
||||||
|
path = "".join(path)
|
||||||
|
if ptn.search(path):
|
||||||
|
continue
|
||||||
|
n += 1
|
||||||
|
if n % ntot != nth:
|
||||||
|
continue
|
||||||
|
url = ubase + path + "fa"
|
||||||
|
if n % 500 == nth:
|
||||||
|
spd = n / (time.time() - t0)
|
||||||
|
print(wlen, n, int(spd), url)
|
||||||
|
|
||||||
|
hdr, r = curl(args, asrv, url)
|
||||||
|
if "fgsfds" in r:
|
||||||
|
with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f:
|
||||||
|
f.write(url)
|
||||||
|
raise Exception("HIT! {}".format(url))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ubase = sys.argv[1]
|
||||||
|
ntot = int(sys.argv[2])
|
||||||
|
nth = int(sys.argv[3])
|
||||||
|
for wlen in range(20):
|
||||||
|
genlen(ubase, ntot, nth, wlen)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
nice pypy3 -m tests.ptrav "" 2 0
|
||||||
|
nice pypy3 -m tests.ptrav "" 2 1
|
||||||
|
nice pypy3 -m tests.ptrav .cpr 2 0
|
||||||
|
nice pypy3 -m tests.ptrav .cpr 2 1
|
||||||
|
(13x faster than /scripts/test/ptrav.py)
|
||||||
|
"""
|
||||||
@@ -178,9 +178,9 @@ class TestVFS(unittest.TestCase):
|
|||||||
self.assertEqual(n.realpath, os.path.join(td, "a"))
|
self.assertEqual(n.realpath, os.path.join(td, "a"))
|
||||||
self.assertAxs(n.axs.uread, ["*"])
|
self.assertAxs(n.axs.uread, ["*"])
|
||||||
self.assertAxs(n.axs.uwrite, [])
|
self.assertAxs(n.axs.uwrite, [])
|
||||||
perm_na = (False, False, False, False, False, False)
|
perm_na = (False, False, False, False, False, False, False)
|
||||||
perm_rw = (True, True, False, False, False, False)
|
perm_rw = (True, True, False, False, False, False, False)
|
||||||
perm_ro = (True, False, False, False, False, False)
|
perm_ro = (True, False, False, False, False, False, False)
|
||||||
self.assertEqual(vfs.can_access("/", "*"), perm_na)
|
self.assertEqual(vfs.can_access("/", "*"), perm_na)
|
||||||
self.assertEqual(vfs.can_access("/", "k"), perm_rw)
|
self.assertEqual(vfs.can_access("/", "k"), perm_rw)
|
||||||
self.assertEqual(vfs.can_access("/a", "*"), perm_ro)
|
self.assertEqual(vfs.can_access("/a", "*"), perm_ro)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ if MACOS:
|
|||||||
|
|
||||||
from copyparty.__init__ import E
|
from copyparty.__init__ import E
|
||||||
from copyparty.__main__ import init_E
|
from copyparty.__main__ import init_E
|
||||||
from copyparty.util import Unrecv, FHC
|
from copyparty.util import Unrecv, FHC, Garda
|
||||||
|
|
||||||
init_E(E)
|
init_E(E)
|
||||||
|
|
||||||
@@ -98,7 +98,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_thumb no_vthumb no_zip nrand nw rand smb vc xdev xlink xvol"
|
ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_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"
|
||||||
@@ -107,16 +107,16 @@ class Cfg(Namespace):
|
|||||||
ex = "css_browser hist js_browser no_forget no_hash no_idx"
|
ex = "css_browser hist js_browser no_forget no_hash no_idx"
|
||||||
ka.update(**{k: None for k in ex.split()})
|
ka.update(**{k: None for k in ex.split()})
|
||||||
|
|
||||||
ex = "s_thead s_tbody"
|
ex = "s_thead s_tbody th_convt"
|
||||||
ka.update(**{k: 9 for k in ex.split()})
|
ka.update(**{k: 9 for k in ex.split()})
|
||||||
|
|
||||||
ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo"
|
ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth textfiles unlist R RS SR"
|
ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth name textfiles unlist R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
ex = "on403 on404 xad xar xau 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()})
|
||||||
|
|
||||||
super(Cfg, self).__init__(
|
super(Cfg, self).__init__(
|
||||||
@@ -126,6 +126,7 @@ class Cfg(Namespace):
|
|||||||
E=E,
|
E=E,
|
||||||
dbd="wal",
|
dbd="wal",
|
||||||
s_wr_sz=512 * 1024,
|
s_wr_sz=512 * 1024,
|
||||||
|
th_size="320x256",
|
||||||
unpost=600,
|
unpost=600,
|
||||||
u2sort="s",
|
u2sort="s",
|
||||||
mtp=[],
|
mtp=[],
|
||||||
@@ -175,6 +176,9 @@ class VHttpSrv(object):
|
|||||||
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||||
self.j2 = {x: J2_FILES for x in aliases}
|
self.j2 = {x: J2_FILES for x in aliases}
|
||||||
|
|
||||||
|
self.gpwd = Garda("")
|
||||||
|
self.g404 = Garda("")
|
||||||
|
|
||||||
def cachebuster(self):
|
def cachebuster(self):
|
||||||
return "a"
|
return "a"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user