rubyzip 0.9.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +354 -0
  3. data/Rakefile +15 -104
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +212 -0
  6. data/lib/zip/compressor.rb +9 -0
  7. data/lib/zip/constants.rb +115 -0
  8. data/lib/zip/crypto/decrypted_io.rb +40 -0
  9. data/lib/zip/crypto/encryption.rb +11 -0
  10. data/lib/zip/crypto/null_encryption.rb +43 -0
  11. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  12. data/lib/zip/decompressor.rb +31 -0
  13. data/lib/zip/deflater.rb +34 -0
  14. data/lib/zip/dos_time.rb +53 -0
  15. data/lib/zip/entry.rb +719 -0
  16. data/lib/zip/entry_set.rb +88 -0
  17. data/lib/zip/errors.rb +19 -0
  18. data/lib/zip/extra_field/generic.rb +44 -0
  19. data/lib/zip/extra_field/ntfs.rb +94 -0
  20. data/lib/zip/extra_field/old_unix.rb +46 -0
  21. data/lib/zip/extra_field/universal_time.rb +77 -0
  22. data/lib/zip/extra_field/unix.rb +39 -0
  23. data/lib/zip/extra_field/zip64.rb +70 -0
  24. data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
  25. data/lib/zip/extra_field.rb +103 -0
  26. data/lib/zip/file.rb +468 -0
  27. data/lib/zip/filesystem.rb +643 -0
  28. data/lib/zip/inflater.rb +54 -0
  29. data/lib/zip/input_stream.rb +180 -0
  30. data/lib/zip/ioextras/abstract_input_stream.rb +122 -0
  31. data/lib/zip/ioextras/abstract_output_stream.rb +43 -0
  32. data/lib/zip/ioextras.rb +21 -140
  33. data/lib/zip/null_compressor.rb +15 -0
  34. data/lib/zip/null_decompressor.rb +19 -0
  35. data/lib/zip/null_input_stream.rb +10 -0
  36. data/lib/zip/output_stream.rb +198 -0
  37. data/lib/zip/pass_thru_compressor.rb +23 -0
  38. data/lib/zip/pass_thru_decompressor.rb +31 -0
  39. data/lib/zip/streamable_directory.rb +15 -0
  40. data/lib/zip/streamable_stream.rb +52 -0
  41. data/lib/zip/version.rb +3 -0
  42. data/lib/zip.rb +72 -0
  43. data/samples/example.rb +44 -32
  44. data/samples/example_filesystem.rb +16 -19
  45. data/samples/example_recursive.rb +54 -0
  46. data/samples/gtk_ruby_zip.rb +84 -0
  47. data/samples/qtzip.rb +25 -34
  48. data/samples/write_simple.rb +10 -13
  49. data/samples/zipfind.rb +38 -45
  50. metadata +182 -91
  51. data/ChangeLog +0 -1504
  52. data/NEWS +0 -144
  53. data/README +0 -72
  54. data/install.rb +0 -22
  55. data/lib/download_quizzes.rb +0 -119
  56. data/lib/quiz1/t/solutions/Bill Guindon/solitaire.rb +0 -205
  57. data/lib/quiz1/t/solutions/Carlos/solitaire.rb +0 -111
  58. data/lib/quiz1/t/solutions/Dennis Ranke/solitaire.rb +0 -111
  59. data/lib/quiz1/t/solutions/Florian Gross/solitaire.rb +0 -301
  60. data/lib/quiz1/t/solutions/Glen M. Lewis/solitaire.rb +0 -268
  61. data/lib/quiz1/t/solutions/James Edward Gray II/solitaire.rb +0 -132
  62. data/lib/quiz1/t/solutions/Jamis Buck/bin/main.rb +0 -13
  63. data/lib/quiz1/t/solutions/Jamis Buck/lib/cipher.rb +0 -230
  64. data/lib/quiz1/t/solutions/Jamis Buck/lib/cli.rb +0 -24
  65. data/lib/quiz1/t/solutions/Jamis Buck/test/tc_deck.rb +0 -30
  66. data/lib/quiz1/t/solutions/Jamis Buck/test/tc_key-stream.rb +0 -19
  67. data/lib/quiz1/t/solutions/Jamis Buck/test/tc_keying-algorithms.rb +0 -31
  68. data/lib/quiz1/t/solutions/Jamis Buck/test/tc_solitaire-cipher.rb +0 -66
  69. data/lib/quiz1/t/solutions/Jamis Buck/test/tc_unkeyed-algorithm.rb +0 -17
  70. data/lib/quiz1/t/solutions/Jamis Buck/test/tests.rb +0 -2
  71. data/lib/quiz1/t/solutions/Jim Menard/solitaire_cypher.rb +0 -204
  72. data/lib/quiz1/t/solutions/Jim Menard/test.rb +0 -47
  73. data/lib/quiz1/t/solutions/Moses Hohman/cipher.rb +0 -97
  74. data/lib/quiz1/t/solutions/Moses Hohman/deck.rb +0 -140
  75. data/lib/quiz1/t/solutions/Moses Hohman/solitaire.rb +0 -14
  76. data/lib/quiz1/t/solutions/Moses Hohman/test_cipher.rb +0 -68
  77. data/lib/quiz1/t/solutions/Moses Hohman/test_deck.rb +0 -146
  78. data/lib/quiz1/t/solutions/Moses Hohman/test_util.rb +0 -38
  79. data/lib/quiz1/t/solutions/Moses Hohman/testsuite.rb +0 -5
  80. data/lib/quiz1/t/solutions/Moses Hohman/util.rb +0 -27
  81. data/lib/quiz1/t/solutions/Niklas Frykholm/solitaire.rb +0 -151
  82. data/lib/quiz1/t/solutions/Thomas Leitner/solitaire.rb +0 -198
  83. data/lib/zip/stdrubyext.rb +0 -111
  84. data/lib/zip/tempfile_bugfixed.rb +0 -195
  85. data/lib/zip/zip.rb +0 -1847
  86. data/lib/zip/zipfilesystem.rb +0 -609
  87. data/lib/zip/ziprequire.rb +0 -90
  88. data/samples/gtkRubyzip.rb +0 -86
  89. data/test/alltests.rb +0 -9
  90. data/test/data/file1.txt +0 -46
  91. data/test/data/file1.txt.deflatedData +0 -0
  92. data/test/data/file2.txt +0 -1504
  93. data/test/data/notzippedruby.rb +0 -7
  94. data/test/data/rubycode.zip +0 -0
  95. data/test/data/rubycode2.zip +0 -0
  96. data/test/data/testDirectory.bin +0 -0
  97. data/test/data/zipWithDirs.zip +0 -0
  98. data/test/gentestfiles.rb +0 -157
  99. data/test/ioextrastest.rb +0 -208
  100. data/test/stdrubyexttest.rb +0 -52
  101. data/test/zipfilesystemtest.rb +0 -831
  102. data/test/ziprequiretest.rb +0 -43
  103. data/test/ziptest.rb +0 -1599
@@ -0,0 +1,88 @@
1
+ module Zip
2
+ class EntrySet #:nodoc:all
3
+ include Enumerable
4
+ attr_accessor :entry_set, :entry_order
5
+
6
+ def initialize(an_enumerable = [])
7
+ super()
8
+ @entry_set = {}
9
+ an_enumerable.each { |o| push(o) }
10
+ end
11
+
12
+ def include?(entry)
13
+ @entry_set.include?(to_key(entry))
14
+ end
15
+
16
+ def find_entry(entry)
17
+ @entry_set[to_key(entry)]
18
+ end
19
+
20
+ def <<(entry)
21
+ @entry_set[to_key(entry)] = entry if entry
22
+ end
23
+
24
+ alias push <<
25
+
26
+ def size
27
+ @entry_set.size
28
+ end
29
+
30
+ alias length size
31
+
32
+ def delete(entry)
33
+ entry if @entry_set.delete(to_key(entry))
34
+ end
35
+
36
+ def each
37
+ @entry_set = sorted_entries.dup.each do |_, value|
38
+ yield(value)
39
+ end
40
+ end
41
+
42
+ def entries
43
+ sorted_entries.values
44
+ end
45
+
46
+ # deep clone
47
+ def dup
48
+ EntrySet.new(@entry_set.values.map(&:dup))
49
+ end
50
+
51
+ def ==(other)
52
+ return false unless other.kind_of?(EntrySet)
53
+
54
+ @entry_set.values == other.entry_set.values
55
+ end
56
+
57
+ def parent(entry)
58
+ @entry_set[to_key(entry.parent_as_string)]
59
+ end
60
+
61
+ def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
62
+ entries.map do |entry|
63
+ next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
64
+
65
+ yield(entry) if block_given?
66
+ entry
67
+ end.compact
68
+ end
69
+
70
+ protected
71
+
72
+ def sorted_entries
73
+ ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
74
+ end
75
+
76
+ private
77
+
78
+ def to_key(entry)
79
+ k = entry.to_s.chomp('/')
80
+ k.downcase! if ::Zip.case_insensitive_match
81
+ k
82
+ end
83
+ end
84
+ end
85
+
86
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
87
+ # rubyzip is free software; you can redistribute it and/or
88
+ # modify it under the terms of the ruby license.
data/lib/zip/errors.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Zip
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 EntrySizeError < Error; end
8
+ class InternalError < Error; end
9
+ class GPFBit3Error < Error; end
10
+ class DecompressionError < Error; end
11
+
12
+ # Backwards compatibility with v1 (delete in v2)
13
+ ZipError = Error
14
+ ZipEntryExistsError = EntryExistsError
15
+ ZipDestinationFileExistsError = DestinationFileExistsError
16
+ ZipCompressionMethodError = CompressionMethodError
17
+ ZipEntryNameError = EntryNameError
18
+ ZipInternalError = InternalError
19
+ end
@@ -0,0 +1,44 @@
1
+ module Zip
2
+ class ExtraField::Generic
3
+ def self.register_map
4
+ return unless const_defined?(:HEADER_ID)
5
+
6
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
7
+ end
8
+
9
+ def self.name
10
+ @name ||= to_s.split('::')[-1]
11
+ end
12
+
13
+ # return field [size, content] or false
14
+ def initial_parse(binstr)
15
+ return false unless binstr
16
+
17
+ if binstr[0, 2] != self.class.const_get(:HEADER_ID)
18
+ warn 'WARNING: weird extra field header ID. Skip parsing it.'
19
+ return false
20
+ end
21
+
22
+ [binstr[2, 2].unpack1('v'), binstr[4..-1]]
23
+ end
24
+
25
+ def ==(other)
26
+ return false if self.class != other.class
27
+
28
+ each do |k, v|
29
+ return false if v != other[k]
30
+ end
31
+ true
32
+ end
33
+
34
+ def to_local_bin
35
+ s = pack_for_local
36
+ self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
37
+ end
38
+
39
+ def to_c_dir_bin
40
+ s = pack_for_c_dir
41
+ self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,94 @@
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
+
23
+ size, content = initial_parse(binstr)
24
+ (size && content) || return
25
+
26
+ content = content[4..-1]
27
+ tags = parse_tags(content)
28
+
29
+ tag1 = tags[1]
30
+ return unless tag1
31
+
32
+ ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
33
+ ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
34
+ ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
35
+ ntfs_ctime && @ctime ||= from_ntfs_time(ntfs_ctime)
36
+ end
37
+
38
+ def ==(other)
39
+ @mtime == other.mtime &&
40
+ @atime == other.atime &&
41
+ @ctime == other.ctime
42
+ end
43
+
44
+ # Info-ZIP note states this extra field is stored at local header
45
+ def pack_for_local
46
+ pack_for_c_dir
47
+ end
48
+
49
+ # But 7-zip for Windows only stores at central dir
50
+ def pack_for_c_dir
51
+ # reserved 0 and tag 1
52
+ s = [0, 1].pack('Vv')
53
+
54
+ tag1 = ''.force_encoding(Encoding::BINARY)
55
+ if @mtime
56
+ tag1 << [to_ntfs_time(@mtime)].pack('Q<')
57
+ if @atime
58
+ tag1 << [to_ntfs_time(@atime)].pack('Q<')
59
+ tag1 << [to_ntfs_time(@ctime)].pack('Q<') if @ctime
60
+ end
61
+ end
62
+ s << [tag1.bytesize].pack('v') << tag1
63
+ s
64
+ end
65
+
66
+ private
67
+
68
+ def parse_tags(content)
69
+ return {} if content.nil?
70
+
71
+ tags = {}
72
+ i = 0
73
+ while i < content.bytesize
74
+ tag, size = content[i, 4].unpack('vv')
75
+ i += 4
76
+ break unless tag && size
77
+
78
+ value = content[i, size]
79
+ i += size
80
+ tags[tag] = value
81
+ end
82
+
83
+ tags
84
+ end
85
+
86
+ def from_ntfs_time(ntfs_time)
87
+ ::Zip::DOSTime.at(ntfs_time / WINDOWS_TICK - SEC_TO_UNIX_EPOCH)
88
+ end
89
+
90
+ def to_ntfs_time(time)
91
+ ((time.to_f + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK).to_i
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
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
+
20
+ size, content = initial_parse(binstr)
21
+ # size: 0 for central directory. 4 for local header
22
+ return if !size || size == 0
23
+
24
+ atime, mtime, uid, gid = content.unpack('VVvv')
25
+ @uid ||= uid
26
+ @gid ||= gid
27
+ @atime ||= atime
28
+ @mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
29
+ end
30
+
31
+ def ==(other)
32
+ @uid == other.uid &&
33
+ @gid == other.gid &&
34
+ @atime == other.atime &&
35
+ @mtime == other.mtime
36
+ end
37
+
38
+ def pack_for_local
39
+ [@atime, @mtime, @uid, @gid].pack('VVvv')
40
+ end
41
+
42
+ def pack_for_c_dir
43
+ [@atime, @mtime].pack('VV')
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,77 @@
1
+ module Zip
2
+ # Info-ZIP Additional timestamp field
3
+ class ExtraField::UniversalTime < ExtraField::Generic
4
+ HEADER_ID = 'UT'
5
+ register_map
6
+
7
+ ATIME_MASK = 0b010
8
+ CTIME_MASK = 0b100
9
+ MTIME_MASK = 0b001
10
+
11
+ def initialize(binstr = nil)
12
+ @ctime = nil
13
+ @mtime = nil
14
+ @atime = nil
15
+ @flag = 0
16
+
17
+ merge(binstr) unless binstr.nil?
18
+ end
19
+
20
+ attr_reader :atime, :ctime, :mtime, :flag
21
+
22
+ def atime=(time)
23
+ @flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
24
+ @atime = time
25
+ end
26
+
27
+ def ctime=(time)
28
+ @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
29
+ @ctime = time
30
+ end
31
+
32
+ def mtime=(time)
33
+ @flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
34
+ @mtime = time
35
+ end
36
+
37
+ def merge(binstr)
38
+ return if binstr.empty?
39
+
40
+ size, content = initial_parse(binstr)
41
+ return if !size || size <= 0
42
+
43
+ @flag, *times = content.unpack('Cl<l<l<')
44
+
45
+ # Parse the timestamps, in order, based on which flags are set.
46
+ return if times[0].nil?
47
+
48
+ @mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
49
+ return if times[0].nil?
50
+
51
+ @atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
52
+ return if times[0].nil?
53
+
54
+ @ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
55
+ end
56
+
57
+ def ==(other)
58
+ @mtime == other.mtime &&
59
+ @atime == other.atime &&
60
+ @ctime == other.ctime
61
+ end
62
+
63
+ def pack_for_local
64
+ s = [@flag].pack('C')
65
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
66
+ s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
67
+ s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
68
+ s
69
+ end
70
+
71
+ def pack_for_c_dir
72
+ s = [@flag].pack('C')
73
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
74
+ s
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,39 @@
1
+ module Zip
2
+ # Info-ZIP Extra for UNIX uid/gid
3
+ class ExtraField::IUnix < ExtraField::Generic
4
+ HEADER_ID = 'Ux'
5
+ register_map
6
+
7
+ def initialize(binstr = nil)
8
+ @uid = 0
9
+ @gid = 0
10
+ binstr && merge(binstr)
11
+ end
12
+
13
+ attr_accessor :uid, :gid
14
+
15
+ def merge(binstr)
16
+ return if binstr.empty?
17
+
18
+ size, content = initial_parse(binstr)
19
+ # size: 0 for central directory. 4 for local header
20
+ return if !size || size == 0
21
+
22
+ uid, gid = content.unpack('vv')
23
+ @uid ||= uid
24
+ @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName
25
+ end
26
+
27
+ def ==(other)
28
+ @uid == other.uid && @gid == other.gid
29
+ end
30
+
31
+ def pack_for_local
32
+ [@uid, @gid].pack('vv')
33
+ end
34
+
35
+ def pack_for_c_dir
36
+ ''
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ module Zip
2
+ # Info-ZIP Extra for Zip64 size
3
+ class ExtraField::Zip64 < ExtraField::Generic
4
+ attr_accessor :original_size, :compressed_size, :relative_header_offset, :disk_start_number
5
+ HEADER_ID = ['0100'].pack('H*')
6
+ register_map
7
+
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
13
+ @original_size = nil
14
+ @compressed_size = nil
15
+ @relative_header_offset = nil
16
+ @disk_start_number = nil
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
25
+ end
26
+
27
+ def merge(binstr)
28
+ return if binstr.empty?
29
+
30
+ _, @content = initial_parse(binstr)
31
+ end
32
+
33
+ # pass the values from the base entry (if applicable)
34
+ # wider values are only present in the extra field for base values set to all FFs
35
+ # returns the final values for the four attributes (from the base or zip64 extra record)
36
+ def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil)
37
+ @original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF
38
+ @compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF
39
+ @relative_header_offset = extract(8, 'Q<') if relative_header_offset && relative_header_offset == 0xFFFFFFFF
40
+ @disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF
41
+ @content = nil
42
+ [@original_size || original_size,
43
+ @compressed_size || compressed_size,
44
+ @relative_header_offset || relative_header_offset,
45
+ @disk_start_number || disk_start_number]
46
+ end
47
+
48
+ def extract(size, format)
49
+ @content.slice!(0, size).unpack1(format)
50
+ end
51
+ private :extract
52
+
53
+ def pack_for_local
54
+ # local header entries must contain original size and compressed size; other fields do not apply
55
+ return '' unless @original_size && @compressed_size
56
+
57
+ [@original_size, @compressed_size].pack('Q<Q<')
58
+ end
59
+
60
+ def pack_for_c_dir
61
+ # central directory entries contain only fields that didn't fit in the main entry part
62
+ packed = ''.force_encoding('BINARY')
63
+ packed << [@original_size].pack('Q<') if @original_size
64
+ packed << [@compressed_size].pack('Q<') if @compressed_size
65
+ packed << [@relative_header_offset].pack('Q<') if @relative_header_offset
66
+ packed << [@disk_start_number].pack('V') if @disk_start_number
67
+ packed
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
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); end
10
+
11
+ def pack_for_local
12
+ "\x00" * 16
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,103 @@
1
+ module Zip
2
+ class ExtraField < Hash
3
+ ID_MAP = {}
4
+
5
+ def initialize(binstr = nil)
6
+ merge(binstr) if binstr
7
+ end
8
+
9
+ def extra_field_type_exist(binstr, id, len, index)
10
+ field_name = ID_MAP[id].name
11
+ if member?(field_name)
12
+ self[field_name].merge(binstr[index, len + 4])
13
+ else
14
+ field_obj = ID_MAP[id].new(binstr[index, len + 4])
15
+ self[field_name] = field_obj
16
+ end
17
+ end
18
+
19
+ def extra_field_type_unknown(binstr, len, index)
20
+ create_unknown_item unless self['Unknown']
21
+ if !len || len + 4 > binstr[index..-1].bytesize
22
+ self['Unknown'] << binstr[index..-1]
23
+ return
24
+ end
25
+ self['Unknown'] << binstr[index, len + 4]
26
+ end
27
+
28
+ def create_unknown_item
29
+ s = +''
30
+ class << s
31
+ alias_method :to_c_dir_bin, :to_s
32
+ alias_method :to_local_bin, :to_s
33
+ end
34
+ self['Unknown'] = s
35
+ end
36
+
37
+ def merge(binstr)
38
+ return if binstr.empty?
39
+
40
+ i = 0
41
+ while i < binstr.bytesize
42
+ id = binstr[i, 2]
43
+ len = binstr[i + 2, 2].to_s.unpack1('v')
44
+ if id && ID_MAP.member?(id)
45
+ extra_field_type_exist(binstr, id, len, i)
46
+ elsif id
47
+ create_unknown_item unless self['Unknown']
48
+ break unless extra_field_type_unknown(binstr, len, i)
49
+ end
50
+ i += len + 4
51
+ end
52
+ end
53
+
54
+ def create(name)
55
+ unless (field_class = ID_MAP.values.find { |k| k.name == name })
56
+ raise Error, "Unknown extra field '#{name}'"
57
+ end
58
+
59
+ self[name] = field_class.new
60
+ end
61
+
62
+ # place Unknown last, so "extra" data that is missing the proper signature/size
63
+ # does not prevent known fields from being read back in
64
+ def ordered_values
65
+ result = []
66
+ each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
67
+ result
68
+ end
69
+
70
+ def to_local_bin
71
+ ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
72
+ end
73
+
74
+ alias to_s to_local_bin
75
+
76
+ def to_c_dir_bin
77
+ ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join
78
+ end
79
+
80
+ def c_dir_size
81
+ to_c_dir_bin.bytesize
82
+ end
83
+
84
+ def local_size
85
+ to_local_bin.bytesize
86
+ end
87
+
88
+ alias length local_size
89
+ alias size local_size
90
+ end
91
+ end
92
+
93
+ require 'zip/extra_field/generic'
94
+ require 'zip/extra_field/universal_time'
95
+ require 'zip/extra_field/old_unix'
96
+ require 'zip/extra_field/unix'
97
+ require 'zip/extra_field/zip64'
98
+ require 'zip/extra_field/zip64_placeholder'
99
+ require 'zip/extra_field/ntfs'
100
+
101
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
102
+ # rubyzip is free software; you can redistribute it and/or
103
+ # modify it under the terms of the ruby license.