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
@@ -1,45 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'dirtyable'
6
+
1
7
  module Zip
2
- class CentralDirectory
3
- include Enumerable
8
+ class CentralDirectory # :nodoc:
9
+ extend Forwardable
10
+ include Dirtyable
11
+
12
+ END_OF_CD_SIG = 0x06054b50
13
+ ZIP64_END_OF_CD_SIG = 0x06064b50
14
+ ZIP64_EOCD_LOCATOR_SIG = 0x07064b50
4
15
 
5
- END_OF_CDS = 0x06054b50
6
- ZIP64_END_OF_CDS = 0x06064b50
7
- ZIP64_EOCD_LOCATOR = 0x07064b50
8
- MAX_END_OF_CDS_SIZE = 65_536 + 18
9
16
  STATIC_EOCD_SIZE = 22
17
+ ZIP64_STATIC_EOCD_SIZE = 56
18
+ ZIP64_EOCD_LOC_SIZE = 20
19
+ MAX_FILE_COMMENT_SIZE = (1 << 16) - 1
20
+ MAX_END_OF_CD_SIZE =
21
+ MAX_FILE_COMMENT_SIZE + STATIC_EOCD_SIZE + ZIP64_EOCD_LOC_SIZE
10
22
 
11
- attr_reader :comment
23
+ attr_accessor :comment
12
24
 
13
- # Returns an Enumerable containing the entries.
14
- def entries
15
- @entry_set.entries
16
- end
25
+ def_delegators :@entry_set,
26
+ :<<, :delete, :each, :entries, :find_entry, :glob,
27
+ :include?, :size
28
+
29
+ mark_dirty :<<, :comment=, :delete
17
30
 
18
31
  def initialize(entries = EntrySet.new, comment = '') #:nodoc:
19
- super()
32
+ super(dirty_on_create: false)
20
33
  @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries)
21
34
  @comment = comment
22
35
  end
23
36
 
37
+ def read_from_stream(io)
38
+ read_eocds(io)
39
+ read_central_directory_entries(io)
40
+ end
41
+
24
42
  def write_to_stream(io) #:nodoc:
25
43
  cdir_offset = io.tell
26
44
  @entry_set.each { |entry| entry.write_c_dir_entry(io) }
27
45
  eocd_offset = io.tell
28
46
  cdir_size = eocd_offset - cdir_offset
29
- if ::Zip.write_zip64_support
30
- need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF
31
- need_zip64_eocd ||= @entry_set.any? { |entry| entry.extra['Zip64'] }
32
- if need_zip64_eocd
33
- write_64_e_o_c_d(io, cdir_offset, cdir_size)
34
- write_64_eocd_locator(io, eocd_offset)
35
- end
47
+ if Zip.write_zip64_support &&
48
+ (cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF)
49
+ write_64_e_o_c_d(io, cdir_offset, cdir_size)
50
+ write_64_eocd_locator(io, eocd_offset)
36
51
  end
37
52
  write_e_o_c_d(io, cdir_offset, cdir_size)
38
53
  end
39
54
 
55
+ # Reads the End of Central Directory Record (and the Zip64 equivalent if
56
+ # needs be) and returns the number of entries in the archive. This is a
57
+ # convenience method that avoids reading in all of the entry data to get a
58
+ # very quick entry count.
59
+ def count_entries(io)
60
+ read_eocds(io)
61
+ @size
62
+ end
63
+
64
+ def ==(other) #:nodoc:
65
+ return false unless other.kind_of?(CentralDirectory)
66
+
67
+ @entry_set.entries.sort == other.entries.sort && comment == other.comment
68
+ end
69
+
70
+ private
71
+
40
72
  def write_e_o_c_d(io, offset, cdir_size) #:nodoc:
41
73
  tmp = [
42
- END_OF_CDS,
74
+ END_OF_CD_SIG,
43
75
  0, # @numberOfThisDisk
44
76
  0, # @numberOfDiskWithStartOfCDir
45
77
  @entry_set ? [@entry_set.size, 0xFFFF].min : 0,
@@ -52,11 +84,9 @@ module Zip
52
84
  io << @comment
53
85
  end
54
86
 
55
- private :write_e_o_c_d
56
-
57
87
  def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc:
58
88
  tmp = [
59
- ZIP64_END_OF_CDS,
89
+ ZIP64_END_OF_CD_SIG,
60
90
  44, # size of zip64 end of central directory record (excludes signature and field itself)
61
91
  VERSION_MADE_BY,
62
92
  VERSION_NEEDED_TO_EXTRACT_ZIP64,
@@ -65,16 +95,14 @@ module Zip
65
95
  @entry_set ? @entry_set.size : 0, # number of entries on this disk
66
96
  @entry_set ? @entry_set.size : 0, # number of entries total
67
97
  cdir_size, # size of central directory
68
- offset, # offset of start of central directory in its disk
98
+ offset # offset of start of central directory in its disk
69
99
  ]
70
100
  io << tmp.pack('VQ<vvVVQ<Q<Q<Q<')
71
101
  end
72
102
 
73
- private :write_64_e_o_c_d
74
-
75
103
  def write_64_eocd_locator(io, zip64_eocd_offset)
76
104
  tmp = [
77
- ZIP64_EOCD_LOCATOR,
105
+ ZIP64_EOCD_LOCATOR_SIG,
78
106
  0, # number of disk containing the start of zip64 eocd record
79
107
  zip64_eocd_offset, # offset of the start of zip64 eocd record in its disk
80
108
  1 # total number of disks
@@ -82,123 +110,145 @@ module Zip
82
110
  io << tmp.pack('VVQ<V')
83
111
  end
84
112
 
85
- private :write_64_eocd_locator
86
-
87
- def read_64_e_o_c_d(buf) #:nodoc:
88
- buf = get_64_e_o_c_d(buf)
89
- @size_of_zip64_e_o_c_d = Entry.read_zip_64_long(buf)
90
- @version_made_by = Entry.read_zip_short(buf)
91
- @version_needed_for_extract = Entry.read_zip_short(buf)
92
- @number_of_this_disk = Entry.read_zip_long(buf)
93
- @number_of_disk_with_start_of_cdir = Entry.read_zip_long(buf)
94
- @total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_64_long(buf)
95
- @size = Entry.read_zip_64_long(buf)
96
- @size_in_bytes = Entry.read_zip_64_long(buf)
97
- @cdir_offset = Entry.read_zip_64_long(buf)
98
- @zip_64_extensible = buf.slice!(0, buf.bytesize)
99
- raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
113
+ def unpack_64_e_o_c_d(buffer) #:nodoc:
114
+ _, # ZIP64_END_OF_CD_SIG. We know we have this at this point.
115
+ @size_of_zip64_e_o_c_d,
116
+ @version_made_by,
117
+ @version_needed_for_extract,
118
+ @number_of_this_disk,
119
+ @number_of_disk_with_start_of_cdir,
120
+ @total_number_of_entries_in_cdir_on_this_disk,
121
+ @size,
122
+ @size_in_bytes,
123
+ @cdir_offset = buffer.unpack('VQ<vvVVQ<Q<Q<Q<')
124
+
125
+ zip64_extensible_data_size =
126
+ @size_of_zip64_e_o_c_d - ZIP64_STATIC_EOCD_SIZE + 12
127
+ @zip64_extensible_data = if zip64_extensible_data_size.zero?
128
+ ''
129
+ else
130
+ buffer.slice(
131
+ ZIP64_STATIC_EOCD_SIZE,
132
+ zip64_extensible_data_size
133
+ )
134
+ end
135
+ end
136
+
137
+ def unpack_64_eocd_locator(buffer) #:nodoc:
138
+ _, # ZIP64_EOCD_LOCATOR_SIG. We know we have this at this point.
139
+ _, zip64_eocd_offset, = buffer.unpack('VVQ<V')
140
+
141
+ zip64_eocd_offset
100
142
  end
101
143
 
102
- def read_e_o_c_d(buf) #:nodoc:
103
- buf = get_e_o_c_d(buf)
104
- @number_of_this_disk = Entry.read_zip_short(buf)
105
- @number_of_disk_with_start_of_cdir = Entry.read_zip_short(buf)
106
- @total_number_of_entries_in_cdir_on_this_disk = Entry.read_zip_short(buf)
107
- @size = Entry.read_zip_short(buf)
108
- @size_in_bytes = Entry.read_zip_long(buf)
109
- @cdir_offset = Entry.read_zip_long(buf)
110
- comment_length = Entry.read_zip_short(buf)
111
- @comment = if comment_length.to_i <= 0
112
- buf.slice!(0, buf.size)
113
- else
114
- buf.read(comment_length)
115
- end
116
- raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
144
+ def unpack_e_o_c_d(buffer) #:nodoc:
145
+ _, # END_OF_CD_SIG. We know we have this at this point.
146
+ num_disk,
147
+ num_disk_cdir,
148
+ num_cdir_disk,
149
+ num_entries,
150
+ size_in_bytes,
151
+ cdir_offset,
152
+ comment_length = buffer.unpack('VvvvvVVv')
153
+
154
+ @number_of_this_disk = num_disk unless num_disk == 0xFFFF
155
+ @number_of_disk_with_start_of_cdir = num_disk_cdir unless num_disk_cdir == 0xFFFF
156
+ @total_number_of_entries_in_cdir_on_this_disk = num_cdir_disk unless num_cdir_disk == 0xFFFF
157
+ @size = num_entries unless num_entries == 0xFFFF
158
+ @size_in_bytes = size_in_bytes unless size_in_bytes == 0xFFFFFFFF
159
+ @cdir_offset = cdir_offset unless cdir_offset == 0xFFFFFFFF
160
+
161
+ @comment = if comment_length.positive?
162
+ buffer.slice(STATIC_EOCD_SIZE, comment_length)
163
+ else
164
+ ''
165
+ end
117
166
  end
118
167
 
119
168
  def read_central_directory_entries(io) #:nodoc:
169
+ # `StringIO` doesn't raise `EINVAL` if you seek beyond the current end,
170
+ # so we need to catch that *and* query `io#eof?` here.
171
+ eof = false
120
172
  begin
121
173
  io.seek(@cdir_offset, IO::SEEK_SET)
122
174
  rescue Errno::EINVAL
123
- raise Error, 'Zip consistency problem while reading central directory entry'
175
+ eof = true
124
176
  end
177
+ raise Error, 'Zip consistency problem while reading central directory entry' if eof || io.eof?
178
+
125
179
  @entry_set = EntrySet.new
126
180
  @size.times do
127
- @entry_set << Entry.read_c_dir_entry(io)
128
- end
129
- end
181
+ entry = Entry.read_c_dir_entry(io)
182
+ next unless entry
183
+
184
+ offset = if entry.zip64?
185
+ entry.extra['Zip64'].relative_header_offset
186
+ else
187
+ entry.local_header_offset
188
+ end
130
189
 
131
- def read_from_stream(io) #:nodoc:
132
- buf = start_buf(io)
133
- if zip64_file?(buf)
134
- read_64_e_o_c_d(buf)
135
- else
136
- read_e_o_c_d(buf)
190
+ unless offset.nil?
191
+ io_save = io.tell
192
+ io.seek(offset, IO::SEEK_SET)
193
+ entry.read_extra_field(read_local_extra_field(io), local: true)
194
+ io.seek(io_save, IO::SEEK_SET)
195
+ end
196
+
197
+ @entry_set << entry
137
198
  end
138
- read_central_directory_entries(io)
139
199
  end
140
200
 
141
- def get_e_o_c_d(buf) #:nodoc:
142
- sig_index = buf.rindex([END_OF_CDS].pack('V'))
143
- raise Error, 'Zip end of central directory signature not found' unless sig_index
144
- buf = buf.slice!((sig_index + 4)..(buf.bytesize))
201
+ def read_local_extra_field(io)
202
+ buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
203
+ return '' unless buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
145
204
 
146
- def buf.read(count)
147
- slice!(0, count)
148
- end
205
+ head, _, _, _, _, _, _, _, _, _, n_len, e_len = buf.unpack('VCCvvvvVVVvv')
206
+ return '' unless head == ::Zip::LOCAL_ENTRY_SIGNATURE
149
207
 
150
- buf
208
+ io.seek(n_len, IO::SEEK_CUR) # Skip over the entry name.
209
+ io.read(e_len)
151
210
  end
152
211
 
153
- def zip64_file?(buf)
154
- buf.rindex([ZIP64_END_OF_CDS].pack('V')) && buf.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
155
- end
212
+ def read_eocds(io) #:nodoc:
213
+ base_location, data = eocd_data(io)
156
214
 
157
- def start_buf(io)
158
- begin
159
- io.seek(-MAX_END_OF_CDS_SIZE, IO::SEEK_END)
160
- rescue Errno::EINVAL
161
- io.seek(0, IO::SEEK_SET)
162
- end
163
- io.read
164
- end
215
+ eocd_location = data.rindex([END_OF_CD_SIG].pack('V'))
216
+ raise Error, 'Zip end of central directory signature not found' unless eocd_location
165
217
 
166
- def get_64_e_o_c_d(buf) #:nodoc:
167
- zip_64_start = buf.rindex([ZIP64_END_OF_CDS].pack('V'))
168
- raise Error, 'Zip64 end of central directory signature not found' unless zip_64_start
169
- zip_64_locator = buf.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
170
- raise Error, 'Zip64 end of central directory signature locator not found' unless zip_64_locator
171
- buf = buf.slice!((zip_64_start + 4)..zip_64_locator)
218
+ zip64_eocd_locator = data.rindex([ZIP64_EOCD_LOCATOR_SIG].pack('V'))
172
219
 
173
- def buf.read(count)
174
- slice!(0, count)
175
- end
220
+ if zip64_eocd_locator
221
+ zip64_eocd_location = data.rindex([ZIP64_END_OF_CD_SIG].pack('V'))
176
222
 
177
- buf
178
- end
223
+ zip64_eocd_data =
224
+ if zip64_eocd_location
225
+ data.slice(zip64_eocd_location..zip64_eocd_locator)
226
+ else
227
+ zip64_eocd_location = unpack_64_eocd_locator(
228
+ data.slice(zip64_eocd_locator..eocd_location)
229
+ )
230
+ unless zip64_eocd_location
231
+ raise Error, 'Zip64 end of central directory signature not found'
232
+ end
179
233
 
180
- # For iterating over the entries.
181
- def each(&proc)
182
- @entry_set.each(&proc)
183
- end
234
+ io.seek(zip64_eocd_location, IO::SEEK_SET)
235
+ io.read(base_location + zip64_eocd_locator - zip64_eocd_location)
236
+ end
184
237
 
185
- # Returns the number of entries in the central directory (and
186
- # consequently in the zip archive).
187
- def size
188
- @entry_set.size
189
- end
238
+ unpack_64_e_o_c_d(zip64_eocd_data)
239
+ end
190
240
 
191
- def self.read_from_stream(io) #:nodoc:
192
- cdir = new
193
- cdir.read_from_stream(io)
194
- return cdir
195
- rescue Error
196
- return nil
241
+ unpack_e_o_c_d(data.slice(eocd_location..-1))
197
242
  end
198
243
 
199
- def ==(other) #:nodoc:
200
- return false unless other.kind_of?(CentralDirectory)
201
- @entry_set.entries.sort == other.entries.sort && comment == other.comment
244
+ def eocd_data(io)
245
+ begin
246
+ io.seek(-MAX_END_OF_CD_SIZE, IO::SEEK_END)
247
+ rescue Errno::EINVAL
248
+ io.seek(0, IO::SEEK_SET)
249
+ end
250
+
251
+ [io.tell, io.read]
202
252
  end
203
253
  end
204
254
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class Compressor #:nodoc:all
4
+ class Compressor # :nodoc:all
3
5
  def finish; end
4
6
  end
5
7
  end
data/lib/zip/constants.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
4
  RUNNING_ON_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i
3
5
 
@@ -11,6 +13,8 @@ module Zip
11
13
  VERSION_NEEDED_TO_EXTRACT = 20
12
14
  VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
13
15
 
16
+ SPLIT_FILE_SIGNATURE = 0x08074b50
17
+
14
18
  FILE_TYPE_FILE = 0o10
15
19
  FILE_TYPE_DIR = 0o04
16
20
  FILE_TYPE_SYMLINK = 0o12
@@ -38,26 +42,78 @@ module Zip
38
42
  FSTYPE_ATHEOS = 30
39
43
 
40
44
  FSTYPES = {
41
- FSTYPE_FAT => 'FAT'.freeze,
42
- FSTYPE_AMIGA => 'Amiga'.freeze,
43
- FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze,
44
- FSTYPE_UNIX => 'Unix'.freeze,
45
- FSTYPE_VM_CMS => 'VM/CMS'.freeze,
46
- FSTYPE_ATARI => 'Atari ST'.freeze,
47
- FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze,
48
- FSTYPE_MAC => 'Macintosh'.freeze,
49
- FSTYPE_Z_SYSTEM => 'Z-System'.freeze,
50
- FSTYPE_CPM => 'CP/M'.freeze,
51
- FSTYPE_TOPS20 => 'TOPS-20'.freeze,
52
- FSTYPE_NTFS => 'NTFS'.freeze,
53
- FSTYPE_QDOS => 'SMS/QDOS'.freeze,
54
- FSTYPE_ACORN => 'Acorn RISC OS'.freeze,
55
- FSTYPE_VFAT => 'Win32 VFAT'.freeze,
56
- FSTYPE_MVS => 'MVS'.freeze,
57
- FSTYPE_BEOS => 'BeOS'.freeze,
58
- FSTYPE_TANDEM => 'Tandem NSK'.freeze,
59
- FSTYPE_THEOS => 'Theos'.freeze,
60
- FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
61
- FSTYPE_ATHEOS => 'AtheOS'.freeze
45
+ FSTYPE_FAT => 'FAT',
46
+ FSTYPE_AMIGA => 'Amiga',
47
+ FSTYPE_VMS => 'VMS (Vax or Alpha AXP)',
48
+ FSTYPE_UNIX => 'Unix',
49
+ FSTYPE_VM_CMS => 'VM/CMS',
50
+ FSTYPE_ATARI => 'Atari ST',
51
+ FSTYPE_HPFS => 'OS/2 or NT HPFS',
52
+ FSTYPE_MAC => 'Macintosh',
53
+ FSTYPE_Z_SYSTEM => 'Z-System',
54
+ FSTYPE_CPM => 'CP/M',
55
+ FSTYPE_TOPS20 => 'TOPS-20',
56
+ FSTYPE_NTFS => 'NTFS',
57
+ FSTYPE_QDOS => 'SMS/QDOS',
58
+ FSTYPE_ACORN => 'Acorn RISC OS',
59
+ FSTYPE_VFAT => 'Win32 VFAT',
60
+ FSTYPE_MVS => 'MVS',
61
+ FSTYPE_BEOS => 'BeOS',
62
+ FSTYPE_TANDEM => 'Tandem NSK',
63
+ FSTYPE_THEOS => 'Theos',
64
+ FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)',
65
+ FSTYPE_ATHEOS => 'AtheOS'
66
+ }.freeze
67
+
68
+ COMPRESSION_METHOD_STORE = 0
69
+ COMPRESSION_METHOD_SHRINK = 1
70
+ COMPRESSION_METHOD_REDUCE_1 = 2
71
+ COMPRESSION_METHOD_REDUCE_2 = 3
72
+ COMPRESSION_METHOD_REDUCE_3 = 4
73
+ COMPRESSION_METHOD_REDUCE_4 = 5
74
+ COMPRESSION_METHOD_IMPLODE = 6
75
+ # RESERVED = 7
76
+ COMPRESSION_METHOD_DEFLATE = 8
77
+ COMPRESSION_METHOD_DEFLATE_64 = 9
78
+ COMPRESSION_METHOD_PKWARE_DCLI = 10
79
+ # RESERVED = 11
80
+ COMPRESSION_METHOD_BZIP2 = 12
81
+ # RESERVED = 13
82
+ COMPRESSION_METHOD_LZMA = 14
83
+ # RESERVED = 15
84
+ COMPRESSION_METHOD_IBM_CMPSC = 16
85
+ # RESERVED = 17
86
+ COMPRESSION_METHOD_IBM_TERSE = 18
87
+ COMPRESSION_METHOD_IBM_LZ77 = 19
88
+ COMPRESSION_METHOD_JPEG = 96
89
+ COMPRESSION_METHOD_WAVPACK = 97
90
+ COMPRESSION_METHOD_PPMD = 98
91
+ COMPRESSION_METHOD_AES = 99
92
+
93
+ COMPRESSION_METHODS = {
94
+ COMPRESSION_METHOD_STORE => 'Store (no compression)',
95
+ COMPRESSION_METHOD_SHRINK => 'Shrink',
96
+ COMPRESSION_METHOD_REDUCE_1 => 'Reduce with compression factor 1',
97
+ COMPRESSION_METHOD_REDUCE_2 => 'Reduce with compression factor 2',
98
+ COMPRESSION_METHOD_REDUCE_3 => 'Reduce with compression factor 3',
99
+ COMPRESSION_METHOD_REDUCE_4 => 'Reduce with compression factor 4',
100
+ COMPRESSION_METHOD_IMPLODE => 'Implode',
101
+ # RESERVED = 7
102
+ COMPRESSION_METHOD_DEFLATE => 'Deflate',
103
+ COMPRESSION_METHOD_DEFLATE_64 => 'Deflate64(tm)',
104
+ COMPRESSION_METHOD_PKWARE_DCLI => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
105
+ # RESERVED = 11
106
+ COMPRESSION_METHOD_BZIP2 => 'BZIP2',
107
+ # RESERVED = 13
108
+ COMPRESSION_METHOD_LZMA => 'LZMA',
109
+ # RESERVED = 15
110
+ COMPRESSION_METHOD_IBM_CMPSC => 'IBM z/OS CMPSC Compression',
111
+ # RESERVED = 17
112
+ COMPRESSION_METHOD_IBM_TERSE => 'IBM TERSE (new)',
113
+ COMPRESSION_METHOD_IBM_LZ77 => 'IBM LZ77 z Architecture (PFS)',
114
+ COMPRESSION_METHOD_JPEG => 'JPEG variant',
115
+ COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data',
116
+ COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1',
117
+ COMPRESSION_METHOD_AES => 'AES encryption'
62
118
  }.freeze
63
119
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ class DecryptedIo # :nodoc:all
5
+ CHUNK_SIZE = 32_768
6
+
7
+ def initialize(io, decrypter)
8
+ @io = io
9
+ @decrypter = decrypter
10
+ end
11
+
12
+ def read(length = nil, outbuf = +'')
13
+ return (length.nil? || length.zero? ? '' : nil) if eof
14
+
15
+ while length.nil? || (buffer.bytesize < length)
16
+ break if input_finished?
17
+
18
+ buffer << produce_input
19
+ end
20
+
21
+ outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize)))
22
+ end
23
+
24
+ private
25
+
26
+ def eof
27
+ buffer.empty? && input_finished?
28
+ end
29
+
30
+ def buffer
31
+ @buffer ||= +''
32
+ end
33
+
34
+ def input_finished?
35
+ @io.eof
36
+ end
37
+
38
+ def produce_input
39
+ @decrypter.decrypt(@io.read(CHUNK_SIZE))
40
+ end
41
+ end
42
+ end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class Encrypter #:nodoc:all
4
+ class Encrypter # :nodoc:all
3
5
  end
4
6
 
5
- class Decrypter
7
+ class Decrypter # :nodoc:all
6
8
  end
7
9
  end
8
10
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module NullEncryption
4
+ module NullEncryption # :nodoc:
3
5
  def header_bytesize
4
6
  0
5
7
  end
@@ -9,7 +11,7 @@ module Zip
9
11
  end
10
12
  end
11
13
 
12
- class NullEncrypter < Encrypter
14
+ class NullEncrypter < Encrypter # :nodoc:
13
15
  include NullEncryption
14
16
 
15
17
  def header(_mtime)
@@ -27,7 +29,7 @@ module Zip
27
29
  def reset!; end
28
30
  end
29
31
 
30
- class NullDecrypter < Decrypter
32
+ class NullDecrypter < Decrypter # :nodoc:
31
33
  include NullEncryption
32
34
 
33
35
  def decrypt(data)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- module TraditionalEncryption
4
+ module TraditionalEncryption # :nodoc:
3
5
  def initialize(password)
4
6
  @password = password
5
7
  reset_keys!
@@ -24,8 +26,8 @@ module Zip
24
26
  end
25
27
  end
26
28
 
27
- def update_keys(n)
28
- @key0 = ~Zlib.crc32(n, ~@key0)
29
+ def update_keys(num)
30
+ @key0 = ~Zlib.crc32(num, ~@key0)
29
31
  @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff
30
32
  @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
31
33
  end
@@ -36,7 +38,7 @@ module Zip
36
38
  end
37
39
  end
38
40
 
39
- class TraditionalEncrypter < Encrypter
41
+ class TraditionalEncrypter < Encrypter # :nodoc:
40
42
  include TraditionalEncryption
41
43
 
42
44
  def header(mtime)
@@ -63,14 +65,14 @@ module Zip
63
65
 
64
66
  private
65
67
 
66
- def encode(n)
68
+ def encode(num)
67
69
  t = decrypt_byte
68
- update_keys(n.chr)
69
- t ^ n
70
+ update_keys(num.chr)
71
+ t ^ num
70
72
  end
71
73
  end
72
74
 
73
- class TraditionalDecrypter < Decrypter
75
+ class TraditionalDecrypter < Decrypter # :nodoc:
74
76
  include TraditionalEncryption
75
77
 
76
78
  def decrypt(data)
@@ -86,10 +88,10 @@ module Zip
86
88
 
87
89
  private
88
90
 
89
- def decode(n)
90
- n ^= decrypt_byte
91
- update_keys(n.chr)
92
- n
91
+ def decode(num)
92
+ num ^= decrypt_byte
93
+ update_keys(num.chr)
94
+ num
93
95
  end
94
96
  end
95
97
  end
@@ -1,9 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zip
2
- class Decompressor #:nodoc:all
4
+ class Decompressor # :nodoc:all
3
5
  CHUNK_SIZE = 32_768
4
- def initialize(input_stream)
6
+
7
+ def self.decompressor_classes
8
+ @decompressor_classes ||= {}
9
+ end
10
+
11
+ def self.register(compression_method, decompressor_class)
12
+ decompressor_classes[compression_method] = decompressor_class
13
+ end
14
+
15
+ def self.find_by_compression_method(compression_method)
16
+ decompressor_classes[compression_method]
17
+ end
18
+
19
+ attr_reader :decompressed_size, :input_stream
20
+
21
+ def initialize(input_stream, decompressed_size = nil)
5
22
  super()
23
+
6
24
  @input_stream = input_stream
25
+ @decompressed_size = decompressed_size
7
26
  end
8
27
  end
9
28
  end