mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
localunzip: Add support for data descriptors
This commit is contained in:
parent
1f40a37eed
commit
2d0bf3d9ef
@ -24,6 +24,7 @@ HEADER_BYTE_SIG = pack(b'<L', HEADER_SIG)
|
|||||||
local_header_fmt = b'<L5HL2L2H'
|
local_header_fmt = b'<L5HL2L2H'
|
||||||
local_header_sz = calcsize(local_header_fmt)
|
local_header_sz = calcsize(local_header_fmt)
|
||||||
ZIP_STORED, ZIP_DEFLATED = 0, 8
|
ZIP_STORED, ZIP_DEFLATED = 0, 8
|
||||||
|
DATA_DESCRIPTOR_SIG = pack(b'<L', 0x08074b50)
|
||||||
|
|
||||||
LocalHeader = namedtuple('LocalHeader',
|
LocalHeader = namedtuple('LocalHeader',
|
||||||
'signature min_version flags compression_method mod_time mod_date '
|
'signature min_version flags compression_method mod_time mod_date '
|
||||||
@ -61,6 +62,27 @@ def find_local_header(f):
|
|||||||
return header
|
return header
|
||||||
f.seek(pos)
|
f.seek(pos)
|
||||||
|
|
||||||
|
def find_data_descriptor(f):
|
||||||
|
pos = f.tell()
|
||||||
|
DD = namedtuple('DataDescriptor', 'crc32 compressed_size uncompressed_size')
|
||||||
|
raw = b'a'*16
|
||||||
|
try:
|
||||||
|
while len(raw) >= 16:
|
||||||
|
raw = f.read(50*1024)
|
||||||
|
idx = raw.find(DATA_DESCRIPTOR_SIG)
|
||||||
|
if idx != -1:
|
||||||
|
f.seek(f.tell() - len(raw) + idx + len(DATA_DESCRIPTOR_SIG))
|
||||||
|
return DD(*unpack(b'<LLL', f.read(12)))
|
||||||
|
# Rewind to handle the case of the signature being cut off
|
||||||
|
# by the 50K boundary
|
||||||
|
f.seek(f.tell()-len(DATA_DESCRIPTOR_SIG))
|
||||||
|
|
||||||
|
raise ValueError('Failed to find data descriptor signature. '
|
||||||
|
'Data descriptors without signatures are not '
|
||||||
|
'supported.')
|
||||||
|
finally:
|
||||||
|
f.seek(pos)
|
||||||
|
|
||||||
def read_local_file_header(f):
|
def read_local_file_header(f):
|
||||||
pos = f.tell()
|
pos = f.tell()
|
||||||
raw = f.read(local_header_sz)
|
raw = f.read(local_header_sz)
|
||||||
@ -77,12 +99,11 @@ def read_local_file_header(f):
|
|||||||
raise ValueError('This ZIP file uses unsupported features')
|
raise ValueError('This ZIP file uses unsupported features')
|
||||||
if header.flags & 0b1:
|
if header.flags & 0b1:
|
||||||
raise ValueError('This ZIP file is encrypted')
|
raise ValueError('This ZIP file is encrypted')
|
||||||
if header.flags & (1 << 3):
|
|
||||||
raise ValueError('This ZIP file uses data descriptors. This is unsupported')
|
|
||||||
if header.flags & (1 << 13):
|
if header.flags & (1 << 13):
|
||||||
raise ValueError('This ZIP file uses masking, unsupported.')
|
raise ValueError('This ZIP file uses masking, unsupported.')
|
||||||
if header.compression_method not in {ZIP_STORED, ZIP_DEFLATED}:
|
if header.compression_method not in {ZIP_STORED, ZIP_DEFLATED}:
|
||||||
raise ValueError('This ZIP file uses an unsupported compression method')
|
raise ValueError('This ZIP file uses an unsupported compression method')
|
||||||
|
has_data_descriptors = header.flags & (1 << 3)
|
||||||
fname = extra = None
|
fname = extra = None
|
||||||
if header.filename_length > 0:
|
if header.filename_length > 0:
|
||||||
fname = f.read(header.filename_length)
|
fname = f.read(header.filename_length)
|
||||||
@ -97,10 +118,16 @@ def read_local_file_header(f):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
fname = decode_arcname(fname).replace('\\', '/')
|
fname = decode_arcname(fname).replace('\\', '/')
|
||||||
|
|
||||||
if header.extra_length > 0:
|
if header.extra_length > 0:
|
||||||
extra = f.read(header.extra_length)
|
extra = f.read(header.extra_length)
|
||||||
if len(extra) != header.extra_length:
|
if len(extra) != header.extra_length:
|
||||||
return
|
return
|
||||||
|
if has_data_descriptors:
|
||||||
|
desc = find_data_descriptor(f)
|
||||||
|
header = header._replace(crc32=desc.crc32,
|
||||||
|
compressed_size=desc.compressed_size,
|
||||||
|
uncompressed_size=desc.uncompressed_size)
|
||||||
return LocalHeader(*(
|
return LocalHeader(*(
|
||||||
header[:-2] + (fname, extra)
|
header[:-2] + (fname, extra)
|
||||||
))
|
))
|
||||||
@ -142,11 +169,13 @@ def _extractall(f, path=None, file_info=None):
|
|||||||
header = read_local_file_header(f)
|
header = read_local_file_header(f)
|
||||||
if not header:
|
if not header:
|
||||||
break
|
break
|
||||||
|
has_data_descriptors = header.flags & (1 << 3)
|
||||||
|
seekval = header.compressed_size + (16 if has_data_descriptors else 0)
|
||||||
found = True
|
found = True
|
||||||
parts = header.filename.split('/')
|
parts = header.filename.split('/')
|
||||||
if header.uncompressed_size == 0:
|
if header.uncompressed_size == 0:
|
||||||
# Directory
|
# Directory
|
||||||
f.seek(f.tell() + header.compressed_size)
|
f.seek(f.tell()+seekval)
|
||||||
if path is not None:
|
if path is not None:
|
||||||
bdir = os.path.join(path, *parts)
|
bdir = os.path.join(path, *parts)
|
||||||
if not os.path.exists(bdir):
|
if not os.path.exists(bdir):
|
||||||
@ -167,7 +196,7 @@ def _extractall(f, path=None, file_info=None):
|
|||||||
else:
|
else:
|
||||||
copy_compressed_file(f, header.compressed_size, o)
|
copy_compressed_file(f, header.compressed_size, o)
|
||||||
else:
|
else:
|
||||||
f.seek(f.tell() + header.compressed_size)
|
f.seek(f.tell()+seekval)
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
raise ValueError('Not a ZIP file')
|
raise ValueError('Not a ZIP file')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user