Skip to content

Commit 032bab4

Browse files
committed
update
Serialize the replacement to a temporary file before overwriting the existing MH message, so invalid input or input read errors leave the original message unchanged. Co-Authored-By: lipengyu <lipengyu@kylinos.cn>
1 parent d97dbce commit 032bab4

2 files changed

Lines changed: 23 additions & 32 deletions

File tree

Lib/mailbox.py

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import email.generator
1919
import io
2020
import contextlib
21+
import shutil
22+
import tempfile
2123
from types import GenericAlias
2224
try:
2325
import fcntl
@@ -1069,44 +1071,22 @@ def __setitem__(self, key, message):
10691071
raise KeyError('No message with key: %s' % key)
10701072
else:
10711073
raise
1072-
file_closed = False
10731074
try:
10741075
if self._locked:
10751076
_lock_file(f)
10761077
try:
1077-
new_file = _create_temporary(path)
1078-
try:
1078+
with tempfile.TemporaryFile(mode='w+b') as new_file:
10791079
self._dump_message(message, new_file)
1080-
_sync_close(new_file)
1081-
info = os.fstat(f.fileno())
1082-
try:
1083-
os.chown(new_file.name, info.st_uid, info.st_gid)
1084-
except (AttributeError, OSError):
1085-
pass
1086-
os.chmod(new_file.name, info.st_mode)
1087-
if os.name == 'nt':
1088-
# Windows cannot replace an open file.
1089-
f.close()
1090-
file_closed = True
1091-
os.replace(new_file.name, path)
1092-
except BaseException:
1093-
try:
1094-
new_file.close()
1095-
except OSError:
1096-
pass
1097-
try:
1098-
os.remove(new_file.name)
1099-
except OSError:
1100-
pass
1101-
raise
1080+
new_file.seek(0)
1081+
os.close(os.open(path, os.O_WRONLY | os.O_TRUNC))
1082+
shutil.copyfileobj(new_file, f)
11021083
if isinstance(message, MHMessage):
11031084
self._dump_sequences(message, key)
11041085
finally:
11051086
if self._locked:
11061087
_unlock_file(f)
11071088
finally:
1108-
if not file_closed:
1109-
f.close()
1089+
_sync_close(f)
11101090

11111091
def get_message(self, key):
11121092
"""Return a Message representation or raise a KeyError."""

Lib/test/test_mailbox.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,18 +1377,29 @@ def readline(self):
13771377
self._box = self._factory(self._path)
13781378
self.assertEqual(self._box.get_bytes(key), original)
13791379

1380-
@unittest.skipUnless(hasattr(os, 'chown'), 'requires os.chown')
1380+
@unittest.skipUnless(os.name == 'posix', 'requires POSIX permissions')
13811381
def test_set_item_preserves_mode(self):
13821382
key = self._box.add(self._template % 'original')
13831383
path = os.path.join(self._path, str(key))
1384-
mode = os.stat(path).st_mode | stat.S_ISUID
1384+
mode = 0o640
13851385
os.chmod(path, mode)
1386-
if os.stat(path).st_mode != mode:
1387-
self.skipTest('filesystem does not support set-user-ID mode')
1386+
if stat.S_IMODE(os.stat(path).st_mode) != mode:
1387+
self.skipTest('filesystem does not support POSIX permissions')
13881388

13891389
self._box[key] = self._template % 'replacement'
13901390

1391-
self.assertEqual(os.stat(path).st_mode, mode)
1391+
self.assertEqual(stat.S_IMODE(os.stat(path).st_mode), mode)
1392+
1393+
def test_set_item_with_open_file(self):
1394+
key = self._box.add(self._template % 'original')
1395+
replacement = self._template % 'replacement'
1396+
self._box.lock()
1397+
try:
1398+
with self._box.get_file(key):
1399+
self._box[key] = replacement
1400+
self.assertEqual(self._box.get_bytes(key), replacement.encode('ascii'))
1401+
finally:
1402+
self._box.unlock()
13921403

13931404
def test_list_folders(self):
13941405
# List folders

0 commit comments

Comments
 (0)