Skip to content

Commit d21b8e8

Browse files
bpo-26730: Fix SpooledTemporaryFile data corruption (GH-17400)
SpooledTemporaryFile.rollback() might cause data corruption when it is in text mode. Co-Authored-By: Serhiy Storchaka <[email protected]> (cherry picked from commit ea9835c) Co-authored-by: Inada Naoki <[email protected]>
1 parent 0f9c9d5 commit d21b8e8

File tree

4 files changed

+27
-19
lines changed

4 files changed

+27
-19
lines changed

Doc/library/tempfile.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ The module defines the following user-callable items:
105105
causes the file to roll over to an on-disk file regardless of its size.
106106

107107
The returned object is a file-like object whose :attr:`_file` attribute
108-
is either an :class:`io.BytesIO` or :class:`io.StringIO` object (depending on
109-
whether binary or text *mode* was specified) or a true file
108+
is either an :class:`io.BytesIO` or :class:`io.TextIOWrapper` object
109+
(depending on whether binary or text *mode* was specified) or a true file
110110
object, depending on whether :func:`rollover` has been called. This
111111
file-like object can be used in a :keyword:`with` statement, just like
112112
a normal file.

Lib/tempfile.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -633,10 +633,9 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
633633
if 'b' in mode:
634634
self._file = _io.BytesIO()
635635
else:
636-
# Setting newline="\n" avoids newline translation;
637-
# this is important because otherwise on Windows we'd
638-
# get double newline translation upon rollover().
639-
self._file = _io.StringIO(newline="\n")
636+
self._file = _io.TextIOWrapper(_io.BytesIO(),
637+
encoding=encoding, errors=errors,
638+
newline=newline)
640639
self._max_size = max_size
641640
self._rolled = False
642641
self._TemporaryFileArgs = {'mode': mode, 'buffering': buffering,
@@ -656,8 +655,12 @@ def rollover(self):
656655
newfile = self._file = TemporaryFile(**self._TemporaryFileArgs)
657656
del self._TemporaryFileArgs
658657

659-
newfile.write(file.getvalue())
660-
newfile.seek(file.tell(), 0)
658+
pos = file.tell()
659+
if hasattr(newfile, 'buffer'):
660+
newfile.buffer.write(file.detach().getvalue())
661+
else:
662+
newfile.write(file.getvalue())
663+
newfile.seek(pos, 0)
661664

662665
self._rolled = True
663666

Lib/test/test_tempfile.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,8 @@ def test_properties(self):
11141114
def test_text_mode(self):
11151115
# Creating a SpooledTemporaryFile with a text mode should produce
11161116
# a file object reading and writing (Unicode) text strings.
1117-
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10)
1117+
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
1118+
encoding="utf-8")
11181119
f.write("abc\n")
11191120
f.seek(0)
11201121
self.assertEqual(f.read(), "abc\n")
@@ -1124,9 +1125,9 @@ def test_text_mode(self):
11241125
self.assertFalse(f._rolled)
11251126
self.assertEqual(f.mode, 'w+')
11261127
self.assertIsNone(f.name)
1127-
self.assertIsNone(f.newlines)
1128-
self.assertIsNone(f.encoding)
1129-
self.assertIsNone(f.errors)
1128+
self.assertEqual(f.newlines, os.linesep)
1129+
self.assertEqual(f.encoding, "utf-8")
1130+
self.assertEqual(f.errors, "strict")
11301131

11311132
f.write("xyzzy\n")
11321133
f.seek(0)
@@ -1139,8 +1140,8 @@ def test_text_mode(self):
11391140
self.assertEqual(f.mode, 'w+')
11401141
self.assertIsNotNone(f.name)
11411142
self.assertEqual(f.newlines, os.linesep)
1142-
self.assertIsNotNone(f.encoding)
1143-
self.assertIsNotNone(f.errors)
1143+
self.assertEqual(f.encoding, "utf-8")
1144+
self.assertEqual(f.errors, "strict")
11441145

11451146
def test_text_newline_and_encoding(self):
11461147
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
@@ -1152,13 +1153,15 @@ def test_text_newline_and_encoding(self):
11521153
self.assertFalse(f._rolled)
11531154
self.assertEqual(f.mode, 'w+')
11541155
self.assertIsNone(f.name)
1155-
self.assertIsNone(f.newlines)
1156-
self.assertIsNone(f.encoding)
1157-
self.assertIsNone(f.errors)
1156+
self.assertIsNotNone(f.newlines)
1157+
self.assertEqual(f.encoding, "utf-8")
1158+
self.assertEqual(f.errors, "ignore")
11581159

1159-
f.write("\u039B" * 20 + "\r\n")
1160+
f.write("\u039C" * 10 + "\r\n")
1161+
f.write("\u039D" * 20)
11601162
f.seek(0)
1161-
self.assertEqual(f.read(), "\u039B\r\n" + ("\u039B" * 20) + "\r\n")
1163+
self.assertEqual(f.read(),
1164+
"\u039B\r\n" + ("\u039C" * 10) + "\r\n" + ("\u039D" * 20))
11621165
self.assertTrue(f._rolled)
11631166
self.assertEqual(f.mode, 'w+')
11641167
self.assertIsNotNone(f.name)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``SpooledTemporaryFile.rollover()`` might corrupt the file when it is in
2+
text mode. Patch by Serhiy Storchaka.

0 commit comments

Comments
 (0)