https://github.com/python/cpython/commit/bc5c2b857330dc077986b9745d7c18415db9d3af
commit: bc5c2b857330dc077986b9745d7c18415db9d3af
branch: 3.13
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: pfmoore <p.f.mo...@gmail.com>
date: 2025-03-03T15:20:05Z
summary:

[3.13] gh-130379: Fix incorrect zipapp logic to avoid including the target in 
itself (gh-130509) (gh-130791)

gh-130379: Fix incorrect zipapp logic to avoid including the target in itself 
(gh-130509)
(cherry picked from commit 64ccbbbf367c7510090a6f5faf826a21102a8bc6)

Co-authored-by: Paul Moore <p.f.mo...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-02-24-14-46-20.gh-issue-130379.lsef7A.rst
M Lib/test/test_zipapp.py
M Lib/zipapp.py

diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py
index 00a5ed6626ddc5..ad132839622f48 100644
--- a/Lib/test/test_zipapp.py
+++ b/Lib/test/test_zipapp.py
@@ -89,6 +89,30 @@ def skip_pyc_files(path):
             self.assertIn('test.py', z.namelist())
             self.assertNotIn('test.pyc', z.namelist())
 
+    def test_create_archive_self_insertion(self):
+        # When creating an archive, we shouldn't
+        # include the archive in the list of files to add.
+        source = self.tmpdir
+        (source / '__main__.py').touch()
+        (source / 'test.py').touch()
+        target = self.tmpdir / 'target.pyz'
+
+        zipapp.create_archive(source, target)
+        with zipfile.ZipFile(target, 'r') as z:
+            self.assertEqual(len(z.namelist()), 2)
+            self.assertIn('__main__.py', z.namelist())
+            self.assertIn('test.py', z.namelist())
+
+    def test_target_overwrites_source_file(self):
+        # The target cannot be one of the files to add.
+        source = self.tmpdir
+        (source / '__main__.py').touch()
+        target = source / 'target.pyz'
+        target.touch()
+
+        with self.assertRaises(zipapp.ZipAppError):
+            zipapp.create_archive(source, target)
+
     def test_create_archive_filter_exclude_dir(self):
         # Test packing a directory and using a filter to exclude a
         # subdirectory (ensures that the path supplied to include
diff --git a/Lib/zipapp.py b/Lib/zipapp.py
index 03a214efa10a20..4ffacc49fa753d 100644
--- a/Lib/zipapp.py
+++ b/Lib/zipapp.py
@@ -131,14 +131,37 @@ def create_archive(source, target=None, interpreter=None, 
main=None,
     elif not hasattr(target, 'write'):
         target = pathlib.Path(target)
 
+    # Create the list of files to add to the archive now, in case
+    # the target is being created in the source directory - we
+    # don't want the target being added to itself
+    files_to_add = sorted(source.rglob('*'))
+
+    # The target cannot be in the list of files to add. If it were, we'd
+    # end up overwriting the source file and writing the archive into
+    # itself, which is an error. We therefore check for that case and
+    # provide a helpful message for the user.
+
+    # Note that we only do a simple path equality check. This won't
+    # catch every case, but it will catch the common case where the
+    # source is the CWD and the target is a file in the CWD. More
+    # thorough checks don't provide enough value to justify the extra
+    # cost.
+
+    # If target is a file-like object, it will simply fail to compare
+    # equal to any of the entries in files_to_add, so there's no need
+    # to add a special check for that.
+    if target in files_to_add:
+        raise ZipAppError(
+            f"The target archive {target} overwrites one of the source files.")
+
     with _maybe_open(target, 'wb') as fd:
         _write_file_prefix(fd, interpreter)
         compression = (zipfile.ZIP_DEFLATED if compressed else
                        zipfile.ZIP_STORED)
         with zipfile.ZipFile(fd, 'w', compression=compression) as z:
-            for child in sorted(source.rglob('*')):
+            for child in files_to_add:
                 arcname = child.relative_to(source)
-                if filter is None or filter(arcname) and child.resolve() != 
arcname.resolve():
+                if filter is None or filter(arcname):
                     z.write(child, arcname.as_posix())
             if main_py:
                 z.writestr('__main__.py', main_py.encode('utf-8'))
diff --git 
a/Misc/NEWS.d/next/Library/2025-02-24-14-46-20.gh-issue-130379.lsef7A.rst 
b/Misc/NEWS.d/next/Library/2025-02-24-14-46-20.gh-issue-130379.lsef7A.rst
new file mode 100644
index 00000000000000..157b2836491c1d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-02-24-14-46-20.gh-issue-130379.lsef7A.rst
@@ -0,0 +1 @@
+The zipapp module now calculates the list of files to be added to the archive 
before creating the archive. This avoids accidentally including the target when 
it is being created in the source directory.

_______________________________________________
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