2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2013-06-03 15:56:24 +08:00
|
|
|
class ExtraField < Hash
|
2010-11-30 16:27:59 +08:00
|
|
|
ID_MAP = {}
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
def initialize(binstr = nil)
|
|
|
|
binstr and merge(binstr)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
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
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
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
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
self['Unknown'] << binstr[i, len + 4]
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
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
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def merge(binstr)
|
2012-02-14 06:03:34 +08:00
|
|
|
return if binstr.empty?
|
2013-07-01 04:52:18 +08:00
|
|
|
i = 0
|
2012-02-14 00:55:08 +08:00
|
|
|
while i < binstr.bytesize
|
2013-07-01 04:52:18 +08:00
|
|
|
id = binstr[i, 2]
|
|
|
|
len = binstr[i + 2, 2].to_s.unpack('v').first
|
2010-11-30 16:27:59 +08:00
|
|
|
if id && ID_MAP.member?(id)
|
2013-07-01 04:52:18 +08:00
|
|
|
extra_field_type_exist(binstr, id, len, i)
|
2010-11-30 16:27:59 +08:00
|
|
|
elsif id
|
2013-07-01 04:52:18 +08:00
|
|
|
create_unknown_item unless self['Unknown']
|
|
|
|
break unless extra_field_type_unknown(binstr, len, i)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2012-02-14 00:55:08 +08:00
|
|
|
i += len + 4
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create(name)
|
2013-07-01 04:52:18 +08:00
|
|
|
unless field_class = ID_MAP.values.find { |k| k.name == name }
|
2014-01-24 17:37:38 +08:00
|
|
|
raise Error, "Unknown extra field '#{name}'"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
self[name] = field_class.new
|
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
|
|
|
# place Unknown last, so "extra" data that is missing the proper signature/size
|
|
|
|
# does not prevent known fields from being read back in
|
|
|
|
def ordered_values
|
2014-02-04 04:15:08 +08:00
|
|
|
result = []
|
|
|
|
self.each { |k,v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
|
|
|
|
result
|
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
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
def to_local_bin
|
2014-02-04 04:15:08 +08:00
|
|
|
ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
alias :to_s :to_local_bin
|
|
|
|
|
|
|
|
def to_c_dir_bin
|
2014-02-04 04:15:08 +08:00
|
|
|
ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def c_dir_size
|
2012-02-14 00:55:08 +08:00
|
|
|
to_c_dir_bin.bytesize
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
def local_size
|
2012-02-14 00:55:08 +08:00
|
|
|
to_local_bin.bytesize
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-07-01 04:52:18 +08:00
|
|
|
|
2013-08-27 04:26:14 +08:00
|
|
|
alias :length :local_size
|
|
|
|
alias :size :local_size
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-07-01 04:52:18 +08:00
|
|
|
require 'zip/extra_field/generic'
|
|
|
|
require 'zip/extra_field/universal_time'
|
|
|
|
require 'zip/extra_field/unix'
|
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
|
|
|
require 'zip/extra_field/zip64'
|
|
|
|
require 'zip/extra_field/zip64_placeholder'
|
2013-08-27 04:26:14 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
|
|
# rubyzip is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the ruby license.
|