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
--- 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