Content-Length: 66189 | pFad | http://github.com/python/cpython/pull/14957.diff

thub.com 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








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/python/cpython/pull/14957.diff

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy