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.

Files changed (107) hide show
  1. checksums.yaml +6 -14
  2. data/README.md +173 -42
  3. data/Rakefile +10 -5
  4. data/TODO +0 -1
  5. data/lib/zip/central_directory.rb +55 -24
  6. data/lib/zip/compressor.rb +0 -0
  7. data/lib/zip/constants.rb +4 -2
  8. data/lib/zip/crypto/encryption.rb +11 -0
  9. data/lib/zip/crypto/null_encryption.rb +45 -0
  10. data/lib/zip/crypto/traditional_encryption.rb +99 -0
  11. data/lib/zip/decompressor.rb +2 -2
  12. data/lib/zip/deflater.rb +11 -6
  13. data/lib/zip/dos_time.rb +4 -5
  14. data/lib/zip/entry.rb +159 -97
  15. data/lib/zip/entry_set.rb +18 -18
  16. data/lib/zip/errors.rb +15 -6
  17. data/lib/zip/extra_field/generic.rb +8 -8
  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 +14 -14
  21. data/lib/zip/extra_field/unix.rb +8 -9
  22. data/lib/zip/extra_field/zip64.rb +44 -6
  23. data/lib/zip/extra_field/zip64_placeholder.rb +16 -0
  24. data/lib/zip/extra_field.rb +20 -8
  25. data/lib/zip/file.rb +126 -114
  26. data/lib/zip/filesystem.rb +140 -139
  27. data/lib/zip/inflater.rb +10 -9
  28. data/lib/zip/input_stream.rb +105 -80
  29. data/lib/zip/ioextras/abstract_input_stream.rb +15 -12
  30. data/lib/zip/ioextras/abstract_output_stream.rb +0 -2
  31. data/lib/zip/ioextras.rb +1 -3
  32. data/lib/zip/null_compressor.rb +2 -2
  33. data/lib/zip/null_decompressor.rb +4 -4
  34. data/lib/zip/null_input_stream.rb +2 -1
  35. data/lib/zip/output_stream.rb +57 -43
  36. data/lib/zip/pass_thru_compressor.rb +4 -4
  37. data/lib/zip/pass_thru_decompressor.rb +4 -5
  38. data/lib/zip/streamable_directory.rb +2 -2
  39. data/lib/zip/streamable_stream.rb +22 -13
  40. data/lib/zip/version.rb +1 -1
  41. data/lib/zip.rb +11 -2
  42. data/samples/example.rb +30 -40
  43. data/samples/example_filesystem.rb +16 -18
  44. data/samples/example_recursive.rb +35 -27
  45. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +25 -27
  46. data/samples/qtzip.rb +19 -28
  47. data/samples/write_simple.rb +12 -13
  48. data/samples/zipfind.rb +29 -37
  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 +53 -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/mimetype +1 -0
  64. data/test/data/notzippedruby.rb +7 -0
  65. data/test/data/ntfs.zip +0 -0
  66. data/test/data/oddExtraField.zip +0 -0
  67. data/test/data/rubycode.zip +0 -0
  68. data/test/data/rubycode2.zip +0 -0
  69. data/test/data/test.xls +0 -0
  70. data/test/data/testDirectory.bin +0 -0
  71. data/test/data/zip64-sample.zip +0 -0
  72. data/test/data/zipWithDirs.zip +0 -0
  73. data/test/data/zipWithEncryption.zip +0 -0
  74. data/test/deflater_test.rb +65 -0
  75. data/test/encryption_test.rb +42 -0
  76. data/test/entry_set_test.rb +152 -0
  77. data/test/entry_test.rb +163 -0
  78. data/test/errors_test.rb +34 -0
  79. data/test/extra_field_test.rb +76 -0
  80. data/test/file_extract_directory_test.rb +54 -0
  81. data/test/file_extract_test.rb +83 -0
  82. data/test/file_permissions_test.rb +69 -0
  83. data/test/file_split_test.rb +57 -0
  84. data/test/file_test.rb +563 -0
  85. data/test/filesystem/dir_iterator_test.rb +58 -0
  86. data/test/filesystem/directory_test.rb +121 -0
  87. data/test/filesystem/file_mutating_test.rb +88 -0
  88. data/test/filesystem/file_nonmutating_test.rb +508 -0
  89. data/test/filesystem/file_stat_test.rb +64 -0
  90. data/test/gentestfiles.rb +122 -0
  91. data/test/inflater_test.rb +14 -0
  92. data/test/input_stream_test.rb +182 -0
  93. data/test/ioextras/abstract_input_stream_test.rb +102 -0
  94. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  95. data/test/ioextras/fake_io_test.rb +18 -0
  96. data/test/local_entry_test.rb +154 -0
  97. data/test/output_stream_test.rb +128 -0
  98. data/test/pass_thru_compressor_test.rb +30 -0
  99. data/test/pass_thru_decompressor_test.rb +14 -0
  100. data/test/samples/example_recursive_test.rb +37 -0
  101. data/test/settings_test.rb +95 -0
  102. data/test/test_helper.rb +221 -0
  103. data/test/unicode_file_names_and_comments_test.rb +50 -0
  104. data/test/zip64_full_test.rb +51 -0
  105. data/test/zip64_support_test.rb +14 -0
  106. metadata +198 -22
  107. 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
- @entry_order.delete(to_key(entry))
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 :push :<<
24
+ alias push <<
28
25
 
29
26
  def size
30
27
  @entry_set.size
31
28
  end
32
29
 
33
- alias :length :size
30
+ alias length size
34
31
 
35
32
  def delete(entry)
36
- if @entry_order.delete(to_key(entry)) && @entry_set.delete(to_key(entry))
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
- @entry_order.each do |key|
45
- block.call @entry_set[key]
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
- @entry_order.map { |key| @entry_set[key] }
43
+ sorted_entries.values
51
44
  end
52
45
 
53
46
  # deep clone
54
47
  def dup
55
- EntrySet.new(@entry_order.map { |key| @entry_set[key].dup })
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 && @entry_order == other.entry_order
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|::File::FNM_DOTMATCH)
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.sub(/\/$/, '')
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 ZipError < StandardError; end
3
- class ZipEntryExistsError < ZipError; end
4
- class ZipDestinationFileExistsError < ZipError; end
5
- class ZipCompressionMethodError < ZipError; end
6
- class ZipEntryNameError < ZipError; end
7
- class ZipInternalError < ZipError; end
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[self.const_get(:HEADER_ID)] = self
5
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
6
  end
7
7
  end
8
8
 
9
9
  def self.name
10
- self.to_s.split("::")[-1]
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 "Warning: weired extra feild header ID. skip parsing"
19
+ $stderr.puts 'Warning: weired extra feild header ID. skip parsing'
20
20
  return false
21
21
  end
22
- [binstr[2, 2].unpack("v")[0], binstr[4..-1]]
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] and return false
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("v") + s
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("v") + s
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 = "UT"
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 and merge(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 or return
21
- @flag, mtime, atime, ctime = content.unpack("CVVV")
22
- mtime and @mtime ||= ::Zip::DOSTime.at(mtime)
23
- atime and @atime ||= ::Zip::DOSTime.at(atime)
24
- ctime and @ctime ||= ::Zip::DOSTime.at(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("C")
35
- @flag & 1 != 0 and s << [@mtime.to_i].pack("V")
36
- @flag & 2 != 0 and s << [@atime.to_i].pack("V")
37
- @flag & 4 != 0 and s << [@ctime.to_i].pack("V")
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("C")
43
- @flag & 1 == 1 and s << [@mtime.to_i].pack("V")
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
@@ -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 = "Ux"
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 and merge(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 (!size || size == 0)
20
- uid, gid = content.unpack("vv")
21
- @uid ||= uid
22
- @gid ||= 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("vv")
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 = "\001\000"
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 and merge(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
- id, size, @original_size, @compressed_size, @relative_header_offset, @disk_start_number = binstr.to_s.unpack("vvQQQV")
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
- return '' unless @original_size && @compressed_sie && @relative_header_offset && @disk_start_number
23
- [1, 16, @original_size, @compressed_size, @relative_header_offset, @disk_start_number].pack("vvQQQV")
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
- pack_for_local
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
@@ -3,7 +3,7 @@ module Zip
3
3
  ID_MAP = {}
4
4
 
5
5
  def initialize(binstr = nil)
6
- binstr and merge(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 ZipError, "Unknown extra field '#{name}'"
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
- self.map { |_, v| v.to_local_bin }.join
69
+ ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join
62
70
  end
63
71
 
64
- alias :to_s :to_local_bin
72
+ alias to_s to_local_bin
65
73
 
66
74
  def to_c_dir_bin
67
- self.map { |_, v| v.to_c_dir_bin }.join
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 :length :local_size
79
- alias :size :local_size
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