From 389a103dcdc51188957472ae8a502c7592d05a3c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 2 Jan 2026 12:40:10 +0200 Subject: [PATCH] gh-143346: Fix calculation of the line width for wrapped Base64 in plistlib It was incorrect in case of mixed tabs and spaces in indentation. --- Lib/plistlib.py | 2 +- Lib/test/test_plistlib.py | 64 ++++++++++++++++++- ...-01-02-12-55-52.gh-issue-143346.iTekce.rst | 2 + 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-01-02-12-55-52.gh-issue-143346.iTekce.rst diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 655c51eea3da5d..5b2b4e42c95a83 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -384,7 +384,7 @@ def write_bytes(self, data): self._indent_level -= 1 maxlinelength = max( 16, - 76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level)) + 76 - len((self.indent * self._indent_level).expandtabs())) for line in _encode_base64(data, maxlinelength).split(b"\n"): if line: diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index de2a2fd1fc34bf..d9216be4d95658 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -509,6 +509,69 @@ def test_bytes(self): data2 = plistlib.dumps(pl2) self.assertEqual(data, data2) + def test_bytes_indent(self): + header = ( + b'\n' + b'\n' + b'\n') + data = [{'bytes': bytes(range(50))}] + pl = plistlib.dumps(data) + self.assertEqual(pl, header + + b'\n' + b'\t\n' + b'\t\tbytes\n' + b'\t\t\n' + b'\t\tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKiss\n' + b'\t\tLS4vMDE=\n' + b'\t\t\n' + b'\t\n' + b'\n' + b'\n') + + def dumps_with_indent(data, indent): + fp = BytesIO() + writer = plistlib._PlistWriter(fp, indent=indent) + writer.write(data) + return fp.getvalue() + + pl = dumps_with_indent(data, b' ') + self.assertEqual(pl, header + + b'\n' + b' \n' + b' bytes\n' + b' \n' + b' AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDE=\n' + b' \n' + b' \n' + b'\n' + b'\n') + + pl = dumps_with_indent(data, b' \t') + self.assertEqual(pl, header + + b'\n' + b' \t\n' + b' \t \tbytes\n' + b' \t \t\n' + b' \t \tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKiss\n' + b' \t \tLS4vMDE=\n' + b' \t \t\n' + b' \t\n' + b'\n' + b'\n') + + pl = dumps_with_indent(data, b'\t ') + self.assertEqual(pl, header + + b'\n' + b'\t \n' + b'\t \t bytes\n' + b'\t \t \n' + b'\t \t AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygp\n' + b'\t \t KissLS4vMDE=\n' + b'\t \t \n' + b'\t \n' + b'\n' + b'\n') + def test_loads_str_with_xml_fmt(self): pl = self._create() b = plistlib.dumps(pl) @@ -581,7 +644,6 @@ def test_appleformatting(self): self.assertEqual(data, TESTDATA[fmt], "generated data was not identical to Apple's output") - def test_appleformattingfromliteral(self): self.maxDiff = None for fmt in ALL_FORMATS: diff --git a/Misc/NEWS.d/next/Library/2026-01-02-12-55-52.gh-issue-143346.iTekce.rst b/Misc/NEWS.d/next/Library/2026-01-02-12-55-52.gh-issue-143346.iTekce.rst new file mode 100644 index 00000000000000..93c45eefe373d3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-02-12-55-52.gh-issue-143346.iTekce.rst @@ -0,0 +1,2 @@ +Fix incorrect wrapping of the Base64 data in :class:`!plistlib._PlistWriter` +when the indent contains a mix of tabs and spaces.