Skip to content

Commit 39b144b

Browse files
committed
[5.1.x] Fixed #36298 -- Truncated the overwritten file content in file_move_safe().
Regression in 58cd490. Thanks Baptiste Mispelon for the report. Backport of 8ad3e80 from main.
1 parent bbf376b commit 39b144b

File tree

5 files changed

+54
-0
lines changed

5 files changed

+54
-0
lines changed

django/core/files/move.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def file_move_safe(
5555
| os.O_CREAT
5656
| getattr(os, "O_BINARY", 0)
5757
| (os.O_EXCL if not allow_overwrite else 0)
58+
| os.O_TRUNC
5859
),
5960
)
6061
try:

docs/releases/4.2.21.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
===========================
2+
Django 4.2.21 release notes
3+
===========================
4+
5+
*Expected May 7, 2025*
6+
7+
Django 4.2.21 fixes a data loss bug in 4.2.20.
8+
9+
Bugfixes
10+
========
11+
12+
* Fixed a data corruption possibility in ``file_move_safe()`` when
13+
``allow_overwrite=True``, where leftover content from a previously larger
14+
file could remain after overwriting with a smaller one due to lack of
15+
truncation (:ticket:`36298`).

docs/releases/5.1.9.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
==========================
2+
Django 5.1.9 release notes
3+
==========================
4+
5+
*Expected May 7, 2025*
6+
7+
Django 5.1.9 fixes a data loss bug in 5.1.8.
8+
9+
Bugfixes
10+
========
11+
12+
* Fixed a data corruption possibility in ``file_move_safe()`` when
13+
``allow_overwrite=True``, where leftover content from a previously larger
14+
file could remain after overwriting with a smaller one due to lack of
15+
truncation (:ticket:`36298`).

docs/releases/index.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
2525
.. toctree::
2626
:maxdepth: 1
2727

28+
5.1.9
2829
5.1.8
2930
5.1.7
3031
5.1.6
@@ -62,6 +63,7 @@ versions of the documentation contain the release notes for any later releases.
6263
.. toctree::
6364
:maxdepth: 1
6465

66+
4.2.21
6567
4.2.20
6668
4.2.19
6769
4.2.18

tests/files/tests.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,27 @@ def test_file_move_permissionerror(self):
496496
os.close(handle_b)
497497
os.close(handle_c)
498498

499+
def test_file_move_ensure_truncation(self):
500+
with tempfile.NamedTemporaryFile(delete=False) as src:
501+
src.write(b"content")
502+
src_name = src.name
503+
self.addCleanup(
504+
lambda: os.remove(src_name) if os.path.exists(src_name) else None
505+
)
506+
507+
with tempfile.NamedTemporaryFile(delete=False) as dest:
508+
dest.write(b"This is a longer content.")
509+
dest_name = dest.name
510+
self.addCleanup(os.remove, dest_name)
511+
512+
with mock.patch("django.core.files.move.os.rename", side_effect=OSError()):
513+
file_move_safe(src_name, dest_name, allow_overwrite=True)
514+
515+
with open(dest_name, "rb") as f:
516+
content = f.read()
517+
518+
self.assertEqual(content, b"content")
519+
499520

500521
class SpooledTempTests(unittest.TestCase):
501522
def test_in_memory_spooled_temp(self):

0 commit comments

Comments
 (0)