Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85a637af09 | ||
|
|
043e3c7dd6 | ||
|
|
8f59afb159 | ||
|
|
77f1e51444 | ||
|
|
22fc4bb938 | ||
|
|
50c7bba6ea | ||
|
|
551d99b71b | ||
|
|
b54b7213a7 | ||
|
|
a14943c8de |
@@ -346,6 +346,7 @@ permissions:
|
||||
* `d` (delete): delete files/folders
|
||||
* `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)
|
||||
* `a` (admin): can see uploader IPs
|
||||
|
||||
examples:
|
||||
* add accounts named u1, u2, u3 with passwords p1, p2, p3: `-a u1:p1 -a u2:p2 -a u3:p3`
|
||||
@@ -1536,6 +1537,7 @@ some notes on hardening
|
||||
|
||||
* set `--rproxy 0` if your copyparty is directly facing the internet (not through a reverse-proxy)
|
||||
* 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:
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ in {
|
||||
"d" (delete): permanently delete files and folders
|
||||
"g" (get): download files, but cannot see folder contents
|
||||
"G" (upget): "get", but can see filekeys of their own uploads
|
||||
"a" (upget): can see uploader IPs
|
||||
|
||||
For example: "rwmd"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: icxes <dev.null@need.moe>
|
||||
pkgname=copyparty
|
||||
pkgver="1.8.0"
|
||||
pkgver="1.8.1"
|
||||
pkgrel=1
|
||||
pkgdesc="Portable file sharing hub"
|
||||
arch=("any")
|
||||
@@ -20,7 +20,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("etc/${pkgname}.d/init" )
|
||||
sha256sums=("e8ac0442abbf4c95428db0ad35a6965ad73038e0f384795907cde08af9e76612")
|
||||
sha256sums=("f43da11ba5d1d5adf99ad642bf068042c46c23d408e7ed17b025065121abab94")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.8.0/copyparty-sfx.py",
|
||||
"version": "1.8.0",
|
||||
"hash": "sha256-7e0XL1r+m7e7scOsvbG9I7jk/Z24OGBuWJHxST8Ko7Y="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.8.1/copyparty-sfx.py",
|
||||
"version": "1.8.1",
|
||||
"hash": "sha256-0Lf5djrgGAM+wwZP66GtSXkmRnIp3tij8j7cANeoE7o="
|
||||
}
|
||||
@@ -492,6 +492,7 @@ def get_sects():
|
||||
"d" (delete): permanently delete files and folders
|
||||
"g" (get): download files, but cannot see folder contents
|
||||
"G" (upget): "get", but can see filekeys of their own uploads
|
||||
"a" (admin): can see uploader IPs
|
||||
|
||||
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[36mxad\033[35m executes CMD after a file delete
|
||||
\033[36mxm\033[35m executes CMD on message
|
||||
\033[36mxban\033[35m executes CMD if someone gets banned
|
||||
\033[0m
|
||||
can be defined as --args or volflags; for example \033[36m
|
||||
--xau notify-send
|
||||
@@ -621,6 +623,9 @@ def get_sects():
|
||||
executed program on STDIN instead of as argv arguments, and
|
||||
it also includes the wark (file-id/hash) as a json property
|
||||
|
||||
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
|
||||
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,
|
||||
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
|
||||
@@ -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("--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("--xban", metavar="CMD", type=u, action="append", help="execute CMD if someone gets banned (pw/404)")
|
||||
|
||||
|
||||
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-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("--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-convt", metavar="SEC", type=int, default=60, help="conversion timeout in seconds")
|
||||
ap2.add_argument("--th-no-crop", action="store_true", help="dynamic height; show full image")
|
||||
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 (volflag=nocrop)")
|
||||
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-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("-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.)",
|
||||
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.)",
|
||||
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")
|
||||
@@ -1337,11 +1343,9 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||
if re.match("c[^,]", opt):
|
||||
mod = True
|
||||
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
|
||||
perm = opt[0]
|
||||
if perm == "a":
|
||||
perm = "rw"
|
||||
na.append(perm + "," + opt[1:])
|
||||
else:
|
||||
na.append(opt)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 8, 1)
|
||||
VERSION = (1, 8, 2)
|
||||
CODENAME = "argon"
|
||||
BUILD_DT = (2023, 7, 7)
|
||||
BUILD_DT = (2023, 7, 14)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
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,
|
||||
uget: Optional[Union[list[str], set[str]]] = None,
|
||||
upget: Optional[Union[list[str], set[str]]] = None,
|
||||
uadmin: Optional[Union[list[str], set[str]]] = None,
|
||||
) -> None:
|
||||
self.uread: set[str] = set(uread or [])
|
||||
self.uwrite: set[str] = set(uwrite or [])
|
||||
@@ -69,14 +70,11 @@ class AXS(object):
|
||||
self.udel: set[str] = set(udel or [])
|
||||
self.uget: set[str] = set(uget or [])
|
||||
self.upget: set[str] = set(upget or [])
|
||||
self.uadmin: set[str] = set(uadmin or [])
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "AXS(%s)" % (
|
||||
", ".join(
|
||||
"%s=%r" % (k, self.__dict__[k])
|
||||
for k in "uread uwrite umove udel uget upget".split()
|
||||
)
|
||||
)
|
||||
ks = "uread uwrite umove udel uget upget uadmin".split()
|
||||
return "AXS(%s)" % (", ".join("%s=%r" % (k, self.__dict__[k]) for k in ks),)
|
||||
|
||||
|
||||
class Lim(object):
|
||||
@@ -435,8 +433,8 @@ class VFS(object):
|
||||
|
||||
def can_access(
|
||||
self, vpath: str, uname: str
|
||||
) -> tuple[bool, bool, bool, bool, bool, bool]:
|
||||
"""can Read,Write,Move,Delete,Get,Upget"""
|
||||
) -> tuple[bool, bool, bool, bool, bool, bool, bool]:
|
||||
"""can Read,Write,Move,Delete,Get,Upget,Admin"""
|
||||
if vpath:
|
||||
vn, _ = self._find(undot(vpath))
|
||||
else:
|
||||
@@ -450,6 +448,7 @@ class VFS(object):
|
||||
uname in c.udel or "*" in c.udel,
|
||||
uname in c.uget or "*" in c.uget,
|
||||
uname in c.upget or "*" in c.upget,
|
||||
uname in c.uadmin or "*" in c.uadmin,
|
||||
)
|
||||
|
||||
def get(
|
||||
@@ -944,7 +943,7 @@ class AuthSrv(object):
|
||||
try:
|
||||
self._l(ln, 5, "volume access config:")
|
||||
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; "
|
||||
raise Exception(err)
|
||||
if " " in re.sub(", *", "", sv).strip():
|
||||
@@ -953,7 +952,7 @@ class AuthSrv(object):
|
||||
self._read_vol_str(sk, sv.replace(" ", ""), daxs[vp], mflags[vp])
|
||||
continue
|
||||
except:
|
||||
err += "accs entries must be 'rwmdgG: user1, user2, ...'"
|
||||
err += "accs entries must be 'rwmdgGa: user1, user2, ...'"
|
||||
raise Exception(err)
|
||||
|
||||
if cat == catf:
|
||||
@@ -989,7 +988,7 @@ class AuthSrv(object):
|
||||
def _read_vol_str(
|
||||
self, lvl: str, uname: str, axs: AXS, flags: dict[str, Any]
|
||||
) -> None:
|
||||
if lvl.strip("crwmdgG"):
|
||||
if lvl.strip("crwmdgGa"):
|
||||
raise Exception("invalid volflag: {},{}".format(lvl, uname))
|
||||
|
||||
if lvl == "c":
|
||||
@@ -1021,6 +1020,7 @@ class AuthSrv(object):
|
||||
("g", axs.uget),
|
||||
("G", axs.uget),
|
||||
("G", axs.upget),
|
||||
("a", axs.uadmin),
|
||||
]: # b bb bbb
|
||||
if ch in lvl:
|
||||
if un == "*":
|
||||
@@ -1047,7 +1047,8 @@ class AuthSrv(object):
|
||||
flags[name] = True
|
||||
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:
|
||||
t = "└─add volflag [{}] = {} ({})"
|
||||
else:
|
||||
@@ -1092,7 +1093,7 @@ class AuthSrv(object):
|
||||
|
||||
if self.args.v:
|
||||
# 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:
|
||||
m = re_vol.match(v_str)
|
||||
if not m:
|
||||
@@ -1196,7 +1197,15 @@ class AuthSrv(object):
|
||||
all_users = {}
|
||||
missing_users = {}
|
||||
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:
|
||||
all_users[usr] = 1
|
||||
if usr != "*" and usr not in acct:
|
||||
@@ -1420,6 +1429,10 @@ class AuthSrv(object):
|
||||
if k in vol.flags:
|
||||
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:
|
||||
if k1 in vol.flags:
|
||||
vol.flags[k2] = True
|
||||
@@ -1445,7 +1458,7 @@ class AuthSrv(object):
|
||||
vol.flags["mth"] = self.args.mth
|
||||
|
||||
# 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:
|
||||
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
||||
|
||||
@@ -1468,6 +1481,10 @@ class AuthSrv(object):
|
||||
hfs = [x for x in hfs if x != "f"]
|
||||
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)
|
||||
vol.flags[hn] = ncmds
|
||||
|
||||
@@ -1607,6 +1624,7 @@ class AuthSrv(object):
|
||||
["delete", "udel"],
|
||||
[" get", "uget"],
|
||||
[" upget", "upget"],
|
||||
["uadmin", "uadmin"],
|
||||
]:
|
||||
u = list(sorted(getattr(zv.axs, attr)))
|
||||
u = ", ".join("\033[35meverybody\033[0m" if x == "*" else x for x in u)
|
||||
@@ -1752,10 +1770,19 @@ class AuthSrv(object):
|
||||
raise Exception("volume not found: " + zs)
|
||||
|
||||
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():
|
||||
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))
|
||||
|
||||
flag_v = "v" in flags
|
||||
@@ -1835,7 +1862,8 @@ class AuthSrv(object):
|
||||
]
|
||||
|
||||
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())
|
||||
|
||||
# keymap from argv to vflag
|
||||
@@ -1894,6 +1922,7 @@ class AuthSrv(object):
|
||||
"d": "udel",
|
||||
"g": "uget",
|
||||
"G": "upget",
|
||||
"a": "uadmin",
|
||||
}
|
||||
users = {}
|
||||
for pkey in perms.values():
|
||||
@@ -2090,7 +2119,7 @@ def upgrade_cfg_fmt(
|
||||
else:
|
||||
sn = sn.replace(",", ", ")
|
||||
ret.append(" " + sn)
|
||||
elif sn[:1] in "rwmdgG":
|
||||
elif sn[:1] in "rwmdgGa":
|
||||
if cat != catx:
|
||||
cat = catx
|
||||
ret.append(cat)
|
||||
|
||||
@@ -13,6 +13,7 @@ def vf_bmap() -> dict[str, str]:
|
||||
"no_dedup": "copydupes",
|
||||
"no_dupe": "nodupe",
|
||||
"no_forget": "noforget",
|
||||
"th_no_crop": "nocrop",
|
||||
"dav_auth": "davauth",
|
||||
"dav_rt": "davrt",
|
||||
}
|
||||
@@ -40,8 +41,8 @@ def vf_bmap() -> dict[str, str]:
|
||||
|
||||
def vf_vmap() -> dict[str, str]:
|
||||
"""argv-to-volflag: simple values"""
|
||||
ret = {}
|
||||
for k in ("lg_sbf", "md_sbf", "unlist"):
|
||||
ret = {"th_convt": "convt", "th_size": "thsize"}
|
||||
for k in ("dbd", "lg_sbf", "md_sbf", "nrand", "unlist"):
|
||||
ret[k] = k
|
||||
return ret
|
||||
|
||||
@@ -49,7 +50,7 @@ def vf_vmap() -> dict[str, str]:
|
||||
def vf_cmap() -> dict[str, str]:
|
||||
"""argv-to-volflag: complex/lists"""
|
||||
ret = {}
|
||||
for k in ("dbd", "html_head", "mte", "mth", "nrand"):
|
||||
for k in ("html_head", "mte", "mth"):
|
||||
ret[k] = k
|
||||
return ret
|
||||
|
||||
@@ -124,6 +125,9 @@ flagcats = {
|
||||
"dvthumb": "disables video thumbnails",
|
||||
"dathumb": "disables audio thumbnails (spectrograms)",
|
||||
"dithumb": "disables image thumbnails",
|
||||
"thsize": "thumbnail res; WxH",
|
||||
"nocrop": "disable center-cropping",
|
||||
"convt": "conversion timeout in seconds",
|
||||
},
|
||||
"handlers\n(better explained in --help-handlers)": {
|
||||
"on404=PY": "handle 404s by executing PY file",
|
||||
@@ -138,6 +142,7 @@ flagcats = {
|
||||
"xbd=CMD": "execute CMD before a file delete",
|
||||
"xad=CMD": "execute CMD after a file delete",
|
||||
"xm=CMD": "execute CMD on message",
|
||||
"xban=CMD": "execute CMD if someone gets banned",
|
||||
},
|
||||
"client and ux": {
|
||||
"grid": "show grid/thumbnails by default",
|
||||
@@ -151,6 +156,7 @@ flagcats = {
|
||||
"sb_lg": "enable js sandbox for prologue/epilogue (default)",
|
||||
"md_sbf": "list of markdown-sandbox safeguards to disable",
|
||||
"lg_sbf": "list of *logue-sandbox safeguards to disable",
|
||||
"nohtml": "return html and markdown as text/html",
|
||||
},
|
||||
"others": {
|
||||
"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_delete = self.can_get = self.can_upget = False
|
||||
self.can_admin = False
|
||||
|
||||
self.listdirinfo = self.listdir
|
||||
self.chdir(".")
|
||||
@@ -168,7 +169,7 @@ class FtpFs(AbstractedFS):
|
||||
if not avfs:
|
||||
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:
|
||||
raise FSE(t.format(vpath), 1)
|
||||
|
||||
@@ -243,6 +244,7 @@ class FtpFs(AbstractedFS):
|
||||
self.can_delete,
|
||||
self.can_get,
|
||||
self.can_upget,
|
||||
self.can_admin,
|
||||
) = avfs.can_access("", self.h.uname)
|
||||
|
||||
def mkdir(self, path: str) -> None:
|
||||
|
||||
@@ -42,6 +42,7 @@ from .util import (
|
||||
Pebkac,
|
||||
UnrecvEOF,
|
||||
alltrace,
|
||||
absreal,
|
||||
atomic_move,
|
||||
exclude_dotfiles,
|
||||
fsenc,
|
||||
@@ -137,6 +138,8 @@ class HttpCli(object):
|
||||
self.uparam: dict[str, str] = {}
|
||||
self.cookies: dict[str, str] = {}
|
||||
self.avn: Optional[VFS] = None
|
||||
self.vn = self.asrv.vfs
|
||||
self.rem = " "
|
||||
self.vpath = " "
|
||||
self.uname = " "
|
||||
self.pw = " "
|
||||
@@ -153,6 +156,7 @@ class HttpCli(object):
|
||||
self.can_delete = False
|
||||
self.can_get = False
|
||||
self.can_upget = False
|
||||
self.can_admin = False
|
||||
# post
|
||||
self.parser: Optional[MultipartParser] = None
|
||||
# end placeholders
|
||||
@@ -431,10 +435,13 @@ class HttpCli(object):
|
||||
self.can_delete,
|
||||
self.can_get,
|
||||
self.can_upget,
|
||||
self.can_admin,
|
||||
) = (
|
||||
avn.can_access("", self.uname) if avn else [False] * 6
|
||||
)
|
||||
self.avn = avn
|
||||
self.vn = vn
|
||||
self.rem = rem
|
||||
|
||||
self.s.settimeout(self.args.s_tbody or None)
|
||||
|
||||
@@ -570,9 +577,8 @@ class HttpCli(object):
|
||||
|
||||
# default to utf8 html if no content-type is set
|
||||
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
|
||||
|
||||
for k, zs in list(self.out_headers.items()) + self.out_headerlist:
|
||||
@@ -597,6 +603,20 @@ class HttpCli(object):
|
||||
if g.lim:
|
||||
bonk, ip = g.bonk(self.ip, self.vpath)
|
||||
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.conn.hsrv.bans[ip] = bonk
|
||||
|
||||
@@ -760,7 +780,14 @@ class HttpCli(object):
|
||||
self.reply(b"", 301, headers=h)
|
||||
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)
|
||||
|
||||
if "cf_challenge" in self.uparam:
|
||||
@@ -771,9 +798,8 @@ class HttpCli(object):
|
||||
t = "@{} has no access to [{}]"
|
||||
self.log(t.format(self.uname, self.vpath))
|
||||
|
||||
if self.avn and "on403" in self.avn.flags:
|
||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
||||
ret = self.on40x(vn.flags["on403"], vn, rem)
|
||||
if "on403" in self.vn.flags:
|
||||
ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
|
||||
if ret == "true":
|
||||
return True
|
||||
elif ret == "false":
|
||||
@@ -782,6 +808,7 @@ class HttpCli(object):
|
||||
self.log("plugin override; access permitted")
|
||||
self.can_read = self.can_write = self.can_move = True
|
||||
self.can_delete = self.can_get = self.can_upget = True
|
||||
self.can_admin = True
|
||||
else:
|
||||
return self.tx_404(True)
|
||||
else:
|
||||
@@ -1039,9 +1066,6 @@ class HttpCli(object):
|
||||
|
||||
from .dxml import mkenod, mktnod, parse_xml
|
||||
|
||||
self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
||||
# abspath = vn.dcanonical(rem)
|
||||
|
||||
buf = b""
|
||||
for rbuf in self.get_body_reader()[0]:
|
||||
buf += rbuf
|
||||
@@ -1098,8 +1122,7 @@ class HttpCli(object):
|
||||
|
||||
from .dxml import mkenod, mktnod, parse_xml
|
||||
|
||||
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
|
||||
abspath = vn.dcanonical(rem)
|
||||
abspath = self.vn.dcanonical(self.rem)
|
||||
|
||||
buf = b""
|
||||
for rbuf in self.get_body_reader()[0]:
|
||||
@@ -1313,20 +1336,17 @@ class HttpCli(object):
|
||||
plain = zb.decode("utf-8", "replace")
|
||||
if buf.startswith(b"msg="):
|
||||
plain = plain[4:]
|
||||
vfs, rem = self.asrv.vfs.get(
|
||||
self.vpath, self.uname, False, False
|
||||
)
|
||||
xm = vfs.flags.get("xm")
|
||||
xm = self.vn.flags.get("xm")
|
||||
if xm:
|
||||
runhook(
|
||||
self.log,
|
||||
xm,
|
||||
vfs.canonical(rem),
|
||||
self.vn.canonical(self.rem),
|
||||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
time.time(),
|
||||
len(xm),
|
||||
len(buf),
|
||||
self.ip,
|
||||
time.time(),
|
||||
plain,
|
||||
@@ -1998,6 +2018,20 @@ class HttpCli(object):
|
||||
if g.lim:
|
||||
bonk, ip = g.bonk(self.ip, pwd)
|
||||
if bonk:
|
||||
xban = self.vn.flags.get("xban")
|
||||
if not xban or not runhook(
|
||||
self.log,
|
||||
xban,
|
||||
self.vn.canonical(self.rem),
|
||||
self.vpath,
|
||||
self.host,
|
||||
self.uname,
|
||||
time.time(),
|
||||
0,
|
||||
self.ip,
|
||||
time.time(),
|
||||
"pw",
|
||||
):
|
||||
self.log("client banned: invalid passwords", 1)
|
||||
self.conn.hsrv.bans[ip] = bonk
|
||||
|
||||
@@ -2728,6 +2762,9 @@ class HttpCli(object):
|
||||
else:
|
||||
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.send_headers(length=upper - lower, status=status, mime=mime)
|
||||
|
||||
@@ -3397,7 +3434,8 @@ class HttpCli(object):
|
||||
|
||||
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)
|
||||
dbv, vrem = vn.get_dbv(rem)
|
||||
|
||||
@@ -3493,8 +3531,14 @@ class HttpCli(object):
|
||||
self.log("wrong filekey, want {}, got {}".format(correct, got))
|
||||
return self.tx_404()
|
||||
|
||||
if abspath.endswith(".md") and (
|
||||
"v" in self.uparam or "edit" in self.uparam or "edit2" in self.uparam
|
||||
if (
|
||||
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)
|
||||
|
||||
@@ -3535,6 +3579,8 @@ class HttpCli(object):
|
||||
perms.append("get")
|
||||
if self.can_upget:
|
||||
perms.append("upget")
|
||||
if self.can_admin:
|
||||
perms.append("admin")
|
||||
|
||||
url_suf = self.urlq({}, ["k"])
|
||||
is_ls = "ls" in self.uparam
|
||||
@@ -3786,22 +3832,33 @@ class HttpCli(object):
|
||||
if vn != dbv:
|
||||
_, 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'"
|
||||
try:
|
||||
r = icur.execute(q, (rd, fn))
|
||||
r = icur.execute(q, erd_efn)
|
||||
except Exception as ex:
|
||||
if "database is locked" in str(ex):
|
||||
break
|
||||
|
||||
try:
|
||||
args = s3enc(idx.mem_cur, rd, fn)
|
||||
r = icur.execute(q, args)
|
||||
erd_efn = s3enc(idx.mem_cur, rd, fn)
|
||||
r = icur.execute(q, erd_efn)
|
||||
except:
|
||||
t = "tag read error, {}/{}\n{}"
|
||||
self.log(t.format(rd, fn, min_ex()))
|
||||
break
|
||||
|
||||
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"]]
|
||||
|
||||
if icur:
|
||||
|
||||
@@ -13,6 +13,7 @@ import time
|
||||
from queue import Queue
|
||||
|
||||
from .__init__ import ANYWIN, TYPE_CHECKING
|
||||
from .authsrv import VFS
|
||||
from .bos import bos
|
||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
||||
from .util import (
|
||||
@@ -110,8 +111,6 @@ class ThumbSrv(object):
|
||||
self.args = hub.args
|
||||
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.mutex = threading.Lock()
|
||||
@@ -119,7 +118,7 @@ class ThumbSrv(object):
|
||||
self.stopping = False
|
||||
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):
|
||||
Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
|
||||
|
||||
@@ -184,6 +183,10 @@ class ThumbSrv(object):
|
||||
with self.mutex:
|
||||
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]:
|
||||
histpath = self.asrv.vfs.histtab.get(ptop)
|
||||
if not histpath:
|
||||
@@ -211,7 +214,13 @@ class ThumbSrv(object):
|
||||
do_conv = True
|
||||
|
||||
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)
|
||||
|
||||
while not self.stopping:
|
||||
@@ -248,7 +257,7 @@ class ThumbSrv(object):
|
||||
if not task:
|
||||
break
|
||||
|
||||
abspath, tpath = task
|
||||
abspath, tpath, vn = task
|
||||
ext = abspath.split(".")[-1].lower()
|
||||
png_ok = False
|
||||
funs = []
|
||||
@@ -281,7 +290,7 @@ class ThumbSrv(object):
|
||||
|
||||
for fun in funs:
|
||||
try:
|
||||
fun(abspath, ttpath)
|
||||
fun(abspath, ttpath, vn)
|
||||
break
|
||||
except Exception as ex:
|
||||
msg = "{} could not create thumbnail of {}\n{}"
|
||||
@@ -315,9 +324,10 @@ class ThumbSrv(object):
|
||||
with self.mutex:
|
||||
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)
|
||||
r = max(*self.res) * 2
|
||||
res = self.getres(vn)
|
||||
r = max(*res) * 2
|
||||
im.thumbnail((r, r), resample=Image.LANCZOS)
|
||||
try:
|
||||
k = next(k for k, v in ExifTags.TAGS.items() if v == "Orientation")
|
||||
@@ -331,23 +341,23 @@ class ThumbSrv(object):
|
||||
if rot in rots:
|
||||
im = im.transpose(rots[rot])
|
||||
|
||||
if self.args.th_no_crop:
|
||||
im.thumbnail(self.res, resample=Image.LANCZOS)
|
||||
if "nocrop" in vn.flags:
|
||||
im.thumbnail(res, resample=Image.LANCZOS)
|
||||
else:
|
||||
iw, ih = im.size
|
||||
dw, dh = self.res
|
||||
dw, dh = res
|
||||
res = (min(iw, dw), min(ih, dh))
|
||||
im = ImageOps.fit(im, res, method=Image.LANCZOS)
|
||||
|
||||
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:
|
||||
try:
|
||||
im = self.fancy_pillow(im)
|
||||
im = self.fancy_pillow(im, vn)
|
||||
except Exception as ex:
|
||||
self.log("fancy_pillow {}".format(ex), "90")
|
||||
im.thumbnail(self.res)
|
||||
im.thumbnail(self.getres(vn))
|
||||
|
||||
fmts = ["RGB", "L"]
|
||||
args = {"quality": 40}
|
||||
@@ -370,12 +380,12 @@ class ThumbSrv(object):
|
||||
|
||||
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"]
|
||||
if self.args.th_no_crop:
|
||||
if "nocrop" in vn.flags:
|
||||
crops = ["none"]
|
||||
|
||||
w, h = self.res
|
||||
w, h = self.getres(vn)
|
||||
kw = {"height": h, "size": "down", "intent": "relative"}
|
||||
|
||||
for c in crops:
|
||||
@@ -389,8 +399,8 @@ class ThumbSrv(object):
|
||||
|
||||
img.write_to_file(tpath, Q=40)
|
||||
|
||||
def conv_ffmpeg(self, abspath: str, tpath: str) -> None:
|
||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
||||
def conv_ffmpeg(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if not ret:
|
||||
return
|
||||
|
||||
@@ -402,12 +412,13 @@ class ThumbSrv(object):
|
||||
seek = [b"-ss", "{:.0f}".format(dur / 3).encode("utf-8")]
|
||||
|
||||
scale = "scale={0}:{1}:force_original_aspect_ratio="
|
||||
if self.args.th_no_crop:
|
||||
if "nocrop" in vn.flags:
|
||||
scale += "decrease,setsar=1:1"
|
||||
else:
|
||||
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
|
||||
cmd = [
|
||||
b"ffmpeg",
|
||||
@@ -439,11 +450,11 @@ class ThumbSrv(object):
|
||||
]
|
||||
|
||||
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"))
|
||||
ret, _, serr = runcmd(cmd, timeout=self.args.th_convt)
|
||||
ret, _, serr = runcmd(cmd, timeout=vn.flags["convt"])
|
||||
if not ret:
|
||||
return
|
||||
|
||||
@@ -486,8 +497,8 @@ class ThumbSrv(object):
|
||||
self.log(t + txt, c=c)
|
||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||
|
||||
def conv_waves(self, abspath: str, tpath: str) -> None:
|
||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
||||
def conv_waves(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in ret:
|
||||
raise Exception("not audio")
|
||||
|
||||
@@ -512,10 +523,10 @@ class ThumbSrv(object):
|
||||
# fmt: on
|
||||
|
||||
cmd += [fsenc(tpath)]
|
||||
self._run_ff(cmd)
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
def conv_spec(self, abspath: str, tpath: str) -> None:
|
||||
ret, _ = ffprobe(abspath, int(self.args.th_convt / 2))
|
||||
def conv_spec(self, abspath: str, tpath: str, vn: VFS) -> None:
|
||||
ret, _ = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
||||
if "ac" not in ret:
|
||||
raise Exception("not audio")
|
||||
|
||||
@@ -555,13 +566,13 @@ class ThumbSrv(object):
|
||||
]
|
||||
|
||||
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:
|
||||
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:
|
||||
raise Exception("not audio")
|
||||
|
||||
@@ -597,7 +608,7 @@ class ThumbSrv(object):
|
||||
fsenc(tmp_opus)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd)
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
# iOS fails to play some "insufficiently complex" files
|
||||
# (average file shorter than 8 seconds), so of course we
|
||||
@@ -621,7 +632,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd)
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
elif want_caf:
|
||||
# simple remux should be safe
|
||||
@@ -639,7 +650,7 @@ class ThumbSrv(object):
|
||||
fsenc(tpath)
|
||||
]
|
||||
# fmt: on
|
||||
self._run_ff(cmd)
|
||||
self._run_ff(cmd, vn)
|
||||
|
||||
if tmp_opus != tpath:
|
||||
try:
|
||||
|
||||
@@ -24,6 +24,7 @@ from queue import Queue
|
||||
from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS
|
||||
from .authsrv import LEELOO_DALLAS, VFS, AuthSrv
|
||||
from .bos import bos
|
||||
from .cfg import vf_bmap, vf_vmap
|
||||
from .fsutil import Fstab
|
||||
from .mtag import MParser, MTag
|
||||
from .util import (
|
||||
@@ -199,6 +200,7 @@ class Up2k(object):
|
||||
if self.stop:
|
||||
# up-mt consistency not guaranteed if init is interrupted;
|
||||
# drop caches for a full scan on next boot
|
||||
with self.mutex:
|
||||
self._drop_caches()
|
||||
|
||||
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]:
|
||||
self.args.re_dhash = False
|
||||
with self.mutex:
|
||||
self._drop_caches()
|
||||
|
||||
for vol in vols:
|
||||
@@ -757,8 +760,9 @@ class Up2k(object):
|
||||
ff = "\033[0;35m{}{:.0}"
|
||||
fv = "\033[0;36m{}:\033[90m{}"
|
||||
fx = set(("html_head",))
|
||||
fdl = ("dbd", "lg_sbf", "md_sbf", "mte", "mth", "mtp", "nrand", "rand")
|
||||
fd = {x: x for x in fdl}
|
||||
fd = vf_bmap()
|
||||
fd.update(vf_vmap())
|
||||
fd = {v: k for k, v in fd.items()}
|
||||
fl = {
|
||||
k: v
|
||||
for k, v in flags.items()
|
||||
@@ -769,6 +773,9 @@ class Up2k(object):
|
||||
for k, v in fl.items()
|
||||
if k not in fx
|
||||
]
|
||||
if not a:
|
||||
a = ["\033[90mall-default"]
|
||||
|
||||
if a:
|
||||
vpath = "?"
|
||||
for k, v in self.asrv.vfs.all_vols.items():
|
||||
@@ -2993,6 +3000,7 @@ class Up2k(object):
|
||||
permsets = [[False, True]]
|
||||
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
|
||||
vn, rem = vn.get_dbv(rem)
|
||||
with self.mutex:
|
||||
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
|
||||
|
||||
t = "you cannot delete this: "
|
||||
|
||||
@@ -2427,7 +2427,7 @@ def killtree(root: int) -> None:
|
||||
|
||||
|
||||
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]:
|
||||
kill = ka.pop("kill", "t") # [t]ree [m]ain [n]one
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
with open(os.devnull, "wb") as f:
|
||||
rv = sp.call(argv, stdout=f, stderr=f)
|
||||
|
||||
@@ -5780,14 +5780,18 @@ var treectl = (function () {
|
||||
|
||||
for (var b = 0; b < res.taglist.length; b++) {
|
||||
var k = res.taglist[b],
|
||||
v = (tn.tags || {})[k] || "";
|
||||
v = (tn.tags || {})[k] || "",
|
||||
sv = null;
|
||||
|
||||
if (k == ".dur") {
|
||||
var sv = v ? s2ms(v) : "";
|
||||
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||
if (k == ".dur")
|
||||
sv = v ? s2ms(v) : "";
|
||||
else if (k == ".up_at")
|
||||
sv = v ? unix2iso(v) : "";
|
||||
else {
|
||||
ln.push(v);
|
||||
continue;
|
||||
}
|
||||
ln.push(v);
|
||||
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||
}
|
||||
ln = ln.concat([tn.ext, unix2iso(tn.ts)]).join('</td><td>');
|
||||
html.push(ln + '</td></tr>');
|
||||
@@ -6066,7 +6070,7 @@ function apply_perms(res) {
|
||||
|
||||
var axs = [],
|
||||
aclass = '>',
|
||||
chk = ['read', 'write', 'move', 'delete', 'get'];
|
||||
chk = ['read', 'write', 'move', 'delete', 'get', 'admin'];
|
||||
|
||||
for (var a = 0; a < chk.length; a++)
|
||||
if (has(perms, chk[a]))
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 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
|
||||
|
||||
|
||||
@@ -178,9 +178,9 @@ class TestVFS(unittest.TestCase):
|
||||
self.assertEqual(n.realpath, os.path.join(td, "a"))
|
||||
self.assertAxs(n.axs.uread, ["*"])
|
||||
self.assertAxs(n.axs.uwrite, [])
|
||||
perm_na = (False, False, False, False, False, False)
|
||||
perm_rw = (True, True, False, False, False, False)
|
||||
perm_ro = (True, False, False, False, False, False)
|
||||
perm_na = (False, False, False, False, False, False, False)
|
||||
perm_rw = (True, 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("/", "k"), perm_rw)
|
||||
self.assertEqual(vfs.can_access("/a", "*"), perm_ro)
|
||||
|
||||
@@ -98,7 +98,7 @@ class Cfg(Namespace):
|
||||
def __init__(self, a=None, v=None, c=None):
|
||||
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 vc xdev xlink xvol"
|
||||
ka.update(**{k: False for k in ex.split()})
|
||||
|
||||
ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"
|
||||
@@ -107,7 +107,7 @@ class Cfg(Namespace):
|
||||
ex = "css_browser hist js_browser no_forget no_hash no_idx"
|
||||
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()})
|
||||
|
||||
ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo"
|
||||
@@ -116,7 +116,7 @@ class Cfg(Namespace):
|
||||
ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth textfiles unlist R RS SR"
|
||||
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()})
|
||||
|
||||
super(Cfg, self).__init__(
|
||||
@@ -126,6 +126,7 @@ class Cfg(Namespace):
|
||||
E=E,
|
||||
dbd="wal",
|
||||
s_wr_sz=512 * 1024,
|
||||
th_size="320x256",
|
||||
unpost=600,
|
||||
u2sort="s",
|
||||
mtp=[],
|
||||
|
||||
Reference in New Issue
Block a user