Skip to content

Commit 178ac66

Browse files
committed
gh-160598: Preserve Babyl labels when reopening mailboxes
1 parent 1159836 commit 178ac66

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

‎Lib/mailbox.py‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,9 +1426,11 @@ def _generate_toc(self):
14261426
if len(stops) < len(starts):
14271427
stops.append(line_pos - len(linesep))
14281428
starts.append(next_pos)
1429-
labels = [label.strip() for label
1430-
in self._file.readline()[1:].split(b',')
1431-
if label.strip()]
1429+
labels = [
1430+
label.strip().decode('utf-8', 'surrogateescape')
1431+
for label in self._file.readline()[1:].split(b',')
1432+
if label.strip()
1433+
]
14321434
label_lists.append(labels)
14331435
elif line == b'\037' or line == b'\037' + linesep:
14341436
if len(stops) < len(starts):
@@ -1447,7 +1449,7 @@ def _pre_mailbox_hook(self, f):
14471449
babyl = b'BABYL OPTIONS:' + linesep
14481450
babyl += b'Version: 5' + linesep
14491451
labels = self.get_labels()
1450-
labels = (label.encode() for label in labels)
1452+
labels = (label.encode('utf-8', 'surrogateescape') for label in labels)
14511453
babyl += b'Labels:' + b','.join(labels) + linesep
14521454
babyl += b'\037'
14531455
f.write(babyl)
@@ -1473,10 +1475,12 @@ def _install_message(self, message):
14731475
labels.append(label)
14741476
self._file.write(b'1')
14751477
for label in special_labels:
1476-
self._file.write(b', ' + label.encode())
1478+
self._file.write(
1479+
b', ' + label.encode('utf-8', 'surrogateescape'))
14771480
self._file.write(b',,')
14781481
for label in labels:
1479-
self._file.write(b' ' + label.encode() + b',')
1482+
self._file.write(
1483+
b' ' + label.encode('utf-8', 'surrogateescape') + b',')
14801484
self._file.write(linesep)
14811485
else:
14821486
self._file.write(b'1,,' + linesep)

‎Lib/test/test_mailbox.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,59 @@ def test_labels(self):
15211521
self._box.remove(key1)
15221522
self.assertEqual(set(self._box.get_labels()), set(['blah']))
15231523

1524+
def test_labels_reopened(self):
1525+
labels = ['unseen', 'work', 'caf\u00e9']
1526+
msg = mailbox.BabylMessage(self._template % 0)
1527+
msg.set_labels(labels)
1528+
self._box.add(msg)
1529+
self._box.add(self._template % 1)
1530+
self._box.close()
1531+
self._box = self._factory(self._path)
1532+
1533+
msg = self._box[0]
1534+
self.assertCountEqual(msg.get_labels(), labels)
1535+
self.assertCountEqual(self._box.get_labels(), ['work', 'caf\u00e9'])
1536+
self.assertNotIn('S', mailbox.MaildirMessage(msg).get_flags())
1537+
self.assertNotIn('R', mailbox.mboxMessage(msg).get_flags())
1538+
1539+
# Copying a message and rewriting the mailbox must preserve labels.
1540+
self._box.add(msg)
1541+
self._box.remove(1)
1542+
self._box.flush()
1543+
self._box.close()
1544+
self._box = self._factory(self._path)
1545+
self.assertEqual(len(self._box), 2)
1546+
for msg in self._box:
1547+
self.assertCountEqual(msg.get_labels(), labels)
1548+
self.assertCountEqual(self._box.get_labels(), ['work', 'caf\u00e9'])
1549+
1550+
def test_labels_non_utf8(self):
1551+
msg = mailbox.BabylMessage(self._template % 0)
1552+
msg.set_labels(['unseen', 'work'])
1553+
self._box.add(msg)
1554+
self._box.add(self._template % 1)
1555+
self._box.close()
1556+
with open(self._path, 'rb') as f:
1557+
data = f.read().replace(b'work', b'w\xffrk')
1558+
with open(self._path, 'wb') as f:
1559+
f.write(data)
1560+
self._box = self._factory(self._path)
1561+
1562+
labels = ['unseen', 'w\udcffrk']
1563+
msg = self._box[0]
1564+
self.assertCountEqual(msg.get_labels(), labels)
1565+
self.assertEqual(self._box.get_labels(), ['w\udcffrk'])
1566+
self._box.add(msg)
1567+
self._box.remove(1)
1568+
self._box.flush()
1569+
self._box.close()
1570+
self._box = self._factory(self._path)
1571+
self.assertEqual(len(self._box), 2)
1572+
for msg in self._box:
1573+
self.assertCountEqual(msg.get_labels(), labels)
1574+
with open(self._path, 'rb') as f:
1575+
self.assertIn(b'Labels:w\xffrk' + mailbox.linesep, f.read())
1576+
15241577
def test_visible_headers(self):
15251578
# gh-157880: visible headers are written to the file and read back
15261579
msg = mailbox.BabylMessage(self._template % 0)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`mailbox.Babyl` returning message labels as bytes after reopening a
2+
mailbox, which could cause incorrect read flags and errors when rewriting or
3+
copying messages. Preserve non-UTF-8 label bytes using ``surrogateescape``.

0 commit comments

Comments
 (0)