diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 0c8ffcdbf14afe..7627f16c233804 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -407,7 +407,7 @@ class BrokenFile(io.BytesIO): def write(self, data): nonlocal count if count is not None: - if count == stop: + if (count > stop): raise OSError count += 1 super().write(data) @@ -424,11 +424,12 @@ def write(self, data): with zipfp.open('file2', 'w') as f: f.write(b'data2') except OSError: - stop += 1 + pass else: break finally: count = None + stop += 1 with zipfile.ZipFile(io.BytesIO(testfile.getvalue())) as zipfp: self.assertEqual(zipfp.namelist(), ['file1']) self.assertEqual(zipfp.read('file1'), b'data1') @@ -1289,7 +1290,8 @@ def test_writestr_extended_local_header_issue1202(self): with zipfile.ZipFile(TESTFN2, 'w') as orig_zip: for data in 'abcdefghijklmnop': zinfo = zipfile.ZipInfo(data) - zinfo.flag_bits |= 0x08 # Include an extended local header. + # Include an extended local header. + zinfo.flag_bits |= zipfile._MASK_USE_DATA_DESCRIPTOR orig_zip.writestr(zinfo, data) def test_close(self): @@ -1719,6 +1721,10 @@ def test_seek_tell(self): self.assertEqual(fp.tell(), len(txt)) fp.seek(0, os.SEEK_SET) self.assertEqual(fp.tell(), 0) + # Read the file completely to definitely call any eof + # integrity checks (crc) and make sure they still pass. + fp.read() + # Check seek on memory file data = io.BytesIO() with zipfile.ZipFile(data, mode="w") as zipf: @@ -1736,6 +1742,9 @@ def test_seek_tell(self): self.assertEqual(fp.tell(), len(txt)) fp.seek(0, os.SEEK_SET) self.assertEqual(fp.tell(), 0) + # Read the file completely to definitely call any eof + # integrity checks (crc) and make sure they still pass. + fp.read() def tearDown(self): unlink(TESTFN) @@ -1894,6 +1903,44 @@ def test_unicode_password(self): self.assertRaises(TypeError, self.zip.open, "test.txt", pwd="python") self.assertRaises(TypeError, self.zip.extract, "test.txt", pwd="python") + def test_seek_tell(self): + self.zip.setpassword(b"python") + txt = self.plain + test_word = b'encryption' + bloc = txt.find(test_word) + bloc_len = len(test_word) + with self.zip.open("test.txt", "r") as fp: + fp.seek(bloc, os.SEEK_SET) + self.assertEqual(fp.tell(), bloc) + fp.seek(-bloc, os.SEEK_CUR) + self.assertEqual(fp.tell(), 0) + fp.seek(bloc, os.SEEK_CUR) + self.assertEqual(fp.tell(), bloc) + self.assertEqual(fp.read(bloc_len), txt[bloc:bloc+bloc_len]) + + # Make sure that the second read after seeking back beyond + # _readbuffer returns the same content (ie. rewind to the start of + # the file to read forward to the required position). + old_read_size = fp.MIN_READ_SIZE + fp.MIN_READ_SIZE = 1 + fp._readbuffer = b'' + fp._offset = 0 + fp.seek(0, os.SEEK_SET) + self.assertEqual(fp.tell(), 0) + fp.seek(bloc, os.SEEK_CUR) + self.assertEqual(fp.read(bloc_len), txt[bloc:bloc+bloc_len]) + fp.MIN_READ_SIZE = old_read_size + + fp.seek(0, os.SEEK_END) + self.assertEqual(fp.tell(), len(txt)) + fp.seek(0, os.SEEK_SET) + self.assertEqual(fp.tell(), 0) + + # Read the file completely to definitely call any eof integrity + # checks (crc) and make sure they still pass. + fp.read() + + class AbstractTestsWithRandomBinaryFiles: @classmethod def setUpClass(cls): diff --git a/Lib/zipfile.py b/Lib/zipfile.py index 3c1f1235034a9e..c59abffac8c031 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -120,6 +120,28 @@ class LargeZipFile(Exception): _CD_EXTERNAL_FILE_ATTRIBUTES = 17 _CD_LOCAL_HEADER_OFFSET = 18 +# General purpose bit flags +# Zip Appnote: 4.4.4 general purpose bit flag: (2 bytes) +_MASK_ENCRYPTED = 1 << 0 +_MASK_COMPRESS_OPTION_1 = 1 << 1 +_MASK_COMPRESS_OPTION_2 = 1 << 2 +_MASK_USE_DATA_DESCRIPTOR = 1 << 3 +# Bit 4: Reserved for use with compression method 8, for enhanced deflating. +_MASK_RESERVED_BIT_4 = 1 << 4 +_MASK_COMPRESSED_PATCH = 1 << 5 +_MASK_STRONG_ENCRYPTION = 1 << 6 +_MASK_UNUSED_BIT_7 = 1 << 7 +_MASK_UNUSED_BIT_8 = 1 << 8 +_MASK_UNUSED_BIT_9 = 1 << 9 +_MASK_UNUSED_BIT_10 = 1 << 10 +_MASK_UTF_FILENAME = 1 << 11 +# Bit 12: Reserved by PKWARE for enhanced compression. +_MASK_RESERVED_BIT_12 = 1 << 12 +_MASK_ENCRYPTED_CENTRAL_DIR = 1 << 13 +# Bit 14, 15: Reserved by PKWARE +_MASK_RESERVED_BIT_14 = 1 << 14 +_MASK_RESERVED_BIT_15 = 1 << 15 + # The "local file header" structure, magic number, size, and indices # (section V.A in the format document) structFileHeader = "<4s2B4HL2L2H" @@ -165,6 +187,11 @@ class LargeZipFile(Exception): _EXTRA_FIELD_STRUCT = struct.Struct('') return ''.join(result) - def FileHeader(self, zip64=None): - """Return the per-file header as a bytes object.""" + @property + def is_encrypted(self): + return self.flag_bits & _MASK_ENCRYPTED + + @property + def is_utf_filename(self): + """Return True if filenames are encoded in UTF-8. + + Bit 11: Language encoding flag (EFS). If this bit is set, the filename + and comment fields for this file MUST be encoded using UTF-8. + """ + return self.flag_bits & _MASK_UTF_FILENAME + + @property + def is_compressed_patch_data(self): + # Zip 2.7: compressed patched data + return self.flag_bits & _MASK_COMPRESSED_PATCH + + @property + def is_strong_encryption(self): + return self.flag_bits & _MASK_STRONG_ENCRYPTION + + @property + def use_datadescriptor(self): + """Returns True if datadescriptor is in use. + + If bit 3 of flags is set, the data descriptor is must exist. It is + byte aligned and immediately follows the last byte of compressed data. + + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + """ + return self.flag_bits & _MASK_USE_DATA_DESCRIPTOR + + def encode_datadescriptor(self, zip64): + fmt = ' ZIP64_LIMIT or compress_size > ZIP64_LIMIT if zip64 is None: - zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT + zip64 = requires_zip64 if zip64: - fmt = ' ZIP64_LIMIT or compress_size > ZIP64_LIMIT: + extra = struct.pack( + ' ZIP64_LIMIT or + self.compress_size > ZIP64_LIMIT): + zip64_fields.append(self.file_size) + file_size = 0xffffffff + zip64_fields.append(self.compress_size) + compress_size = 0xffffffff + else: + file_size = self.file_size + compress_size = self.compress_size + + if self.header_offset > ZIP64_LIMIT: + zip64_fields.append(self.header_offset) + header_offset = 0xffffffff + else: + header_offset = self.header_offset + + # Here for completeness - We don't support writing disks with multiple + # parts so the number of disks is always going to be 0. Definitely not + # more than 65,535. + # ZIP64_DISK_LIMIT = (1 << 16) - 1 + # if self.disk_start > ZIP64_DISK_LIMIT: + # zip64_fields.append(self.disk_start) + # disk_start = 0xffff + # else: + # disk_start = self.disk_start + + min_version = 0 + if zip64_fields: + extra = struct.pack( + '= 4: - tp, ln = unpack(' len(extra): + extra_decoders = self.get_extra_decoders() + idx = 0 + total_len = len(extra) + extra_left = total_len + while idx < total_len: + if extra_left < 4: + break + tp, ln = struct.unpack(' extra_left: raise BadZipFile("Corrupt extra field %04x (size=%d)" % (tp, ln)) - if tp == 0x0001: - if ln >= 24: - counts = unpack('>= 1 return crc -# ZIP supports a password-based form of encryption. Even though known -# plaintext attacks have been found against it, it is still useful -# to be able to get data out of such a file. -# -# Usage: -# zd = _ZipDecrypter(mypwd) -# plain_bytes = zd(cypher_bytes) - -def _ZipDecrypter(pwd): - key0 = 305419896 - key1 = 591751049 - key2 = 878082192 - - global _crctable - if _crctable is None: - _crctable = list(map(_gen_crc, range(256))) - crctable = _crctable - - def crc32(ch, crc): + +class CRCZipDecrypter(BaseDecrypter): + """PKWARE Encryption Decrypter + + ZIP supports a password-based form of encryption. Even though known + plaintext attacks have been found against it, it is still useful + to be able to get data out of such a file. + + Usage: + zd = CRCZipDecrypter(zinfo, mypwd) + zd.start_decrypt(fileobj) + plain_bytes = zd.decrypt(cypher_bytes) + """ + + encryption_header_length = 12 + + def __init__(self, zinfo, pwd): + self.zinfo = zinfo + self.name = zinfo.filename + + if not pwd: + raise RuntimeError("File %r is encrypted, a password is " + "required for extraction" % self.name) + self.pwd = pwd + + def start_decrypt(self, fileobj): + + self.key0 = 305419896 + self.key1 = 591751049 + self.key2 = 878082192 + + global _crctable + if _crctable is None: + _crctable = list(map(_gen_crc, range(256))) + + for p in self.pwd: + self.update_keys(p) + + # The first 12 bytes in the cypher stream is an encryption header + # used to strengthen the algorithm. The first 11 bytes are + # completely random, while the 12th contains the MSB of the CRC, + # or the MSB of the file time depending on the header type + # and is used to check the correctness of the password. + header = fileobj.read(self.encryption_header_length) + h = self.decrypt(header[0:12]) + + if self.zinfo.use_datadescriptor: + # compare against the file type from extended local headers + check_byte = (self.zinfo._raw_time >> 8) & 0xff + else: + # compare against the CRC otherwise + check_byte = (self.zinfo.CRC >> 24) & 0xff + + if h[11] != check_byte: + raise RuntimeError("Bad password for file %r" % self.name) + + return self.encryption_header_length + + def crc32(self, ch, crc): """Compute the CRC32 primitive on one byte.""" - return (crc >> 8) ^ crctable[(crc ^ ch) & 0xFF] + return (crc >> 8) ^ _crctable[(crc ^ ch) & 0xFF] - def update_keys(c): - nonlocal key0, key1, key2 - key0 = crc32(c, key0) + def _update_keys(self, c, key0, key1, key2): + key0 = self.crc32(c, key0) key1 = (key1 + (key0 & 0xFF)) & 0xFFFFFFFF key1 = (key1 * 134775813 + 1) & 0xFFFFFFFF - key2 = crc32(key1 >> 24, key2) - - for p in pwd: - update_keys(p) - - def decrypter(data): + key2 = self.crc32(key1 >> 24, key2) + return key0, key1, key2 + + def update_keys(self, c): + self.key0, self.key1, self.key2 = self._update_keys( + c, + self.key0, + self.key1, + self.key2, + ) + + def decrypt(self, data): """Decrypt a bytes object.""" result = bytearray() + key0 = self.key0 + key1 = self.key1 + key2 = self.key2 append = result.append for c in data: k = key2 | 2 c ^= ((k * (k^1)) >> 8) & 0xFF - update_keys(c) + key0, key1, key2 = self._update_keys(c, key0, key1, key2) append(c) - return bytes(result) - return decrypter + self.key0 = key0 + self.key1 = key1 + self.key2 = key2 + + return bytes(result) class LZMACompressor: + # The LZMA SDK version is not related to the XZ Util's liblzma version that + # the python library links to. The LZMA SDK is associated with the 7-zip + # project by Igor Pavlov. If there is a breaking change in how the + # properties are packed or their contents, these version identifiers can be + # used to specify the strategy for decompression. + LZMA_SDK_MAJOR_VERSION = 9 + LZMA_SDK_MINOR_VERSION = 4 def __init__(self): self._comp = None @@ -605,7 +989,12 @@ def _init(self): self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[ lzma._decode_filter_properties(lzma.FILTER_LZMA1, props) ]) - return struct.pack(' self._orig_file_size: - new_pos = self._orig_file_size + if new_pos > self._zinfo.file_size: + new_pos = self._zinfo.file_size if new_pos < 0: new_pos = 0 @@ -1050,14 +1514,8 @@ def seek(self, offset, whence=0): read_offset = 0 elif read_offset < 0: # Position is before the current position. Reset the ZipExtFile - self._fileobj.seek(self._orig_compress_start) - self._running_crc = self._orig_start_crc - self._compress_left = self._orig_compress_size - self._left = self._orig_file_size - self._readbuffer = b'' - self._offset = 0 - self._decompressor = _get_decompressor(self._compress_type) - self._eof = False + self._fileobj.seek(self._compress_start) + self.read_init() read_offset = new_pos while read_offset > 0: @@ -1070,7 +1528,7 @@ def seek(self, offset, whence=0): def tell(self): if not self._seekable: raise io.UnsupportedOperation("underlying stream is not seekable") - filepos = self._orig_file_size - self._left - len(self._readbuffer) + self._offset + filepos = self._zinfo.file_size - self._left - len(self._readbuffer) + self._offset return filepos @@ -1079,19 +1537,40 @@ def __init__(self, zf, zinfo, zip64): self._zinfo = zinfo self._zip64 = zip64 self._zipfile = zf - self._compressor = _get_compressor(zinfo.compress_type, - zinfo._compresslevel) + self._compressor = self.get_compressor( + zinfo.compress_type, zinfo._compresslevel + ) self._file_size = 0 self._compress_size = 0 self._crc = 0 + self.write_local_header() + @property def _fileobj(self): return self._zipfile.fp + def get_compressor(self, compress_type, compresslevel=None): + if compress_type == ZIP_DEFLATED: + if compresslevel is not None: + return zlib.compressobj(compresslevel, zlib.DEFLATED, -15) + return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) + elif compress_type == ZIP_BZIP2: + if compresslevel is not None: + return bz2.BZ2Compressor(compresslevel) + return bz2.BZ2Compressor() + # compresslevel is ignored for ZIP_LZMA + elif compress_type == ZIP_LZMA: + return LZMACompressor() + else: + return None + def writable(self): return True + def write_local_header(self): + self._fileobj.write(self._zinfo.FileHeader(self._zip64)) + def write(self, data): if self.closed: raise ValueError('I/O operation on closed file.') @@ -1100,32 +1579,31 @@ def write(self, data): self._crc = crc32(data, self._crc) if self._compressor: data = self._compressor.compress(data) - self._compress_size += len(data) + self._compress_size += len(data) self._fileobj.write(data) return nbytes + def flush_data(self): + if self._compressor: + buf = self._compressor.flush() + self._compress_size += len(buf) + self._fileobj.write(buf) + def close(self): if self.closed: return try: super().close() + self.flush_data() # Flush any data from the compressor, and update header info - if self._compressor: - buf = self._compressor.flush() - self._compress_size += len(buf) - self._fileobj.write(buf) - self._zinfo.compress_size = self._compress_size - else: - self._zinfo.compress_size = self._file_size + self._zinfo.compress_size = self._compress_size self._zinfo.CRC = self._crc self._zinfo.file_size = self._file_size # Write updated header info - if self._zinfo.flag_bits & 0x08: + if self._zinfo.use_datadescriptor: # Write CRC and file sizes after the file data - fmt = '> 8) & 0xff - else: - # compare against the CRC otherwise - check_byte = (zinfo.CRC >> 24) & 0xff - if h[11] != check_byte: - raise RuntimeError("Bad password for file %r" % name) - - return ZipExtFile(zef_file, mode, zinfo, zd, True) - except: + return self.get_zipextfile(zef_file, mode, zinfo, pwd, **kwargs) + except: # noqa zef_file.close() raise - def _open_to_write(self, zinfo, force_zip64=False): + def get_zipwritefile(self, zinfo, zip64, pwd, **kwargs): + if pwd: + raise ValueError("pwd is only supported for reading files") + return self.zipwritefile_cls(self, zinfo, zip64) + + def _open_to_write(self, zinfo, force_zip64=False, pwd=None, **kwargs): if force_zip64 and not self._allowZip64: raise ValueError( "force_zip64 is True, but allowZip64 was False when opening " @@ -1572,9 +2023,9 @@ def _open_to_write(self, zinfo, force_zip64=False): zinfo.flag_bits = 0x00 if zinfo.compress_type == ZIP_LZMA: # Compressed data includes an end-of-stream (EOS) marker - zinfo.flag_bits |= 0x02 + zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1 if not self._seekable: - zinfo.flag_bits |= 0x08 + zinfo.flag_bits |= _MASK_USE_DATA_DESCRIPTOR if not zinfo.external_attr: zinfo.external_attr = 0o600 << 16 # permissions: ?rw------- @@ -1589,11 +2040,8 @@ def _open_to_write(self, zinfo, force_zip64=False): self._writecheck(zinfo) self._didModify = True - - self.fp.write(zinfo.FileHeader(zip64)) - self._writing = True - return _ZipWriteFile(self, zinfo, zip64) + return self.get_zipwritefile(zinfo, zip64, pwd, **kwargs) def extract(self, member, path=None, pwd=None): """Extract a member from the archive to the current working directory, @@ -1644,7 +2092,7 @@ def _extract_member(self, member, targetpath, pwd): """Extract the ZipInfo object 'member' to a physical file on the path targetpath. """ - if not isinstance(member, ZipInfo): + if not isinstance(member, self.zipinfo_cls): member = self.getinfo(member) # build the destination pathname, replacing @@ -1692,7 +2140,7 @@ def _writecheck(self, zinfo): if not self.fp: raise ValueError( "Attempt to write ZIP archive that was already closed") - _check_compression(zinfo.compress_type) + self.check_compression(zinfo.compress_type) if not self._allowZip64: requires_zip64 = None if len(self.filelist) >= ZIP_FILECOUNT_LIMIT: @@ -1717,8 +2165,8 @@ def write(self, filename, arcname=None, "Can't write to ZIP archive while an open writing handle exists" ) - zinfo = ZipInfo.from_file(filename, arcname, - strict_timestamps=self._strict_timestamps) + zinfo = self.zipinfo_cls.from_file( + filename, arcname, strict_timestamps=self._strict_timestamps) if zinfo.is_dir(): zinfo.compress_size = 0 @@ -1741,7 +2189,7 @@ def write(self, filename, arcname=None, zinfo.header_offset = self.fp.tell() # Start of header bytes if zinfo.compress_type == ZIP_LZMA: # Compressed data includes an end-of-stream (EOS) marker - zinfo.flag_bits |= 0x02 + zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1 self._writecheck(zinfo) self._didModify = True @@ -1763,9 +2211,10 @@ def writestr(self, zinfo_or_arcname, data, the name of the file in the archive.""" if isinstance(data, str): data = data.encode("utf-8") - if not isinstance(zinfo_or_arcname, ZipInfo): - zinfo = ZipInfo(filename=zinfo_or_arcname, - date_time=time.localtime(time.time())[:6]) + if not isinstance(zinfo_or_arcname, self.zipinfo_cls): + zinfo = self.zipinfo_cls( + filename=zinfo_or_arcname, + date_time=time.localtime(time.time())[:6]) zinfo.compress_type = self.compression zinfo._compresslevel = self.compresslevel if zinfo.filename[-1] == '/': @@ -1821,75 +2270,167 @@ def close(self): self.fp = None self._fpclose(fp) + def get_zip64_endrec_params(self, centDirCount, centDirSize, centDirOffset): + return { + "create_version": ZIP64_VERSION, + # version needed to extract this zip64endrec + "extract_version": ZIP64_VERSION, + # number of this disk + "diskno": 0, + # number of the disk with the start of the central + # directory + "cent_dir_start_diskno": 0, + # total number of entries in the central directory on + # this disk + "disk_cent_dir_count": centDirCount, + # total number of entries in the central directory + "total_cent_dir_count": centDirCount, + # size of the central directory + "cent_dir_size": centDirSize, + # offset of start of central directory with respect to + # the starting disk number + "cent_dir_offset": centDirOffset, + # zip64 extensible data sector (variable size) + "variable_data": b"", + } + + def _encode_zip64_endrec( + self, + create_version, + extract_version, + diskno, + cent_dir_start_diskno, + disk_cent_dir_count, + total_cent_dir_count, + cent_dir_size, + cent_dir_offset, + variable_data=b"", + ): + # size of zip64 end of central directory record + # size = SizeOfFixedFields + SizeOfVariableData - 12 + zip64_endrec_size = 44 + len(variable_data) + zip64endrec = struct.pack( + structEndArchive64, + stringEndArchive64, + zip64_endrec_size, + # version zip64endrec was made by + create_version, + # version needed to extract this zip64endrec + extract_version, + # number of this disk + diskno, + # number of the disk with the start of the central directory + cent_dir_start_diskno, + # total number of entries in the central directory on this + # disk + disk_cent_dir_count, + # total number of entries in the central directory + total_cent_dir_count, + # size of the central directory + cent_dir_size, + # offset of start of central directory with respect to the + # starting disk number + cent_dir_offset, + # zip64 extensible data sector (variable size) + ) + return zip64endrec + variable_data + + def zip64_endrec(self, centDirCount, centDirSize, centDirOffset): + params = self.get_zip64_endrec_params( + centDirCount, + centDirSize, + centDirOffset, + ) + return self._encode_zip64_endrec(**params) + + def get_zip64_endrec_locator_params(self, zip64_endrec_offset): + return { + "zip64_endrec_offset": zip64_endrec_offset, + "zip64_cent_dir_start_diskno": 0, + "total_disk_count": 1, + } + + def _encode_zip64_endrec_locator( + self, zip64_endrec_offset, zip64_cent_dir_start_diskno, total_disk_count + ): + return struct.pack( + structEndArchive64Locator, + stringEndArchive64Locator, + # number of the disk with the start of the zip64 end of central + # directory + zip64_cent_dir_start_diskno, + # relative offset of the zip64 end of central directory record + zip64_endrec_offset, + # total number of disks + total_disk_count, + ) + + def zip64_endrec_locator(self, zip64_endrec_offset): + params = self.get_zip64_endrec_locator_params(zip64_endrec_offset) + return self._encode_zip64_endrec_locator(**params) + + def get_endrec_params(self, centDirCount, centDirSize, centDirOffset): + return { + "diskno": 0, + "cent_dir_start_diskno": 0, + "disk_cent_dir_count": centDirCount, + # total number of entries in the central directory + "total_cent_dir_count": centDirCount, + # size of the central directory + "cent_dir_size": centDirSize, + # offset of start of central directory with respect to the + # starting disk number + "cent_dir_offset": centDirOffset, + "comment": self._comment, + } + + def _encode_endrec( + self, + diskno, + cent_dir_start_diskno, + disk_cent_dir_count, + total_cent_dir_count, + cent_dir_size, + cent_dir_offset, + comment, + ): + + endrec = struct.pack( + structEndArchive, + stringEndArchive, + # number of this disk + diskno, + # number of the disk with the start of the central directory + cent_dir_start_diskno, + # total number of entries in the central directory on this + # disk + disk_cent_dir_count, + # total number of entries in the central directory + total_cent_dir_count, + # size of the central directory + cent_dir_size, + # offset of start of central directory with respect to the + # starting disk number + cent_dir_offset, + # .ZIP file comment length + len(comment) + ) + return endrec + comment + + def endrec(self, centDirCount, centDirSize, centDirOffset): + params = self.get_endrec_params(centDirCount, centDirSize, centDirOffset) + return self._encode_endrec(**params) + def _write_end_record(self): - for zinfo in self.filelist: # write central directory - dt = zinfo.date_time - dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] - dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) - extra = [] - if zinfo.file_size > ZIP64_LIMIT \ - or zinfo.compress_size > ZIP64_LIMIT: - extra.append(zinfo.file_size) - extra.append(zinfo.compress_size) - file_size = 0xffffffff - compress_size = 0xffffffff - else: - file_size = zinfo.file_size - compress_size = zinfo.compress_size + for zinfo in self.filelist: + self.fp.write(zinfo.central_directory()) - if zinfo.header_offset > ZIP64_LIMIT: - extra.append(zinfo.header_offset) - header_offset = 0xffffffff - else: - header_offset = zinfo.header_offset - - extra_data = zinfo.extra - min_version = 0 - if extra: - # Append a ZIP64 field to the extra's - extra_data = _strip_extra(extra_data, (1,)) - extra_data = struct.pack( - ' ZIP_FILECOUNT_LIMIT: requires_zip64 = "Files count" @@ -1902,25 +2443,17 @@ def _write_end_record(self): if not self._allowZip64: raise LargeZipFile(requires_zip64 + " would require ZIP64 extensions") - zip64endrec = struct.pack( - structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset) - self.fp.write(zip64endrec) - - zip64locrec = struct.pack( - structEndArchive64Locator, - stringEndArchive64Locator, 0, pos2, 1) - self.fp.write(zip64locrec) + + self.fp.write( + self.zip64_endrec(centDirCount, centDirSize, centDirOffset) + ) + self.fp.write(self.zip64_endrec_locator(pos)) + centDirCount = min(centDirCount, 0xFFFF) centDirSize = min(centDirSize, 0xFFFFFFFF) centDirOffset = min(centDirOffset, 0xFFFFFFFF) - endrec = struct.pack(structEndArchive, stringEndArchive, - 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset, len(self._comment)) - self.fp.write(endrec) - self.fp.write(self._comment) + self.fp.write(self.endrec(centDirCount, centDirSize, centDirOffset)) self.fp.flush() def _fpclose(self, fp): diff --git a/Misc/NEWS.d/next/Library/2019-07-26-09-33-51.bpo-37538.yPF58-.rst b/Misc/NEWS.d/next/Library/2019-07-26-09-33-51.bpo-37538.yPF58-.rst new file mode 100644 index 00000000000000..9d9f9419e0b215 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-07-26-09-33-51.bpo-37538.yPF58-.rst @@ -0,0 +1 @@ +Refactor :mod:`zipfile` module to ease extending functionality in subclasses and fix seeking in encrypted files. \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy