diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 5bb9843eba31b4..3ac10d989b0f91 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -1426,9 +1426,11 @@ def _generate_toc(self): if len(stops) < len(starts): stops.append(line_pos - len(linesep)) starts.append(next_pos) - labels = [label.strip() for label - in self._file.readline()[1:].split(b',') - if label.strip()] + labels = [ + label.strip().decode('utf-8', 'surrogateescape') + for label in self._file.readline()[1:].split(b',') + if label.strip() + ] label_lists.append(labels) elif line == b'\037' or line == b'\037' + linesep: if len(stops) < len(starts): @@ -1447,7 +1449,7 @@ def _pre_mailbox_hook(self, f): babyl = b'BABYL OPTIONS:' + linesep babyl += b'Version: 5' + linesep labels = self.get_labels() - labels = (label.encode() for label in labels) + labels = (label.encode('utf-8', 'surrogateescape') for label in labels) babyl += b'Labels:' + b','.join(labels) + linesep babyl += b'\037' f.write(babyl) @@ -1473,10 +1475,12 @@ def _install_message(self, message): labels.append(label) self._file.write(b'1') for label in special_labels: - self._file.write(b', ' + label.encode()) + self._file.write( + b', ' + label.encode('utf-8', 'surrogateescape')) self._file.write(b',,') for label in labels: - self._file.write(b' ' + label.encode() + b',') + self._file.write( + b' ' + label.encode('utf-8', 'surrogateescape') + b',') self._file.write(linesep) else: self._file.write(b'1,,' + linesep) diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index f1100f98ab9a06..b22de2ed8add2a 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -1521,6 +1521,59 @@ def test_labels(self): self._box.remove(key1) self.assertEqual(set(self._box.get_labels()), set(['blah'])) + def test_labels_reopened(self): + labels = ['unseen', 'work', 'caf\u00e9'] + msg = mailbox.BabylMessage(self._template % 0) + msg.set_labels(labels) + self._box.add(msg) + self._box.add(self._template % 1) + self._box.close() + self._box = self._factory(self._path) + + msg = self._box[0] + self.assertCountEqual(msg.get_labels(), labels) + self.assertCountEqual(self._box.get_labels(), ['work', 'caf\u00e9']) + self.assertNotIn('S', mailbox.MaildirMessage(msg).get_flags()) + self.assertNotIn('R', mailbox.mboxMessage(msg).get_flags()) + + # Copying a message and rewriting the mailbox must preserve labels. + self._box.add(msg) + self._box.remove(1) + self._box.flush() + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 2) + for msg in self._box: + self.assertCountEqual(msg.get_labels(), labels) + self.assertCountEqual(self._box.get_labels(), ['work', 'caf\u00e9']) + + def test_labels_non_utf8(self): + msg = mailbox.BabylMessage(self._template % 0) + msg.set_labels(['unseen', 'work']) + self._box.add(msg) + self._box.add(self._template % 1) + self._box.close() + with open(self._path, 'rb') as f: + data = f.read().replace(b'work', b'w\xffrk') + with open(self._path, 'wb') as f: + f.write(data) + self._box = self._factory(self._path) + + labels = ['unseen', 'w\udcffrk'] + msg = self._box[0] + self.assertCountEqual(msg.get_labels(), labels) + self.assertEqual(self._box.get_labels(), ['w\udcffrk']) + self._box.add(msg) + self._box.remove(1) + self._box.flush() + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 2) + for msg in self._box: + self.assertCountEqual(msg.get_labels(), labels) + with open(self._path, 'rb') as f: + self.assertIn(b'Labels:w\xffrk' + mailbox.linesep, f.read()) + def test_visible_headers(self): # gh-157880: visible headers are written to the file and read back msg = mailbox.BabylMessage(self._template % 0) diff --git a/Misc/NEWS.d/next/Library/2026-10-25-00-00-00.gh-issue-160598.Babyl.rst b/Misc/NEWS.d/next/Library/2026-10-25-00-00-00.gh-issue-160598.Babyl.rst new file mode 100644 index 00000000000000..33f34c424bfb03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-25-00-00-00.gh-issue-160598.Babyl.rst @@ -0,0 +1,3 @@ +Fix :class:`mailbox.Babyl` returning message labels as bytes after reopening a +mailbox, which could cause incorrect read flags and errors when rewriting or +copying messages. Preserve non-UTF-8 label bytes using ``surrogateescape``.