Refactor ::Zip::Entry and ::Zip::ExtraField

This commit is contained in:
Alexander Simonov 2013-06-30 23:52:18 +03:00
parent d7523f13e1
commit 20d79feb99
5 changed files with 214 additions and 202 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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