Skip to content

Commit

Permalink
bpo-26730: Fix SpooledTemporaryFile data corruption (GH-17400)
Browse files Browse the repository at this point in the history
SpooledTemporaryFile.rollback() might cause data corruption
when it is in text mode.

Co-Authored-By: Serhiy Storchaka <storchaka@gmail.com>.
(cherry picked from commit ea9835c)
  • Loading branch information
methane committed Nov 28, 2019
1 parent cd27d22 commit e65b3fa
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Doc/library/tempfile.rst
Expand Up @@ -95,8 +95,8 @@ The module defines the following user-callable items:
causes the file to roll over to an on-disk file regardless of its size.

The returned object is a file-like object whose :attr:`_file` attribute
is either an :class:`io.BytesIO` or :class:`io.StringIO` object (depending on
whether binary or text *mode* was specified) or a true file
is either an :class:`io.BytesIO` or :class:`io.TextIOWrapper` object
(depending on whether binary or text *mode* was specified) or a true file
object, depending on whether :func:`rollover` has been called. This
file-like object can be used in a :keyword:`with` statement, just like
a normal file.
Expand Down
14 changes: 8 additions & 6 deletions Lib/tempfile.py
Expand Up @@ -637,10 +637,8 @@ def __init__(self, max_size=0, mode='w+b', buffering=-1,
if 'b' in mode:
self._file = _io.BytesIO()
else:
# Setting newline="\n" avoids newline translation;
# this is important because otherwise on Windows we'd
# get double newline translation upon rollover().
self._file = _io.StringIO(newline="\n")
self._file = _io.TextIOWrapper(_io.BytesIO(),
encoding=encoding, newline=newline)
self._max_size = max_size
self._rolled = False
self._TemporaryFileArgs = {'mode': mode, 'buffering': buffering,
Expand All @@ -660,8 +658,12 @@ def rollover(self):
newfile = self._file = TemporaryFile(**self._TemporaryFileArgs)
del self._TemporaryFileArgs

newfile.write(file.getvalue())
newfile.seek(file.tell(), 0)
pos = file.tell()
if hasattr(newfile, 'buffer'):
newfile.buffer.write(file.detach().getvalue())
else:
newfile.write(file.getvalue())
newfile.seek(pos, 0)

self._rolled = True

Expand Down
19 changes: 11 additions & 8 deletions Lib/test/test_tempfile.py
Expand Up @@ -1119,7 +1119,8 @@ def test_properties(self):
def test_text_mode(self):
# Creating a SpooledTemporaryFile with a text mode should produce
# a file object reading and writing (Unicode) text strings.
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10)
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
encoding="utf-8")
f.write("abc\n")
f.seek(0)
self.assertEqual(f.read(), "abc\n")
Expand All @@ -1129,8 +1130,8 @@ def test_text_mode(self):
self.assertFalse(f._rolled)
self.assertEqual(f.mode, 'w+')
self.assertIsNone(f.name)
self.assertIsNone(f.newlines)
self.assertIsNone(f.encoding)
self.assertEqual(f.newlines, os.linesep)
self.assertEqual(f.encoding, "utf-8")

f.write("xyzzy\n")
f.seek(0)
Expand All @@ -1143,7 +1144,7 @@ def test_text_mode(self):
self.assertEqual(f.mode, 'w+')
self.assertIsNotNone(f.name)
self.assertEqual(f.newlines, os.linesep)
self.assertIsNotNone(f.encoding)
self.assertEqual(f.encoding, "utf-8")

def test_text_newline_and_encoding(self):
f = tempfile.SpooledTemporaryFile(mode='w+', max_size=10,
Expand All @@ -1154,12 +1155,14 @@ def test_text_newline_and_encoding(self):
self.assertFalse(f._rolled)
self.assertEqual(f.mode, 'w+')
self.assertIsNone(f.name)
self.assertIsNone(f.newlines)
self.assertIsNone(f.encoding)
self.assertIsNotNone(f.newlines)
self.assertEqual(f.encoding, "utf-8")

f.write("\u039B" * 20 + "\r\n")
f.write("\u039C" * 10 + "\r\n")
f.write("\u039D" * 20)
f.seek(0)
self.assertEqual(f.read(), "\u039B\r\n" + ("\u039B" * 20) + "\r\n")
self.assertEqual(f.read(),
"\u039B\r\n" + ("\u039C" * 10) + "\r\n" + ("\u039D" * 20))
self.assertTrue(f._rolled)
self.assertEqual(f.mode, 'w+')
self.assertIsNotNone(f.name)
Expand Down
@@ -0,0 +1,2 @@
Fix ``SpooledTemporaryFile.rollover()`` might corrupt the file when it is in
text mode. Patch by Serhiy Storchaka.

0 comments on commit e65b3fa

Please sign in to comment.