From 1792c8fb5fbca5f2b77d296c2a223ce67586dba3 Mon Sep 17 00:00:00 2001 From: "Nor.na" <2573438329@qq.com> Date: Sun, 27 Sep 2026 09:51:37 +0800 Subject: [PATCH] Escape content appended to a String via __add__ String.__add__ spliced the appended text into the raw literal without escaping it, so appending a quote or backslash to a basic string produced a literal that is not valid TOML - including to this library's own parser. Escape the appended plain text with the string type's escape sequences (a String operand keeps contributing its already-escaped raw form), mirroring what from_raw already does. --- tests/test_items.py | 28 ++++++++++++++++++++++++++++ tomlkit/items.py | 8 +++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_items.py b/tests/test_items.py index d7c06fbf..2b07e123 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -830,6 +830,34 @@ def test_string_add_preserve_escapes() -> None: assert i.as_string() == '"foo\\"bar baz"' +def test_string_add_escapes_appended_content() -> None: + # Appending a character that must be escaped in a basic string used + # to be spliced into the raw value unescaped, producing a literal + # that no TOML parser (including this one) can read back. + i = item("foo") + i += '"' + assert i == 'foo"' + assert i.as_string() == '"foo\\""' + assert parse(f"k = {i.as_string()}")["k"] == 'foo"' + + i = item("foo") + i += "\\" + assert i == "foo\\" + assert i.as_string() == '"foo\\\\"' + assert parse(f"k = {i.as_string()}")["k"] == "foo\\" + + # Appending a String splices its raw (already escaped) form. + j = item("foo") + j += item('"') + assert j == 'foo"' + assert j.as_string() == '"foo\\""' + + # Plain text without special characters is appended verbatim. + k = item("foo") + k += " bar" + assert k.as_string() == '"foo bar"' + + def test_tables_behave_like_dicts() -> None: t = item({"foo": "bar"}) diff --git a/tomlkit/items.py b/tomlkit/items.py index c950e5d0..2afda856 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -2223,7 +2223,13 @@ def type(self) -> StringType: def __add__(self, other: str) -> String: result = super().__add__(other) - original = self._original + getattr(other, "_original", other) + if isinstance(other, String): + original = self._original + other._original + else: + escaped = self._t.escaped_sequences + original = self._original + ( + escape_string(other, escaped) if escaped else other + ) return self._new(result, original)