#11116: roll back on error during add so mailbox isn't left corrupted.
This commit is contained in:
parent
8b24aac936
commit
fc14114ca9
|
@ -277,8 +277,11 @@ class Maildir(Mailbox):
|
||||||
tmp_file = self._create_tmp()
|
tmp_file = self._create_tmp()
|
||||||
try:
|
try:
|
||||||
self._dump_message(message, tmp_file)
|
self._dump_message(message, tmp_file)
|
||||||
finally:
|
except BaseException:
|
||||||
_sync_close(tmp_file)
|
tmp_file.close()
|
||||||
|
os.remove(tmp_file.name)
|
||||||
|
raise
|
||||||
|
_sync_close(tmp_file)
|
||||||
if isinstance(message, MaildirMessage):
|
if isinstance(message, MaildirMessage):
|
||||||
subdir = message.get_subdir()
|
subdir = message.get_subdir()
|
||||||
suffix = self.colon + message.get_info()
|
suffix = self.colon + message.get_info()
|
||||||
|
@ -724,9 +727,14 @@ class _singlefileMailbox(Mailbox):
|
||||||
def _append_message(self, message):
|
def _append_message(self, message):
|
||||||
"""Append message to mailbox and return (start, stop) offsets."""
|
"""Append message to mailbox and return (start, stop) offsets."""
|
||||||
self._file.seek(0, 2)
|
self._file.seek(0, 2)
|
||||||
self._pre_message_hook(self._file)
|
before = self._file.tell()
|
||||||
offsets = self._install_message(message)
|
try:
|
||||||
self._post_message_hook(self._file)
|
self._pre_message_hook(self._file)
|
||||||
|
offsets = self._install_message(message)
|
||||||
|
self._post_message_hook(self._file)
|
||||||
|
except BaseException:
|
||||||
|
self._file.truncate(before)
|
||||||
|
raise
|
||||||
self._file.flush()
|
self._file.flush()
|
||||||
self._file_length = self._file.tell() # Record current length of mailbox
|
self._file_length = self._file.tell() # Record current length of mailbox
|
||||||
return offsets
|
return offsets
|
||||||
|
@ -906,7 +914,11 @@ class MH(Mailbox):
|
||||||
if self._locked:
|
if self._locked:
|
||||||
_lock_file(f)
|
_lock_file(f)
|
||||||
try:
|
try:
|
||||||
self._dump_message(message, f)
|
try:
|
||||||
|
self._dump_message(message, f)
|
||||||
|
except BaseException:
|
||||||
|
os.remove(new_path)
|
||||||
|
raise
|
||||||
if isinstance(message, MHMessage):
|
if isinstance(message, MHMessage):
|
||||||
self._dump_sequences(message, new_key)
|
self._dump_sequences(message, new_key)
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -107,9 +107,22 @@ class TestMailbox(TestBase):
|
||||||
'Subject: =?unknown-8bit?b?RmFsaW5hcHThciBo4Xpob3pzeuFsbO104XNz'
|
'Subject: =?unknown-8bit?b?RmFsaW5hcHThciBo4Xpob3pzeuFsbO104XNz'
|
||||||
'YWwuIE3hciByZW5kZWx06Ww/?=\n\n')
|
'YWwuIE3hciByZW5kZWx06Ww/?=\n\n')
|
||||||
|
|
||||||
def test_add_nonascii_header_raises(self):
|
def test_add_nonascii_string_header_raises(self):
|
||||||
with self.assertRaisesRegex(ValueError, "ASCII-only"):
|
with self.assertRaisesRegex(ValueError, "ASCII-only"):
|
||||||
self._box.add(self._nonascii_msg)
|
self._box.add(self._nonascii_msg)
|
||||||
|
self._box.flush()
|
||||||
|
self.assertEqual(len(self._box), 0)
|
||||||
|
self.assertMailboxEmpty()
|
||||||
|
|
||||||
|
def test_add_that_raises_leaves_mailbox_empty(self):
|
||||||
|
# XXX This test will start failing when Message learns to handle
|
||||||
|
# non-ASCII string headers, and a different internal failure will
|
||||||
|
# need to be found or manufactured.
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self._box.add(email.message_from_string("From: Alphöso"))
|
||||||
|
self.assertEqual(len(self._box), 0)
|
||||||
|
self._box.close()
|
||||||
|
self.assertMailboxEmpty()
|
||||||
|
|
||||||
_non_latin_bin_msg = textwrap.dedent("""\
|
_non_latin_bin_msg = textwrap.dedent("""\
|
||||||
From: foo@bar.com
|
From: foo@bar.com
|
||||||
|
@ -174,6 +187,9 @@ class TestMailbox(TestBase):
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertWarns(DeprecationWarning):
|
||||||
with self.assertRaisesRegex(ValueError, "ASCII-only"):
|
with self.assertRaisesRegex(ValueError, "ASCII-only"):
|
||||||
self._box.add(io.StringIO(self._nonascii_msg))
|
self._box.add(io.StringIO(self._nonascii_msg))
|
||||||
|
self.assertEqual(len(self._box), 0)
|
||||||
|
self._box.close()
|
||||||
|
self.assertMailboxEmpty()
|
||||||
|
|
||||||
def test_remove(self):
|
def test_remove(self):
|
||||||
# Remove messages using remove()
|
# Remove messages using remove()
|
||||||
|
@ -571,6 +587,9 @@ class TestMaildir(TestMailbox):
|
||||||
if os.name in ('nt', 'os2') or sys.platform == 'cygwin':
|
if os.name in ('nt', 'os2') or sys.platform == 'cygwin':
|
||||||
self._box.colon = '!'
|
self._box.colon = '!'
|
||||||
|
|
||||||
|
def assertMailboxEmpty(self):
|
||||||
|
self.assertEqual(os.listdir(os.path.join(self._path, 'tmp')), [])
|
||||||
|
|
||||||
def test_add_MM(self):
|
def test_add_MM(self):
|
||||||
# Add a MaildirMessage instance
|
# Add a MaildirMessage instance
|
||||||
msg = mailbox.MaildirMessage(self._template % 0)
|
msg = mailbox.MaildirMessage(self._template % 0)
|
||||||
|
@ -890,6 +909,10 @@ class _TestMboxMMDF(TestMailbox):
|
||||||
for lock_remnant in glob.glob(self._path + '.*'):
|
for lock_remnant in glob.glob(self._path + '.*'):
|
||||||
support.unlink(lock_remnant)
|
support.unlink(lock_remnant)
|
||||||
|
|
||||||
|
def assertMailboxEmpty(self):
|
||||||
|
with open(self._path) as f:
|
||||||
|
self.assertEqual(f.readlines(), [])
|
||||||
|
|
||||||
def test_add_from_string(self):
|
def test_add_from_string(self):
|
||||||
# Add a string starting with 'From ' to the mailbox
|
# Add a string starting with 'From ' to the mailbox
|
||||||
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0')
|
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0')
|
||||||
|
@ -1012,6 +1035,9 @@ class TestMH(TestMailbox):
|
||||||
|
|
||||||
_factory = lambda self, path, factory=None: mailbox.MH(path, factory)
|
_factory = lambda self, path, factory=None: mailbox.MH(path, factory)
|
||||||
|
|
||||||
|
def assertMailboxEmpty(self):
|
||||||
|
self.assertEqual(os.listdir(self._path), ['.mh_sequences'])
|
||||||
|
|
||||||
def test_list_folders(self):
|
def test_list_folders(self):
|
||||||
# List folders
|
# List folders
|
||||||
self._box.add_folder('one')
|
self._box.add_folder('one')
|
||||||
|
@ -1144,6 +1170,10 @@ class TestBabyl(TestMailbox):
|
||||||
|
|
||||||
_factory = lambda self, path, factory=None: mailbox.Babyl(path, factory)
|
_factory = lambda self, path, factory=None: mailbox.Babyl(path, factory)
|
||||||
|
|
||||||
|
def assertMailboxEmpty(self):
|
||||||
|
with open(self._path) as f:
|
||||||
|
self.assertEqual(f.readlines(), [])
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
self._box.close()
|
self._box.close()
|
||||||
|
|
|
@ -22,6 +22,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #11116: any error during addition of a message to a mailbox now causes
|
||||||
|
a rollback, instead of leaving the mailbox partially modified.
|
||||||
|
|
||||||
- Issue #11132: Fix passing of "optimize" parameter when recursing
|
- Issue #11132: Fix passing of "optimize" parameter when recursing
|
||||||
in compileall.compile_dir().
|
in compileall.compile_dir().
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue