2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2013-06-03 15:56:24 +08:00
|
|
|
class CentralDirectory
|
2010-11-30 16:27:59 +08:00
|
|
|
include Enumerable
|
2011-11-18 04:53:04 +08:00
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
END_OF_CDS = 0x06054b50
|
|
|
|
ZIP64_END_OF_CDS = 0x06064b50
|
|
|
|
ZIP64_EOCD_LOCATOR = 0x07064b50
|
|
|
|
MAX_END_OF_CDS_SIZE = 65536 + 18
|
|
|
|
STATIC_EOCD_SIZE = 22
|
2010-11-30 16:27:59 +08:00
|
|
|
|
|
|
|
attr_reader :comment
|
|
|
|
|
|
|
|
# Returns an Enumerable containing the entries.
|
|
|
|
def entries
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set.entries
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def initialize(entries = EntrySet.new, comment = '') #:nodoc:
|
2010-11-30 16:27:59 +08:00
|
|
|
super()
|
2013-06-03 15:56:24 +08:00
|
|
|
@entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries)
|
2013-08-27 04:26:14 +08:00
|
|
|
@comment = comment
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def write_to_stream(io) #:nodoc:
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
cdir_offset = io.tell
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set.each { |entry| entry.write_c_dir_entry(io) }
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
eocd_offset = io.tell
|
|
|
|
cdir_size = eocd_offset - cdir_offset
|
2014-03-14 00:48:04 +08:00
|
|
|
if ::Zip.write_zip64_support
|
|
|
|
need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF
|
|
|
|
need_zip64_eocd ||= @entry_set.any? { |entry| entry.extra['Zip64'] }
|
|
|
|
if need_zip64_eocd
|
|
|
|
write_64_e_o_c_d(io, cdir_offset, cdir_size)
|
|
|
|
write_64_eocd_locator(io, eocd_offset)
|
|
|
|
end
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
end
|
|
|
|
write_e_o_c_d(io, cdir_offset, cdir_size)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
def write_e_o_c_d(io, offset, cdir_size) #:nodoc:
|
2012-02-14 06:03:34 +08:00
|
|
|
tmp = [
|
2013-08-27 04:26:14 +08:00
|
|
|
END_OF_CDS,
|
2013-06-03 02:33:03 +08:00
|
|
|
0, # @numberOfThisDisk
|
|
|
|
0, # @numberOfDiskWithStartOfCDir
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
@entry_set ? [@entry_set.size, 0xFFFF].min : 0,
|
|
|
|
@entry_set ? [@entry_set.size, 0xFFFF].min : 0,
|
|
|
|
[cdir_size, 0xFFFFFFFF].min,
|
|
|
|
[offset, 0xFFFFFFFF].min,
|
2014-07-23 17:54:43 +08:00
|
|
|
@comment ? @comment.bytesize : 0
|
2012-02-14 06:03:34 +08:00
|
|
|
]
|
|
|
|
io << tmp.pack('VvvvvVVv')
|
|
|
|
io << @comment
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
private :write_e_o_c_d
|
|
|
|
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc:
|
|
|
|
tmp = [
|
|
|
|
ZIP64_END_OF_CDS,
|
|
|
|
44, # size of zip64 end of central directory record (excludes signature and field itself)
|
|
|
|
VERSION_MADE_BY,
|
|
|
|
VERSION_NEEDED_TO_EXTRACT_ZIP64,
|
|
|
|
0, # @numberOfThisDisk
|
|
|
|
0, # @numberOfDiskWithStartOfCDir
|
|
|
|
@entry_set ? @entry_set.size : 0, # number of entries on this disk
|
|
|
|
@entry_set ? @entry_set.size : 0, # number of entries total
|
|
|
|
cdir_size, # size of central directory
|
|
|
|
offset, # offset of start of central directory in its disk
|
|
|
|
]
|
|
|
|
io << tmp.pack('VQ<vvVVQ<Q<Q<Q<')
|
|
|
|
end
|
|
|
|
|
|
|
|
private :write_64_e_o_c_d
|
|
|
|
|
|
|
|
def write_64_eocd_locator(io, zip64_eocd_offset)
|
|
|
|
tmp = [
|
|
|
|
ZIP64_EOCD_LOCATOR,
|
|
|
|
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
|
|
|
|
1 # total number of disks
|
|
|
|
]
|
|
|
|
io << tmp.pack('VVQ<V')
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
|
Add read/write support for zip64 extensions
This commit adds the capability of creating archives larger than
4GB via zip64 extensions. It also fixes bugs reading archives of
this size (specifically, the 64-bit offset of the local file
header was not being read from the central directory entry).
To maximize compatibility, zip64 extensions are used only when
required. Unfortunately, at the time we write a local file header,
we don't know the size of the file and thus whether a Zip64
Extended Information Extra Field will be required. Therefore
this commit writes a 'placeholder' extra field to reserve space
for the zip64 entry, which will be written if necessary when
we update the local entry with the final sizes and CRC. I use
the signature "\x99\x99" for this field, following the example
of DotNetZip which does the same.
This commit also adds a rake task, zip64_full_test, which
fully tests zip64 by actually creating and verifying a 4GB zip
file. Please note, however, that this test requires UnZip
version 6.00 or newer, which may not be supplied by your OS.
This test doesn't run along with the main unit tests because
it takes a few minutes to complete.
2013-09-28 10:41:00 +08:00
|
|
|
private :write_64_eocd_locator
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def read_64_e_o_c_d(buf) #:nodoc:
|
|
|
|
buf = get_64_e_o_c_d(buf)
|
|
|
|
@size_of_zip64_e_o_c_d = Entry.read_zip_64_long(buf)
|
|
|
|
@version_made_by = Entry.read_zip_short(buf)
|
|
|
|
@version_needed_for_extract = Entry.read_zip_short(buf)
|
|
|
|
@number_of_this_disk = Entry.read_zip_long(buf)
|
|
|
|
@number_of_disk_with_start_of_cdir = Entry.read_zip_long(buf)
|
|
|
|
@total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_64_long(buf)
|
|
|
|
@size = Entry.read_zip_64_long(buf)
|
|
|
|
@size_in_bytes = Entry.read_zip_64_long(buf)
|
|
|
|
@cdir_offset = Entry.read_zip_64_long(buf)
|
|
|
|
@zip_64_extensible = buf.slice!(0, buf.bytesize)
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip consistency problem while reading eocd structure" unless buf.size == 0
|
2013-08-27 04:26:14 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def read_e_o_c_d(buf) #:nodoc:
|
|
|
|
buf = get_e_o_c_d(buf)
|
|
|
|
@number_of_this_disk = Entry.read_zip_short(buf)
|
|
|
|
@number_of_disk_with_start_of_cdir = Entry.read_zip_short(buf)
|
|
|
|
@total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_short(buf)
|
|
|
|
@size = Entry.read_zip_short(buf)
|
|
|
|
@size_in_bytes = Entry.read_zip_long(buf)
|
|
|
|
@cdir_offset = Entry.read_zip_long(buf)
|
|
|
|
comment_length = Entry.read_zip_short(buf)
|
2014-03-01 18:17:22 +08:00
|
|
|
@comment = if comment_length.to_i <= 0
|
2013-08-27 04:26:14 +08:00
|
|
|
buf.slice!(0, buf.size)
|
|
|
|
else
|
|
|
|
buf.read(comment_length)
|
|
|
|
end
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip consistency problem while reading eocd structure" unless buf.size == 0
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-18 04:53:04 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def read_central_directory_entries(io) #:nodoc:
|
2010-11-30 16:27:59 +08:00
|
|
|
begin
|
2013-08-27 04:26:14 +08:00
|
|
|
io.seek(@cdir_offset, IO::SEEK_SET)
|
2010-11-30 16:27:59 +08:00
|
|
|
rescue Errno::EINVAL
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip consistency problem while reading central directory entry"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 15:56:24 +08:00
|
|
|
@entry_set = EntrySet.new
|
2012-02-14 00:55:08 +08:00
|
|
|
@size.times do
|
2013-08-27 04:26:14 +08:00
|
|
|
@entry_set << Entry.read_c_dir_entry(io)
|
2012-02-14 00:55:08 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-18 04:53:04 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def read_from_stream(io) #:nodoc:
|
2013-08-27 04:26:14 +08:00
|
|
|
buf = start_buf(io)
|
|
|
|
if self.zip64_file?(buf)
|
|
|
|
read_64_e_o_c_d(buf)
|
|
|
|
else
|
|
|
|
read_e_o_c_d(buf)
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
read_central_directory_entries(io)
|
|
|
|
end
|
2011-11-18 04:53:04 +08:00
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def get_e_o_c_d(buf) #:nodoc:
|
|
|
|
sig_index = buf.rindex([END_OF_CDS].pack('V'))
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip end of central directory signature not found" unless sig_index
|
2013-08-27 04:26:14 +08:00
|
|
|
buf = buf.slice!((sig_index + 4)..(buf.bytesize))
|
|
|
|
|
|
|
|
def buf.read(count)
|
|
|
|
slice!(0, count)
|
|
|
|
end
|
|
|
|
|
|
|
|
buf
|
|
|
|
end
|
|
|
|
|
|
|
|
def zip64_file?(buf)
|
|
|
|
buf.rindex([ZIP64_END_OF_CDS].pack('V')) && buf.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
|
|
|
|
end
|
|
|
|
|
|
|
|
def start_buf(io)
|
2010-11-30 16:27:59 +08:00
|
|
|
begin
|
2013-08-27 04:26:14 +08:00
|
|
|
io.seek(-MAX_END_OF_CDS_SIZE, IO::SEEK_END)
|
2010-11-30 16:27:59 +08:00
|
|
|
rescue Errno::EINVAL
|
2011-11-18 04:53:04 +08:00
|
|
|
io.seek(0, IO::SEEK_SET)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-08-27 04:26:14 +08:00
|
|
|
io.read
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_64_e_o_c_d(buf) #:nodoc:
|
|
|
|
zip_64_start = buf.rindex([ZIP64_END_OF_CDS].pack('V'))
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip64 end of central directory signature not found" unless zip_64_start
|
2013-08-27 04:26:14 +08:00
|
|
|
zip_64_locator = buf.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Zip64 end of central directory signature locator not found" unless zip_64_locator
|
2013-08-27 04:26:14 +08:00
|
|
|
buf = buf.slice!((zip_64_start + 4)..zip_64_locator)
|
2012-02-14 00:55:08 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
def buf.read(count)
|
2011-11-18 04:53:04 +08:00
|
|
|
slice!(0, count)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2012-02-14 00:55:08 +08:00
|
|
|
|
|
|
|
buf
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# For iterating over the entries.
|
|
|
|
def each(&proc)
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set.each(&proc)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2014-01-24 17:37:38 +08:00
|
|
|
# Returns the number of entries in the central directory (and
|
2010-11-30 16:27:59 +08:00
|
|
|
# consequently in the zip archive).
|
|
|
|
def size
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set.size
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def self.read_from_stream(io) #:nodoc:
|
2013-06-03 02:33:03 +08:00
|
|
|
cdir = new
|
2010-11-30 16:27:59 +08:00
|
|
|
cdir.read_from_stream(io)
|
|
|
|
return cdir
|
2014-01-24 17:37:38 +08:00
|
|
|
rescue Error
|
2010-11-30 16:27:59 +08:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2012-02-14 00:55:08 +08:00
|
|
|
def ==(other) #:nodoc:
|
2013-06-03 15:56:24 +08:00
|
|
|
return false unless other.kind_of?(CentralDirectory)
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set.entries.sort == other.entries.sort && comment == other.comment
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
|
|
# rubyzip is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the ruby license.
|