Skip to content

Commit e65b3fa

Browse files
authored
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)
1 parent cd27d22 commit e65b3fa

File tree

4 files changed

+23
-16
lines changed

4 files changed

+23
-16
lines changed

Doc/library/tempfile.rst

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

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

Lib/tempfile.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -637,10 +637,8 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
637637
if 'b' in mode:
638638
self._file = _io.BytesIO()
639639
else:
640-
# Setting newline="\n" avoids newline translation;
641-
# this is important because otherwise on Windows we'd
642-
# get double newline translation upon rollover().
643-
self._file = _io.StringIO(newline="\n")
640+
self._file = _io.TextIOWrapper(_io.BytesIO(),
641+
encoding=encoding, newline=newline)
644642
self._max_size = max_size
645643
self._rolled = False
646644
self._TemporaryFileArgs = {'mode': mode, 'buffering': buffering,
@@ -660,8 +658,12 @@ def rollover(self):
660658
newfile = self._file = TemporaryFile(**self._TemporaryFileArgs)
661659
del self._TemporaryFileArgs
662660

663-
newfile.write(file.getvalue())
664-
newfile.seek(file.tell(), 0)
661+
pos = file.tell()
662+
if hasattr(newfile, 'buffer'):
663+
newfile.buffer.write(file.detach().getvalue())
664+
else:
665+
newfile.write(file.getvalue())
666+
newfile.seek(pos, 0)
665667

666668
self._rolled = True
667669

Lib/test/test_tempfile.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,8 @@ def test_properties(self):
11191119
def test_text_mode(self):
11201120
# Creating a SpooledTemporaryFile with a text mode should produce
11211121
# a file object reading and writing (Unicode) text strings.
1122-
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10)
1122+
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
1123+
encoding="utf-8")
11231124
f.write("abc\n")
11241125
f.seek(0)
11251126
self.assertEqual(f.read(), "abc\n")
@@ -1129,8 +1130,8 @@ def test_text_mode(self):
11291130
self.assertFalse(f._rolled)
11301131
self.assertEqual(f.mode, 'w+')
11311132
self.assertIsNone(f.name)
1132-
self.assertIsNone(f.newlines)
1133-
self.assertIsNone(f.encoding)
1133+
self.assertEqual(f.newlines, os.linesep)
1134+
self.assertEqual(f.encoding, "utf-8")
11341135

11351136
f.write("xyzzy\n")
11361137
f.seek(0)
@@ -1143,7 +1144,7 @@ def test_text_mode(self):
11431144
self.assertEqual(f.mode, 'w+')
11441145
self.assertIsNotNone(f.name)
11451146
self.assertEqual(f.newlines, os.linesep)
1146-
self.assertIsNotNone(f.encoding)
1147+
self.assertEqual(f.encoding, "utf-8")
11471148

11481149
def test_text_newline_and_encoding(self):
11491150
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
@@ -1154,12 +1155,14 @@ def test_text_newline_and_encoding(self):
11541155
self.assertFalse(f._rolled)
11551156
self.assertEqual(f.mode, 'w+')
11561157
self.assertIsNone(f.name)
1157-
self.assertIsNone(f.newlines)
1158-
self.assertIsNone(f.encoding)
1158+
self.assertIsNotNone(f.newlines)
1159+
self.assertEqual(f.encoding, "utf-8")
11591160

1160-
f.write("\u039B" * 20 + "\r\n")
1161+
f.write("\u039C" * 10 + "\r\n")
1162+
f.write("\u039D" * 20)
11611163
f.seek(0)
1162-
self.assertEqual(f.read(), "\u039B\r\n" + ("\u039B" * 20) + "\r\n")
1164+
self.assertEqual(f.read(),
1165+
"\u039B\r\n" + ("\u039C" * 10) + "\r\n" + ("\u039D" * 20))
11631166
self.assertTrue(f._rolled)
11641167
self.assertEqual(f.mode, 'w+')
11651168
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)