2021-05-24 01:24:22 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-02-11 00:51:29 +08:00
|
|
|
require 'pathname'
|
2022-02-07 23:24:55 +08:00
|
|
|
|
|
|
|
require_relative 'dirtyable'
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2013-06-03 15:56:24 +08:00
|
|
|
class Entry
|
2022-02-07 23:24:55 +08:00
|
|
|
include Dirtyable
|
|
|
|
|
2020-06-20 23:03:46 +08:00
|
|
|
STORED = ::Zip::COMPRESSION_METHOD_STORE
|
|
|
|
DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE
|
|
|
|
|
2013-08-15 06:00:27 +08:00
|
|
|
# Language encoding flag (EFS) bit
|
|
|
|
EFS = 0b100000000000
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2020-08-08 02:45:56 +08:00
|
|
|
# Compression level flags (used as part of the gp flags).
|
|
|
|
COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110
|
|
|
|
COMPRESSION_LEVEL_FAST_GPFLAG = 0b100
|
|
|
|
COMPRESSION_LEVEL_MAX_GPFLAG = 0b010
|
|
|
|
|
2020-06-15 07:16:17 +08:00
|
|
|
attr_accessor :comment, :compressed_size, :follow_symlinks, :name,
|
|
|
|
:restore_ownership, :restore_permissions, :restore_times,
|
2022-02-07 01:06:06 +08:00
|
|
|
:size, :unix_gid, :unix_perms, :unix_uid
|
2020-06-15 07:16:17 +08:00
|
|
|
|
2022-02-08 07:04:49 +08:00
|
|
|
attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags,
|
2020-06-15 07:16:17 +08:00
|
|
|
:internal_file_attributes, :local_header_offset # :nodoc:
|
|
|
|
|
|
|
|
attr_reader :extra, :compression_level, :ftype, :filepath # :nodoc:
|
2011-11-16 23:09:14 +08:00
|
|
|
|
2022-02-08 07:04:49 +08:00
|
|
|
mark_dirty :comment=, :compressed_size=, :external_file_attributes=,
|
|
|
|
:fstype=, :gp_flags=, :name=, :size=,
|
2022-02-07 01:46:25 +08:00
|
|
|
:unix_gid=, :unix_perms=, :unix_uid=
|
|
|
|
|
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
|
|
|
|
|
2021-05-31 16:11:39 +08:00
|
|
|
@restore_times = DEFAULT_RESTORE_OPTIONS[:restore_times]
|
|
|
|
@restore_permissions = DEFAULT_RESTORE_OPTIONS[:restore_permissions]
|
|
|
|
@restore_ownership = DEFAULT_RESTORE_OPTIONS[:restore_ownership]
|
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
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
def check_name(name)
|
2021-06-27 02:17:38 +08:00
|
|
|
error =
|
|
|
|
if name.start_with?('/')
|
|
|
|
"Illegal entry name '#{name}'. Names must not start with '/'"
|
|
|
|
elsif name.length > 65_535
|
|
|
|
'Illegal entry name. Names must have fewer than 65,536 characters'
|
|
|
|
else
|
|
|
|
''
|
|
|
|
end
|
|
|
|
return if error.empty?
|
|
|
|
|
|
|
|
raise EntryNameError, error
|
2013-06-03 02:33:03 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2020-08-03 00:51:08 +08:00
|
|
|
def initialize(
|
|
|
|
zipfile = '', name = '',
|
|
|
|
comment: '', size: 0, compressed_size: 0, crc: 0,
|
|
|
|
compression_method: DEFLATED,
|
|
|
|
compression_level: ::Zip.default_compression,
|
|
|
|
time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new
|
|
|
|
)
|
2022-02-07 23:24:55 +08:00
|
|
|
super()
|
2020-08-03 00:51:08 +08:00
|
|
|
@name = name
|
2020-06-08 00:38:46 +08:00
|
|
|
check_name(@name)
|
2013-06-03 02:33:03 +08:00
|
|
|
|
|
|
|
set_default_vars_values
|
|
|
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
2020-06-08 00:38:46 +08:00
|
|
|
@ftype = name_is_directory? ? :directory : :file
|
2011-11-17 17:48:42 +08:00
|
|
|
|
2020-08-03 00:51:08 +08:00
|
|
|
@zipfile = zipfile
|
2021-11-08 06:15:18 +08:00
|
|
|
@comment = comment || ''
|
|
|
|
@compression_method = compression_method || DEFLATED
|
|
|
|
@compression_level = compression_level || ::Zip.default_compression
|
|
|
|
@compressed_size = compressed_size || 0
|
|
|
|
@crc = crc || 0
|
|
|
|
@size = size || 0
|
|
|
|
@time = case time
|
|
|
|
when ::Zip::DOSTime
|
|
|
|
time
|
|
|
|
when Time
|
|
|
|
::Zip::DOSTime.from_time(time)
|
|
|
|
else
|
|
|
|
::Zip::DOSTime.now
|
|
|
|
end
|
2020-08-03 00:51:08 +08:00
|
|
|
@extra =
|
|
|
|
extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s)
|
|
|
|
|
2020-06-14 00:33:39 +08:00
|
|
|
set_compression_level_flags
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2019-12-21 17:38:26 +08:00
|
|
|
def encrypted?
|
|
|
|
gp_flags & 1 == 1
|
|
|
|
end
|
|
|
|
|
2019-12-21 17:41:24 +08:00
|
|
|
def incomplete?
|
|
|
|
gp_flags & 8 == 8
|
|
|
|
end
|
|
|
|
|
2022-02-23 20:39:01 +08:00
|
|
|
def time(component: :mtime)
|
|
|
|
time =
|
|
|
|
if @extra['UniversalTime']
|
|
|
|
@extra['UniversalTime'].send(component)
|
|
|
|
elsif @extra['NTFS']
|
|
|
|
@extra['NTFS'].send(component)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Standard time field in central directory has local time
|
|
|
|
# under archive creator. Then, we can't get timezone.
|
|
|
|
time || (@time if component == :mtime)
|
2010-11-30 16:27:59 +08:00
|
|
|
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)
|
2022-02-21 17:15:50 +08:00
|
|
|
@dirty = true
|
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
|
2021-05-19 02:51:06 +08:00
|
|
|
|
|
|
|
value = DOSTime.from_time(value)
|
2014-09-12 14:04:23 +08:00
|
|
|
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
2020-02-09 18:52:06 +08:00
|
|
|
@time = value
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2020-06-08 00:38:46 +08:00
|
|
|
def compression_method
|
2020-06-21 00:36:17 +08:00
|
|
|
return STORED if @ftype == :directory || @compression_level == 0
|
|
|
|
|
|
|
|
@compression_method
|
2020-06-08 00:38:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def compression_method=(method)
|
2022-02-21 17:15:50 +08:00
|
|
|
@dirty = true
|
2020-06-08 00:38:46 +08:00
|
|
|
@compression_method = (@ftype == :directory ? STORED : method)
|
|
|
|
end
|
|
|
|
|
2022-02-23 18:13:13 +08:00
|
|
|
def zip64?
|
|
|
|
!@extra['Zip64'].nil?
|
|
|
|
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
|
2020-02-09 21:13:21 +08:00
|
|
|
|
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?
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2018-08-27 02:55:26 +08:00
|
|
|
root = ::File::SEPARATOR
|
2021-05-31 02:08:31 +08:00
|
|
|
naive = ::File.join(root, cleanpath.to_s)
|
|
|
|
# Allow for Windows drive mappings at the root.
|
|
|
|
::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i)
|
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?
|
2020-02-09 21:13:21 +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
|
|
|
new_size = calculate_local_header_size
|
2021-06-26 05:31:34 +08:00
|
|
|
return unless @local_header_size != new_size
|
|
|
|
|
|
|
|
raise Error,
|
|
|
|
"Local header size changed (#{@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
|
2021-06-28 04:43:03 +08:00
|
|
|
local_entry_offset + compressed_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 }
|
|
|
|
|
2020-02-09 23:50:00 +08:00
|
|
|
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2020-02-09 23:50:00 +08:00
|
|
|
__send__("create_#{@ftype}", dest_path, &block)
|
2010-11-30 16:27:59 +08:00
|
|
|
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
|
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
|
2021-06-26 18:26:06 +08:00
|
|
|
rescue SplitArchiveError
|
|
|
|
raise
|
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
|
|
|
|
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
|
2022-02-07 00:46:41 +08:00
|
|
|
@dirty = false # No changes at this point.
|
2013-06-03 02:33:03 +08:00
|
|
|
@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)
|
|
|
|
|
2021-06-26 18:26:06 +08:00
|
|
|
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
|
|
|
if @header_signature == SPLIT_FILE_SIGNATURE
|
|
|
|
raise SplitArchiveError,
|
|
|
|
'Rubyzip cannot extract from split archives at this time'
|
|
|
|
end
|
|
|
|
|
|
|
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
set_time(@last_mod_date, @last_mod_time)
|
|
|
|
|
|
|
|
@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
|
2021-06-02 05:33:47 +08:00
|
|
|
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
2013-06-03 02:33:03 +08:00
|
|
|
|
2021-06-02 05:33:47 +08:00
|
|
|
extra = io.read(@extra_length)
|
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'
|
2019-09-22 02:11:37 +08:00
|
|
|
end
|
|
|
|
|
2021-11-17 05:42:11 +08:00
|
|
|
read_extra_field(extra, local: true)
|
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
|
2020-06-08 00:38:46 +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
|
|
|
|
2021-06-09 00:13:20 +08:00
|
|
|
def write_local_entry(io, rewrite: false) #: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(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
|
2021-06-26 05:31:34 +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 be 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)
|
2021-06-18 19:07:10 +08:00
|
|
|
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2015-03-25 00:16:03 +08:00
|
|
|
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
|
2020-06-15 07:16:17 +08:00
|
|
|
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2015-03-25 00:16:03 +08:00
|
|
|
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
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2015-03-25 00:16:03 +08:00
|
|
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
2013-07-01 04:52:18 +08:00
|
|
|
end
|
|
|
|
|
2021-11-17 05:42:11 +08:00
|
|
|
def read_extra_field(buf, local: false)
|
2019-10-01 02:32:25 +08:00
|
|
|
if @extra.kind_of?(::Zip::ExtraField)
|
2021-11-17 05:42:11 +08:00
|
|
|
@extra.merge(buf, local: local) if buf
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2021-11-17 05:42:11 +08:00
|
|
|
@extra = ::Zip::ExtraField.new(buf, local: local)
|
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
|
2022-02-07 00:46:41 +08:00
|
|
|
@dirty = false # No changes at this point.
|
2013-07-01 04:52:18 +08:00
|
|
|
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)
|
2021-06-02 05:33:47 +08:00
|
|
|
|
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
|
2021-06-02 05:33:47 +08:00
|
|
|
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
|
|
|
2020-09-20 22:18:34 +08:00
|
|
|
read_extra_field(io.read(@extra_length))
|
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:
|
2021-05-19 04:55:03 +08:00
|
|
|
stat = file_stat(path)
|
|
|
|
@time = DOSTime.from_time(stat.mtime)
|
|
|
|
return if ::Zip::RUNNING_ON_WINDOWS
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2015-03-25 00:16:03 +08:00
|
|
|
@unix_uid = stat.uid
|
|
|
|
@unix_gid = stat.gid
|
2017-06-29 10:57:12 +08:00
|
|
|
@unix_perms = stat.mode & 0o7777
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2021-06-26 05:31:34 +08:00
|
|
|
# rubocop:disable Style/GuardClause
|
2019-10-07 02:37:03 +08:00
|
|
|
def set_unix_attributes_on_path(dest_path)
|
2021-06-26 05:31:34 +08:00
|
|
|
# Ignore setuid/setgid bits by default. Honour if @restore_ownership.
|
|
|
|
unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777)
|
|
|
|
if @restore_permissions && @unix_perms
|
|
|
|
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path)
|
|
|
|
end
|
|
|
|
if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
|
|
|
::FileUtils.chown(@unix_uid, @unix_gid, dest_path)
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2021-06-26 05:31:34 +08:00
|
|
|
# rubocop:enable Style/GuardClause
|
2010-11-30 16:27:59 +08:00
|
|
|
|
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
|
2021-06-01 00:44:25 +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
|
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
|
2020-06-08 00:38:46 +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
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
# Compares contents of local entry and exposed fields
|
2020-11-29 05:19:58 +08:00
|
|
|
%w[compression_method crc compressed_size size name extra filepath time].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
|
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
|
2021-05-25 04:50:20 +08:00
|
|
|
yield ::Zip::NullInputStream if block
|
2013-08-30 04:50:12 +08:00
|
|
|
::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)
|
2021-05-25 04:50:20 +08:00
|
|
|
yield(stringio) if block
|
2013-07-01 04:52:18 +08:00
|
|
|
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
|
2021-06-18 21:34:41 +08:00
|
|
|
zis = ::Zip::InputStream.new(@zipfile, offset: 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
|
2021-05-25 04:50:20 +08:00
|
|
|
if block
|
2010-11-30 16:27:59 +08:00
|
|
|
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
|
2020-05-25 01:50:18 +08:00
|
|
|
zip_output_stream.put_next_entry(self)
|
2010-11-30 16:27:59 +08:00
|
|
|
elsif @filepath
|
2020-05-25 01:50:18 +08:00
|
|
|
zip_output_stream.put_next_entry(self)
|
|
|
|
get_input_stream do |is|
|
|
|
|
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
|
|
|
end
|
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
|
2022-02-07 00:46:41 +08:00
|
|
|
@dirty = false # Any changes are written at this point.
|
2014-04-05 05:32:11 +08:00
|
|
|
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
|
2020-02-05 10:40:56 +08:00
|
|
|
buf = +''
|
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-26 05:44:48 +08:00
|
|
|
next unless bytes_written > size && !warned
|
2020-02-09 23:50:00 +08:00
|
|
|
|
2019-09-26 05:44:48 +08:00
|
|
|
message = "entry '#{name}' should be #{size}B, but is larger when inflated."
|
|
|
|
raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes
|
|
|
|
|
|
|
|
warn "WARNING: #{message}"
|
|
|
|
warned = true
|
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)
|
2020-02-09 21:13:21 +08:00
|
|
|
|
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
|
2022-02-23 18:13:13 +08:00
|
|
|
return unless zip64?
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2015-03-21 03:54:28 +08:00
|
|
|
if for_local_header
|
|
|
|
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
|
|
|
else
|
2021-06-26 05:31:34 +08:00
|
|
|
@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
|
|
|
|
|
2020-06-14 00:33:39 +08:00
|
|
|
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
|
|
|
# indicate compression level. This seems to be mainly cosmetic but they are
|
|
|
|
# generally set by other tools - including in docx files. It is these flags
|
|
|
|
# that are used by commandline tools (and elsewhere) to give an indication
|
|
|
|
# of how compressed a file is. See the PKWARE APPNOTE for more information:
|
|
|
|
# https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
|
|
#
|
|
|
|
# It's safe to simply OR these flags here as compression_level is read only.
|
|
|
|
def set_compression_level_flags
|
|
|
|
return unless compression_method == DEFLATED
|
|
|
|
|
|
|
|
case @compression_level
|
|
|
|
when 1
|
2020-08-08 02:45:56 +08:00
|
|
|
@gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG
|
2020-06-14 00:33:39 +08:00
|
|
|
when 2
|
2020-08-08 02:45:56 +08:00
|
|
|
@gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG
|
2020-06-14 00:33:39 +08:00
|
|
|
when 8, 9
|
2020-08-08 02:45:56 +08:00
|
|
|
@gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG
|
2020-06-14 00:33:39 +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
|
|
|
# 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
|
2020-02-09 21:13:21 +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
|
|
|
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.
|