rubyzip 0.9.9 → 1.3.0

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +284 -41
  3. data/Rakefile +11 -6
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +208 -0
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +59 -7
  8. data/lib/zip/crypto/encryption.rb +11 -0
  9. data/lib/zip/crypto/null_encryption.rb +43 -0
  10. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  11. data/lib/zip/decompressor.rb +4 -4
  12. data/lib/zip/deflater.rb +17 -13
  13. data/lib/zip/dos_time.rb +13 -14
  14. data/lib/zip/entry.rb +700 -0
  15. data/lib/zip/entry_set.rb +86 -0
  16. data/lib/zip/errors.rb +18 -0
  17. data/lib/zip/extra_field/generic.rb +43 -0
  18. data/lib/zip/extra_field/ntfs.rb +90 -0
  19. data/lib/zip/extra_field/old_unix.rb +44 -0
  20. data/lib/zip/extra_field/universal_time.rb +47 -0
  21. data/lib/zip/extra_field/unix.rb +37 -0
  22. data/lib/zip/extra_field/zip64.rb +68 -0
  23. data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
  24. data/lib/zip/extra_field.rb +101 -0
  25. data/lib/zip/file.rb +443 -0
  26. data/lib/zip/{zipfilesystem.rb → filesystem.rb} +162 -157
  27. data/lib/zip/inflater.rb +29 -28
  28. data/lib/zip/input_stream.rb +173 -0
  29. data/lib/zip/ioextras/abstract_input_stream.rb +111 -0
  30. data/lib/zip/ioextras/abstract_output_stream.rb +43 -0
  31. data/lib/zip/ioextras.rb +21 -149
  32. data/lib/zip/null_compressor.rb +2 -2
  33. data/lib/zip/null_decompressor.rb +8 -6
  34. data/lib/zip/null_input_stream.rb +3 -2
  35. data/lib/zip/output_stream.rb +189 -0
  36. data/lib/zip/pass_thru_compressor.rb +6 -6
  37. data/lib/zip/pass_thru_decompressor.rb +19 -19
  38. data/lib/zip/{zip_streamable_directory.rb → streamable_directory.rb} +3 -3
  39. data/lib/zip/streamable_stream.rb +56 -0
  40. data/lib/zip/version.rb +3 -0
  41. data/lib/zip.rb +71 -0
  42. data/samples/example.rb +44 -32
  43. data/samples/example_filesystem.rb +16 -18
  44. data/samples/example_recursive.rb +33 -28
  45. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +26 -28
  46. data/samples/qtzip.rb +22 -31
  47. data/samples/write_simple.rb +12 -13
  48. data/samples/zipfind.rb +31 -39
  49. data/test/basic_zip_file_test.rb +60 -0
  50. data/test/case_sensitivity_test.rb +69 -0
  51. data/test/central_directory_entry_test.rb +69 -0
  52. data/test/central_directory_test.rb +100 -0
  53. data/test/crypto/null_encryption_test.rb +57 -0
  54. data/test/crypto/traditional_encryption_test.rb +80 -0
  55. data/test/data/WarnInvalidDate.zip +0 -0
  56. data/test/data/file1.txt +46 -0
  57. data/test/data/file1.txt.deflatedData +0 -0
  58. data/test/data/file2.txt +1504 -0
  59. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  60. data/test/data/globTest/foo.txt +0 -0
  61. data/test/data/globTest/food.txt +0 -0
  62. data/test/data/globTest.zip +0 -0
  63. data/test/data/gpbit3stored.zip +0 -0
  64. data/test/data/mimetype +1 -0
  65. data/test/data/notzippedruby.rb +7 -0
  66. data/test/data/ntfs.zip +0 -0
  67. data/test/data/oddExtraField.zip +0 -0
  68. data/test/data/path_traversal/Makefile +10 -0
  69. data/test/data/path_traversal/jwilk/README.md +5 -0
  70. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  71. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  72. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  73. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  74. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  75. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  76. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  77. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  78. data/test/data/path_traversal/relative1.zip +0 -0
  79. data/test/data/path_traversal/tilde.zip +0 -0
  80. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  81. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  82. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  83. data/test/data/rubycode.zip +0 -0
  84. data/test/data/rubycode2.zip +0 -0
  85. data/test/data/test.xls +0 -0
  86. data/test/data/testDirectory.bin +0 -0
  87. data/test/data/zip64-sample.zip +0 -0
  88. data/test/data/zipWithDirs.zip +0 -0
  89. data/test/data/zipWithEncryption.zip +0 -0
  90. data/test/deflater_test.rb +65 -0
  91. data/test/encryption_test.rb +42 -0
  92. data/test/entry_set_test.rb +163 -0
  93. data/test/entry_test.rb +154 -0
  94. data/test/errors_test.rb +35 -0
  95. data/test/extra_field_test.rb +76 -0
  96. data/test/file_extract_directory_test.rb +54 -0
  97. data/test/file_extract_test.rb +145 -0
  98. data/test/file_permissions_test.rb +65 -0
  99. data/test/file_split_test.rb +57 -0
  100. data/test/file_test.rb +666 -0
  101. data/test/filesystem/dir_iterator_test.rb +58 -0
  102. data/test/filesystem/directory_test.rb +139 -0
  103. data/test/filesystem/file_mutating_test.rb +87 -0
  104. data/test/filesystem/file_nonmutating_test.rb +508 -0
  105. data/test/filesystem/file_stat_test.rb +64 -0
  106. data/test/gentestfiles.rb +126 -0
  107. data/test/inflater_test.rb +14 -0
  108. data/test/input_stream_test.rb +182 -0
  109. data/test/ioextras/abstract_input_stream_test.rb +102 -0
  110. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  111. data/test/ioextras/fake_io_test.rb +18 -0
  112. data/test/local_entry_test.rb +154 -0
  113. data/test/output_stream_test.rb +128 -0
  114. data/test/pass_thru_compressor_test.rb +30 -0
  115. data/test/pass_thru_decompressor_test.rb +14 -0
  116. data/test/path_traversal_test.rb +141 -0
  117. data/test/samples/example_recursive_test.rb +37 -0
  118. data/test/settings_test.rb +95 -0
  119. data/test/test_helper.rb +234 -0
  120. data/test/unicode_file_names_and_comments_test.rb +62 -0
  121. data/test/zip64_full_test.rb +51 -0
  122. data/test/zip64_support_test.rb +14 -0
  123. metadata +274 -41
  124. data/NEWS +0 -172
  125. data/lib/zip/settings.rb +0 -10
  126. data/lib/zip/tempfile_bugfixed.rb +0 -195
  127. data/lib/zip/zip.rb +0 -56
  128. data/lib/zip/zip_central_directory.rb +0 -135
  129. data/lib/zip/zip_entry.rb +0 -638
  130. data/lib/zip/zip_entry_set.rb +0 -77
  131. data/lib/zip/zip_extra_field.rb +0 -213
  132. data/lib/zip/zip_file.rb +0 -340
  133. data/lib/zip/zip_input_stream.rb +0 -144
  134. data/lib/zip/zip_output_stream.rb +0 -173
  135. data/lib/zip/zip_streamable_stream.rb +0 -47
@@ -0,0 +1,86 @@
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
+ @entry_set.values == other.entry_set.values
54
+ end
55
+
56
+ def parent(entry)
57
+ @entry_set[to_key(entry.parent_as_string)]
58
+ end
59
+
60
+ def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
61
+ entries.map do |entry|
62
+ next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
63
+ yield(entry) if block_given?
64
+ entry
65
+ end.compact
66
+ end
67
+
68
+ protected
69
+
70
+ def sorted_entries
71
+ ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
72
+ end
73
+
74
+ private
75
+
76
+ def to_key(entry)
77
+ k = entry.to_s.chomp('/')
78
+ k.downcase! if ::Zip.case_insensitive_match
79
+ k
80
+ end
81
+ end
82
+ end
83
+
84
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
85
+ # rubyzip is free software; you can redistribute it and/or
86
+ # modify it under the terms of the ruby license.
data/lib/zip/errors.rb ADDED
@@ -0,0 +1,18 @@
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
+
11
+ # Backwards compatibility with v1 (delete in v2)
12
+ ZipError = Error
13
+ ZipEntryExistsError = EntryExistsError
14
+ ZipDestinationFileExistsError = DestinationFileExistsError
15
+ ZipCompressionMethodError = CompressionMethodError
16
+ ZipEntryNameError = EntryNameError
17
+ ZipInternalError = InternalError
18
+ end
@@ -0,0 +1,43 @@
1
+ module Zip
2
+ class ExtraField::Generic
3
+ def self.register_map
4
+ if const_defined?(:HEADER_ID)
5
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
+ end
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
+ if !binstr
16
+ # If nil, start with empty.
17
+ return false
18
+ elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
19
+ $stderr.puts 'Warning: weired extra feild header ID. skip parsing'
20
+ return false
21
+ end
22
+ [binstr[2, 2].unpack('v')[0], binstr[4..-1]]
23
+ end
24
+
25
+ def ==(other)
26
+ return false if self.class != other.class
27
+ each do |k, v|
28
+ return false if v != other[k]
29
+ end
30
+ true
31
+ end
32
+
33
+ def to_local_bin
34
+ s = pack_for_local
35
+ self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
36
+ end
37
+
38
+ def to_c_dir_bin
39
+ s = pack_for_c_dir
40
+ self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s
41
+ end
42
+ 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
@@ -0,0 +1,47 @@
1
+ module Zip
2
+ # Info-ZIP Additional timestamp field
3
+ class ExtraField::UniversalTime < ExtraField::Generic
4
+ HEADER_ID = 'UT'
5
+ register_map
6
+
7
+ def initialize(binstr = nil)
8
+ @ctime = nil
9
+ @mtime = nil
10
+ @atime = nil
11
+ @flag = nil
12
+ binstr && merge(binstr)
13
+ end
14
+
15
+ attr_accessor :atime, :ctime, :mtime, :flag
16
+
17
+ def merge(binstr)
18
+ return if binstr.empty?
19
+ size, content = initial_parse(binstr)
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
+ end
26
+
27
+ def ==(other)
28
+ @mtime == other.mtime &&
29
+ @atime == other.atime &&
30
+ @ctime == other.ctime
31
+ end
32
+
33
+ def pack_for_local
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
+ s
39
+ end
40
+
41
+ def pack_for_c_dir
42
+ s = [@flag].pack('C')
43
+ @flag & 1 == 1 && s << [@mtime.to_i].pack('V')
44
+ s
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,37 @@
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
+ size, content = initial_parse(binstr)
18
+ # size: 0 for central directory. 4 for local header
19
+ return if !size || size == 0
20
+ uid, gid = content.unpack('vv')
21
+ @uid ||= uid
22
+ @gid ||= gid
23
+ end
24
+
25
+ def ==(other)
26
+ @uid == other.uid && @gid == other.gid
27
+ end
28
+
29
+ def pack_for_local
30
+ [@uid, @gid].pack('vv')
31
+ end
32
+
33
+ def pack_for_c_dir
34
+ ''
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,68 @@
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
+ _, @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]
49
+ end
50
+ private :extract
51
+
52
+ def pack_for_local
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<')
56
+ end
57
+
58
+ def pack_for_c_dir
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
66
+ end
67
+ end
68
+ 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,101 @@
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, i)
10
+ field_name = ID_MAP[id].name
11
+ if member?(field_name)
12
+ self[field_name].merge(binstr[i, len + 4])
13
+ else
14
+ field_obj = ID_MAP[id].new(binstr[i, len + 4])
15
+ self[field_name] = field_obj
16
+ end
17
+ end
18
+
19
+ def extra_field_type_unknown(binstr, len, i)
20
+ create_unknown_item unless self['Unknown']
21
+ if !len || len + 4 > binstr[i..-1].bytesize
22
+ self['Unknown'] << binstr[i..-1]
23
+ return
24
+ end
25
+ self['Unknown'] << binstr[i, len + 4]
26
+ end
27
+
28
+ def create_unknown_item
29
+ s = ''.dup
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
+ i = 0
40
+ while i < binstr.bytesize
41
+ id = binstr[i, 2]
42
+ len = binstr[i + 2, 2].to_s.unpack('v').first
43
+ if id && ID_MAP.member?(id)
44
+ extra_field_type_exist(binstr, id, len, i)
45
+ elsif id
46
+ create_unknown_item unless self['Unknown']
47
+ break unless extra_field_type_unknown(binstr, len, i)
48
+ end
49
+ i += len + 4
50
+ end
51
+ end
52
+
53
+ def create(name)
54
+ unless (field_class = ID_MAP.values.find { |k| k.name == name })
55
+ raise Error, "Unknown extra field '#{name}'"
56
+ end
57
+ self[name] = field_class.new
58
+ end
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
+
68
+ def to_local_bin
69
+ ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
70
+ end
71
+
72
+ alias to_s to_local_bin
73
+
74
+ def to_c_dir_bin
75
+ ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join
76
+ end
77
+
78
+ def c_dir_size
79
+ to_c_dir_bin.bytesize
80
+ end
81
+
82
+ def local_size
83
+ to_local_bin.bytesize
84
+ end
85
+
86
+ alias length local_size
87
+ alias size local_size
88
+ end
89
+ end
90
+
91
+ require 'zip/extra_field/generic'
92
+ require 'zip/extra_field/universal_time'
93
+ require 'zip/extra_field/old_unix'
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'
98
+
99
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
100
+ # rubyzip is free software; you can redistribute it and/or
101
+ # modify it under the terms of the ruby license.