Index: Lib/mailbox.py =================================================================== --- Lib/mailbox.py (revision 88349) +++ Lib/mailbox.py (working copy) @@ -277,7 +277,11 @@ tmp_file = self._create_tmp() try: self._dump_message(message, tmp_file) - finally: + except Exception: + tmp_file.close() + os.remove(tmp_file.name) + raise + else: _sync_close(tmp_file) if isinstance(message, MaildirMessage): subdir = message.get_subdir() @@ -724,9 +728,14 @@ def _append_message(self, message): """Append message to mailbox and return (start, stop) offsets.""" self._file.seek(0, 2) - self._pre_message_hook(self._file) - offsets = self._install_message(message) - self._post_message_hook(self._file) + before = self._file.tell() + try: + self._pre_message_hook(self._file) + offsets = self._install_message(message) + self._post_message_hook(self._file) + except Exception: + self._file.truncate(before) + raise self._file.flush() self._file_length = self._file.tell() # Record current length of mailbox return offsets @@ -906,7 +915,11 @@ if self._locked: _lock_file(f) try: - self._dump_message(message, f) + try: + self._dump_message(message, f) + except Exception: + os.remove(new_path) + raise if isinstance(message, MHMessage): self._dump_sequences(message, new_key) finally: Index: Lib/test/test_mailbox.py =================================================================== --- Lib/test/test_mailbox.py (revision 88349) +++ Lib/test/test_mailbox.py (working copy) @@ -107,10 +107,23 @@ 'Subject: =?unknown-8bit?b?RmFsaW5hcHThciBo4Xpob3pzeuFsbO104XNz' 'YWwuIE3hciByZW5kZWx06Ww/?=\n\n') - def test_add_nonascii_header_raises(self): + def test_add_nonascii_string_header_raises(self): with self.assertRaisesRegex(ValueError, "ASCII-only"): 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("""\ From: foo@bar.com To: báz @@ -174,6 +187,9 @@ with self.assertWarns(DeprecationWarning): with self.assertRaisesRegex(ValueError, "ASCII-only"): self._box.add(io.StringIO(self._nonascii_msg)) + self.assertEqual(len(self._box), 0) + self._box.close() + self.assertMailboxEmpty() def test_remove(self): # Remove messages using remove() @@ -571,6 +587,9 @@ if os.name in ('nt', 'os2') or sys.platform == 'cygwin': self._box.colon = '!' + def assertMailboxEmpty(self): + self.assertEqual(os.listdir(os.path.join(self._path, 'tmp')), []) + def test_add_MM(self): # Add a MaildirMessage instance msg = mailbox.MaildirMessage(self._template % 0) @@ -890,6 +909,10 @@ for lock_remnant in glob.glob(self._path + '.*'): support.unlink(lock_remnant) + def assertMailboxEmpty(self): + with open(self._path) as f: + self.assertEqual(f.readlines(), []) + def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox key = self._box.add('From foo@bar blah\nFrom: foo\n\n0') @@ -1012,6 +1035,9 @@ _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): # List folders self._box.add_folder('one') @@ -1144,6 +1170,10 @@ _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): super().tearDown() self._box.close()