mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00: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:
		@@ -395,7 +395,7 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
 | 
			
		||||
 | 
			
		||||
def args_from_cfg(cfg_path: str) -> 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, "")
 | 
			
		||||
 | 
			
		||||
    ret: list[str] = []
 | 
			
		||||
 
 | 
			
		||||
@@ -863,7 +863,7 @@ class AuthSrv(object):
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.line_ctr = 0
 | 
			
		||||
 | 
			
		||||
        expand_config_file(cfg_lines, fp, "")
 | 
			
		||||
        expand_config_file(self.log, cfg_lines, fp, "")
 | 
			
		||||
        if self.args.vc:
 | 
			
		||||
            lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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"""
 | 
			
		||||
    fp = absreal(fp)
 | 
			
		||||
    if len(ipath.split(" -> ")) > 64:
 | 
			
		||||
        raise Exception("hit max depth of 64 includes")
 | 
			
		||||
 | 
			
		||||
    if os.path.isdir(fp):
 | 
			
		||||
        names = os.listdir(fp)
 | 
			
		||||
        crumb = "#\033[36m cfg files in {} => {}\033[0m".format(fp, names)
 | 
			
		||||
        ret.append(crumb)
 | 
			
		||||
        for fn in sorted(names):
 | 
			
		||||
        names = list(sorted(os.listdir(fp)))
 | 
			
		||||
        cnames = [x for x in names if x.lower().endswith(".conf")]
 | 
			
		||||
        if not cnames:
 | 
			
		||||
            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)
 | 
			
		||||
            if not fp2.endswith(".conf") or fp2 in ipath:
 | 
			
		||||
            if fp2 in ipath:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            expand_config_file(ret, fp2, ipath)
 | 
			
		||||
            expand_config_file(log, ret, fp2, ipath)
 | 
			
		||||
 | 
			
		||||
        if ret[-1] == crumb:
 | 
			
		||||
            # no config files below; remove breadcrumb
 | 
			
		||||
            ret.pop()
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    ipath += " -> " + fp
 | 
			
		||||
@@ -2135,7 +2155,7 @@ def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
 | 
			
		||||
                fp2 = ln[1:].strip()
 | 
			
		||||
                fp2 = os.path.join(os.path.dirname(fp), fp2)
 | 
			
		||||
                ofs = len(ret)
 | 
			
		||||
                expand_config_file(ret, fp2, ipath)
 | 
			
		||||
                expand_config_file(log, ret, fp2, ipath)
 | 
			
		||||
                for n in range(ofs, len(ret)):
 | 
			
		||||
                    ret[n] = pad + ret[n]
 | 
			
		||||
                continue
 | 
			
		||||
 
 | 
			
		||||
@@ -518,9 +518,13 @@ class HttpCli(object):
 | 
			
		||||
                return self.handle_options() and self.keepalive
 | 
			
		||||
 | 
			
		||||
            if not cors_k:
 | 
			
		||||
                host = self.headers.get("host", "<?>")
 | 
			
		||||
                origin = self.headers.get("origin", "<?>")
 | 
			
		||||
                self.log("cors-reject {} from {}".format(self.mode, origin), 3)
 | 
			
		||||
                raise Pebkac(403, "no surfing")
 | 
			
		||||
                proto = "https://" if self.is_https else "http://"
 | 
			
		||||
                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
 | 
			
		||||
            if self.mode == "POST":
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ if True:  # pylint: disable=using-constant-test
 | 
			
		||||
    import typing
 | 
			
		||||
    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 .cert import ensure_cert
 | 
			
		||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
 | 
			
		||||
@@ -154,6 +154,8 @@ class SvcHub(object):
 | 
			
		||||
        lg.handlers = [lh]
 | 
			
		||||
        lg.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
        self._check_env()
 | 
			
		||||
 | 
			
		||||
        if args.stackmon:
 | 
			
		||||
            start_stackmon(args.stackmon, 0)
 | 
			
		||||
 | 
			
		||||
@@ -385,6 +387,17 @@ class SvcHub(object):
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
        al = self.args
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1995,15 +1995,19 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
 | 
			
		||||
    if (tag === undefined)
 | 
			
		||||
        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'],
 | 
			
		||||
        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)
 | 
			
		||||
        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)
 | 
			
		||||
        return toast.err(0, prefix + e404, tag);
 | 
			
		||||
        return toast.err(0, prefix + e404 + suf, tag);
 | 
			
		||||
 | 
			
		||||
    if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
 | 
			
		||||
        var now = Date.now(), td = now - cf_cha_t;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user