https://github.com/python/cpython/commit/397e69d0092cceb94840bfb8afd72713bc770efe
commit: 397e69d0092cceb94840bfb8afd72713bc770efe
branch: 3.12
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: Yhg1s <tho...@python.org>
date: 2025-03-05T18:42:35+01:00
summary:

[3.12] gh-127371 Avoid unbounded growth SpooledTempfile.writelines (GH-127372) 
(#130885)

gh-127371 Avoid unbounded growth SpooledTempfile.writelines (GH-127372)
(cherry picked from commit cb67b44ca92f9930b3aa2aba8420c89d12a25303)

Co-authored-by: Bert Peters <b...@bertptrs.nl>

files:
A Misc/NEWS.d/next/Security/2024-11-28-20-29-21.gh-issue-127371.PeEhUd.rst
M Lib/tempfile.py
M Lib/test/test_tempfile.py

diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index cbfc172a789b25..67c02db124d478 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -848,10 +848,14 @@ def write(self, s):
         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()
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index a5e182cef23dc5..31982ae6ea2aab 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -1288,6 +1288,34 @@ def test_writelines(self):
         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
index 00000000000000..029c348918e0d1
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-11-28-20-29-21.gh-issue-127371.PeEhUd.rst
@@ -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.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to