Skip to content

Commit eff9bbb

Browse files
authored
fix(config_file): refuse breaklines in config dict (jxmorris12#169)
1 parent 210a8a2 commit eff9bbb

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

language_tool_python/config_file.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@
1212
logger = logging.getLogger(__name__)
1313

1414

15+
def _reject_line_breaks(field_name: str, value: str) -> None:
16+
"""
17+
Reject values that would break the one-option-per-line config format.
18+
19+
:param field_name: The name of the configuration field being validated.
20+
:type field_name: str
21+
:param value: The value of the configuration field to validate.
22+
:type value: str
23+
:raises ValueError: If the value contains line break characters or ends with an odd number of backslashes.
24+
"""
25+
if "\n" in value or "\r" in value:
26+
err = f"config {field_name} cannot contain line breaks"
27+
raise ValueError(err)
28+
29+
trailing_backslashes = len(value) - len(value.rstrip("\\"))
30+
if trailing_backslashes % 2 == 1:
31+
err = f"config {field_name} cannot end with an odd number of backslashes"
32+
raise ValueError(err)
33+
34+
1535
@dataclass(frozen=True)
1636
class OptionSpec:
1737
"""
@@ -176,14 +196,17 @@ def _encode_config(config: Dict[str, Any]) -> Dict[str, str]:
176196
logger.debug("Encoding LanguageTool config with keys: %s", list(config.keys()))
177197
encoded: Dict[str, str] = {}
178198
for key, value in config.items():
199+
_reject_line_breaks("key", key)
179200
if _is_lang_key(key) and key.count("-") == 1: # lang-<code>
180201
logger.debug("Encoding language option %s=%r", key, value)
181202
encoded[key] = str(value)
203+
_reject_line_breaks(key, encoded[key])
182204
continue
183205
if _is_lang_key(key) and key.count("-") == 2: # lang-<code>-dictPath
184206
logger.debug("Encoding language dictPath %s=%r", key, value)
185207
_path_validator(value)
186208
encoded[key] = _path_encoder(value)
209+
_reject_line_breaks(key, encoded[key])
187210
continue
188211

189212
spec = CONFIG_SCHEMA.get(key)
@@ -197,6 +220,7 @@ def _encode_config(config: Dict[str, Any]) -> Dict[str, str]:
197220
if spec.validator is not None:
198221
spec.validator(value)
199222
encoded[key] = spec.encoder(value)
223+
_reject_line_breaks(key, encoded[key])
200224
return encoded
201225

202226

tests/test_config.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77

8+
from language_tool_python.config_file import LanguageToolConfig
89
from language_tool_python.exceptions import LanguageToolError
910

1011

@@ -175,3 +176,37 @@ def test_disabled_rule_in_config() -> None:
175176
text = "He realised that the organization was in jeopardy."
176177
matches = tool.check(text)
177178
assert len(matches) == 0
179+
180+
181+
@pytest.mark.parametrize( # type: ignore[untyped-decorator]
182+
"config",
183+
[
184+
{"blockedReferrers": "example.com\ntrustXForwardForHeader=true"},
185+
{"disabledRuleIds": ["MORFOLOGIK_RULE_EN_US", "SAFE\rrequestLimit=0"]},
186+
{"lang-en\ntrustXForwardForHeader": "true"},
187+
{"lang-en": "custom-word\nrequestLimit=0"},
188+
],
189+
)
190+
def test_config_rejects_line_break_injection(config: dict[str, object]) -> None:
191+
"""
192+
Test that config serialization cannot be escaped with CR/LF characters.
193+
"""
194+
with pytest.raises(ValueError, match="cannot contain line breaks"):
195+
LanguageToolConfig(config)
196+
197+
198+
@pytest.mark.parametrize( # type: ignore[untyped-decorator]
199+
"config",
200+
[
201+
{"blockedReferrers": "example.com\\"},
202+
{"disabledRuleIds": ["MORFOLOGIK_RULE_EN_US", "SAFE\\"]},
203+
{"lang-en\\": "true"},
204+
{"lang-en": "custom-word\\"},
205+
],
206+
)
207+
def test_config_rejects_odd_trailing_backslashes(config: dict[str, object]) -> None:
208+
"""
209+
Test that config serialization cannot escape the line ending with a backslash.
210+
"""
211+
with pytest.raises(ValueError, match="odd number of backslashes"):
212+
LanguageToolConfig(config)

0 commit comments

Comments
 (0)