rubyzip 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubyzip might be problematic. Click here for more details.
- checksums.yaml +6 -14
- data/README.md +173 -42
- data/Rakefile +10 -5
- data/TODO +0 -1
- data/lib/zip/central_directory.rb +55 -24
- data/lib/zip/compressor.rb +0 -0
- data/lib/zip/constants.rb +4 -2
- data/lib/zip/crypto/encryption.rb +11 -0
- data/lib/zip/crypto/null_encryption.rb +45 -0
- data/lib/zip/crypto/traditional_encryption.rb +99 -0
- data/lib/zip/decompressor.rb +2 -2
- data/lib/zip/deflater.rb +11 -6
- data/lib/zip/dos_time.rb +4 -5
- data/lib/zip/entry.rb +159 -97
- data/lib/zip/entry_set.rb +18 -18
- data/lib/zip/errors.rb +15 -6
- data/lib/zip/extra_field/generic.rb +8 -8
- data/lib/zip/extra_field/ntfs.rb +90 -0
- data/lib/zip/extra_field/old_unix.rb +44 -0
- data/lib/zip/extra_field/universal_time.rb +14 -14
- data/lib/zip/extra_field/unix.rb +8 -9
- data/lib/zip/extra_field/zip64.rb +44 -6
- data/lib/zip/extra_field/zip64_placeholder.rb +16 -0
- data/lib/zip/extra_field.rb +20 -8
- data/lib/zip/file.rb +126 -114
- data/lib/zip/filesystem.rb +140 -139
- data/lib/zip/inflater.rb +10 -9
- data/lib/zip/input_stream.rb +105 -80
- data/lib/zip/ioextras/abstract_input_stream.rb +15 -12
- data/lib/zip/ioextras/abstract_output_stream.rb +0 -2
- data/lib/zip/ioextras.rb +1 -3
- data/lib/zip/null_compressor.rb +2 -2
- data/lib/zip/null_decompressor.rb +4 -4
- data/lib/zip/null_input_stream.rb +2 -1
- data/lib/zip/output_stream.rb +57 -43
- data/lib/zip/pass_thru_compressor.rb +4 -4
- data/lib/zip/pass_thru_decompressor.rb +4 -5
- data/lib/zip/streamable_directory.rb +2 -2
- data/lib/zip/streamable_stream.rb +22 -13
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +11 -2
- data/samples/example.rb +30 -40
- data/samples/example_filesystem.rb +16 -18
- data/samples/example_recursive.rb +35 -27
- data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +25 -27
- data/samples/qtzip.rb +19 -28
- data/samples/write_simple.rb +12 -13
- data/samples/zipfind.rb +29 -37
- data/test/basic_zip_file_test.rb +60 -0
- data/test/case_sensitivity_test.rb +69 -0
- data/test/central_directory_entry_test.rb +69 -0
- data/test/central_directory_test.rb +100 -0
- data/test/crypto/null_encryption_test.rb +53 -0
- data/test/crypto/traditional_encryption_test.rb +80 -0
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +46 -0
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +1504 -0
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/mimetype +1 -0
- data/test/data/notzippedruby.rb +7 -0
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +65 -0
- data/test/encryption_test.rb +42 -0
- data/test/entry_set_test.rb +152 -0
- data/test/entry_test.rb +163 -0
- data/test/errors_test.rb +34 -0
- data/test/extra_field_test.rb +76 -0
- data/test/file_extract_directory_test.rb +54 -0
- data/test/file_extract_test.rb +83 -0
- data/test/file_permissions_test.rb +69 -0
- data/test/file_split_test.rb +57 -0
- data/test/file_test.rb +563 -0
- data/test/filesystem/dir_iterator_test.rb +58 -0
- data/test/filesystem/directory_test.rb +121 -0
- data/test/filesystem/file_mutating_test.rb +88 -0
- data/test/filesystem/file_nonmutating_test.rb +508 -0
- data/test/filesystem/file_stat_test.rb +64 -0
- data/test/gentestfiles.rb +122 -0
- data/test/inflater_test.rb +14 -0
- data/test/input_stream_test.rb +182 -0
- data/test/ioextras/abstract_input_stream_test.rb +102 -0
- data/test/ioextras/abstract_output_stream_test.rb +106 -0
- data/test/ioextras/fake_io_test.rb +18 -0
- data/test/local_entry_test.rb +154 -0
- data/test/output_stream_test.rb +128 -0
- data/test/pass_thru_compressor_test.rb +30 -0
- data/test/pass_thru_decompressor_test.rb +14 -0
- data/test/samples/example_recursive_test.rb +37 -0
- data/test/settings_test.rb +95 -0
- data/test/test_helper.rb +221 -0
- data/test/unicode_file_names_and_comments_test.rb +50 -0
- data/test/zip64_full_test.rb +51 -0
- data/test/zip64_support_test.rb +14 -0
- metadata +198 -22
- data/NEWS +0 -182
data/lib/zip/entry_set.rb
CHANGED
@@ -6,7 +6,6 @@ module Zip
|
|
6
6
|
def initialize(an_enumerable = [])
|
7
7
|
super()
|
8
8
|
@entry_set = {}
|
9
|
-
@entry_order = []
|
10
9
|
an_enumerable.each { |o| push(o) }
|
11
10
|
end
|
12
11
|
|
@@ -19,52 +18,46 @@ module Zip
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def <<(entry)
|
22
|
-
@
|
23
|
-
@entry_order << to_key(entry)
|
24
|
-
@entry_set[to_key(entry)] = entry
|
21
|
+
@entry_set[to_key(entry)] = entry if entry
|
25
22
|
end
|
26
23
|
|
27
|
-
alias
|
24
|
+
alias push <<
|
28
25
|
|
29
26
|
def size
|
30
27
|
@entry_set.size
|
31
28
|
end
|
32
29
|
|
33
|
-
alias
|
30
|
+
alias length size
|
34
31
|
|
35
32
|
def delete(entry)
|
36
|
-
|
37
|
-
entry
|
38
|
-
else
|
39
|
-
nil
|
40
|
-
end
|
33
|
+
entry if @entry_set.delete(to_key(entry))
|
41
34
|
end
|
42
35
|
|
43
36
|
def each(&block)
|
44
|
-
@
|
45
|
-
block.call
|
37
|
+
@entry_set = sorted_entries.dup.each do |_, value|
|
38
|
+
block.call(value)
|
46
39
|
end
|
47
40
|
end
|
48
41
|
|
49
42
|
def entries
|
50
|
-
|
43
|
+
sorted_entries.values
|
51
44
|
end
|
52
45
|
|
53
46
|
# deep clone
|
54
47
|
def dup
|
55
|
-
EntrySet.new(@
|
48
|
+
EntrySet.new(@entry_set.values.map(&:dup))
|
56
49
|
end
|
57
50
|
|
58
51
|
def ==(other)
|
59
52
|
return false unless other.kind_of?(EntrySet)
|
60
|
-
@entry_set == other.entry_set
|
53
|
+
@entry_set.values == other.entry_set.values
|
61
54
|
end
|
62
55
|
|
63
56
|
def parent(entry)
|
64
57
|
@entry_set[to_key(entry.parent_as_string)]
|
65
58
|
end
|
66
59
|
|
67
|
-
def glob(pattern, flags = ::File::FNM_PATHNAME
|
60
|
+
def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH)
|
68
61
|
entries.map do |entry|
|
69
62
|
next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
|
70
63
|
yield(entry) if block_given?
|
@@ -74,9 +67,16 @@ module Zip
|
|
74
67
|
|
75
68
|
protected
|
76
69
|
|
70
|
+
def sorted_entries
|
71
|
+
::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
|
72
|
+
end
|
73
|
+
|
77
74
|
private
|
75
|
+
|
78
76
|
def to_key(entry)
|
79
|
-
entry.to_s.
|
77
|
+
k = entry.to_s.chomp('/')
|
78
|
+
k.downcase! if ::Zip.case_insensitive_match
|
79
|
+
k
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
data/lib/zip/errors.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
module Zip
|
2
|
-
class
|
3
|
-
class
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class
|
7
|
-
class
|
2
|
+
class Error < StandardError; end
|
3
|
+
class EntryExistsError < Error; end
|
4
|
+
class DestinationFileExistsError < Error; end
|
5
|
+
class CompressionMethodError < Error; end
|
6
|
+
class EntryNameError < Error; end
|
7
|
+
class InternalError < Error; end
|
8
|
+
class GPFBit3Error < Error; end
|
9
|
+
|
10
|
+
# Backwards compatibility with v1 (delete in v2)
|
11
|
+
ZipError = Error
|
12
|
+
ZipEntryExistsError = EntryExistsError
|
13
|
+
ZipDestinationFileExistsError = DestinationFileExistsError
|
14
|
+
ZipCompressionMethodError = CompressionMethodError
|
15
|
+
ZipEntryNameError = EntryNameError
|
16
|
+
ZipInternalError = InternalError
|
8
17
|
end
|
@@ -2,12 +2,12 @@ module Zip
|
|
2
2
|
class ExtraField::Generic
|
3
3
|
def self.register_map
|
4
4
|
if self.const_defined?(:HEADER_ID)
|
5
|
-
::Zip::ExtraField::ID_MAP[
|
5
|
+
::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.name
|
10
|
-
|
10
|
+
@name ||= to_s.split('::')[-1]
|
11
11
|
end
|
12
12
|
|
13
13
|
# return field [size, content] or false
|
@@ -16,28 +16,28 @@ module Zip
|
|
16
16
|
# If nil, start with empty.
|
17
17
|
return false
|
18
18
|
elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
19
|
-
$stderr.puts
|
19
|
+
$stderr.puts 'Warning: weired extra feild header ID. skip parsing'
|
20
20
|
return false
|
21
21
|
end
|
22
|
-
[binstr[2, 2].unpack(
|
22
|
+
[binstr[2, 2].unpack('v')[0], binstr[4..-1]]
|
23
23
|
end
|
24
24
|
|
25
25
|
def ==(other)
|
26
26
|
return false if self.class != other.class
|
27
27
|
each do |k, v|
|
28
|
-
v != other[k]
|
28
|
+
return false if v != other[k]
|
29
29
|
end
|
30
30
|
true
|
31
31
|
end
|
32
32
|
|
33
33
|
def to_local_bin
|
34
34
|
s = pack_for_local
|
35
|
-
self.class.const_get(:HEADER_ID) + [s.bytesize].pack(
|
35
|
+
self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
|
36
36
|
end
|
37
37
|
|
38
38
|
def to_c_dir_bin
|
39
39
|
s = pack_for_c_dir
|
40
|
-
self.class.const_get(:HEADER_ID) + [s.bytesize].pack(
|
40
|
+
self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
|
41
41
|
end
|
42
42
|
end
|
43
|
-
end
|
43
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Zip
|
2
|
+
# PKWARE NTFS Extra Field (0x000a)
|
3
|
+
# Only Tag 0x0001 is supported
|
4
|
+
class ExtraField::NTFS < ExtraField::Generic
|
5
|
+
HEADER_ID = [0x000A].pack('v')
|
6
|
+
register_map
|
7
|
+
|
8
|
+
WINDOWS_TICK = 10_000_000.0
|
9
|
+
SEC_TO_UNIX_EPOCH = 11_644_473_600
|
10
|
+
|
11
|
+
def initialize(binstr = nil)
|
12
|
+
@ctime = nil
|
13
|
+
@mtime = nil
|
14
|
+
@atime = nil
|
15
|
+
binstr && merge(binstr)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :atime, :ctime, :mtime
|
19
|
+
|
20
|
+
def merge(binstr)
|
21
|
+
return if binstr.empty?
|
22
|
+
size, content = initial_parse(binstr)
|
23
|
+
(size && content) || return
|
24
|
+
|
25
|
+
content = content[4..-1]
|
26
|
+
tags = parse_tags(content)
|
27
|
+
|
28
|
+
tag1 = tags[1]
|
29
|
+
return unless tag1
|
30
|
+
ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
|
31
|
+
ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
|
32
|
+
ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
|
33
|
+
ntfs_ctime && @ctime ||= from_ntfs_time(ntfs_ctime)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
@mtime == other.mtime &&
|
38
|
+
@atime == other.atime &&
|
39
|
+
@ctime == other.ctime
|
40
|
+
end
|
41
|
+
|
42
|
+
# Info-ZIP note states this extra field is stored at local header
|
43
|
+
def pack_for_local
|
44
|
+
pack_for_c_dir
|
45
|
+
end
|
46
|
+
|
47
|
+
# But 7-zip for Windows only stores at central dir
|
48
|
+
def pack_for_c_dir
|
49
|
+
# reserved 0 and tag 1
|
50
|
+
s = [0, 1].pack('Vv')
|
51
|
+
|
52
|
+
tag1 = ''.force_encoding(Encoding::BINARY)
|
53
|
+
if @mtime
|
54
|
+
tag1 << [to_ntfs_time(@mtime)].pack('Q<')
|
55
|
+
if @atime
|
56
|
+
tag1 << [to_ntfs_time(@atime)].pack('Q<')
|
57
|
+
tag1 << [to_ntfs_time(@ctime)].pack('Q<') if @ctime
|
58
|
+
end
|
59
|
+
end
|
60
|
+
s << [tag1.bytesize].pack('v') << tag1
|
61
|
+
s
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def parse_tags(content)
|
67
|
+
return {} if content.nil?
|
68
|
+
tags = {}
|
69
|
+
i = 0
|
70
|
+
while i < content.bytesize
|
71
|
+
tag, size = content[i, 4].unpack('vv')
|
72
|
+
i += 4
|
73
|
+
break unless tag && size
|
74
|
+
value = content[i, size]
|
75
|
+
i += size
|
76
|
+
tags[tag] = value
|
77
|
+
end
|
78
|
+
|
79
|
+
tags
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_ntfs_time(ntfs_time)
|
83
|
+
::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_ntfs_time(time)
|
87
|
+
((time.to_f + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK).to_i
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Zip
|
2
|
+
# Olf Info-ZIP Extra for UNIX uid/gid and file timestampes
|
3
|
+
class ExtraField::OldUnix < ExtraField::Generic
|
4
|
+
HEADER_ID = 'UX'
|
5
|
+
register_map
|
6
|
+
|
7
|
+
def initialize(binstr = nil)
|
8
|
+
@uid = 0
|
9
|
+
@gid = 0
|
10
|
+
@atime = nil
|
11
|
+
@mtime = nil
|
12
|
+
binstr && merge(binstr)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :uid, :gid, :atime, :mtime
|
16
|
+
|
17
|
+
def merge(binstr)
|
18
|
+
return if binstr.empty?
|
19
|
+
size, content = initial_parse(binstr)
|
20
|
+
# size: 0 for central directory. 4 for local header
|
21
|
+
return if !size || size == 0
|
22
|
+
atime, mtime, uid, gid = content.unpack('VVvv')
|
23
|
+
@uid ||= uid
|
24
|
+
@gid ||= gid
|
25
|
+
@atime ||= atime
|
26
|
+
@mtime ||= mtime
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
@uid == other.uid &&
|
31
|
+
@gid == other.gid &&
|
32
|
+
@atime == other.atime &&
|
33
|
+
@mtime == other.mtime
|
34
|
+
end
|
35
|
+
|
36
|
+
def pack_for_local
|
37
|
+
[@atime, @mtime, @uid, @gid].pack('VVvv')
|
38
|
+
end
|
39
|
+
|
40
|
+
def pack_for_c_dir
|
41
|
+
[@atime, @mtime].pack('VV')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Zip
|
2
2
|
# Info-ZIP Additional timestamp field
|
3
3
|
class ExtraField::UniversalTime < ExtraField::Generic
|
4
|
-
HEADER_ID =
|
4
|
+
HEADER_ID = 'UT'
|
5
5
|
register_map
|
6
6
|
|
7
7
|
def initialize(binstr = nil)
|
@@ -9,7 +9,7 @@ module Zip
|
|
9
9
|
@mtime = nil
|
10
10
|
@atime = nil
|
11
11
|
@flag = nil
|
12
|
-
binstr
|
12
|
+
binstr && merge(binstr)
|
13
13
|
end
|
14
14
|
|
15
15
|
attr_accessor :atime, :ctime, :mtime, :flag
|
@@ -17,11 +17,11 @@ module Zip
|
|
17
17
|
def merge(binstr)
|
18
18
|
return if binstr.empty?
|
19
19
|
size, content = initial_parse(binstr)
|
20
|
-
size
|
21
|
-
@flag, mtime, atime, ctime = content.unpack(
|
22
|
-
mtime
|
23
|
-
atime
|
24
|
-
ctime
|
20
|
+
size || return
|
21
|
+
@flag, mtime, atime, ctime = content.unpack('CVVV')
|
22
|
+
mtime && @mtime ||= ::Zip::DOSTime.at(mtime)
|
23
|
+
atime && @atime ||= ::Zip::DOSTime.at(atime)
|
24
|
+
ctime && @ctime ||= ::Zip::DOSTime.at(ctime)
|
25
25
|
end
|
26
26
|
|
27
27
|
def ==(other)
|
@@ -31,17 +31,17 @@ module Zip
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def pack_for_local
|
34
|
-
s = [@flag].pack(
|
35
|
-
@flag & 1 != 0
|
36
|
-
@flag & 2 != 0
|
37
|
-
@flag & 4 != 0
|
34
|
+
s = [@flag].pack('C')
|
35
|
+
@flag & 1 != 0 && s << [@mtime.to_i].pack('V')
|
36
|
+
@flag & 2 != 0 && s << [@atime.to_i].pack('V')
|
37
|
+
@flag & 4 != 0 && s << [@ctime.to_i].pack('V')
|
38
38
|
s
|
39
39
|
end
|
40
40
|
|
41
41
|
def pack_for_c_dir
|
42
|
-
s = [@flag].pack(
|
43
|
-
@flag & 1 == 1
|
42
|
+
s = [@flag].pack('C')
|
43
|
+
@flag & 1 == 1 && s << [@mtime.to_i].pack('V')
|
44
44
|
s
|
45
45
|
end
|
46
46
|
end
|
47
|
-
end
|
47
|
+
end
|
data/lib/zip/extra_field/unix.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Zip
|
2
2
|
# Info-ZIP Extra for UNIX uid/gid
|
3
3
|
class ExtraField::IUnix < ExtraField::Generic
|
4
|
-
HEADER_ID =
|
4
|
+
HEADER_ID = 'Ux'
|
5
5
|
register_map
|
6
6
|
|
7
7
|
def initialize(binstr = nil)
|
8
8
|
@uid = 0
|
9
9
|
@gid = 0
|
10
|
-
binstr
|
10
|
+
binstr && merge(binstr)
|
11
11
|
end
|
12
12
|
|
13
13
|
attr_accessor :uid, :gid
|
@@ -16,10 +16,10 @@ module Zip
|
|
16
16
|
return if binstr.empty?
|
17
17
|
size, content = initial_parse(binstr)
|
18
18
|
# size: 0 for central directory. 4 for local header
|
19
|
-
return if
|
20
|
-
uid, gid = content.unpack(
|
21
|
-
@uid
|
22
|
-
@gid
|
19
|
+
return if !size || size == 0
|
20
|
+
uid, gid = content.unpack('vv')
|
21
|
+
@uid ||= uid
|
22
|
+
@gid ||= gid
|
23
23
|
end
|
24
24
|
|
25
25
|
def ==(other)
|
@@ -27,12 +27,11 @@ module Zip
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def pack_for_local
|
30
|
-
[@uid, @gid].pack(
|
30
|
+
[@uid, @gid].pack('vv')
|
31
31
|
end
|
32
32
|
|
33
33
|
def pack_for_c_dir
|
34
34
|
''
|
35
35
|
end
|
36
36
|
end
|
37
|
-
|
38
|
-
end
|
37
|
+
end
|
@@ -2,29 +2,67 @@ module Zip
|
|
2
2
|
# Info-ZIP Extra for Zip64 size
|
3
3
|
class ExtraField::Zip64 < ExtraField::Generic
|
4
4
|
attr_accessor :original_size, :compressed_size, :relative_header_offset, :disk_start_number
|
5
|
-
HEADER_ID =
|
5
|
+
HEADER_ID = ['0100'].pack('H*')
|
6
6
|
register_map
|
7
7
|
|
8
8
|
def initialize(binstr = nil)
|
9
|
+
# unparsed binary; we don't actually know what this contains
|
10
|
+
# without looking for FFs in the associated file header
|
11
|
+
# call parse after initializing with a binary string
|
12
|
+
@content = nil
|
9
13
|
@original_size = nil
|
10
14
|
@compressed_size = nil
|
11
15
|
@relative_header_offset = nil
|
12
16
|
@disk_start_number = nil
|
13
|
-
binstr
|
17
|
+
binstr && merge(binstr)
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
other.original_size == @original_size &&
|
22
|
+
other.compressed_size == @compressed_size &&
|
23
|
+
other.relative_header_offset == @relative_header_offset &&
|
24
|
+
other.disk_start_number == @disk_start_number
|
14
25
|
end
|
15
26
|
|
16
27
|
def merge(binstr)
|
17
28
|
return if binstr.empty?
|
18
|
-
|
29
|
+
_, @content = initial_parse(binstr)
|
30
|
+
end
|
31
|
+
|
32
|
+
# pass the values from the base entry (if applicable)
|
33
|
+
# wider values are only present in the extra field for base values set to all FFs
|
34
|
+
# returns the final values for the four attributes (from the base or zip64 extra record)
|
35
|
+
def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil)
|
36
|
+
@original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF
|
37
|
+
@compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF
|
38
|
+
@relative_header_offset = extract(8, 'Q<') if relative_header_offset && relative_header_offset == 0xFFFFFFFF
|
39
|
+
@disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF
|
40
|
+
@content = nil
|
41
|
+
[@original_size || original_size,
|
42
|
+
@compressed_size || compressed_size,
|
43
|
+
@relative_header_offset || relative_header_offset,
|
44
|
+
@disk_start_number || disk_start_number]
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract(size, format)
|
48
|
+
@content.slice!(0, size).unpack(format)[0]
|
19
49
|
end
|
50
|
+
private :extract
|
20
51
|
|
21
52
|
def pack_for_local
|
22
|
-
|
23
|
-
|
53
|
+
# local header entries must contain original size and compressed size; other fields do not apply
|
54
|
+
return '' unless @original_size && @compressed_size
|
55
|
+
[@original_size, @compressed_size].pack('Q<Q<')
|
24
56
|
end
|
25
57
|
|
26
58
|
def pack_for_c_dir
|
27
|
-
|
59
|
+
# central directory entries contain only fields that didn't fit in the main entry part
|
60
|
+
packed = ''.force_encoding('BINARY')
|
61
|
+
packed << [@original_size].pack('Q<') if @original_size
|
62
|
+
packed << [@compressed_size].pack('Q<') if @compressed_size
|
63
|
+
packed << [@relative_header_offset].pack('Q<') if @relative_header_offset
|
64
|
+
packed << [@disk_start_number].pack('V') if @disk_start_number
|
65
|
+
packed
|
28
66
|
end
|
29
67
|
end
|
30
68
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Zip
|
2
|
+
# placeholder to reserve space for a Zip64 extra information record, for the
|
3
|
+
# local file header only, that we won't know if we'll need until after
|
4
|
+
# we write the file data
|
5
|
+
class ExtraField::Zip64Placeholder < ExtraField::Generic
|
6
|
+
HEADER_ID = ['9999'].pack('H*') # this ID is used by other libraries such as .NET's Ionic.zip
|
7
|
+
register_map
|
8
|
+
|
9
|
+
def initialize(_binstr = nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def pack_for_local
|
13
|
+
"\x00" * 16
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/zip/extra_field.rb
CHANGED
@@ -3,7 +3,7 @@ module Zip
|
|
3
3
|
ID_MAP = {}
|
4
4
|
|
5
5
|
def initialize(binstr = nil)
|
6
|
-
binstr
|
6
|
+
merge(binstr) if binstr
|
7
7
|
end
|
8
8
|
|
9
9
|
def extra_field_type_exist(binstr, id, len, i)
|
@@ -51,20 +51,28 @@ module Zip
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def create(name)
|
54
|
-
unless field_class = ID_MAP.values.find { |k| k.name == name }
|
55
|
-
raise
|
54
|
+
unless (field_class = ID_MAP.values.find { |k| k.name == name })
|
55
|
+
raise Error, "Unknown extra field '#{name}'"
|
56
56
|
end
|
57
57
|
self[name] = field_class.new
|
58
58
|
end
|
59
59
|
|
60
|
+
# place Unknown last, so "extra" data that is missing the proper signature/size
|
61
|
+
# does not prevent known fields from being read back in
|
62
|
+
def ordered_values
|
63
|
+
result = []
|
64
|
+
each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
60
68
|
def to_local_bin
|
61
|
-
|
69
|
+
ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
|
62
70
|
end
|
63
71
|
|
64
|
-
alias
|
72
|
+
alias to_s to_local_bin
|
65
73
|
|
66
74
|
def to_c_dir_bin
|
67
|
-
|
75
|
+
ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join
|
68
76
|
end
|
69
77
|
|
70
78
|
def c_dir_size
|
@@ -75,14 +83,18 @@ module Zip
|
|
75
83
|
to_local_bin.bytesize
|
76
84
|
end
|
77
85
|
|
78
|
-
alias
|
79
|
-
alias
|
86
|
+
alias length local_size
|
87
|
+
alias size local_size
|
80
88
|
end
|
81
89
|
end
|
82
90
|
|
83
91
|
require 'zip/extra_field/generic'
|
84
92
|
require 'zip/extra_field/universal_time'
|
93
|
+
require 'zip/extra_field/old_unix'
|
85
94
|
require 'zip/extra_field/unix'
|
95
|
+
require 'zip/extra_field/zip64'
|
96
|
+
require 'zip/extra_field/zip64_placeholder'
|
97
|
+
require 'zip/extra_field/ntfs'
|
86
98
|
|
87
99
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
88
100
|
# rubyzip is free software; you can redistribute it and/or
|