mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
add filekeys
This commit is contained in:
parent
abc0424c26
commit
ef1c55286f
|
@ -834,6 +834,11 @@ on public copyparty instances with anonymous upload enabled:
|
||||||
* unless `--no-readme` is set: by uploading/modifying a file named `readme.md`
|
* unless `--no-readme` is set: by uploading/modifying a file named `readme.md`
|
||||||
* if `move` access is granted AND none of `--no-logues`, `--no-dot-mv`, `--no-dot-ren` is set: by uploading some .html file and renaming it to `.epilogue.html` (uploading it directly is blocked)
|
* if `move` access is granted AND none of `--no-logues`, `--no-dot-mv`, `--no-dot-ren` is set: by uploading some .html file and renaming it to `.epilogue.html` (uploading it directly is blocked)
|
||||||
|
|
||||||
|
other misc:
|
||||||
|
|
||||||
|
* you can disable directory listings by giving accesslevel `g` instead of `r`, only accepting direct URLs to files
|
||||||
|
* combine this with volume-flag `c,fk` to generate per-file accesskeys; users which have full read-access will then see URLs with `?k=...` appended to the end, and `g` users must provide that URL including the correct key to avoid a 404
|
||||||
|
|
||||||
|
|
||||||
## gotchas
|
## gotchas
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ def ensure_cert():
|
||||||
cert_insec = os.path.join(E.mod, "res/insecure.pem")
|
cert_insec = os.path.join(E.mod, "res/insecure.pem")
|
||||||
cert_cfg = os.path.join(E.cfg, "cert.pem")
|
cert_cfg = os.path.join(E.cfg, "cert.pem")
|
||||||
if not os.path.exists(cert_cfg):
|
if not os.path.exists(cert_cfg):
|
||||||
shutil.copy2(cert_insec, cert_cfg)
|
shutil.copy(cert_insec, cert_cfg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if filecmp.cmp(cert_cfg, cert_insec):
|
if filecmp.cmp(cert_cfg, cert_insec):
|
||||||
|
@ -203,6 +203,11 @@ def run_argparse(argv, formatter):
|
||||||
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fk_salt = unicode(os.path.getmtime(os.path.join(E.cfg, "cert.pem")))
|
||||||
|
except:
|
||||||
|
fk_salt = "hunter2"
|
||||||
|
|
||||||
sects = [
|
sects = [
|
||||||
[
|
[
|
||||||
"accounts",
|
"accounts",
|
||||||
|
@ -280,6 +285,10 @@ def run_argparse(argv, formatter):
|
||||||
\033[36mmtp=.bpm=f,audio-bpm.py\033[35m uses the "audio-bpm.py" program to
|
\033[36mmtp=.bpm=f,audio-bpm.py\033[35m uses the "audio-bpm.py" program to
|
||||||
generate ".bpm" tags from uploads (f = overwrite tags)
|
generate ".bpm" tags from uploads (f = overwrite tags)
|
||||||
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
|
\033[36mmtp=ahash,vhash=media-hash.py\033[35m collects two tags at once
|
||||||
|
|
||||||
|
\033[0mothers:
|
||||||
|
\033[36mfk=8\033[35m generates per-file accesskeys,
|
||||||
|
which will then be required at the "g" accesslevel
|
||||||
\033[0m"""
|
\033[0m"""
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -361,6 +370,7 @@ def run_argparse(argv, formatter):
|
||||||
ap2 = ap.add_argument_group('safety options')
|
ap2 = ap.add_argument_group('safety options')
|
||||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||||
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
|
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
|
||||||
|
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt")
|
||||||
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
|
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
|
||||||
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
|
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
|
||||||
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
||||||
|
|
|
@ -834,6 +834,11 @@ class AuthSrv(object):
|
||||||
if use:
|
if use:
|
||||||
vol.lim = lim
|
vol.lim = lim
|
||||||
|
|
||||||
|
for vol in vfs.all_vols.values():
|
||||||
|
fk = vol.flags.get("fk")
|
||||||
|
if fk:
|
||||||
|
vol.flags["fk"] = int(fk) if fk is not True else 8
|
||||||
|
|
||||||
for vol in vfs.all_vols.values():
|
for vol in vfs.all_vols.values():
|
||||||
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
|
if "pk" in vol.flags and "gz" not in vol.flags and "xz" not in vol.flags:
|
||||||
vol.flags["gz"] = False # def.pk
|
vol.flags["gz"] = False # def.pk
|
||||||
|
|
|
@ -1394,7 +1394,9 @@ class HttpCli(object):
|
||||||
#
|
#
|
||||||
# send reply
|
# send reply
|
||||||
|
|
||||||
if is_compressed or "cache" in self.uparam:
|
if not is_compressed and "cache" not in self.uparam:
|
||||||
|
self.out_headers.update(NO_CACHE)
|
||||||
|
else:
|
||||||
self.out_headers.pop("Cache-Control")
|
self.out_headers.pop("Cache-Control")
|
||||||
|
|
||||||
self.out_headers["Accept-Ranges"] = "bytes"
|
self.out_headers["Accept-Ranges"] = "bytes"
|
||||||
|
@ -1617,7 +1619,7 @@ class HttpCli(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def tx_404(self):
|
def tx_404(self):
|
||||||
m = '<h1>404 not found</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
|
m = '<h1>404 not found ┐( ´ -`)┌</h1><p>or maybe you don\'t have access -- try logging in or <a href="/?h">go home</a></p>'
|
||||||
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
|
html = self.j2("splash", this=self, qvpath=quotep(self.vpath), msg=m)
|
||||||
self.reply(html.encode("utf-8"), status=404)
|
self.reply(html.encode("utf-8"), status=404)
|
||||||
return True
|
return True
|
||||||
|
@ -1829,6 +1831,15 @@ class HttpCli(object):
|
||||||
return self.tx_ico(rem)
|
return self.tx_ico(rem)
|
||||||
|
|
||||||
if not is_dir and (self.can_read or self.can_get):
|
if not is_dir and (self.can_read or self.can_get):
|
||||||
|
if not self.can_read and "fk" in vn.flags:
|
||||||
|
correct = gen_filekey(
|
||||||
|
self.args.fk_salt, abspath, st.st_size, 0 if ANYWIN else st.st_ino
|
||||||
|
)[: vn.flags["fk"]]
|
||||||
|
got = self.uparam.get("k")
|
||||||
|
if got != correct:
|
||||||
|
self.log("wrong filekey, want {}, got {}".format(correct, got))
|
||||||
|
return self.tx_404()
|
||||||
|
|
||||||
if abspath.endswith(".md") and "raw" not in self.uparam:
|
if abspath.endswith(".md") and "raw" not in self.uparam:
|
||||||
return self.tx_md(abspath)
|
return self.tx_md(abspath)
|
||||||
|
|
||||||
|
@ -1987,6 +1998,8 @@ class HttpCli(object):
|
||||||
idx = self.conn.get_u2idx()
|
idx = self.conn.get_u2idx()
|
||||||
icur = idx.get_cur(dbv.realpath)
|
icur = idx.get_cur(dbv.realpath)
|
||||||
|
|
||||||
|
add_fk = vn.flags.get("fk")
|
||||||
|
|
||||||
dirs = []
|
dirs = []
|
||||||
files = []
|
files = []
|
||||||
for fn in vfs_ls:
|
for fn in vfs_ls:
|
||||||
|
@ -2032,9 +2045,19 @@ class HttpCli(object):
|
||||||
except:
|
except:
|
||||||
ext = "%"
|
ext = "%"
|
||||||
|
|
||||||
|
if add_fk:
|
||||||
|
href = "{}?k={}".format(
|
||||||
|
quotep(href),
|
||||||
|
gen_filekey(
|
||||||
|
self.args.fk_salt, fspath, sz, 0 if ANYWIN else inf.st_ino
|
||||||
|
)[:add_fk],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
href = quotep(href)
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
"lead": margin,
|
"lead": margin,
|
||||||
"href": quotep(href),
|
"href": href,
|
||||||
"name": fn,
|
"name": fn,
|
||||||
"sz": sz,
|
"sz": sz,
|
||||||
"ext": ext,
|
"ext": ext,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import subprocess as sp # nosec
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, ANYWIN, VT100
|
from .__init__ import PY2, WINDOWS, ANYWIN, VT100, unicode
|
||||||
from .stolen import surrogateescape
|
from .stolen import surrogateescape
|
||||||
|
|
||||||
FAKE_MP = False
|
FAKE_MP = False
|
||||||
|
@ -745,6 +745,14 @@ def read_header(sr):
|
||||||
return ret[:ofs].decode("utf-8", "surrogateescape").lstrip("\r\n").split("\r\n")
|
return ret[:ofs].decode("utf-8", "surrogateescape").lstrip("\r\n").split("\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def gen_filekey(salt, fspath, fsize, inode):
|
||||||
|
return base64.urlsafe_b64encode(
|
||||||
|
hashlib.sha512(
|
||||||
|
"{} {} {} {}".format(salt, fspath, fsize, inode).encode("utf-8", "replace")
|
||||||
|
).digest()
|
||||||
|
).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
def humansize(sz, terse=False):
|
def humansize(sz, terse=False):
|
||||||
for unit in ["B", "KiB", "MiB", "GiB", "TiB"]:
|
for unit in ["B", "KiB", "MiB", "GiB", "TiB"]:
|
||||||
if sz < 1024:
|
if sz < 1024:
|
||||||
|
|
Loading…
Reference in a new issue