diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 98d2a5e5cdf00e2..3c1305b35c4881f 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -178,7 +178,7 @@ ZipFile objects .. class:: ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True, \ compresslevel=None, *, strict_timestamps=True, \ - metadata_encoding=None) + metadata_encoding=None, max_entries=None) Open a ZIP file, where *file* can be a path to a file (a string), a file-like object or a :term:`path-like object`. @@ -231,6 +231,10 @@ ZipFile objects which will be used to decode metadata such as the names of members and ZIP comments. + The *max_entries* argument can be set to a nonnegative integer, + which will be the limit of how many entries the internal cache will store metadata for. + If the limit is reached, a :exc:`BadZipFile` will be raised. + If the file is created with mode ``'w'``, ``'x'`` or ``'a'`` and then :meth:`closed ` without adding any files to the archive, the appropriate ZIP structures for an empty archive will be written to the file. diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 418933a2e8d9e87..2c8ba5d486500ed 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -1397,6 +1397,10 @@ def close(self): self._fileobj.seek(self._zipfile.start_dir) # Successfully written: Add file to our caches + if self._zipfile._max_entries is not None: + if self._zipfile._entry_count >= self._zipfile._max_entries: + raise BadZipFile("max_entries reached") + self._zipfile._entry_count += 1 self._zipfile.filelist.append(self._zinfo) self._zipfile.NameToInfo[self._zinfo.filename] = self._zinfo finally: @@ -1896,7 +1900,8 @@ class ZipFile: _ignore_invalid_names = False def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, - compresslevel=None, *, strict_timestamps=True, metadata_encoding=None): + compresslevel=None, *, strict_timestamps=True, metadata_encoding=None, + max_entries=None): """Open the ZIP file with mode read 'r', write 'w', exclusive create 'x', or append 'a'.""" if mode not in ('r', 'w', 'x', 'a'): @@ -1909,6 +1914,8 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, self.debug = 0 # Level of printing: 0 through 3 self.NameToInfo = {} # Find file info given name self.filelist = [] # List of ZipInfo instances for archive + self._entry_count = None + self._max_entries = max_entries self.compression = compression # Method of compression self.compresslevel = compresslevel self.mode = mode @@ -1917,6 +1924,16 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, self._strict_timestamps = strict_timestamps self.metadata_encoding = metadata_encoding + if self._max_entries is not None: + if not isinstance(self._max_entries, int): + raise TypeError( + "max_entries: expected int, got %s" + % type(max_entries).__name__ + ) + if self._max_entries < 0: + raise ValueError("max_entries cannot be negative") + self._entry_count = 0 + # Check that we don't try to write with nonconforming codecs if self.metadata_encoding and mode != 'r': raise ValueError( @@ -2072,6 +2089,10 @@ def _RealGetContents(self): t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) x._decodeExtra(orig_filename_crc) x.header_offset = x.header_offset + concat + if self._max_entries is not None: + if self._entry_count >= self._max_entries: + raise BadZipFile("max_entries reached") + self._entry_count += 1 self.filelist.append(x) self.NameToInfo[x.filename] = x @@ -2367,6 +2388,8 @@ def remove(self, zinfo_or_arcname): try: self.filelist.remove(zinfo) + if self._max_entries is not None: + self._entry_count -= 1 except ValueError: raise KeyError('There is no item %r in the archive' % zinfo) from None @@ -2609,6 +2632,10 @@ def mkdir(self, zinfo_or_directory_name, mode=511): self._writecheck(zinfo) self._didModify = True + if self._max_entries is not None: + if self._entry_count >= self._max_entries: + raise BadZipFile("max_entries reached") + self._entry_count += 1 self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo self.fp.write(zinfo.FileHeader(False)) diff --git a/Misc/NEWS.d/next/Library/2026-07-09-23-48-10.gh-issue-153460.ZvfZLK.rst b/Misc/NEWS.d/next/Library/2026-07-09-23-48-10.gh-issue-153460.ZvfZLK.rst new file mode 100644 index 000000000000000..ead6efdf3d07a0f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-09-23-48-10.gh-issue-153460.ZvfZLK.rst @@ -0,0 +1,2 @@ +Add new backwards-compatible optional keyword parameter ``max_entries`` to +:class:`zipfile.ZipFile`.