Skip to content

Commit 74d1f3b

Browse files
authored
Prevent {} and tighten parsing of {:} (#1715)
Fixes #1711
1 parent 5cc7da8 commit 74d1f3b

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

docs/changelog/1711.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent ``{}`` and require ``{:`` is only followed by ``}``. - by :user:`jayvdb`

src/tox/config/__init__.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,18 +1819,24 @@ def _replace_match(self, match):
18191819
start, end = match.span()
18201820
return match.string[start:end]
18211821

1822-
# special case: all empty values means ":" which is os.pathsep
1823-
if not any(g.values()):
1822+
full_match = match.group(0)
1823+
# ":" is swallowed by the regex, so the raw matched string is checked
1824+
if full_match.startswith("{:"):
1825+
if full_match != "{:}":
1826+
raise tox.exception.ConfigError(
1827+
"Malformed substitution with prefix ':': {}".format(full_match),
1828+
)
1829+
18241830
return os.pathsep
18251831

18261832
if sub_value == "posargs":
18271833
return self.reader.getposargs(match.group("default_value"))
18281834

1829-
try:
1830-
sub_type = g["sub_type"]
1831-
except KeyError:
1835+
sub_type = g["sub_type"]
1836+
if not sub_type and not sub_value:
18321837
raise tox.exception.ConfigError(
1833-
"Malformed substitution; no substitution type provided",
1838+
"Malformed substitution; no substitution type provided. "
1839+
"If you were using `{}` for `os.pathsep`, please use `{:}`.",
18341840
)
18351841

18361842
if not sub_type and not g["default_value"] and sub_value == "/":

tests/unit/config/test_config.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,33 @@ def test_getstring_single(self, newconfig):
603603
x = reader.getstring("hello", "world")
604604
assert x == "world"
605605

606+
def test_substitution_empty(self, newconfig):
607+
config = newconfig(
608+
"""
609+
[mydefault]
610+
key2={}
611+
""",
612+
)
613+
reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"])
614+
assert reader is not None
615+
with pytest.raises(tox.exception.ConfigError, match="no substitution type provided"):
616+
reader.getstring("key2")
617+
618+
def test_substitution_colon_prefix(self, newconfig):
619+
config = newconfig(
620+
"""
621+
[mydefault]
622+
key2={:abc}
623+
""",
624+
)
625+
reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"])
626+
assert reader is not None
627+
with pytest.raises(
628+
tox.exception.ConfigError,
629+
match="Malformed substitution with prefix ':'",
630+
):
631+
reader.getstring("key2")
632+
606633
def test_missing_substitution(self, newconfig):
607634
config = newconfig(
608635
"""
@@ -612,7 +639,7 @@ def test_missing_substitution(self, newconfig):
612639
)
613640
reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"])
614641
assert reader is not None
615-
with pytest.raises(tox.exception.ConfigError):
642+
with pytest.raises(tox.exception.ConfigError, match="substitution key '.*' not found"):
616643
reader.getstring("key2")
617644

618645
def test_getstring_fallback_sections(self, newconfig):

0 commit comments

Comments
 (0)