Refactor ::Zip::Entry and ::Zip::ExtraField
This commit is contained in:
parent
d7523f13e1
commit
20d79feb99
|
@ -99,7 +99,7 @@ module Zip
|
|||
end
|
||||
|
||||
def name_is_directory? #:nodoc:all
|
||||
%r{\/\z} =~ @name
|
||||
@name.end_with?('/')
|
||||
end
|
||||
|
||||
def local_entry_offset #:nodoc:all
|
||||
|
@ -303,36 +303,46 @@ module Zip
|
|||
end
|
||||
end
|
||||
|
||||
def read_c_dir_entry(io) #:nodoc:all
|
||||
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
||||
unless static_sized_fields_buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
||||
def check_c_dir_entry_static_header_length(buf)
|
||||
unless buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
||||
raise ZipError, 'Premature end of file. Not enough data for zip cdir entry header'
|
||||
end
|
||||
end
|
||||
|
||||
unpack_c_dir_entry(static_sized_fields_buf)
|
||||
|
||||
unless @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
||||
def check_c_dir_entry_signature
|
||||
unless header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
||||
raise ZipError, "Zip local header magic not found at location '#{local_header_offset}'"
|
||||
end
|
||||
set_time(@last_mod_date, @last_mod_time)
|
||||
end
|
||||
|
||||
@name = io.read(@name_length)
|
||||
until @name.sub!('\\', '/').nil? do
|
||||
end # some zip files use backslashes instead of slashes as path separators
|
||||
if ::Zip::ExtraField === @extra
|
||||
def check_c_dir_entry_comment_size
|
||||
unless @comment && @comment.bytesize == @comment_length
|
||||
raise ::Zip::ZipError, "Truncated cdir zip entry header"
|
||||
end
|
||||
end
|
||||
|
||||
def read_c_dir_extra_field(io)
|
||||
if @extra.is_a?(::Zip::ExtraField)
|
||||
@extra.merge(io.read(@extra_length))
|
||||
else
|
||||
@extra = ::Zip::ExtraField.new(io.read(@extra_length))
|
||||
end
|
||||
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)
|
||||
@name = io.read(@name_length).gsub('\\', '/')
|
||||
read_c_dir_extra_field(io)
|
||||
@comment = io.read(@comment_length)
|
||||
unless @comment && @comment.bytesize == @comment_length
|
||||
raise ::Zip::ZipError, "Truncated cdir zip entry header"
|
||||
end
|
||||
check_c_dir_entry_comment_size
|
||||
set_ftype_from_c_dir_entry
|
||||
@local_header_size = calculate_local_header_size
|
||||
end
|
||||
|
||||
|
||||
def file_stat(path) # :nodoc:
|
||||
if @follow_symlinks
|
||||
::File::stat(path)
|
||||
|
@ -422,17 +432,13 @@ module Zip
|
|||
io << @comment
|
||||
end
|
||||
|
||||
def == (other)
|
||||
def ==(other)
|
||||
return false unless other.class == self.class
|
||||
# Compares contents of local entry and exposed fields
|
||||
(@compression_method == other.compression_method &&
|
||||
@crc == other.crc &&
|
||||
@compressed_size == other.compressed_size &&
|
||||
@size == other.size &&
|
||||
@name == other.name &&
|
||||
@extra == other.extra &&
|
||||
@filepath == other.filepath &&
|
||||
self.time.dos_equals(other.time))
|
||||
keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
|
||||
other.__send__(k.to_sym) == self.__send__(k.to_sym)
|
||||
end
|
||||
keys_equal && self.time.dos_equals(other.time)
|
||||
end
|
||||
|
||||
def <=> (other)
|
||||
|
@ -443,17 +449,17 @@ module Zip
|
|||
# Warning: may behave weird with symlinks.
|
||||
def get_input_stream(&block)
|
||||
if @ftype == :directory
|
||||
return yield(::Zip::NullInputStream.instance) if block_given?
|
||||
return ::Zip::NullInputStream.instance
|
||||
yield(::Zip::NullInputStream.instance) if block_given?
|
||||
::Zip::NullInputStream.instance
|
||||
elsif @filepath
|
||||
case @ftype
|
||||
when :file
|
||||
return ::File.open(@filepath, 'rb', &block)
|
||||
::File.open(@filepath, 'rb', &block)
|
||||
when :symlink
|
||||
linkpath = ::File::readlink(@filepath)
|
||||
linkpath = ::File.readlink(@filepath)
|
||||
stringio = ::StringIO.new(linkpath)
|
||||
return yield(stringio) if block_given?
|
||||
return stringio
|
||||
yield(stringio) if block_given?
|
||||
stringio
|
||||
else
|
||||
raise "unknown @file_type #{@ftype}"
|
||||
end
|
||||
|
@ -462,12 +468,12 @@ module Zip
|
|||
zis.get_next_entry
|
||||
if block_given?
|
||||
begin
|
||||
return yield(zis)
|
||||
yield(zis)
|
||||
ensure
|
||||
zis.close
|
||||
end
|
||||
else
|
||||
return zis
|
||||
zis
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,212 +2,89 @@ module Zip
|
|||
class ExtraField < Hash
|
||||
ID_MAP = {}
|
||||
|
||||
# Meta class for extra fields
|
||||
class Generic
|
||||
def self.register_map
|
||||
if self.const_defined?(:HEADER_ID)
|
||||
ID_MAP[self.const_get(:HEADER_ID)] = self
|
||||
end
|
||||
end
|
||||
|
||||
def self.name
|
||||
self.to_s.split("::")[-1]
|
||||
end
|
||||
|
||||
# return field [size, content] or false
|
||||
def initial_parse(binstr)
|
||||
if ! binstr
|
||||
# If nil, start with empty.
|
||||
return false
|
||||
elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
|
||||
$stderr.puts "Warning: weired extra feild header ID. skip parsing"
|
||||
return false
|
||||
end
|
||||
[binstr[2,2].unpack("v")[0], binstr[4..-1]]
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return false if self.class != other.class
|
||||
each do |k, v|
|
||||
v != other[k] and return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def to_local_bin
|
||||
s = pack_for_local
|
||||
self.class.const_get(:HEADER_ID) + [s.bytesize].pack("v") + s
|
||||
end
|
||||
|
||||
def to_c_dir_bin
|
||||
s = pack_for_c_dir
|
||||
self.class.const_get(:HEADER_ID) + [s.bytesize].pack("v") + s
|
||||
end
|
||||
end
|
||||
|
||||
# Info-ZIP Additional timestamp field
|
||||
class UniversalTime < Generic
|
||||
HEADER_ID = "UT"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@ctime = nil
|
||||
@mtime = nil
|
||||
@atime = nil
|
||||
@flag = nil
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
attr_accessor :atime, :ctime, :mtime, :flag
|
||||
|
||||
def merge(binstr)
|
||||
return if binstr.empty?
|
||||
size, content = initial_parse(binstr)
|
||||
size or return
|
||||
@flag, mtime, atime, ctime = content.unpack("CVVV")
|
||||
mtime and @mtime ||= DOSTime.at(mtime)
|
||||
atime and @atime ||= DOSTime.at(atime)
|
||||
ctime and @ctime ||= DOSTime.at(ctime)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@mtime == other.mtime &&
|
||||
@atime == other.atime &&
|
||||
@ctime == other.ctime
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 != 0 and s << [@mtime.to_i].pack("V")
|
||||
@flag & 2 != 0 and s << [@atime.to_i].pack("V")
|
||||
@flag & 4 != 0 and s << [@ctime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 == 1 and s << [@mtime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
# Info-ZIP Extra for UNIX uid/gid
|
||||
class IUnix < Generic
|
||||
HEADER_ID = "Ux"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@uid = 0
|
||||
@gid = 0
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
attr_accessor :uid, :gid
|
||||
|
||||
def merge(binstr)
|
||||
return if binstr.empty?
|
||||
size, content = initial_parse(binstr)
|
||||
# size: 0 for central directory. 4 for local header
|
||||
return if(!size || size == 0)
|
||||
uid, gid = content.unpack("vv")
|
||||
@uid ||= uid
|
||||
@gid ||= gid
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@uid == other.uid &&
|
||||
@gid == other.gid
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
[@uid, @gid].pack("vv")
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
## start main of ZipExtraField < Hash
|
||||
def initialize(binstr = nil)
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
|
||||
def extra_field_type_exist(binstr, id, len, i)
|
||||
field_name = ID_MAP[id].name
|
||||
if self.member?(field_name)
|
||||
self[field_name].merge(binstr[i, len + 4])
|
||||
else
|
||||
field_obj = ID_MAP[id].new(binstr[i, len + 4])
|
||||
self[field_name] = field_obj
|
||||
end
|
||||
end
|
||||
|
||||
def extra_field_type_unknown(binstr, len, i)
|
||||
create_unknown_item unless self['Unknown']
|
||||
if !len || len + 4 > binstr[i..-1].bytesize
|
||||
self['Unknown'] << binstr[i..-1]
|
||||
return
|
||||
end
|
||||
self['Unknown'] << binstr[i, len + 4]
|
||||
end
|
||||
|
||||
def create_unknown_item
|
||||
s = ''
|
||||
class << s
|
||||
alias_method :to_c_dir_bin, :to_s
|
||||
alias_method :to_local_bin, :to_s
|
||||
end
|
||||
self['Unknown'] = s
|
||||
end
|
||||
|
||||
def merge(binstr)
|
||||
return if binstr.empty?
|
||||
i = 0
|
||||
while i < binstr.bytesize
|
||||
id = binstr[i,2]
|
||||
len = binstr[i + 2,2].to_s.unpack("v")[0]
|
||||
id = binstr[i, 2]
|
||||
len = binstr[i + 2, 2].to_s.unpack('v').first
|
||||
if id && ID_MAP.member?(id)
|
||||
field_name = ID_MAP[id].name
|
||||
if self.member?(field_name)
|
||||
self[field_name].mergea(binstr[i, len + 4])
|
||||
else
|
||||
field_obj = ID_MAP[id].new(binstr[i, len + 4])
|
||||
self[field_name] = field_obj
|
||||
end
|
||||
extra_field_type_exist(binstr, id, len, i)
|
||||
elsif id
|
||||
unless self["Unknown"]
|
||||
s = ""
|
||||
class << s
|
||||
alias_method :to_c_dir_bin, :to_s
|
||||
alias_method :to_local_bin, :to_s
|
||||
end
|
||||
self["Unknown"] = s
|
||||
end
|
||||
if !len || len + 4 > binstr[i..-1].bytesize
|
||||
self["Unknown"] << binstr[i..-1]
|
||||
break
|
||||
end
|
||||
self["Unknown"] << binstr[i, len + 4]
|
||||
create_unknown_item unless self['Unknown']
|
||||
break unless extra_field_type_unknown(binstr, len, i)
|
||||
end
|
||||
i += len + 4
|
||||
end
|
||||
end
|
||||
|
||||
def create(name)
|
||||
field_class = nil
|
||||
ID_MAP.each { |id, klass|
|
||||
if klass.name == name
|
||||
field_class = klass
|
||||
break
|
||||
end
|
||||
}
|
||||
if ! field_class
|
||||
unless field_class = ID_MAP.values.find { |k| k.name == name }
|
||||
raise ZipError, "Unknown extra field '#{name}'"
|
||||
end
|
||||
self[name] = field_class.new()
|
||||
self[name] = field_class.new
|
||||
end
|
||||
|
||||
def to_local_bin
|
||||
s = ""
|
||||
each do |k, v|
|
||||
s << v.to_local_bin
|
||||
end
|
||||
s
|
||||
self.map { |_, v| v.to_local_bin }.join
|
||||
end
|
||||
|
||||
alias :to_s :to_local_bin
|
||||
|
||||
def to_c_dir_bin
|
||||
s = ""
|
||||
each do |k, v|
|
||||
s << v.to_c_dir_bin
|
||||
end
|
||||
s
|
||||
self.map { |_, v| v.to_c_dir_bin }.join
|
||||
end
|
||||
|
||||
def c_dir_length
|
||||
to_c_dir_bin.bytesize
|
||||
end
|
||||
|
||||
def local_length
|
||||
to_local_bin.bytesize
|
||||
end
|
||||
|
||||
alias :c_dir_size :c_dir_length
|
||||
alias :local_size :local_length
|
||||
alias :length :local_length
|
||||
alias :size :local_length
|
||||
alias :length :local_length
|
||||
alias :size :local_length
|
||||
end
|
||||
end
|
||||
|
||||
require 'zip/extra_field/generic'
|
||||
require 'zip/extra_field/universal_time'
|
||||
require 'zip/extra_field/unix'
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
module Zip
|
||||
class ExtraField::Generic
|
||||
def self.register_map
|
||||
if self.const_defined?(:HEADER_ID)
|
||||
::Zip::ExtraField::ID_MAP[self.const_get(:HEADER_ID)] = self
|
||||
end
|
||||
end
|
||||
|
||||
def self.name
|
||||
self.to_s.split("::")[-1]
|
||||
end
|
||||
|
||||
# return field [size, content] or false
|
||||
def initial_parse(binstr)
|
||||
if !binstr
|
||||
# If nil, start with empty.
|
||||
return false
|
||||
elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
||||
$stderr.puts "Warning: weired extra feild header ID. skip parsing"
|
||||
return false
|
||||
end
|
||||
[binstr[2, 2].unpack("v")[0], binstr[4..-1]]
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return false if self.class != other.class
|
||||
each do |k, v|
|
||||
v != other[k] and return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def to_local_bin
|
||||
s = pack_for_local
|
||||
self.class.const_get(:HEADER_ID) + [s.bytesize].pack("v") + s
|
||||
end
|
||||
|
||||
def to_c_dir_bin
|
||||
s = pack_for_c_dir
|
||||
self.class.const_get(:HEADER_ID) + [s.bytesize].pack("v") + s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
module Zip
|
||||
# Info-ZIP Additional timestamp field
|
||||
class ExtraField::UniversalTime < ExtraField::Generic
|
||||
HEADER_ID = "UT"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@ctime = nil
|
||||
@mtime = nil
|
||||
@atime = nil
|
||||
@flag = nil
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
|
||||
attr_accessor :atime, :ctime, :mtime, :flag
|
||||
|
||||
def merge(binstr)
|
||||
return if binstr.empty?
|
||||
size, content = initial_parse(binstr)
|
||||
size or return
|
||||
@flag, mtime, atime, ctime = content.unpack("CVVV")
|
||||
mtime and @mtime ||= ::Zip::DOSTime.at(mtime)
|
||||
atime and @atime ||= ::Zip::DOSTime.at(atime)
|
||||
ctime and @ctime ||= ::Zip::DOSTime.at(ctime)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@mtime == other.mtime &&
|
||||
@atime == other.atime &&
|
||||
@ctime == other.ctime
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 != 0 and s << [@mtime.to_i].pack("V")
|
||||
@flag & 2 != 0 and s << [@atime.to_i].pack("V")
|
||||
@flag & 4 != 0 and s << [@ctime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
s = [@flag].pack("C")
|
||||
@flag & 1 == 1 and s << [@mtime.to_i].pack("V")
|
||||
s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module Zip
|
||||
# Info-ZIP Extra for UNIX uid/gid
|
||||
class ExtraField::IUnix < ExtraField::Generic
|
||||
HEADER_ID = "Ux"
|
||||
register_map
|
||||
|
||||
def initialize(binstr = nil)
|
||||
@uid = 0
|
||||
@gid = 0
|
||||
binstr and merge(binstr)
|
||||
end
|
||||
|
||||
attr_accessor :uid, :gid
|
||||
|
||||
def merge(binstr)
|
||||
return if binstr.empty?
|
||||
size, content = initial_parse(binstr)
|
||||
# size: 0 for central directory. 4 for local header
|
||||
return if (!size || size == 0)
|
||||
uid, gid = content.unpack("vv")
|
||||
@uid ||= uid
|
||||
@gid ||= gid
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@uid == other.uid &&
|
||||
@gid == other.gid
|
||||
end
|
||||
|
||||
def pack_for_local
|
||||
[@uid, @gid].pack("vv")
|
||||
end
|
||||
|
||||
def pack_for_c_dir
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue