Skip to content

Commit 64ccbbb

Browse files
authored
gh-130379: Fix incorrect zipapp logic to avoid including the target in itself (gh-130509)
1 parent f976892 commit 64ccbbb

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

Lib/test/test_zipapp.py

+24
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,30 @@ def skip_pyc_files(path):
8989
self.assertIn('test.py', z.namelist())
9090
self.assertNotIn('test.pyc', z.namelist())
9191

92+
def test_create_archive_self_insertion(self):
93+
# When creating an archive, we shouldn't
94+
# include the archive in the list of files to add.
95+
source = self.tmpdir
96+
(source / '__main__.py').touch()
97+
(source / 'test.py').touch()
98+
target = self.tmpdir / 'target.pyz'
99+
100+
zipapp.create_archive(source, target)
101+
with zipfile.ZipFile(target, 'r') as z:
102+
self.assertEqual(len(z.namelist()), 2)
103+
self.assertIn('__main__.py', z.namelist())
104+
self.assertIn('test.py', z.namelist())
105+
106+
def test_target_overwrites_source_file(self):
107+
# The target cannot be one of the files to add.
108+
source = self.tmpdir
109+
(source / '__main__.py').touch()
110+
target = source / 'target.pyz'
111+
target.touch()
112+
113+
with self.assertRaises(zipapp.ZipAppError):
114+
zipapp.create_archive(source, target)
115+
92116
def test_create_archive_filter_exclude_dir(self):
93117
# Test packing a directory and using a filter to exclude a
94118
# subdirectory (ensures that the path supplied to include

Lib/zipapp.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,37 @@ def create_archive(source, target=None, interpreter=None, main=None,
131131
elif not hasattr(target, 'write'):
132132
target = pathlib.Path(target)
133133

134+
# Create the list of files to add to the archive now, in case
135+
# the target is being created in the source directory - we
136+
# don't want the target being added to itself
137+
files_to_add = sorted(source.rglob('*'))
138+
139+
# The target cannot be in the list of files to add. If it were, we'd
140+
# end up overwriting the source file and writing the archive into
141+
# itself, which is an error. We therefore check for that case and
142+
# provide a helpful message for the user.
143+
144+
# Note that we only do a simple path equality check. This won't
145+
# catch every case, but it will catch the common case where the
146+
# source is the CWD and the target is a file in the CWD. More
147+
# thorough checks don't provide enough value to justify the extra
148+
# cost.
149+
150+
# If target is a file-like object, it will simply fail to compare
151+
# equal to any of the entries in files_to_add, so there's no need
152+
# to add a special check for that.
153+
if target in files_to_add:
154+
raise ZipAppError(
155+
f"The target archive {target} overwrites one of the source files.")
156+
134157
with _maybe_open(target, 'wb') as fd:
135158
_write_file_prefix(fd, interpreter)
136159
compression = (zipfile.ZIP_DEFLATED if compressed else
137160
zipfile.ZIP_STORED)
138161
with zipfile.ZipFile(fd, 'w', compression=compression) as z:
139-
for child in sorted(source.rglob('*')):
162+
for child in files_to_add:
140163
arcname = child.relative_to(source)
141-
if filter is None or filter(arcname) and child.resolve() != arcname.resolve():
164+
if filter is None or filter(arcname):
142165
z.write(child, arcname.as_posix())
143166
if main_py:
144167
z.writestr('__main__.py', main_py.encode('utf-8'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)