mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
improve smoketests, warnings and error-messages:
* docker: warn if there are config-files in ~/.config/copyparty because somebody copied their config into /cfg/copyparty instead of /cfg as intended * docker: warn if there are no config-files in an included directory * make misconfigured reverse-proxies more obvious * explain cors rejections in server log * indicate cors rejection in error toast
This commit is contained in:
parent
8ca996e2f7
commit
d744f3ff8f
|
@ -395,7 +395,7 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
|
||||||
|
|
||||||
def args_from_cfg(cfg_path: str) -> list[str]:
|
def args_from_cfg(cfg_path: str) -> list[str]:
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
expand_config_file(lines, cfg_path, "")
|
expand_config_file(None, lines, cfg_path, "")
|
||||||
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
|
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
|
||||||
|
|
||||||
ret: list[str] = []
|
ret: list[str] = []
|
||||||
|
|
|
@ -863,7 +863,7 @@ class AuthSrv(object):
|
||||||
) -> None:
|
) -> None:
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
|
|
||||||
expand_config_file(cfg_lines, fp, "")
|
expand_config_file(self.log, cfg_lines, fp, "")
|
||||||
if self.args.vc:
|
if self.args.vc:
|
||||||
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
|
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
|
||||||
self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
|
self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
|
||||||
|
@ -2101,27 +2101,47 @@ def split_cfg_ln(ln: str) -> dict[str, Any]:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
|
def expand_config_file(log: Optional["NamedLogger"], ret: list[str], fp: str, ipath: str) -> None:
|
||||||
"""expand all % file includes"""
|
"""expand all % file includes"""
|
||||||
fp = absreal(fp)
|
fp = absreal(fp)
|
||||||
if len(ipath.split(" -> ")) > 64:
|
if len(ipath.split(" -> ")) > 64:
|
||||||
raise Exception("hit max depth of 64 includes")
|
raise Exception("hit max depth of 64 includes")
|
||||||
|
|
||||||
if os.path.isdir(fp):
|
if os.path.isdir(fp):
|
||||||
names = os.listdir(fp)
|
names = list(sorted(os.listdir(fp)))
|
||||||
crumb = "#\033[36m cfg files in {} => {}\033[0m".format(fp, names)
|
cnames = [x for x in names if x.lower().endswith(".conf")]
|
||||||
ret.append(crumb)
|
if not cnames:
|
||||||
for fn in sorted(names):
|
t = "warning: tried to read config-files from folder '%s' but it does not contain any "
|
||||||
|
if names:
|
||||||
|
t += ".conf files; the following files were ignored: %s"
|
||||||
|
t = t % (fp, ", ".join(names[:8]))
|
||||||
|
else:
|
||||||
|
t += "files at all"
|
||||||
|
t = t % (fp,)
|
||||||
|
|
||||||
|
if log:
|
||||||
|
log(t, 3)
|
||||||
|
|
||||||
|
ret.append("#\033[33m %s\033[0m" % (t,))
|
||||||
|
else:
|
||||||
|
zs = "#\033[36m cfg files in %s => %s\033[0m" % (fp, cnames)
|
||||||
|
ret.append(zs)
|
||||||
|
|
||||||
|
for fn in cnames:
|
||||||
fp2 = os.path.join(fp, fn)
|
fp2 = os.path.join(fp, fn)
|
||||||
if not fp2.endswith(".conf") or fp2 in ipath:
|
if fp2 in ipath:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
expand_config_file(ret, fp2, ipath)
|
expand_config_file(log, ret, fp2, ipath)
|
||||||
|
|
||||||
if ret[-1] == crumb:
|
return
|
||||||
# no config files below; remove breadcrumb
|
|
||||||
ret.pop()
|
|
||||||
|
|
||||||
|
if not os.path.exists(fp):
|
||||||
|
t = "warning: tried to read config from '%s' but the file/folder does not exist" % (fp,)
|
||||||
|
if log:
|
||||||
|
log(t, 3)
|
||||||
|
|
||||||
|
ret.append("#\033[31m %s\033[0m" % (t,))
|
||||||
return
|
return
|
||||||
|
|
||||||
ipath += " -> " + fp
|
ipath += " -> " + fp
|
||||||
|
@ -2135,7 +2155,7 @@ def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
|
||||||
fp2 = ln[1:].strip()
|
fp2 = ln[1:].strip()
|
||||||
fp2 = os.path.join(os.path.dirname(fp), fp2)
|
fp2 = os.path.join(os.path.dirname(fp), fp2)
|
||||||
ofs = len(ret)
|
ofs = len(ret)
|
||||||
expand_config_file(ret, fp2, ipath)
|
expand_config_file(log, ret, fp2, ipath)
|
||||||
for n in range(ofs, len(ret)):
|
for n in range(ofs, len(ret)):
|
||||||
ret[n] = pad + ret[n]
|
ret[n] = pad + ret[n]
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -518,9 +518,13 @@ class HttpCli(object):
|
||||||
return self.handle_options() and self.keepalive
|
return self.handle_options() and self.keepalive
|
||||||
|
|
||||||
if not cors_k:
|
if not cors_k:
|
||||||
|
host = self.headers.get("host", "<?>")
|
||||||
origin = self.headers.get("origin", "<?>")
|
origin = self.headers.get("origin", "<?>")
|
||||||
self.log("cors-reject {} from {}".format(self.mode, origin), 3)
|
proto = "https://" if self.is_https else "http://"
|
||||||
raise Pebkac(403, "no surfing")
|
guess = "modifying" if (origin and host) else "stripping"
|
||||||
|
t = "cors-reject %s because request-header Origin='%s' does not match request-protocol '%s' and host '%s' based on request-header Host='%s' (note: if this request is not malicious, check if your reverse-proxy is accidentally %s request headers, in particular 'Origin', for example by running copyparty with --ihead='*' to show all request headers)"
|
||||||
|
self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
|
||||||
|
raise Pebkac(403, "rejected by cors-check")
|
||||||
|
|
||||||
# getattr(self.mode) is not yet faster than this
|
# getattr(self.mode) is not yet faster than this
|
||||||
if self.mode == "POST":
|
if self.mode == "POST":
|
||||||
|
|
|
@ -28,7 +28,7 @@ if True: # pylint: disable=using-constant-test
|
||||||
import typing
|
import typing
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
from .__init__ import ANYWIN, E, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
||||||
from .authsrv import BAD_CFG, AuthSrv
|
from .authsrv import BAD_CFG, AuthSrv
|
||||||
from .cert import ensure_cert
|
from .cert import ensure_cert
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
||||||
|
@ -154,6 +154,8 @@ class SvcHub(object):
|
||||||
lg.handlers = [lh]
|
lg.handlers = [lh]
|
||||||
lg.setLevel(logging.DEBUG)
|
lg.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
self._check_env()
|
||||||
|
|
||||||
if args.stackmon:
|
if args.stackmon:
|
||||||
start_stackmon(args.stackmon, 0)
|
start_stackmon(args.stackmon, 0)
|
||||||
|
|
||||||
|
@ -385,6 +387,17 @@ class SvcHub(object):
|
||||||
|
|
||||||
Daemon(self.sd_notify, "sd-notify")
|
Daemon(self.sd_notify, "sd-notify")
|
||||||
|
|
||||||
|
def _check_env(self) -> None:
|
||||||
|
try:
|
||||||
|
files = os.listdir(E.cfg)
|
||||||
|
except:
|
||||||
|
files = []
|
||||||
|
|
||||||
|
hits = [x for x in files if x.lower().endswith(".conf")]
|
||||||
|
if hits:
|
||||||
|
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
|
||||||
|
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
|
||||||
|
|
||||||
def _process_config(self) -> bool:
|
def _process_config(self) -> bool:
|
||||||
al = self.args
|
al = self.args
|
||||||
|
|
||||||
|
|
|
@ -1995,15 +1995,19 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
|
||||||
if (tag === undefined)
|
if (tag === undefined)
|
||||||
tag = prefix;
|
tag = prefix;
|
||||||
|
|
||||||
var errtxt = (xhr.response && xhr.response.err) || xhr.responseText,
|
var errtxt = ((xhr.response && xhr.response.err) || xhr.responseText) || '',
|
||||||
|
suf = '',
|
||||||
fun = toast[lvl || 'err'],
|
fun = toast[lvl || 'err'],
|
||||||
is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);
|
is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);
|
||||||
|
|
||||||
|
if (errtxt.startsWith('<pre>'))
|
||||||
|
suf = '\n\nerror-details: «' + errtxt.slice(5).split('\n')[0].trim() + '»';
|
||||||
|
|
||||||
if (xhr.status == 403 && !is_cf)
|
if (xhr.status == 403 && !is_cf)
|
||||||
return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out"), tag);
|
return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out") + suf, tag);
|
||||||
|
|
||||||
if (xhr.status == 404)
|
if (xhr.status == 404)
|
||||||
return toast.err(0, prefix + e404, tag);
|
return toast.err(0, prefix + e404 + suf, tag);
|
||||||
|
|
||||||
if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
|
if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
|
||||||
var now = Date.now(), td = now - cf_cha_t;
|
var now = Date.now(), td = now - cf_cha_t;
|
||||||
|
|
Loading…
Reference in a new issue