rubyzip 1.3.0 → 3.0.0.alpha

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +368 -0
  3. data/README.md +123 -46
  4. data/Rakefile +13 -6
  5. data/lib/zip/central_directory.rb +166 -116
  6. data/lib/zip/compressor.rb +3 -1
  7. data/lib/zip/constants.rb +77 -21
  8. data/lib/zip/crypto/decrypted_io.rb +42 -0
  9. data/lib/zip/crypto/encryption.rb +4 -2
  10. data/lib/zip/crypto/null_encryption.rb +5 -3
  11. data/lib/zip/crypto/traditional_encryption.rb +14 -12
  12. data/lib/zip/decompressor.rb +21 -2
  13. data/lib/zip/deflater.rb +10 -8
  14. data/lib/zip/dirtyable.rb +32 -0
  15. data/lib/zip/dos_time.rb +53 -12
  16. data/lib/zip/entry.rb +306 -184
  17. data/lib/zip/entry_set.rb +11 -7
  18. data/lib/zip/errors.rb +115 -15
  19. data/lib/zip/extra_field/generic.rb +11 -17
  20. data/lib/zip/extra_field/ntfs.rb +8 -2
  21. data/lib/zip/extra_field/old_unix.rb +6 -2
  22. data/lib/zip/extra_field/universal_time.rb +45 -13
  23. data/lib/zip/extra_field/unix.rb +7 -3
  24. data/lib/zip/extra_field/unknown.rb +33 -0
  25. data/lib/zip/extra_field/zip64.rb +16 -7
  26. data/lib/zip/extra_field.rb +22 -26
  27. data/lib/zip/file.rb +196 -240
  28. data/lib/zip/file_split.rb +97 -0
  29. data/lib/zip/filesystem/dir.rb +86 -0
  30. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  31. data/lib/zip/filesystem/file.rb +262 -0
  32. data/lib/zip/filesystem/file_stat.rb +110 -0
  33. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  34. data/lib/zip/filesystem.rb +31 -584
  35. data/lib/zip/inflater.rb +27 -37
  36. data/lib/zip/input_stream.rb +67 -42
  37. data/lib/zip/ioextras/abstract_input_stream.rb +32 -16
  38. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  39. data/lib/zip/ioextras.rb +7 -7
  40. data/lib/zip/null_compressor.rb +3 -1
  41. data/lib/zip/null_decompressor.rb +4 -10
  42. data/lib/zip/null_input_stream.rb +3 -1
  43. data/lib/zip/output_stream.rb +58 -43
  44. data/lib/zip/pass_thru_compressor.rb +5 -3
  45. data/lib/zip/pass_thru_decompressor.rb +16 -23
  46. data/lib/zip/streamable_directory.rb +6 -4
  47. data/lib/zip/streamable_stream.rb +9 -10
  48. data/lib/zip/version.rb +3 -1
  49. data/lib/zip.rb +19 -4
  50. data/rubyzip.gemspec +38 -0
  51. data/samples/example.rb +9 -4
  52. data/samples/example_filesystem.rb +3 -2
  53. data/samples/example_recursive.rb +3 -1
  54. data/samples/gtk_ruby_zip.rb +22 -20
  55. data/samples/qtzip.rb +12 -11
  56. data/samples/write_simple.rb +3 -4
  57. data/samples/zipfind.rb +24 -22
  58. metadata +86 -179
  59. data/TODO +0 -15
  60. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
  61. data/test/basic_zip_file_test.rb +0 -60
  62. data/test/case_sensitivity_test.rb +0 -69
  63. data/test/central_directory_entry_test.rb +0 -69
  64. data/test/central_directory_test.rb +0 -100
  65. data/test/crypto/null_encryption_test.rb +0 -57
  66. data/test/crypto/traditional_encryption_test.rb +0 -80
  67. data/test/data/WarnInvalidDate.zip +0 -0
  68. data/test/data/file1.txt +0 -46
  69. data/test/data/file1.txt.deflatedData +0 -0
  70. data/test/data/file2.txt +0 -1504
  71. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  72. data/test/data/globTest/foo.txt +0 -0
  73. data/test/data/globTest/food.txt +0 -0
  74. data/test/data/globTest.zip +0 -0
  75. data/test/data/gpbit3stored.zip +0 -0
  76. data/test/data/mimetype +0 -1
  77. data/test/data/notzippedruby.rb +0 -7
  78. data/test/data/ntfs.zip +0 -0
  79. data/test/data/oddExtraField.zip +0 -0
  80. data/test/data/path_traversal/Makefile +0 -10
  81. data/test/data/path_traversal/jwilk/README.md +0 -5
  82. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  83. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  84. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  85. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  86. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  87. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  88. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  89. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  90. data/test/data/path_traversal/relative1.zip +0 -0
  91. data/test/data/path_traversal/tilde.zip +0 -0
  92. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  93. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  94. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  95. data/test/data/rubycode.zip +0 -0
  96. data/test/data/rubycode2.zip +0 -0
  97. data/test/data/test.xls +0 -0
  98. data/test/data/testDirectory.bin +0 -0
  99. data/test/data/zip64-sample.zip +0 -0
  100. data/test/data/zipWithDirs.zip +0 -0
  101. data/test/data/zipWithEncryption.zip +0 -0
  102. data/test/deflater_test.rb +0 -65
  103. data/test/encryption_test.rb +0 -42
  104. data/test/entry_set_test.rb +0 -163
  105. data/test/entry_test.rb +0 -154
  106. data/test/errors_test.rb +0 -35
  107. data/test/extra_field_test.rb +0 -76
  108. data/test/file_extract_directory_test.rb +0 -54
  109. data/test/file_extract_test.rb +0 -145
  110. data/test/file_permissions_test.rb +0 -65
  111. data/test/file_split_test.rb +0 -57
  112. data/test/file_test.rb +0 -666
  113. data/test/filesystem/dir_iterator_test.rb +0 -58
  114. data/test/filesystem/directory_test.rb +0 -139
  115. data/test/filesystem/file_mutating_test.rb +0 -87
  116. data/test/filesystem/file_nonmutating_test.rb +0 -508
  117. data/test/filesystem/file_stat_test.rb +0 -64
  118. data/test/gentestfiles.rb +0 -126
  119. data/test/inflater_test.rb +0 -14
  120. data/test/input_stream_test.rb +0 -182
  121. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  122. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  123. data/test/ioextras/fake_io_test.rb +0 -18
  124. data/test/local_entry_test.rb +0 -154
  125. data/test/output_stream_test.rb +0 -128
  126. data/test/pass_thru_compressor_test.rb +0 -30
  127. data/test/pass_thru_decompressor_test.rb +0 -14
  128. data/test/path_traversal_test.rb +0 -141
  129. data/test/samples/example_recursive_test.rb +0 -37
  130. data/test/settings_test.rb +0 -95
  131. data/test/test_helper.rb +0 -234
  132. data/test/unicode_file_names_and_comments_test.rb +0 -62
  133. data/test/zip64_full_test.rb +0 -51
  134. data/test/zip64_support_test.rb +0 -14
data/lib/zip/entry_set.rb CHANGED
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class EntrySet #:nodoc:all
4
+ class EntrySet # :nodoc:all
3
5
  include Enumerable
4
- attr_accessor :entry_set, :entry_order
6
+
7
+ attr_reader :entry_set
8
+ protected :entry_set
5
9
 
6
10
  def initialize(an_enumerable = [])
7
11
  super()
@@ -33,10 +37,8 @@ module Zip
33
37
  entry if @entry_set.delete(to_key(entry))
34
38
  end
35
39
 
36
- def each
37
- @entry_set = sorted_entries.dup.each do |_, value|
38
- yield(value)
39
- end
40
+ def each(&block)
41
+ entries.each(&block)
40
42
  end
41
43
 
42
44
  def entries
@@ -50,6 +52,7 @@ module Zip
50
52
 
51
53
  def ==(other)
52
54
  return false unless other.kind_of?(EntrySet)
55
+
53
56
  @entry_set.values == other.entry_set.values
54
57
  end
55
58
 
@@ -60,6 +63,7 @@ module Zip
60
63
  def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
61
64
  entries.map do |entry|
62
65
  next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
66
+
63
67
  yield(entry) if block_given?
64
68
  entry
65
69
  end.compact
@@ -68,7 +72,7 @@ module Zip
68
72
  protected
69
73
 
70
74
  def sorted_entries
71
- ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
75
+ ::Zip.sort_entries ? @entry_set.sort.to_h : @entry_set
72
76
  end
73
77
 
74
78
  private
data/lib/zip/errors.rb CHANGED
@@ -1,18 +1,118 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
4
+ # The superclass for all rubyzip error types. Simply rescue this one if
5
+ # you don't need to know what sort of error has been raised.
2
6
  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
7
+
8
+ # Error raised if an unsupported compression method is used.
9
+ class CompressionMethodError < Error
10
+ attr_reader :compression_method
11
+
12
+ def initialize(method)
13
+ super()
14
+ @compression_method = method
15
+ end
16
+
17
+ def message
18
+ "Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}."
19
+ end
20
+ end
21
+
22
+ # Error raised if there is a problem while decompressing an archive entry.
23
+ class DecompressionError < Error
24
+ attr_reader :zlib_error
25
+
26
+ def initialize(zlib_error)
27
+ super()
28
+ @zlib_error = zlib_error
29
+ end
30
+
31
+ def message
32
+ "Zlib error ('#{@zlib_error.message}') while inflating."
33
+ end
34
+ end
35
+
36
+ # Error raised when trying to extract an archive entry over an
37
+ # existing file.
38
+ class DestinationExistsError < Error
39
+ def initialize(destination)
40
+ super()
41
+ @destination = destination
42
+ end
43
+
44
+ def message
45
+ "Cannot create file or directory '#{@destination}'. " \
46
+ 'A file already exists with that name.'
47
+ end
48
+ end
49
+
50
+ # Error raised when trying to add an entry to an archive where the
51
+ # entry name already exists.
52
+ class EntryExistsError < Error
53
+ def initialize(source, name)
54
+ super()
55
+ @source = source
56
+ @name = name
57
+ end
58
+
59
+ def message
60
+ "'#{@source}' failed. Entry #{@name} already exists."
61
+ end
62
+ end
63
+
64
+ # Error raised when an entry name is invalid.
65
+ class EntryNameError < Error
66
+ def initialize(name = nil)
67
+ super()
68
+ @name = name
69
+ end
70
+
71
+ def message
72
+ if @name.nil?
73
+ 'Illegal entry name. Names must have fewer than 65,536 characters.'
74
+ else
75
+ "Illegal entry name '#{@name}'. Names must not start with '/'."
76
+ end
77
+ end
78
+ end
79
+
80
+ # Error raised if an entry is larger on extraction than it is advertised
81
+ # to be.
82
+ class EntrySizeError < Error
83
+ attr_reader :entry
84
+
85
+ def initialize(entry)
86
+ super()
87
+ @entry = entry
88
+ end
89
+
90
+ def message
91
+ "Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated."
92
+ end
93
+ end
94
+
95
+ # Error raised if a split archive is read. Rubyzip does not support reading
96
+ # split archives.
97
+ class SplitArchiveError < Error
98
+ def message
99
+ 'Rubyzip cannot extract from split archives at this time.'
100
+ end
101
+ end
102
+
103
+ # Error raised if there is not enough metadata for the entry to be streamed.
104
+ class StreamingError < Error
105
+ attr_reader :entry
106
+
107
+ def initialize(entry)
108
+ super()
109
+ @entry = entry
110
+ end
111
+
112
+ def message
113
+ "The local header of this entry ('#{@entry.name}') does not contain " \
114
+ 'the correct metadata for `Zip::InputStream` to be able to ' \
115
+ 'uncompress it. Please use `Zip::File` instead of `Zip::InputStream`.'
116
+ end
117
+ end
18
118
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class ExtraField::Generic
4
+ class ExtraField::Generic # :nodoc:
3
5
  def self.register_map
4
- if const_defined?(:HEADER_ID)
5
- ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
- end
6
+ return unless const_defined?(:HEADER_ID)
7
+
8
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
7
9
  end
8
10
 
9
11
  def self.name
@@ -12,22 +14,14 @@ module Zip
12
14
 
13
15
  # return field [size, content] or false
14
16
  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'
17
+ return false unless binstr
18
+
19
+ if binstr[0, 2] != self.class.const_get(:HEADER_ID)
20
+ warn 'WARNING: weird extra field header ID. Skip parsing it.'
20
21
  return false
21
22
  end
22
- [binstr[2, 2].unpack('v')[0], binstr[4..-1]]
23
- end
24
23
 
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
24
+ [binstr[2, 2].unpack1('v'), binstr[4..-1]]
31
25
  end
32
26
 
33
27
  def to_local_bin
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # PKWARE NTFS Extra Field (0x000a)
3
5
  # Only Tag 0x0001 is supported
4
- class ExtraField::NTFS < ExtraField::Generic
6
+ class ExtraField::NTFS < ExtraField::Generic # :nodoc:
5
7
  HEADER_ID = [0x000A].pack('v')
6
8
  register_map
7
9
 
@@ -19,6 +21,7 @@ module Zip
19
21
 
20
22
  def merge(binstr)
21
23
  return if binstr.empty?
24
+
22
25
  size, content = initial_parse(binstr)
23
26
  (size && content) || return
24
27
 
@@ -27,6 +30,7 @@ module Zip
27
30
 
28
31
  tag1 = tags[1]
29
32
  return unless tag1
33
+
30
34
  ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
31
35
  ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
32
36
  ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
@@ -49,7 +53,7 @@ module Zip
49
53
  # reserved 0 and tag 1
50
54
  s = [0, 1].pack('Vv')
51
55
 
52
- tag1 = ''.force_encoding(Encoding::BINARY)
56
+ tag1 = (+'').force_encoding(Encoding::BINARY)
53
57
  if @mtime
54
58
  tag1 << [to_ntfs_time(@mtime)].pack('Q<')
55
59
  if @atime
@@ -65,12 +69,14 @@ module Zip
65
69
 
66
70
  def parse_tags(content)
67
71
  return {} if content.nil?
72
+
68
73
  tags = {}
69
74
  i = 0
70
75
  while i < content.bytesize
71
76
  tag, size = content[i, 4].unpack('vv')
72
77
  i += 4
73
78
  break unless tag && size
79
+
74
80
  value = content[i, size]
75
81
  i += size
76
82
  tags[tag] = value
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Olf Info-ZIP Extra for UNIX uid/gid and file timestampes
3
- class ExtraField::OldUnix < ExtraField::Generic
5
+ class ExtraField::OldUnix < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'UX'
5
7
  register_map
6
8
 
@@ -16,14 +18,16 @@ module Zip
16
18
 
17
19
  def merge(binstr)
18
20
  return if binstr.empty?
21
+
19
22
  size, content = initial_parse(binstr)
20
23
  # size: 0 for central directory. 4 for local header
21
24
  return if !size || size == 0
25
+
22
26
  atime, mtime, uid, gid = content.unpack('VVvv')
23
27
  @uid ||= uid
24
28
  @gid ||= gid
25
29
  @atime ||= atime
26
- @mtime ||= mtime
30
+ @mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
27
31
  end
28
32
 
29
33
  def ==(other)
@@ -1,27 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Info-ZIP Additional timestamp field
3
- class ExtraField::UniversalTime < ExtraField::Generic
5
+ class ExtraField::UniversalTime < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'UT'
5
7
  register_map
6
8
 
9
+ ATIME_MASK = 0b010
10
+ CTIME_MASK = 0b100
11
+ MTIME_MASK = 0b001
12
+
7
13
  def initialize(binstr = nil)
8
14
  @ctime = nil
9
15
  @mtime = nil
10
16
  @atime = nil
11
- @flag = nil
12
- binstr && merge(binstr)
17
+ @flag = 0
18
+
19
+ merge(binstr) unless binstr.nil?
20
+ end
21
+
22
+ attr_reader :atime, :ctime, :mtime, :flag
23
+
24
+ def atime=(time)
25
+ @flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
26
+ @atime = time
13
27
  end
14
28
 
15
- attr_accessor :atime, :ctime, :mtime, :flag
29
+ def ctime=(time)
30
+ @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
31
+ @ctime = time
32
+ end
33
+
34
+ def mtime=(time)
35
+ @flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
36
+ @mtime = time
37
+ end
16
38
 
17
39
  def merge(binstr)
18
40
  return if binstr.empty?
41
+
19
42
  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)
43
+ return if !size || size <= 0
44
+
45
+ @flag, *times = content.unpack('Cl<l<l<')
46
+
47
+ # Parse the timestamps, in order, based on which flags are set.
48
+ return if times[0].nil?
49
+
50
+ @mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
51
+ return if times[0].nil?
52
+
53
+ @atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
54
+ return if times[0].nil?
55
+
56
+ @ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
25
57
  end
26
58
 
27
59
  def ==(other)
@@ -32,15 +64,15 @@ module Zip
32
64
 
33
65
  def pack_for_local
34
66
  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')
67
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
68
+ s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
69
+ s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
38
70
  s
39
71
  end
40
72
 
41
73
  def pack_for_c_dir
42
74
  s = [@flag].pack('C')
43
- @flag & 1 == 1 && s << [@mtime.to_i].pack('V')
75
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
44
76
  s
45
77
  end
46
78
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # Info-ZIP Extra for UNIX uid/gid
3
- class ExtraField::IUnix < ExtraField::Generic
5
+ class ExtraField::IUnix < ExtraField::Generic # :nodoc:
4
6
  HEADER_ID = 'Ux'
5
7
  register_map
6
8
 
@@ -14,12 +16,14 @@ module Zip
14
16
 
15
17
  def merge(binstr)
16
18
  return if binstr.empty?
19
+
17
20
  size, content = initial_parse(binstr)
18
21
  # size: 0 for central directory. 4 for local header
19
22
  return if !size || size == 0
23
+
20
24
  uid, gid = content.unpack('vv')
21
- @uid ||= uid
22
- @gid ||= gid
25
+ @uid = uid
26
+ @gid = gid
23
27
  end
24
28
 
25
29
  def ==(other)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ # A class to hold unknown extra fields so that they are preserved.
5
+ class ExtraField::Unknown # :nodoc:
6
+ def initialize
7
+ @local_bin = +''
8
+ @cdir_bin = +''
9
+ end
10
+
11
+ def merge(binstr, local: false)
12
+ return if binstr.empty?
13
+
14
+ if local
15
+ @local_bin << binstr
16
+ else
17
+ @cdir_bin << binstr
18
+ end
19
+ end
20
+
21
+ def to_local_bin
22
+ @local_bin
23
+ end
24
+
25
+ def to_c_dir_bin
26
+ @cdir_bin
27
+ end
28
+
29
+ def ==(other)
30
+ @local_bin == other.to_local_bin && @cdir_bin == other.to_c_dir_bin
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  # 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
+ class ExtraField::Zip64 < ExtraField::Generic # :nodoc:
6
+ attr_accessor :compressed_size, :disk_start_number,
7
+ :original_size, :relative_header_offset
8
+
5
9
  HEADER_ID = ['0100'].pack('H*')
6
10
  register_map
7
11
 
@@ -9,7 +13,7 @@ module Zip
9
13
  # unparsed binary; we don't actually know what this contains
10
14
  # without looking for FFs in the associated file header
11
15
  # call parse after initializing with a binary string
12
- @content = nil
16
+ @content = nil
13
17
  @original_size = nil
14
18
  @compressed_size = nil
15
19
  @relative_header_offset = nil
@@ -26,6 +30,7 @@ module Zip
26
30
 
27
31
  def merge(binstr)
28
32
  return if binstr.empty?
33
+
29
34
  _, @content = initial_parse(binstr)
30
35
  end
31
36
 
@@ -35,7 +40,9 @@ module Zip
35
40
  def parse(original_size, compressed_size, relative_header_offset = nil, disk_start_number = nil)
36
41
  @original_size = extract(8, 'Q<') if original_size == 0xFFFFFFFF
37
42
  @compressed_size = extract(8, 'Q<') if compressed_size == 0xFFFFFFFF
38
- @relative_header_offset = extract(8, 'Q<') if relative_header_offset && relative_header_offset == 0xFFFFFFFF
43
+ if relative_header_offset && relative_header_offset == 0xFFFFFFFF
44
+ @relative_header_offset = extract(8, 'Q<')
45
+ end
39
46
  @disk_start_number = extract(4, 'V') if disk_start_number && disk_start_number == 0xFFFF
40
47
  @content = nil
41
48
  [@original_size || original_size,
@@ -45,19 +52,21 @@ module Zip
45
52
  end
46
53
 
47
54
  def extract(size, format)
48
- @content.slice!(0, size).unpack(format)[0]
55
+ @content.slice!(0, size).unpack1(format)
49
56
  end
50
57
  private :extract
51
58
 
52
59
  def pack_for_local
53
- # local header entries must contain original size and compressed size; other fields do not apply
60
+ # Local header entries must contain original size and compressed size;
61
+ # other fields do not apply.
54
62
  return '' unless @original_size && @compressed_size
63
+
55
64
  [@original_size, @compressed_size].pack('Q<Q<')
56
65
  end
57
66
 
58
67
  def pack_for_c_dir
59
68
  # central directory entries contain only fields that didn't fit in the main entry part
60
- packed = ''.force_encoding('BINARY')
69
+ packed = (+'').force_encoding('BINARY')
61
70
  packed << [@original_size].pack('Q<') if @original_size
62
71
  packed << [@compressed_size].pack('Q<') if @compressed_size
63
72
  packed << [@relative_header_offset].pack('Q<') if @relative_header_offset
@@ -1,50 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class ExtraField < Hash
4
+ class ExtraField < Hash # :nodoc:all
3
5
  ID_MAP = {}
4
6
 
5
- def initialize(binstr = nil)
6
- merge(binstr) if binstr
7
+ def initialize(binstr = nil, local: false)
8
+ merge(binstr, local: local) if binstr
7
9
  end
8
10
 
9
- def extra_field_type_exist(binstr, id, len, i)
11
+ def extra_field_type_exist(binstr, id, len, index)
10
12
  field_name = ID_MAP[id].name
11
13
  if member?(field_name)
12
- self[field_name].merge(binstr[i, len + 4])
14
+ self[field_name].merge(binstr[index, len + 4])
13
15
  else
14
- field_obj = ID_MAP[id].new(binstr[i, len + 4])
16
+ field_obj = ID_MAP[id].new(binstr[index, len + 4])
15
17
  self[field_name] = field_obj
16
18
  end
17
19
  end
18
20
 
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]
21
+ def extra_field_type_unknown(binstr, len, index, local)
22
+ self['Unknown'] ||= Unknown.new
23
+
24
+ if !len || len + 4 > binstr[index..-1].bytesize
25
+ self['Unknown'].merge(binstr[index..-1], local: local)
23
26
  return
24
27
  end
25
- self['Unknown'] << binstr[i, len + 4]
26
- end
27
28
 
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
29
+ self['Unknown'].merge(binstr[index, len + 4], local: local)
35
30
  end
36
31
 
37
- def merge(binstr)
32
+ def merge(binstr, local: false)
38
33
  return if binstr.empty?
34
+
39
35
  i = 0
40
36
  while i < binstr.bytesize
41
37
  id = binstr[i, 2]
42
- len = binstr[i + 2, 2].to_s.unpack('v').first
38
+ len = binstr[i + 2, 2].to_s.unpack1('v')
43
39
  if id && ID_MAP.member?(id)
44
40
  extra_field_type_exist(binstr, id, len, i)
45
41
  elsif id
46
- create_unknown_item unless self['Unknown']
47
- break unless extra_field_type_unknown(binstr, len, i)
42
+ break unless extra_field_type_unknown(binstr, len, i, local)
48
43
  end
49
44
  i += len + 4
50
45
  end
@@ -54,11 +49,12 @@ module Zip
54
49
  unless (field_class = ID_MAP.values.find { |k| k.name == name })
55
50
  raise Error, "Unknown extra field '#{name}'"
56
51
  end
52
+
57
53
  self[name] = field_class.new
58
54
  end
59
55
 
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
56
+ # Place Unknown last, so "extra" data that is missing the proper
57
+ # signature/size does not prevent known fields from being read back in.
62
58
  def ordered_values
63
59
  result = []
64
60
  each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) }
@@ -88,12 +84,12 @@ module Zip
88
84
  end
89
85
  end
90
86
 
87
+ require 'zip/extra_field/unknown'
91
88
  require 'zip/extra_field/generic'
92
89
  require 'zip/extra_field/universal_time'
93
90
  require 'zip/extra_field/old_unix'
94
91
  require 'zip/extra_field/unix'
95
92
  require 'zip/extra_field/zip64'
96
- require 'zip/extra_field/zip64_placeholder'
97
93
  require 'zip/extra_field/ntfs'
98
94
 
99
95
  # Copyright (C) 2002, 2003 Thomas Sondergaard