use ${ENV} syntax for env-vars;

only expand environment-variables of the form ${ENV}

by default, crash on startup if the old $ENV syntax is found,
explaning that the old syntax can be enabled with an option
This commit is contained in:
ed 2026-04-20 23:43:00 +02:00
parent b31f29024a
commit cbd82b654a
9 changed files with 101 additions and 23 deletions

View file

@ -2542,6 +2542,7 @@ buggy feature? rip it out by setting any of the following environment variables
| -------------------- | ------------ |
| `PRTY_NO_CTYPES` | do not use features from external libraries such as kernel32 |
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
| `PRTY_NO_ENVEXPAND` | do not expand environment-variables in configs and args |
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |

View file

@ -8,7 +8,7 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
# q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
# q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)

View file

@ -16,7 +16,7 @@
# and copyparty replaces %Y-%m%d with Year-MonthDay, so the
# full path will be something like /var/log/copyparty/2023-1130.txt
# (note: enable compression by adding .xz at the end)
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
q, lo: ${LOGS_DIRECTORY}/%Y-%m%d.log
# enable version-checker by uncommenting one of the 'vc-url' lines below; this will
# periodically check if your copyparty version has a known security vulnerability,

View file

@ -65,6 +65,10 @@ from .util import (
b64enc,
ctypes,
dedent,
expand_osenv_c,
expand_osenv_cs,
expand_osenv_noop,
expand_osenv_s,
has_resource,
load_resource,
min_ex,
@ -427,9 +431,22 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
sys.exit(0)
def expand_cvars(argv) -> list[str]:
n = 0
for v in argv:
if "=" in v:
a, b = v.split("=", 1)
v = "%s=%s" % (a, os.path.expanduser(expand_osenv_c(b)))
else:
v = os.path.expanduser(expand_osenv_c(v))
argv[n] = v
n += 1
return argv
def args_from_cfg(cfg_path: str) -> list[str]:
lines: list[str] = []
expand_config_file(None, lines, cfg_path, "")
expand_config_file(None, expand_osenv_c, lines, cfg_path, "")
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
ret: list[str] = []
@ -453,10 +470,12 @@ def args_from_cfg(cfg_path: str) -> list[str]:
else:
ret.append(prefix + k + "=" + v)
return ret
return expand_cvars(ret)
def expand_cfg(argv) -> list[str]:
argv = expand_cvars(argv)
if CFG_DEF:
supp = args_from_cfg(CFG_DEF[0])
argv = argv[:1] + supp + argv[1:]
@ -1197,6 +1216,7 @@ def add_general(ap, nc, srvname):
ap2.add_argument("--name-url", metavar="TXT", type=u, help="URL for server name hyperlink (displayed topleft in browser)")
ap2.add_argument("--name-html", type=u, help=argparse.SUPPRESS)
ap2.add_argument("--site", metavar="URL", type=u, default="", help="public URL to assume when creating links; example: [\033[32mhttps://example.com/\033[0m]")
ap2.add_argument("--env-expand", metavar="N", type=int, default=-1, help="syntax to expect for environment-variables to expand in config-files; [\033[32m0\033[0m]=disable, [\033[32m1\033[0m]=$VAR (old syntax (scary)), [\033[32m2\033[0m]=${VAR} (new syntax (recommended))")
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="\033[34mREPEATABLE:\033[0m map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
@ -2179,6 +2199,15 @@ def main(argv: Optional[list[str]] = None) -> None:
quotecheck(al)
if al.env_expand == 2:
al.shenvexp = expand_osenv_c
elif al.env_expand == 1:
al.shenvexp = expand_osenv_s
elif al.env_expand == 0:
al.shenvexp = expand_osenv_noop
else:
al.shenvexp = expand_osenv_cs
if al.chdir:
os.chdir(al.chdir)

View file

@ -56,7 +56,7 @@ if HAVE_SQLITE3:
if True: # pylint: disable=using-constant-test
from collections.abc import Iterable
from typing import Any, Generator, Optional, Sequence, Union
from typing import Any, Callable, Generator, Optional, Sequence, Union
from .util import NamedLogger, RootLogger
@ -541,13 +541,11 @@ class VFS(object):
hist = flags.get("hist")
if hist and hist != "-":
zs = "{}/{}".format(hist.rstrip("/"), name)
flags["hist"] = os.path.expandvars(os.path.expanduser(zs))
flags["hist"] = "%s/%s" % (hist.rstrip("/"), name)
dbp = flags.get("dbpath")
if dbp and dbp != "-":
zs = "{}/{}".format(dbp.rstrip("/"), name)
flags["dbpath"] = os.path.expandvars(os.path.expanduser(zs))
flags["dbpath"] = "%s/%s" % (dbp.rstrip("/"), name)
return flags
@ -1279,7 +1277,7 @@ class AuthSrv(object):
daxs: dict[str, AXS],
mflags: dict[str, dict[str, Any]],
) -> tuple[str, str]:
src = os.path.expandvars(os.path.expanduser(src))
src = os.path.expanduser(self.args.shenvexp(src))
src = absreal(src)
dst = dst.strip("/")
@ -1372,7 +1370,7 @@ class AuthSrv(object):
) -> None:
self.line_ctr = 0
expand_config_file(self.log, cfg_lines, fp, "")
expand_config_file(self.log, self.args.shenvexp, 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))
@ -2174,7 +2172,7 @@ class AuthSrv(object):
if vflag == "-":
pass
elif vflag:
vflag = os.path.expandvars(os.path.expanduser(vflag))
vflag = os.path.expanduser(self.args.shenvexp(vflag))
vol.histpath = vol.dbpath = uncyg(vflag) if WINDOWS else vflag
elif self.args.hist:
for nch in range(len(hid)):
@ -2209,7 +2207,7 @@ class AuthSrv(object):
if vflag == "-":
pass
elif vflag:
vflag = os.path.expandvars(os.path.expanduser(vflag))
vflag = os.path.expanduser(self.args.shenvexp(vflag))
vol.dbpath = uncyg(vflag) if WINDOWS else vflag
elif self.args.dbpath:
for nch in range(len(hid)):
@ -3964,10 +3962,14 @@ def split_cfg_ln(ln: str) -> dict[str, Any]:
def expand_config_file(
log: Optional["NamedLogger"], ret: list[str], fp: str, ipath: str
log: Optional["NamedLogger"],
shenvexp: "Callable[[str], str]",
ret: list[str],
fp: str,
ipath: str,
) -> None:
"""expand all % file includes"""
fp = absreal(fp)
fp = absreal(os.path.expanduser(shenvexp(fp)))
if len(ipath.split(" -> ")) > 64:
raise Exception("hit max depth of 64 includes")
@ -3998,7 +4000,7 @@ def expand_config_file(
if fp2 in ipath:
continue
expand_config_file(log, ret, fp2, ipath)
expand_config_file(log, shenvexp, ret, fp2, ipath)
return
@ -4023,7 +4025,7 @@ def expand_config_file(
fp2 = ln[1:].strip()
fp2 = os.path.join(os.path.dirname(fp), fp2)
ofs = len(ret)
expand_config_file(log, ret, fp2, ipath)
expand_config_file(log, shenvexp, ret, fp2, ipath)
for n in range(ofs, len(ret)):
ret[n] = pad + ret[n]
continue

View file

@ -17,6 +17,7 @@ from .util import (
FFMPEG_URL,
REKOBO_LKEY,
VF_CAREFUL,
expand_osenv_c,
fsenc,
gzip,
min_ex,
@ -86,7 +87,7 @@ class MParser(object):
while True:
try:
bp = os.path.expanduser(args)
bp = os.path.expanduser(expand_osenv_c(args))
if WINDOWS:
bp = uncyg(bp)

View file

@ -1133,17 +1133,23 @@ class SvcHub(object):
al.th_coversd_set = set(al.th_coversd)
for k in "c".split(" "):
if self.args.env_expand in (0, 2):
break
vl = getattr(al, k)
if not vl:
continue
vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
vl = [os.path.expanduser(self.args.shenvexp(x)) for x in vl]
setattr(al, k, vl)
for k in "lo hist dbpath ssl_log".split(" "):
if self.args.env_expand in (0, 2):
break
vs = getattr(al, k)
if vs:
vs = os.path.expandvars(os.path.expanduser(vs))
vs = os.path.expanduser(self.args.shenvexp(vs))
setattr(al, k, vs)
for k in "idp_adm stats_u".split(" "):

View file

@ -1562,6 +1562,43 @@ def dedent(txt: str) -> str:
return "\n".join([ln[pad:] for ln in lns])
def expand_osenv_noop(txt) -> str:
return txt
def _expand_osenv_c(txt) -> str:
if "${" not in txt:
return txt
zsl = txt.split("${")
ret = zsl[0]
for v in zsl[1:]:
if "}" not in v:
raise Exception("missing '}' after %r in config-value %r" % (v, txt))
a, b = v.split("}", 1)
try:
ret += os.environ[a] + b
except:
raise Exception("env-var %r not defined; config-value %r" % (a, txt))
return ret
if os.environ.get("PRTY_NO_ENVEXPAND"):
expand_osenv_c = expand_osenv_noop
expand_osenv_s = expand_osenv_noop
else:
expand_osenv_c = _expand_osenv_c
expand_osenv_s = os.path.expandvars
def expand_osenv_cs(txt) -> str:
a = expand_osenv_c(txt)
b = expand_osenv_s(txt)
if a == b:
return a
t = "config-value %r is using the old syntax for environment-variables; choose one of the following options:\noption 1: update the config-value to the new syntax, ${VAR} instead of $VAR or %%VAR%%\noption 2: tell copyparty to allow the old syntax with global-option --env-expand 1 (risky)\noption 3: tell copyparty to only use the new syntax (and not expand this variable) with global-option --env-expand 2\noption 4: disable all environment-variable expansions with PRTY_NO_ENVEXPAND=1 or global-option --env-expand 0"
raise Exception(t % (txt,))
def rice_tid() -> str:
tid = threading.current_thread().ident
c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:])
@ -3847,7 +3884,7 @@ def _parsehook(
argv = cmd.split(",") if "," in cmd else [cmd]
argv[0] = os.path.expandvars(os.path.expanduser(argv[0]))
argv[0] = os.path.expanduser(expand_osenv_c(argv[0]))
return areq, chk, imp, fork, sin, jtxt, wait, sp_ka, argv
@ -4190,7 +4227,7 @@ def loadpy(ap: str, hot: bool) -> Any:
depending on what other inconveniently named files happen
to be in the same folder
"""
ap = os.path.expandvars(os.path.expanduser(ap))
ap = os.path.expanduser(expand_osenv_c(ap))
mdir, mfile = os.path.split(absreal(ap))
mname = mfile.rsplit(".", 1)[0]
sys.path.insert(0, mdir)

View file

@ -38,7 +38,7 @@ from copyparty.broker_thr import BrokerThr
from copyparty.ico import Ico
from copyparty.u2idx import U2idx
from copyparty.up2k import Up2k
from copyparty.util import FHC, CachedDict, Garda, Unrecv
from copyparty.util import FHC, CachedDict, Garda, Unrecv, expand_osenv_c
init_E(E)
@ -195,6 +195,7 @@ class Cfg(Namespace):
du_who="all",
dk_salt="b" * 16,
fk_salt="a" * 16,
env_expand=2,
fsnt="lin",
grp_all="acct",
idp_gsep=re.compile("[|:;+,]"),
@ -217,6 +218,7 @@ class Cfg(Namespace):
rw_edit="md",
s_rd_sz=256 * 1024,
s_wr_sz=256 * 1024,
shenvexp=expand_osenv_c,
shr_who="auth",
sort="href",
srch_hits=99999,