]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-127371 Avoid unbounded growth SpooledTempfile.writelines (GH-127372) (...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 5 Mar 2025 17:42:35 +0000 (18:42 +0100)
committerGitHub <noreply@github.com>
Wed, 5 Mar 2025 17:42:35 +0000 (18:42 +0100)
gh-127371 Avoid unbounded growth SpooledTempfile.writelines (GH-127372)
(cherry picked from commit cb67b44ca92f9930b3aa2aba8420c89d12a25303)

Co-authored-by: Bert Peters <bert@bertptrs.nl>
Lib/tempfile.py
Lib/test/test_tempfile.py
Misc/NEWS.d/next/Security/2024-11-28-20-29-21.gh-issue-127371.PeEhUd.rst [new file with mode: 0644]

index cbfc172a789b2519b62e98b7c48b65158cf77818..67c02db124d4789febd315fed4225ca361a7f2e4 100644 (file)
@@ -848,10 +848,14 @@ class SpooledTemporaryFile(_io.IOBase):
         return rv
 
     def writelines(self, iterable):
-        file = self._file
-        rv = file.writelines(iterable)
-        self._check(file)
-        return rv
+        if self._max_size == 0 or self._rolled:
+            return self._file.writelines(iterable)
+
+        it = iter(iterable)
+        for line in it:
+            self.write(line)
+            if self._rolled:
+                return self._file.writelines(it)
 
     def detach(self):
         return self._file.detach()
index a5e182cef23dc54f534fe04dcc2aafcf0e35fe6a..31982ae6ea2aabd52d3676503f322a28490fba49 100644 (file)
@@ -1288,6 +1288,34 @@ class TestSpooledTemporaryFile(BaseTestCase):
         buf = f.read()
         self.assertEqual(buf, b'xyz')
 
+    def test_writelines_rollover(self):
+        # Verify writelines rolls over before exhausting the iterator
+        f = self.do_create(max_size=2)
+
+        def it():
+            yield b'xy'
+            self.assertFalse(f._rolled)
+            yield b'z'
+            self.assertTrue(f._rolled)
+
+        f.writelines(it())
+        pos = f.seek(0)
+        self.assertEqual(pos, 0)
+        buf = f.read()
+        self.assertEqual(buf, b'xyz')
+
+    def test_writelines_fast_path(self):
+        f = self.do_create(max_size=2)
+        f.write(b'abc')
+        self.assertTrue(f._rolled)
+
+        f.writelines([b'd', b'e', b'f'])
+        pos = f.seek(0)
+        self.assertEqual(pos, 0)
+        buf = f.read()
+        self.assertEqual(buf, b'abcdef')
+
+
     def test_writelines_sequential(self):
         # A SpooledTemporaryFile should hold exactly max_size bytes, and roll
         # over afterward
diff --git a/Misc/NEWS.d/next/Security/2024-11-28-20-29-21.gh-issue-127371.PeEhUd.rst b/Misc/NEWS.d/next/Security/2024-11-28-20-29-21.gh-issue-127371.PeEhUd.rst
new file mode 100644 (file)
index 0000000..029c348
--- /dev/null
@@ -0,0 +1,3 @@
+Avoid unbounded buffering for :meth:`!tempfile.SpooledTemporaryFile.writelines`.
+Previously, disk spillover was only checked after the lines iterator had been
+exhausted. This is now done after each line is written.