Fix reading zip64 files with max length file comment.

If a zip file has a comment that is 65,535 characters long - which is a
valid length and the maximum allowable length - the initial read of the
archive fails to find the Zip64 End of Central Directory Locator and
therefore cannot read the rest of the file.

This commit fixes this by making sure that we look far enough back into
the file from the end to find this locator, and then use the
information in it to find the Zip64 End of Central Directory Record.

Test added to catch regressions.

Fixes #509.
This commit is contained in:
Robert Haines 2022-01-17 17:45:19 +00:00
parent 1d6bfb7e69
commit 9c3f8254c7
3 changed files with 59 additions and 36 deletions

View File

@ -6,15 +6,16 @@ module Zip
class CentralDirectory class CentralDirectory
extend Forwardable extend Forwardable
END_OF_CDS = 0x06054b50 END_OF_CD_SIG = 0x06054b50
ZIP64_END_OF_CDS = 0x06064b50 ZIP64_END_OF_CD_SIG = 0x06064b50
ZIP64_EOCD_LOCATOR = 0x07064b50 ZIP64_EOCD_LOCATOR_SIG = 0x07064b50
STATIC_EOCD_SIZE = 22 STATIC_EOCD_SIZE = 22
ZIP64_STATIC_EOCD_SIZE = 56 ZIP64_STATIC_EOCD_SIZE = 56
ZIP64_EOCD_LOC_SIZE = 20 ZIP64_EOCD_LOC_SIZE = 20
MAX_FILE_COMMENT_SIZE = 1 << 16 MAX_FILE_COMMENT_SIZE = (1 << 16) - 1
MAX_END_OF_CDS_SIZE = MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE MAX_END_OF_CD_SIZE =
MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE + ZIP64_EOCD_LOC_SIZE
attr_accessor :comment attr_accessor :comment
@ -47,7 +48,7 @@ module Zip
def write_e_o_c_d(io, offset, cdir_size) #:nodoc: def write_e_o_c_d(io, offset, cdir_size) #:nodoc:
tmp = [ tmp = [
END_OF_CDS, END_OF_CD_SIG,
0, # @numberOfThisDisk 0, # @numberOfThisDisk
0, # @numberOfDiskWithStartOfCDir 0, # @numberOfDiskWithStartOfCDir
@entry_set ? [@entry_set.size, 0xFFFF].min : 0, @entry_set ? [@entry_set.size, 0xFFFF].min : 0,
@ -64,7 +65,7 @@ module Zip
def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc:
tmp = [ tmp = [
ZIP64_END_OF_CDS, ZIP64_END_OF_CD_SIG,
44, # size of zip64 end of central directory record (excludes signature and field itself) 44, # size of zip64 end of central directory record (excludes signature and field itself)
VERSION_MADE_BY, VERSION_MADE_BY,
VERSION_NEEDED_TO_EXTRACT_ZIP64, VERSION_NEEDED_TO_EXTRACT_ZIP64,
@ -82,7 +83,7 @@ module Zip
def write_64_eocd_locator(io, zip64_eocd_offset) def write_64_eocd_locator(io, zip64_eocd_offset)
tmp = [ tmp = [
ZIP64_EOCD_LOCATOR, ZIP64_EOCD_LOCATOR_SIG,
0, # number of disk containing the start of zip64 eocd record 0, # number of disk containing the start of zip64 eocd record
zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk
1 # total number of disks 1 # total number of disks
@ -93,15 +94,7 @@ module Zip
private :write_64_eocd_locator private :write_64_eocd_locator
def unpack_64_e_o_c_d(buffer) #:nodoc: def unpack_64_e_o_c_d(buffer) #:nodoc:
index = buffer.rindex([ZIP64_END_OF_CDS].pack('V')) _, # ZIP64_END_OF_CD_SIG. We know we have this at this point.
raise Error, 'Zip64 end of central directory signature not found' unless index
l_index = buffer.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
raise Error, 'Zip64 end of central directory signature locator not found' unless l_index
buf = buffer.slice(index..l_index)
_, # ZIP64_END_OF_CDS signature. We know we have this at this point.
@size_of_zip64_e_o_c_d, @size_of_zip64_e_o_c_d,
@version_made_by, @version_made_by,
@version_needed_for_extract, @version_needed_for_extract,
@ -110,7 +103,7 @@ module Zip
@total_number_of_entries_in_cdir_on_this_disk, @total_number_of_entries_in_cdir_on_this_disk,
@size, @size,
@size_in_bytes, @size_in_bytes,
@cdir_offset = buf.unpack('VQ<vvVVQ<Q<Q<Q<') @cdir_offset = buffer.unpack('VQ<vvVVQ<Q<Q<Q<')
zip64_extensible_data_size = zip64_extensible_data_size =
@size_of_zip64_e_o_c_d - ZIP64_STATIC_EOCD_SIZE + 12 @size_of_zip64_e_o_c_d - ZIP64_STATIC_EOCD_SIZE + 12
@ -124,20 +117,22 @@ module Zip
end end
end end
def unpack_64_eocd_locator(buffer) #:nodoc:
_, # ZIP64_EOCD_LOCATOR_SIG. We know we have this at this point.
_, zip64_eocd_offset, = buffer.unpack('VVQ<V')
zip64_eocd_offset
end
def unpack_e_o_c_d(buffer) #:nodoc: def unpack_e_o_c_d(buffer) #:nodoc:
index = buffer.rindex([END_OF_CDS].pack('V')) _, # END_OF_CD_SIG. We know we have this at this point.
raise Error, 'Zip end of central directory signature not found' unless index
buf = buffer.slice(index, buffer.size)
_, # END_OF_CDS signature. We know we have this at this point.
num_disk, num_disk,
num_disk_cdir, num_disk_cdir,
num_cdir_disk, num_cdir_disk,
num_entries, num_entries,
size_in_bytes, size_in_bytes,
cdir_offset, cdir_offset,
comment_length = buf.unpack('VvvvvVVv') comment_length = buffer.unpack('VvvvvVVv')
@number_of_this_disk = num_disk unless num_disk == 0xFFFF @number_of_this_disk = num_disk unless num_disk == 0xFFFF
@number_of_disk_with_start_of_cdir = num_disk_cdir unless num_disk_cdir == 0xFFFF @number_of_disk_with_start_of_cdir = num_disk_cdir unless num_disk_cdir == 0xFFFF
@ -147,7 +142,7 @@ module Zip
@cdir_offset = cdir_offset unless cdir_offset == 0xFFFFFFFF @cdir_offset = cdir_offset unless cdir_offset == 0xFFFFFFFF
@comment = if comment_length.positive? @comment = if comment_length.positive?
buf.slice(STATIC_EOCD_SIZE, comment_length) buffer.slice(STATIC_EOCD_SIZE, comment_length)
else else
'' ''
end end
@ -203,22 +198,45 @@ module Zip
end end
def read_eocds(io) #:nodoc: def read_eocds(io) #:nodoc:
buf = start_buf(io) base_location, data = eocd_data(io)
unpack_64_e_o_c_d(buf) if zip64_file?(buf)
unpack_e_o_c_d(buf) eocd_location = data.rindex([END_OF_CD_SIG].pack('V'))
raise Error, 'Zip end of central directory signature not found' unless eocd_location
zip64_eocd_locator = data.rindex([ZIP64_EOCD_LOCATOR_SIG].pack('V'))
if zip64_eocd_locator
zip64_eocd_location = data.rindex([ZIP64_END_OF_CD_SIG].pack('V'))
zip64_eocd_data =
if zip64_eocd_location
data.slice(zip64_eocd_location..zip64_eocd_locator)
else
zip64_eocd_location = unpack_64_eocd_locator(
data.slice(zip64_eocd_locator..eocd_location)
)
unless zip64_eocd_location
raise Error, 'Zip64 end of central directory signature not found'
end end
def zip64_file?(buf) io.seek(zip64_eocd_location, IO::SEEK_SET)
buf.rindex([ZIP64_END_OF_CDS].pack('V')) && buf.rindex([ZIP64_EOCD_LOCATOR].pack('V')) io.read(base_location + zip64_eocd_locator - zip64_eocd_location)
end end
def start_buf(io) unpack_64_e_o_c_d(zip64_eocd_data)
end
unpack_e_o_c_d(data.slice(eocd_location..-1))
end
def eocd_data(io)
begin begin
io.seek(-MAX_END_OF_CDS_SIZE, IO::SEEK_END) io.seek(-MAX_END_OF_CD_SIZE, IO::SEEK_END)
rescue Errno::EINVAL rescue Errno::EINVAL
io.seek(0, IO::SEEK_SET) io.seek(0, IO::SEEK_SET)
end end
io.read
[io.tell, io.read]
end end
# Reads the End of Central Directory Record (and the Zip64 equivalent if # Reads the End of Central Directory Record (and the Zip64 equivalent if

Binary file not shown.

View File

@ -208,6 +208,11 @@ class ZipFileTest < MiniTest::Test
Zip::File.open('test/data/max_length_file_comment.zip') Zip::File.open('test/data/max_length_file_comment.zip')
end end
def test_open_zip64_file_with_max_length_comment
# Should not raise any errors.
Zip::File.open('test/data/zip64_max_length_file_comment.zip')
end
def test_count_entries def test_count_entries
[ [
['test/data/osx-archive.zip', 4], ['test/data/osx-archive.zip', 4],