2019-02-11 00:51:29 +08:00
|
|
|
require 'pathname'
|
2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2013-06-03 15:56:24 +08:00
|
|
|
class Entry
|
2013-06-03 02:33:03 +08:00
|
|
|
STORED = 0
|
2010-11-30 16:27:59 +08:00
|
|
|
DEFLATED = 8
|
2013-08-15 06:00:27 +08:00
|
|
|
# Language encoding flag (EFS) bit
|
|
|
|
EFS = 0b100000000000
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
|
|
|
:name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
|
2016-10-11 05:04:36 +08:00
|
|
|
:internal_file_attributes,
|
2013-06-03 02:33:03 +08:00
|
|
|
:gp_flags, :header_signature, :follow_symlinks,
|
|
|
|
:restore_times, :restore_permissions, :restore_ownership,
|
|
|
|
:unix_uid, :unix_gid, :unix_perms,
|
|
|
|
:dirty
|
2010-11-30 16:27:59 +08:00
|
|
|
attr_reader :ftype, :filepath # :nodoc:
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def set_default_vars_values
|
|
|
|
@local_header_offset = 0
|
2014-03-13 05:42:53 +08:00
|
|
|
@local_header_size = nil # not known until local entry is created or read
|
2013-06-03 02:33:03 +08:00
|
|
|
@internal_file_attributes = 1
|
|
|
|
@external_file_attributes = 0
|
|
|
|
@header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT
|
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
|
|
|
@version = VERSION_MADE_BY
|
2013-06-03 02:33:03 +08:00
|
|
|
|
|
|
|
@ftype = nil # unspecified or unknown
|
|
|
|
@filepath = nil
|
|
|
|
@gp_flags = 0
|
2013-08-15 06:00:27 +08:00
|
|
|
if ::Zip.unicode_names
|
|
|
|
@gp_flags |= EFS
|
|
|
|
@version = 63
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
@follow_symlinks = false
|
|
|
|
|
2019-11-01 01:30:14 +08:00
|
|
|
@restore_times = false
|
2010-11-30 16:27:59 +08:00
|
|
|
@restore_permissions = false
|
2013-06-03 02:33:03 +08:00
|
|
|
@restore_ownership = false
|
2013-08-15 06:00:27 +08:00
|
|
|
# BUG: need an extra field to support uid/gid's
|
2013-06-03 02:33:03 +08:00
|
|
|
@unix_uid = nil
|
|
|
|
@unix_gid = nil
|
|
|
|
@unix_perms = nil
|
2015-03-24 00:06:01 +08:00
|
|
|
# @posix_acl = nil
|
|
|
|
# @ntfs_acl = nil
|
2013-06-03 02:33:03 +08:00
|
|
|
@dirty = false
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def check_name(name)
|
2015-03-25 00:16:03 +08:00
|
|
|
return unless name.start_with?('/')
|
|
|
|
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def initialize(*args)
|
|
|
|
name = args[1] || ''
|
|
|
|
check_name(name)
|
|
|
|
|
|
|
|
set_default_vars_values
|
|
|
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
2011-11-17 17:48:42 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
@zipfile = args[0] || ''
|
|
|
|
@name = name
|
|
|
|
@comment = args[2] || ''
|
|
|
|
@extra = args[3] || ''
|
|
|
|
@compressed_size = args[4] || 0
|
|
|
|
@crc = args[5] || 0
|
2013-06-03 15:56:24 +08:00
|
|
|
@compression_method = args[6] || ::Zip::Entry::DEFLATED
|
2013-06-03 02:33:03 +08:00
|
|
|
@size = args[7] || 0
|
|
|
|
@time = args[8] || ::Zip::DOSTime.now
|
|
|
|
|
|
|
|
@ftype = name_is_directory? ? :directory : :file
|
2015-03-25 00:44:47 +08:00
|
|
|
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def time
|
2013-06-03 02:33:03 +08:00
|
|
|
if @extra['UniversalTime']
|
|
|
|
@extra['UniversalTime'].mtime
|
2014-09-12 14:04:23 +08:00
|
|
|
elsif @extra['NTFS']
|
|
|
|
@extra['NTFS'].mtime
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2012-02-06 08:52:27 +08:00
|
|
|
# Standard time field in central directory has local time
|
2010-11-30 16:27:59 +08:00
|
|
|
# under archive creator. Then, we can't get timezone.
|
|
|
|
@time
|
|
|
|
end
|
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
|
2015-03-21 18:14:21 +08:00
|
|
|
alias mtime time
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def time=(value)
|
2014-09-12 14:04:23 +08:00
|
|
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
2013-06-03 02:33:03 +08:00
|
|
|
@extra.create('UniversalTime')
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2014-09-12 14:04:23 +08:00
|
|
|
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
2013-06-03 02:33:03 +08:00
|
|
|
@time = value
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def file_type_is?(type)
|
2015-03-23 00:30:24 +08:00
|
|
|
raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
|
2013-06-03 02:33:03 +08:00
|
|
|
@ftype == type
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
# Dynamic checkers
|
2017-06-29 10:57:12 +08:00
|
|
|
%w[directory file symlink].each do |k|
|
2013-06-03 02:33:03 +08:00
|
|
|
define_method "#{k}?" do
|
|
|
|
file_type_is?(k.to_sym)
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def name_is_directory? #:nodoc:all
|
2013-07-01 04:52:18 +08:00
|
|
|
@name.end_with?('/')
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
|
|
|
|
2018-08-26 19:32:18 +08:00
|
|
|
# Is the name a relative path, free of `..` patterns that could lead to
|
|
|
|
# path traversal attacks? This does NOT handle symlinks; if the path
|
|
|
|
# contains symlinks, this check is NOT enough to guarantee safety.
|
|
|
|
def name_safe?
|
|
|
|
cleanpath = Pathname.new(@name).cleanpath
|
|
|
|
return false unless cleanpath.relative?
|
2018-08-27 02:55:26 +08:00
|
|
|
root = ::File::SEPARATOR
|
|
|
|
naive_expanded_path = ::File.join(root, cleanpath.to_s)
|
2019-03-03 22:46:49 +08:00
|
|
|
::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
|
2018-08-26 19:32:18 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def local_entry_offset #:nodoc:all
|
|
|
|
local_header_offset + @local_header_size
|
|
|
|
end
|
|
|
|
|
|
|
|
def name_size
|
|
|
|
@name ? @name.bytesize : 0
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def extra_size
|
|
|
|
@extra ? @extra.local_size : 0
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def comment_size
|
|
|
|
@comment ? @comment.bytesize : 0
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def calculate_local_header_size #:nodoc:all
|
|
|
|
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_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
|
|
|
# check before rewriting an entry (after file sizes are known)
|
|
|
|
# that we didn't change the header size (and thus clobber file data or something)
|
|
|
|
def verify_local_header_size!
|
2014-03-13 05:42:53 +08:00
|
|
|
return if @local_header_size.nil?
|
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
|
|
|
new_size = calculate_local_header_size
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
|
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
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def cdir_header_size #:nodoc:all
|
|
|
|
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
|
|
|
(@extra ? @extra.c_dir_size : 0) + comment_size
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def next_header_offset #:nodoc:all
|
2015-03-23 00:30:24 +08:00
|
|
|
local_entry_offset + compressed_size + data_descriptor_size
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
# Extracts entry to file dest_path (defaults to @name).
|
2018-08-26 19:32:18 +08:00
|
|
|
# NB: The caller is responsible for making sure dest_path is safe, if it
|
|
|
|
# is passed.
|
2018-07-02 02:57:50 +08:00
|
|
|
def extract(dest_path = nil, &block)
|
2018-08-26 19:32:18 +08:00
|
|
|
if dest_path.nil? && !name_safe?
|
2019-10-12 14:26:15 +08:00
|
|
|
warn "WARNING: skipped '#{@name}' as unsafe."
|
2018-07-02 04:45:06 +08:00
|
|
|
return self
|
2017-02-08 19:43:14 +08:00
|
|
|
end
|
|
|
|
|
2018-07-02 02:57:50 +08:00
|
|
|
dest_path ||= @name
|
|
|
|
block ||= proc { ::Zip.on_exists_proc }
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
if directory? || file? || symlink?
|
2015-03-23 00:30:24 +08:00
|
|
|
__send__("create_#{@ftype}", dest_path, &block)
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2015-03-23 00:32:47 +08:00
|
|
|
raise "unknown file type #{inspect}"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
@name
|
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2012-02-14 00:55:08 +08:00
|
|
|
class << self
|
|
|
|
def read_zip_short(io) # :nodoc:
|
|
|
|
io.read(2).unpack('v')[0]
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_zip_long(io) # :nodoc:
|
|
|
|
io.read(4).unpack('V')[0]
|
|
|
|
end
|
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def read_zip_64_long(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
|
|
|
io.read(8).unpack('Q<')[0]
|
2013-08-27 04:26:14 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def read_c_dir_entry(io) #:nodoc:all
|
2015-09-03 21:16:32 +08:00
|
|
|
path = if io.respond_to?(:path)
|
2015-03-21 04:00:20 +08:00
|
|
|
io.path
|
|
|
|
else
|
|
|
|
io
|
|
|
|
end
|
2014-01-07 02:29:08 +08:00
|
|
|
entry = new(path)
|
2012-02-14 00:55:08 +08:00
|
|
|
entry.read_c_dir_entry(io)
|
|
|
|
entry
|
2014-01-24 17:37:38 +08:00
|
|
|
rescue Error
|
2012-02-14 00:55:08 +08:00
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_local_entry(io)
|
2015-03-23 00:30:24 +08:00
|
|
|
entry = new(io)
|
2012-02-14 00:55:08 +08:00
|
|
|
entry.read_local_entry(io)
|
|
|
|
entry
|
2014-01-24 17:37:38 +08:00
|
|
|
rescue Error
|
2012-02-14 00:55:08 +08:00
|
|
|
nil
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2012-02-14 00:55:08 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
public
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def unpack_local_entry(buf)
|
|
|
|
@header_signature,
|
|
|
|
@version,
|
|
|
|
@fstype,
|
|
|
|
@gp_flags,
|
|
|
|
@compression_method,
|
|
|
|
@last_mod_time,
|
|
|
|
@last_mod_date,
|
|
|
|
@crc,
|
|
|
|
@compressed_size,
|
|
|
|
@size,
|
|
|
|
@name_length,
|
|
|
|
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_local_entry(io) #:nodoc:all
|
|
|
|
@local_header_offset = io.tell
|
|
|
|
|
2015-03-07 17:50:13 +08:00
|
|
|
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
2015-03-21 16:27:44 +08:00
|
|
|
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
unpack_local_entry(static_sized_fields_buf)
|
|
|
|
|
|
|
|
unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
|
2014-01-24 17:37:38 +08:00
|
|
|
raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
set_time(@last_mod_date, @last_mod_time)
|
|
|
|
|
|
|
|
@name = io.read(@name_length)
|
|
|
|
extra = io.read(@extra_length)
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2017-06-29 10:57:12 +08:00
|
|
|
@name.tr!('\\', '/')
|
2017-10-18 23:20:56 +08:00
|
|
|
if ::Zip.force_entry_names_encoding
|
|
|
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
|
|
|
|
if extra && extra.bytesize != @extra_length
|
2015-03-21 16:27:44 +08:00
|
|
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2015-03-25 00:44:47 +08:00
|
|
|
if @extra.is_a?(::Zip::ExtraField)
|
2015-09-30 15:55:50 +08:00
|
|
|
@extra.merge(extra) if extra
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-06-03 15:56:24 +08:00
|
|
|
@extra = ::Zip::ExtraField.new(extra)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
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
|
|
|
parse_zip64_extra(true)
|
2010-11-30 16:27:59 +08:00
|
|
|
@local_header_size = calculate_local_header_size
|
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def pack_local_entry
|
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
|
|
|
zip64 = @extra['Zip64']
|
2013-06-03 02:33:03 +08:00
|
|
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
|
|
|
@version_needed_to_extract, # version needed to extract
|
2018-12-03 23:14:32 +08:00
|
|
|
@gp_flags, # @gp_flags
|
2013-06-03 02:33:03 +08:00
|
|
|
@compression_method,
|
2018-12-03 23:14:32 +08:00
|
|
|
@time.to_binary_dos_time, # @last_mod_time
|
|
|
|
@time.to_binary_dos_date, # @last_mod_date
|
2013-06-03 02:33:03 +08:00
|
|
|
@crc,
|
2017-06-29 10:57:12 +08:00
|
|
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
|
|
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
2013-06-03 02:33:03 +08:00
|
|
|
name_size,
|
2013-08-27 04:26:14 +08:00
|
|
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
2012-02-14 00:55:08 +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
|
|
|
def write_local_entry(io, rewrite = false) #:nodoc:all
|
|
|
|
prep_zip64_extra(true)
|
|
|
|
verify_local_header_size! if rewrite
|
2013-06-03 02:33:03 +08:00
|
|
|
@local_header_offset = io.tell
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
io << pack_local_entry
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
io << @name
|
2014-04-17 20:23:12 +08:00
|
|
|
io << @extra.to_local_bin if @extra
|
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
|
|
|
@local_header_size = io.tell - @local_header_offset
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def unpack_c_dir_entry(buf)
|
|
|
|
@header_signature,
|
|
|
|
@version, # version of encoding software
|
|
|
|
@fstype, # filesystem type
|
|
|
|
@version_needed_to_extract,
|
|
|
|
@gp_flags,
|
|
|
|
@compression_method,
|
|
|
|
@last_mod_time,
|
|
|
|
@last_mod_date,
|
|
|
|
@crc,
|
|
|
|
@compressed_size,
|
|
|
|
@size,
|
|
|
|
@name_length,
|
|
|
|
@extra_length,
|
|
|
|
@comment_length,
|
|
|
|
_, # diskNumberStart
|
|
|
|
@internal_file_attributes,
|
|
|
|
@external_file_attributes,
|
|
|
|
@local_header_offset,
|
|
|
|
@name,
|
|
|
|
@extra,
|
|
|
|
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_ftype_from_c_dir_entry
|
|
|
|
@ftype = case @fstype
|
|
|
|
when ::Zip::FSTYPE_UNIX
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
2013-06-03 02:33:03 +08:00
|
|
|
case (@external_file_attributes >> 28)
|
2013-06-03 15:56:24 +08:00
|
|
|
when ::Zip::FILE_TYPE_DIR
|
2013-06-03 02:33:03 +08:00
|
|
|
:directory
|
2013-06-03 15:56:24 +08:00
|
|
|
when ::Zip::FILE_TYPE_FILE
|
2013-06-03 02:33:03 +08:00
|
|
|
:file
|
2013-06-03 15:56:24 +08:00
|
|
|
when ::Zip::FILE_TYPE_SYMLINK
|
2013-06-03 02:33:03 +08:00
|
|
|
:symlink
|
|
|
|
else
|
2015-03-24 00:06:01 +08:00
|
|
|
# best case guess for whether it is a file or not
|
|
|
|
# Otherwise this would be set to unknown and that entry would never be able to extracted
|
2013-06-03 02:33:03 +08:00
|
|
|
if name_is_directory?
|
|
|
|
:directory
|
|
|
|
else
|
|
|
|
:file
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if name_is_directory?
|
|
|
|
:directory
|
|
|
|
else
|
|
|
|
:file
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
def check_c_dir_entry_static_header_length(buf)
|
2015-03-25 00:16:03 +08:00
|
|
|
return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
|
|
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
def check_c_dir_entry_signature
|
2015-03-25 00:16:03 +08:00
|
|
|
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
|
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
def check_c_dir_entry_comment_size
|
2015-03-25 00:16:03 +08:00
|
|
|
return if @comment && @comment.bytesize == @comment_length
|
|
|
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def read_c_dir_extra_field(io)
|
|
|
|
if @extra.is_a?(::Zip::ExtraField)
|
2013-06-03 02:33:03 +08:00
|
|
|
@extra.merge(io.read(@extra_length))
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-06-03 15:56:24 +08:00
|
|
|
@extra = ::Zip::ExtraField.new(io.read(@extra_length))
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def read_c_dir_entry(io) #:nodoc:all
|
|
|
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
|
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
|
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
|
|
check_c_dir_entry_signature
|
|
|
|
set_time(@last_mod_date, @last_mod_time)
|
2016-12-07 21:35:33 +08:00
|
|
|
@name = io.read(@name_length)
|
2017-10-18 23:20:56 +08:00
|
|
|
if ::Zip.force_entry_names_encoding
|
|
|
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
read_c_dir_extra_field(io)
|
2013-06-03 02:33:03 +08:00
|
|
|
@comment = io.read(@comment_length)
|
2013-07-01 04:52:18 +08:00
|
|
|
check_c_dir_entry_comment_size
|
2013-06-03 02:33:03 +08:00
|
|
|
set_ftype_from_c_dir_entry
|
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
|
|
|
parse_zip64_extra(false)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def file_stat(path) # :nodoc:
|
2010-11-30 16:27:59 +08:00
|
|
|
if @follow_symlinks
|
2015-03-21 16:16:06 +08:00
|
|
|
::File.stat(path)
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2015-03-21 16:16:06 +08:00
|
|
|
::File.lstat(path)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def get_extra_attributes_from_path(path) # :nodoc:
|
2015-03-25 00:16:03 +08:00
|
|
|
return if Zip::RUNNING_ON_WINDOWS
|
|
|
|
stat = file_stat(path)
|
|
|
|
@unix_uid = stat.uid
|
|
|
|
@unix_gid = stat.gid
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms = stat.mode & 0o7777
|
2019-10-07 03:01:14 +08:00
|
|
|
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2019-10-07 02:37:03 +08:00
|
|
|
def set_unix_attributes_on_path(dest_path)
|
2013-06-03 15:56:24 +08:00
|
|
|
# ignore setuid/setgid bits by default. honor if @restore_ownership
|
2017-06-29 10:57:12 +08:00
|
|
|
unix_perms_mask = 0o1777
|
|
|
|
unix_perms_mask = 0o7777 if @restore_ownership
|
2013-06-03 15:56:24 +08:00
|
|
|
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
|
|
|
|
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
2019-10-07 02:37:03 +08:00
|
|
|
|
|
|
|
# Restore the timestamp on a file. This will either have come from the
|
|
|
|
# original source file that was copied into the archive, or from the
|
|
|
|
# creation date of the archive if there was no original source file.
|
|
|
|
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 15:56:24 +08:00
|
|
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
2015-03-23 00:25:35 +08:00
|
|
|
return unless file? || directory?
|
2013-06-03 15:56:24 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
case @fstype
|
2013-06-03 02:33:03 +08:00
|
|
|
when ::Zip::FSTYPE_UNIX
|
2019-10-07 02:37:03 +08:00
|
|
|
set_unix_attributes_on_path(dest_path)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 15:56:24 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 15:56:24 +08:00
|
|
|
def pack_c_dir_entry
|
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
|
|
|
zip64 = @extra['Zip64']
|
2013-06-03 15:56:24 +08:00
|
|
|
[
|
2012-02-14 00:55:08 +08:00
|
|
|
@header_signature,
|
2013-06-03 02:33:03 +08:00
|
|
|
@version, # version of encoding software
|
|
|
|
@fstype, # filesystem type
|
2018-12-03 23:14:32 +08:00
|
|
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
|
|
@gp_flags, # @gp_flags
|
2013-06-03 02:33:03 +08:00
|
|
|
@compression_method,
|
2018-12-03 23:14:32 +08:00
|
|
|
@time.to_binary_dos_time, # @last_mod_time
|
|
|
|
@time.to_binary_dos_date, # @last_mod_date
|
2013-06-03 02:33:03 +08:00
|
|
|
@crc,
|
2017-06-29 10:57:12 +08:00
|
|
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
|
|
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
2013-06-03 02:33:03 +08:00
|
|
|
name_size,
|
2013-08-27 04:26:14 +08:00
|
|
|
@extra ? @extra.c_dir_size : 0,
|
2013-06-03 02:33:03 +08:00
|
|
|
comment_size,
|
2017-06-29 10:57:12 +08:00
|
|
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
2013-06-03 02:33:03 +08:00
|
|
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
|
|
@external_file_attributes, # native filesystem attributes
|
2017-06-29 10:57:12 +08:00
|
|
|
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
|
2014-01-24 17:37:38 +08:00
|
|
|
@name,
|
|
|
|
@extra,
|
|
|
|
@comment
|
2013-06-03 15:56:24 +08:00
|
|
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
|
|
end
|
|
|
|
|
|
|
|
def write_c_dir_entry(io) #:nodoc:all
|
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
|
|
|
prep_zip64_extra(false)
|
2013-06-03 15:56:24 +08:00
|
|
|
case @fstype
|
|
|
|
when ::Zip::FSTYPE_UNIX
|
|
|
|
ft = case @ftype
|
|
|
|
when :file
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms ||= 0o644
|
2013-06-03 15:56:24 +08:00
|
|
|
::Zip::FILE_TYPE_FILE
|
|
|
|
when :directory
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms ||= 0o755
|
2013-06-03 15:56:24 +08:00
|
|
|
::Zip::FILE_TYPE_DIR
|
|
|
|
when :symlink
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms ||= 0o755
|
2013-06-03 15:56:24 +08:00
|
|
|
::Zip::FILE_TYPE_SYMLINK
|
|
|
|
end
|
|
|
|
|
|
|
|
unless ft.nil?
|
2017-06-29 10:57:12 +08:00
|
|
|
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
2013-06-03 15:56:24 +08:00
|
|
|
end
|
|
|
|
end
|
2012-02-14 00:55:08 +08:00
|
|
|
|
2013-06-03 15:56:24 +08:00
|
|
|
io << pack_c_dir_entry
|
2010-11-30 16:27:59 +08:00
|
|
|
|
|
|
|
io << @name
|
2013-06-03 15:56:24 +08:00
|
|
|
io << (@extra ? @extra.to_c_dir_bin : '')
|
2010-11-30 16:27:59 +08:00
|
|
|
io << @comment
|
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
def ==(other)
|
2010-11-30 16:27:59 +08:00
|
|
|
return false unless other.class == self.class
|
|
|
|
# Compares contents of local entry and exposed fields
|
2017-06-29 10:57:12 +08:00
|
|
|
keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
|
2015-03-23 00:30:24 +08:00
|
|
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
2015-03-23 00:30:24 +08:00
|
|
|
keys_equal && time.dos_equals(other.time)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2015-03-23 00:43:44 +08:00
|
|
|
def <=>(other)
|
2015-03-23 00:30:24 +08:00
|
|
|
to_s <=> other.to_s
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an IO like object for the given ZipEntry.
|
|
|
|
# Warning: may behave weird with symlinks.
|
2013-06-03 15:56:24 +08:00
|
|
|
def get_input_stream(&block)
|
2010-11-30 16:27:59 +08:00
|
|
|
if @ftype == :directory
|
2013-08-30 04:50:12 +08:00
|
|
|
yield ::Zip::NullInputStream if block_given?
|
|
|
|
::Zip::NullInputStream
|
2010-11-30 16:27:59 +08:00
|
|
|
elsif @filepath
|
|
|
|
case @ftype
|
|
|
|
when :file
|
2013-07-01 04:52:18 +08:00
|
|
|
::File.open(@filepath, 'rb', &block)
|
2010-11-30 16:27:59 +08:00
|
|
|
when :symlink
|
2013-07-01 04:52:18 +08:00
|
|
|
linkpath = ::File.readlink(@filepath)
|
2013-06-03 15:56:24 +08:00
|
|
|
stringio = ::StringIO.new(linkpath)
|
2013-07-01 04:52:18 +08:00
|
|
|
yield(stringio) if block_given?
|
|
|
|
stringio
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-06-03 02:33:03 +08:00
|
|
|
raise "unknown @file_type #{@ftype}"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
else
|
2013-06-03 15:56:24 +08:00
|
|
|
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
2018-04-04 04:07:18 +08:00
|
|
|
zis.instance_variable_set(:@complete_entry, self)
|
2010-11-30 16:27:59 +08:00
|
|
|
zis.get_next_entry
|
|
|
|
if block_given?
|
|
|
|
begin
|
2013-07-01 04:52:18 +08:00
|
|
|
yield(zis)
|
2011-11-16 23:17:19 +08:00
|
|
|
ensure
|
|
|
|
zis.close
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-07-01 04:52:18 +08:00
|
|
|
zis
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def gather_fileinfo_from_srcpath(src_path) # :nodoc:
|
|
|
|
stat = file_stat(src_path)
|
|
|
|
@ftype = case stat.ftype
|
|
|
|
when 'file'
|
|
|
|
if name_is_directory?
|
|
|
|
raise ArgumentError,
|
2015-03-24 00:03:28 +08:00
|
|
|
"entry name '#{newEntry}' indicates directory entry, but " \
|
2015-03-23 01:03:50 +08:00
|
|
|
"'#{src_path}' is not a directory"
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
|
|
|
:file
|
|
|
|
when 'directory'
|
2014-01-24 17:37:38 +08:00
|
|
|
@name += '/' unless name_is_directory?
|
2013-06-03 02:33:03 +08:00
|
|
|
:directory
|
|
|
|
when 'link'
|
|
|
|
if name_is_directory?
|
|
|
|
raise ArgumentError,
|
2015-03-24 00:03:28 +08:00
|
|
|
"entry name '#{newEntry}' indicates directory entry, but " \
|
2015-03-23 01:03:50 +08:00
|
|
|
"'#{src_path}' is not a directory"
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
|
|
|
:symlink
|
|
|
|
else
|
2015-03-23 00:32:47 +08:00
|
|
|
raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@filepath = src_path
|
2010-11-30 16:27:59 +08:00
|
|
|
get_extra_attributes_from_path(@filepath)
|
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
|
2010-11-30 16:27:59 +08:00
|
|
|
if @ftype == :directory
|
2014-03-10 02:38:14 +08:00
|
|
|
zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
|
2010-11-30 16:27:59 +08:00
|
|
|
elsif @filepath
|
2015-03-23 00:30:24 +08:00
|
|
|
zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
|
2013-06-03 02:33:03 +08:00
|
|
|
get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-06-03 02:33:03 +08:00
|
|
|
zip_output_stream.copy_raw_entry(self)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def parent_as_string
|
2013-06-03 02:33:03 +08:00
|
|
|
entry_name = name.chomp('/')
|
|
|
|
slash_index = entry_name.rindex('/')
|
2015-03-23 01:03:50 +08:00
|
|
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def get_raw_input_stream(&block)
|
2015-09-03 21:16:32 +08:00
|
|
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
2014-01-19 19:45:58 +08:00
|
|
|
yield @zipfile
|
|
|
|
else
|
2015-03-21 16:27:44 +08:00
|
|
|
::File.open(@zipfile, 'rb', &block)
|
2014-01-19 19:45:58 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2014-04-05 05:32:11 +08:00
|
|
|
def clean_up
|
|
|
|
# By default, do nothing
|
|
|
|
end
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
private
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def set_time(binary_dos_date, binary_dos_time)
|
|
|
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
2010-11-30 16:27:59 +08:00
|
|
|
rescue ArgumentError
|
2019-10-12 14:26:15 +08:00
|
|
|
warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2015-03-21 04:09:41 +08:00
|
|
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
2014-02-07 07:00:38 +08:00
|
|
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
2014-01-24 17:37:38 +08:00
|
|
|
raise ::Zip::DestinationFileExistsError,
|
2013-06-03 02:33:03 +08:00
|
|
|
"Destination '#{dest_path}' already exists"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2015-03-21 16:27:44 +08:00
|
|
|
::File.open(dest_path, 'wb') do |os|
|
2010-11-30 16:27:59 +08:00
|
|
|
get_input_stream do |is|
|
2019-09-13 05:01:38 +08:00
|
|
|
bytes_written = 0
|
2019-09-19 01:34:23 +08:00
|
|
|
warned = false
|
2019-02-28 00:23:29 +08:00
|
|
|
buf = ''.dup
|
2015-03-21 03:57:38 +08:00
|
|
|
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
2010-11-30 16:27:59 +08:00
|
|
|
os << buf
|
2019-09-13 05:01:38 +08:00
|
|
|
bytes_written += buf.bytesize
|
2019-09-19 01:34:23 +08:00
|
|
|
if bytes_written > size && !warned
|
2019-10-12 14:26:15 +08:00
|
|
|
message = "entry '#{name}' should be #{size}B, but is larger when inflated."
|
2019-09-19 01:34:23 +08:00
|
|
|
if ::Zip.validate_entry_sizes
|
|
|
|
raise ::Zip::EntrySizeError, message
|
|
|
|
else
|
2019-10-12 02:31:42 +08:00
|
|
|
warn "WARNING: #{message}"
|
2019-09-19 01:34:23 +08:00
|
|
|
warned = true
|
|
|
|
end
|
2019-09-13 05:01:38 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-10-07 02:37:03 +08:00
|
|
|
|
|
|
|
set_extra_attributes_on_path(dest_path)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def create_directory(dest_path)
|
2013-08-15 06:00:27 +08:00
|
|
|
return if ::File.directory?(dest_path)
|
2014-02-07 07:00:38 +08:00
|
|
|
if ::File.exist?(dest_path)
|
2013-06-03 02:33:03 +08:00
|
|
|
if block_given? && yield(self, dest_path)
|
2015-03-21 16:16:06 +08:00
|
|
|
::FileUtils.rm_f dest_path
|
2011-11-16 23:17:19 +08:00
|
|
|
else
|
2014-01-24 17:37:38 +08:00
|
|
|
raise ::Zip::DestinationFileExistsError,
|
2015-03-24 00:03:28 +08:00
|
|
|
"Cannot create directory '#{dest_path}'. " \
|
2015-03-23 01:03:50 +08:00
|
|
|
'A file already exists with that name'
|
2011-11-16 23:17:19 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
::FileUtils.mkdir_p(dest_path)
|
|
|
|
set_extra_attributes_on_path(dest_path)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-15 06:00:27 +08:00
|
|
|
# BUG: create_symlink() does not use &block
|
2013-06-03 02:33:03 +08:00
|
|
|
def create_symlink(dest_path)
|
2018-08-26 19:32:18 +08:00
|
|
|
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
|
|
|
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
2019-10-12 14:26:15 +08:00
|
|
|
warn "WARNING: skipped symlink '#{dest_path}'."
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-08-27 04:26:14 +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
|
|
|
# apply missing data from the zip64 extra information field, if present
|
|
|
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
|
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
2015-03-21 03:54:28 +08:00
|
|
|
return if @extra['Zip64'].nil?
|
|
|
|
if for_local_header
|
|
|
|
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
|
|
|
else
|
|
|
|
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
|
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
|
|
|
|
end
|
|
|
|
|
2015-01-08 17:30:32 +08:00
|
|
|
def data_descriptor_size
|
|
|
|
(@gp_flags & 0x0008) > 0 ? 16 : 0
|
|
|
|
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
|
|
|
# create a zip64 extra information field if we need one
|
|
|
|
def prep_zip64_extra(for_local_header) #:nodoc:all
|
2014-01-24 17:37:38 +08:00
|
|
|
return unless ::Zip.write_zip64_support
|
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
|
|
|
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
2015-03-25 00:09:22 +08:00
|
|
|
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
|
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
|
|
|
if need_zip64
|
|
|
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
|
|
@extra.delete('Zip64Placeholder')
|
|
|
|
zip64 = @extra.create('Zip64')
|
|
|
|
if for_local_header
|
|
|
|
# local header always includes size and compressed size
|
|
|
|
zip64.original_size = @size
|
|
|
|
zip64.compressed_size = @compressed_size
|
|
|
|
else
|
|
|
|
# central directory entry entries include whichever fields are necessary
|
|
|
|
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
|
|
|
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
|
|
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
|
|
end
|
|
|
|
else
|
|
|
|
@extra.delete('Zip64')
|
|
|
|
|
|
|
|
# if this is a local header entry, create a placeholder
|
|
|
|
# so we have room to write a zip64 extra field afterward
|
|
|
|
# (we won't know if it's needed until the file data is written)
|
|
|
|
if for_local_header
|
|
|
|
@extra.create('Zip64Placeholder')
|
|
|
|
else
|
|
|
|
@extra.delete('Zip64Placeholder')
|
|
|
|
end
|
2013-08-27 04:26:14 +08:00
|
|
|
end
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
|
|
# rubyzip is free software; you can redistribute it and/or
|
2012-03-13 04:45:02 +08:00
|
|
|
# modify it under the terms of the ruby license.
|