rubyzip 3.0.2 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4afc6447f838d0d2edc82bb44ab842a5c45f50bd47d453aae1a7b487c9dcbee
4
- data.tar.gz: 776833d664351c8e0323826263d16b2b11486fee80a7ceda1a8385d05dd2a38e
3
+ metadata.gz: 67852d915e2ec168efb617d7577ac60196bf7433b3c2800981a14c5a43b939c5
4
+ data.tar.gz: '039b39e7e9e7f46e056da4d0070554eba081e26a4dd99e3dd47d76428628d29f'
5
5
  SHA512:
6
- metadata.gz: b52059d2cfba5c05959bb347383d4bfe79c8f3c0f945cba64362462267c750f0e16e13f63fbf272b2cf0935173678b876164c3993ddae85fb89dec726afea940
7
- data.tar.gz: 83a932fbadf6fbafa0a1e64434f0b2a2b7f58fb56d9015350eae217430f9afaf5492254a56de9be124bdf9676a8a5aa373515c996ce2f74966aceb87b72472d1
6
+ metadata.gz: ba24363c26265acbd295289685d5baa4d351a913d98de67064171c35f0055f04ac34b2e6db384af248e124b12abc505bac558205eadb2975d3d4da59178b9a54
7
+ data.tar.gz: b654167d21076c70ea58b6a185f4be83a6ef172d208bf4f8a8928ac82aa909773b20df462870dc833b1182f669334e18fd7283e23cf4040b770faea1a49d1e6a
data/Changelog.md CHANGED
@@ -1,3 +1,52 @@
1
+ # 3.2.2 (2025-11-02)
2
+
3
+ - Fix reading EOCDs when header signatures are in an Entry payload. [#656](https://github.com/rubyzip/rubyzip/issues/656)
4
+
5
+ Tooling/internal:
6
+
7
+ - Stop using macos-13 runners in GitHub Actions.
8
+ - Update YJIT GitHub Actions runners.
9
+
10
+ # 3.2.1 (2025-10-24)
11
+
12
+ - Fix `Entry#gather_fileinfo_from_srcpath` error messages. [#654](https://github.com/rubyzip/rubyzip/issues/654)
13
+
14
+ Tooling/internal:
15
+
16
+ - Add some simple benchmarks for reading the cdir.
17
+
18
+ # 3.2.0 (2025-10-14)
19
+
20
+ - Add option to suppress extra fields. [#653](https://github.com/rubyzip/rubyzip/pull/653) (fixes [#34](https://github.com/rubyzip/rubyzip/issues/34), [#398](https://github.com/rubyzip/rubyzip/issues/398) and [#648](https://github.com/rubyzip/rubyzip/issues/648))
21
+
22
+ Tooling/internal:
23
+
24
+ - Entry: clean up reading and writing the Central Directory headers.
25
+ - Improve Zip64 tests for `OutputStream`.
26
+ - Extra fields: use symbols as indices as opposed to strings.
27
+ - Ensure that `Unknown` extra field has a superclass.
28
+
29
+ # 3.1.1 (2025-09-26)
30
+
31
+ - Improve the IO pipeline when decompressing. [#649](https://github.com/rubyzip/rubyzip/pull/649) (which also fixes [#647](https://github.com/rubyzip/rubyzip/issues/647))
32
+
33
+ Tooling/internal:
34
+
35
+ - Improve the `DecryptedIo` class with various updates and optimizations.
36
+ - Remove the `NullDecrypter` class.
37
+ - Properly convert the test suite to use minitest.
38
+ - Move all test helper code into separate files.
39
+ - Updates to the Actions CI, including new OS versions.
40
+ - Update rubocop versions and fix resultant cop failures. [#646](https://github.com/rubyzip/rubyzip/pull/646)
41
+
42
+ # 3.1.0 (2025-09-06)
43
+
44
+ - Support AES decryption. [#579](https://github.com/rubyzip/rubyzip/pull/579) and [#645](https://github.com/rubyzip/rubyzip/pull/645)
45
+
46
+ Tooling/internal:
47
+
48
+ - Add various useful zip specification documents to the repo for ease of finding them in the future. These are not included in the gem release.
49
+
1
50
  # 3.0.2 (2025-08-21)
2
51
 
3
52
  - Fix `InputStream#sysread` to handle frozen string literals. [#643](https://github.com/rubyzip/rubyzip/pull/643)
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip)
4
4
  [![Tests](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml)
5
5
  [![Linter](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml/badge.svg)](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml)
6
- [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip)
6
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
7
+ [![Maintainability](https://qlty.sh/gh/rubyzip/projects/rubyzip/maintainability.svg)](https://qlty.sh/gh/rubyzip/projects/rubyzip)
7
8
  [![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master)
8
9
 
9
10
  Rubyzip is a ruby library for reading and writing zip files.
@@ -66,6 +67,48 @@ Zip::File.open(zipfile_name, create: true) do |zipfile|
66
67
  end
67
68
  ```
68
69
 
70
+ ### Creating a Zip file with `Zip::OutputStream`
71
+
72
+ ```ruby
73
+ require 'rubygems'
74
+ require 'zip'
75
+
76
+ Zip::OutputStream.open('archive.zip') do |zos|
77
+ # Quick.
78
+ zos.put_next_entry('greeting.txt')
79
+ zos << 'Hello, World!'
80
+
81
+ # More control.
82
+ # You MUST NOT make any calls on your `Entry` after calling `put_next_entry`.
83
+ entry = Zip::Entry.new(nil, 'parting.txt')
84
+ entry.atime = Time.now
85
+ zos.put_next_entry(entry)
86
+ zos.write('TTFN')
87
+ end
88
+ ```
89
+
90
+ You can generate a Zip archive in memory using `Zip::OutputStream.write_buffer`.
91
+
92
+ ### Suppressing extra fields
93
+
94
+ If you wish to suppress extra fields from being added to your entries, you can do so by passing the `suppress_extra_fields` parameter to any of the archive opening calls within `Zip::File` or `Zip::OutputStream`, e.g.:
95
+
96
+ ```ruby
97
+ # Suppress all extra fields.
98
+ Zip::File.open('archive.zip', create: true, suppress_extra_fields: true)
99
+ Zip::OutputStream.open('archive.zip', suppress_extra_fields: true)
100
+
101
+ # Suppress an individual extra field.
102
+ Zip::File.open('archive.zip', create: true, suppress_extra_fields: :zip64)
103
+ Zip::OutputStream.open('archive.zip', suppress_extra_fields: :zip64)
104
+
105
+ # Suppress multiple extra fields.
106
+ Zip::File.open('archive.zip', create: true, suppress_extra_fields: [:ntfs, :zip64])
107
+ Zip::OutputStream.open('archive.zip', suppress_extra_fields: [:ntfs, :zip64])
108
+ ```
109
+
110
+ Note that there are some extra fields that cannot be suppressed at all (e.g. `:aes`), and some which will only be suppressed if it is safe to do so (e.g. `:zip64`).
111
+
69
112
  ### Zipping a directory recursively
70
113
 
71
114
  Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb)
@@ -179,7 +222,7 @@ Zip::File.open('foo.zip') do |zip_file|
179
222
  end
180
223
  ```
181
224
 
182
- ### Notes on `Zip::InputStream`
225
+ ### Reading a Zip file with `Zip::InputStream`
183
226
 
184
227
  `Zip::InputStream` can be used for faster reading of zip file content because it does not read the Central directory up front.
185
228
 
@@ -189,23 +232,23 @@ There is one exception where it can not work however, and this is if the file do
189
232
 
190
233
  If `Zip::InputStream` finds such an entry in the zip archive it will raise an exception (`Zip::StreamingError`).
191
234
 
192
- `Zip::InputStream` is not designed to be used for random access in a zip file. When performing any operations on an entry that you are accessing via `Zip::InputStream.get_next_entry` then you should complete any such operations before the next call to `get_next_entry`.
235
+ `Zip::InputStream` is not designed to be used for random access in a zip file. When performing any operations on an entry that you are accessing via `Zip::InputStream#get_next_entry` then you should complete any such operations before the next call to `get_next_entry`.
193
236
 
194
237
  ```ruby
195
- zip_stream = Zip::InputStream.new(File.open('file.zip'))
196
-
197
- while entry = zip_stream.get_next_entry
198
- # All required operations on `entry` go here.
199
- end
238
+ Zip::InputStream.open('file.zip') do |zip_stream|
239
+ while entry = zip_stream.get_next_entry
240
+ # All required operations on `entry` go here.
241
+ end
242
+ end # The `InputStream` is closed at the end of the block.
200
243
  ```
201
244
 
202
245
  Any attempt to move about in a zip file opened with `Zip::InputStream` could result in the incorrect entry being accessed and/or Zlib buffer errors. If you need random access in a zip file, use `Zip::File`.
203
246
 
204
- ### Password Protection (Experimental)
247
+ ### Password Protection (experimental)
205
248
 
206
- Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.:
249
+ Rubyzip supports reading zip files with AES encryption (version 3.1 and later), and reading and writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). Encryption is currently only available with the stream API, with either files or buffers, e.g.:
207
250
 
208
- #### Version 2.x
251
+ #### Version 2.x (ZipCrypto only)
209
252
 
210
253
  ```ruby
211
254
  # Writing.
@@ -224,9 +267,17 @@ Zip::InputStream.open(buffer, 0, dec) do |input|
224
267
  end
225
268
  ```
226
269
 
227
- #### Version 3.x
270
+ #### Version 3.x (AES reading and ZipCrypto read/write)
228
271
 
229
272
  ```ruby
273
+ # Reading AES, version 3.1 and later.
274
+ dec = Zip::AESDecrypter.new('password', Zip::AESEncryption::STRENGTH_256_BIT)
275
+ Zip::InputStream.open('aes-encrypted-file.zip', decrypter: dec) do |input|
276
+ entry = input.get_next_entry
277
+ puts "Contents of '#{entry.name}':"
278
+ puts input.read
279
+ end
280
+
230
281
  # Writing.
231
282
  enc = Zip::TraditionalEncrypter.new('password')
232
283
  buffer = Zip::OutputStream.write_buffer(encrypter: enc) do |output|
@@ -243,7 +294,7 @@ Zip::InputStream.open(buffer, decrypter: dec) do |input|
243
294
  end
244
295
  ```
245
296
 
246
- _This is an experimental feature and the interface for encryption may change in future versions._
297
+ _This is an evolving feature and the interface for encryption may change in future versions._
247
298
 
248
299
  ## Known issues
249
300
 
data/Rakefile CHANGED
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/gem_tasks'
4
- require 'rake/testtask'
4
+ require 'minitest/test_task'
5
5
  require 'rdoc/task'
6
6
  require 'rubocop/rake_task'
7
7
 
8
8
  task default: :test
9
9
 
10
- Rake::TestTask.new(:test) do |test|
11
- test.libs << 'lib'
12
- test.libs << 'test'
13
- test.pattern = 'test/**/*_test.rb'
14
- test.verbose = true
10
+ Minitest::TestTask.create do |test|
11
+ test.framework = 'require "simplecov"'
12
+ test.test_globs = 'test/**/*_test.rb'
15
13
  end
16
14
 
17
15
  RDoc::Task.new do |rdoc|
@@ -39,9 +39,11 @@ module Zip
39
39
  read_central_directory_entries(io)
40
40
  end
41
41
 
42
- def write_to_stream(io) # :nodoc:
42
+ def write_to_stream(io, suppress_extra_fields: false) # :nodoc:
43
43
  cdir_offset = io.tell
44
- @entry_set.each { |entry| entry.write_c_dir_entry(io) }
44
+ @entry_set.each do |entry|
45
+ entry.write_c_dir_entry(io, suppress_extra_fields: suppress_extra_fields)
46
+ end
45
47
  eocd_offset = io.tell
46
48
  cdir_size = eocd_offset - cdir_offset
47
49
  if Zip.write_zip64_support &&
@@ -141,28 +143,27 @@ module Zip
141
143
  zip64_eocd_offset
142
144
  end
143
145
 
144
- def unpack_e_o_c_d(buffer) # :nodoc:
146
+ # Unpack the EOCD and return a boolean indicating whether this header is
147
+ # complete without needing Zip64 extensions.
148
+ def unpack_e_o_c_d(buffer) # :nodoc: # rubocop:disable Naming/PredicateMethod
145
149
  _, # 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,
150
+ @number_of_this_disk,
151
+ @number_of_disk_with_start_of_cdir,
152
+ @total_number_of_entries_in_cdir_on_this_disk,
153
+ @size,
154
+ @size_in_bytes,
155
+ @cdir_offset,
152
156
  comment_length = buffer.unpack('VvvvvVVv')
153
157
 
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
158
  @comment = if comment_length.positive?
162
159
  buffer.slice(STATIC_EOCD_SIZE, comment_length)
163
160
  else
164
161
  ''
165
162
  end
163
+
164
+ !([@number_of_this_disk, @number_of_disk_with_start_of_cdir,
165
+ @total_number_of_entries_in_cdir_on_this_disk, @size].any?(0xFFFF) ||
166
+ @size_in_bytes == 0xFFFFFFFF || @cdir_offset == 0xFFFFFFFF)
166
167
  end
167
168
 
168
169
  def read_central_directory_entries(io) # :nodoc:
@@ -182,7 +183,7 @@ module Zip
182
183
  next unless entry
183
184
 
184
185
  offset = if entry.zip64?
185
- entry.extra['Zip64'].relative_header_offset
186
+ entry.extra[:zip64].relative_header_offset
186
187
  else
187
188
  entry.local_header_offset
188
189
  end
@@ -215,30 +216,37 @@ module Zip
215
216
  eocd_location = data.rindex([END_OF_CD_SIG].pack('V'))
216
217
  raise Error, 'Zip end of central directory signature not found' unless eocd_location
217
218
 
218
- zip64_eocd_locator = data.rindex([ZIP64_EOCD_LOCATOR_SIG].pack('V'))
219
-
220
- if zip64_eocd_locator
221
- zip64_eocd_location = data.rindex([ZIP64_END_OF_CD_SIG].pack('V'))
219
+ # Parse the EOCD and return if it is complete without Zip64 extensions.
220
+ return if unpack_e_o_c_d(data.slice(eocd_location..-1))
222
221
 
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
222
+ # Need to read in the Zip64 EOCD locator and then the Zip64 EOCD.
223
+ zip64_eocd_locator = data.rindex([ZIP64_EOCD_LOCATOR_SIG].pack('V'), eocd_location)
224
+ unless zip64_eocd_locator
225
+ raise Error, 'Zip64 end of central directory locator signature expected but not found'
226
+ end
233
227
 
234
- io.seek(zip64_eocd_location, IO::SEEK_SET)
235
- io.read(base_location + zip64_eocd_locator - zip64_eocd_location)
228
+ # Do we already have the Zip64 EOCD in the data we've read?
229
+ zip64_eocd_location = data.rindex([ZIP64_END_OF_CD_SIG].pack('V'), zip64_eocd_locator)
230
+
231
+ zip64_eocd_data =
232
+ if zip64_eocd_location
233
+ # Yes.
234
+ data.slice(zip64_eocd_location..zip64_eocd_locator)
235
+ else
236
+ # No. Read its location from the locator and then read it in.
237
+ zip64_eocd_location = unpack_64_eocd_locator(
238
+ data.slice(zip64_eocd_locator..eocd_location)
239
+ )
240
+ unless zip64_eocd_location
241
+ raise Error, 'Zip64 end of central directory signature not found'
236
242
  end
237
243
 
238
- unpack_64_e_o_c_d(zip64_eocd_data)
239
- end
244
+ io.seek(zip64_eocd_location, IO::SEEK_SET)
245
+ io.read(base_location + zip64_eocd_locator - zip64_eocd_location)
246
+ end
240
247
 
241
- unpack_e_o_c_d(data.slice(eocd_location..-1))
248
+ # Finally, unpack the Zip64 EOCD.
249
+ unpack_64_e_o_c_d(zip64_eocd_data)
242
250
  end
243
251
 
244
252
  def eocd_data(io)
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Zip
6
+ module AESEncryption # :nodoc:
7
+ VERIFIER_LENGTH = 2
8
+ BLOCK_SIZE = 16
9
+ AUTHENTICATION_CODE_LENGTH = 10
10
+
11
+ VERSION_AE_1 = 0x01
12
+ VERSION_AE_2 = 0x02
13
+
14
+ VERSIONS = [
15
+ VERSION_AE_1,
16
+ VERSION_AE_2
17
+ ].freeze
18
+
19
+ STRENGTH_128_BIT = 0x01
20
+ STRENGTH_192_BIT = 0x02
21
+ STRENGTH_256_BIT = 0x03
22
+
23
+ STRENGTHS = [
24
+ STRENGTH_128_BIT,
25
+ STRENGTH_192_BIT,
26
+ STRENGTH_256_BIT
27
+ ].freeze
28
+
29
+ BITS = {
30
+ STRENGTH_128_BIT => 128,
31
+ STRENGTH_192_BIT => 192,
32
+ STRENGTH_256_BIT => 256
33
+ }.freeze
34
+
35
+ KEY_LENGTHS = {
36
+ STRENGTH_128_BIT => 16,
37
+ STRENGTH_192_BIT => 24,
38
+ STRENGTH_256_BIT => 32
39
+ }.freeze
40
+
41
+ SALT_LENGTHS = {
42
+ STRENGTH_128_BIT => 8,
43
+ STRENGTH_192_BIT => 12,
44
+ STRENGTH_256_BIT => 16
45
+ }.freeze
46
+
47
+ def initialize(password, strength)
48
+ @password = password
49
+ @strength = strength
50
+ @bits = BITS[@strength]
51
+ @key_length = KEY_LENGTHS[@strength]
52
+ @salt_length = SALT_LENGTHS[@strength]
53
+ end
54
+
55
+ def header_bytesize
56
+ @salt_length + VERIFIER_LENGTH
57
+ end
58
+
59
+ def gp_flags
60
+ 0x0001
61
+ end
62
+ end
63
+
64
+ class AESDecrypter < Decrypter # :nodoc:
65
+ include AESEncryption
66
+
67
+ def decrypt(encrypted_data)
68
+ @hmac.update(encrypted_data)
69
+
70
+ idx = 0
71
+ decrypted_data = +''
72
+ amount_to_read = encrypted_data.size
73
+
74
+ while amount_to_read.positive?
75
+ @cipher.iv = [@counter + 1].pack('Vx12')
76
+ begin_index = BLOCK_SIZE * idx
77
+ end_index = begin_index + [BLOCK_SIZE, amount_to_read].min
78
+ decrypted_data << @cipher.update(encrypted_data[begin_index...end_index])
79
+ amount_to_read -= BLOCK_SIZE
80
+ @counter += 1
81
+ idx += 1
82
+ end
83
+
84
+ # JRuby requires finalization of the cipher. This is a bug, as noted in
85
+ # jruby/jruby-openssl#182 and jruby/jruby-openssl#183.
86
+ decrypted_data << @cipher.final if defined?(JRUBY_VERSION)
87
+ decrypted_data
88
+ end
89
+
90
+ def reset!(header)
91
+ raise Error, "Unsupported encryption AES-#{@bits}" unless STRENGTHS.include? @strength
92
+
93
+ salt = header[0...@salt_length]
94
+ pwd_verify = header[-VERIFIER_LENGTH..]
95
+ key_material = OpenSSL::KDF.pbkdf2_hmac(
96
+ @password,
97
+ salt: salt,
98
+ iterations: 1000,
99
+ length: (2 * @key_length) + VERIFIER_LENGTH,
100
+ hash: 'sha1'
101
+ )
102
+ enc_key = key_material[0...@key_length]
103
+ enc_hmac_key = key_material[@key_length...(2 * @key_length)]
104
+ enc_pwd_verify = key_material[-VERIFIER_LENGTH..]
105
+
106
+ raise Error, 'Bad password' if enc_pwd_verify != pwd_verify
107
+
108
+ @counter = 0
109
+ @cipher = OpenSSL::Cipher::AES.new(@bits, :CTR)
110
+ @cipher.decrypt
111
+ @cipher.key = enc_key
112
+ @hmac = OpenSSL::HMAC.new(enc_hmac_key, OpenSSL::Digest.new('SHA1'))
113
+ end
114
+
115
+ def check_integrity!(io)
116
+ auth_code = io.read(AUTHENTICATION_CODE_LENGTH)
117
+ raise Error, 'Integrity fault' if @hmac.digest[0...AUTHENTICATION_CODE_LENGTH] != auth_code
118
+ end
119
+ end
120
+ end
@@ -4,39 +4,43 @@ module Zip
4
4
  class DecryptedIo # :nodoc:all
5
5
  CHUNK_SIZE = 32_768
6
6
 
7
- def initialize(io, decrypter)
7
+ def initialize(io, decrypter, compressed_size)
8
8
  @io = io
9
9
  @decrypter = decrypter
10
+ @bytes_remaining = compressed_size
11
+ @buffer = +''
10
12
  end
11
13
 
12
14
  def read(length = nil, outbuf = +'')
13
- return (length.nil? || length.zero? ? '' : nil) if eof
15
+ return (length.nil? || length.zero? ? '' : nil) if eof?
14
16
 
15
- while length.nil? || (buffer.bytesize < length)
17
+ while length.nil? || (@buffer.bytesize < length)
16
18
  break if input_finished?
17
19
 
18
- buffer << produce_input
20
+ @buffer << produce_input
19
21
  end
20
22
 
21
- outbuf.replace(buffer.slice!(0...(length || buffer.bytesize)))
23
+ @decrypter.check_integrity!(@io) if input_finished?
24
+
25
+ outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
22
26
  end
23
27
 
24
28
  private
25
29
 
26
- def eof
27
- buffer.empty? && input_finished?
28
- end
29
-
30
- def buffer
31
- @buffer ||= +''
30
+ def eof?
31
+ @buffer.empty? && input_finished?
32
32
  end
33
33
 
34
34
  def input_finished?
35
- @io.eof
35
+ !@bytes_remaining.positive?
36
36
  end
37
37
 
38
38
  def produce_input
39
- @decrypter.decrypt(@io.read(CHUNK_SIZE))
39
+ chunk_size = [@bytes_remaining, CHUNK_SIZE].min
40
+ return '' unless chunk_size.positive?
41
+
42
+ @bytes_remaining -= chunk_size
43
+ @decrypter.decrypt(@io.read(chunk_size))
40
44
  end
41
45
  end
42
46
  end
@@ -28,16 +28,6 @@ module Zip
28
28
 
29
29
  def reset!; end
30
30
  end
31
-
32
- class NullDecrypter < Decrypter # :nodoc:
33
- include NullEncryption
34
-
35
- def decrypt(data)
36
- data
37
- end
38
-
39
- def reset!(_header); end
40
- end
41
31
  end
42
32
 
43
33
  # Copyright (C) 2002, 2003 Thomas Sondergaard
@@ -86,6 +86,8 @@ module Zip
86
86
  end
87
87
  end
88
88
 
89
+ def check_integrity!(_io); end
90
+
89
91
  private
90
92
 
91
93
  def decode(num)
data/lib/zip/dos_time.rb CHANGED
@@ -21,7 +21,7 @@ module Zip
21
21
  def absolute_time?
22
22
  # If absolute time is not set, we can assume it is an absolute time
23
23
  # because times do have timezone information by default.
24
- @absolute_time.nil? ? true : @absolute_time
24
+ @absolute_time.nil? || @absolute_time
25
25
  end
26
26
 
27
27
  def to_binary_dos_time
@@ -36,7 +36,8 @@ module Zip
36
36
  ((year - 1980) << 9)
37
37
  end
38
38
 
39
- def dos_equals(other)
39
+ # Deprecated. Remove for version 4.
40
+ def dos_equals(other) # rubocop:disable Naming/PredicateMethod
40
41
  warn 'Zip::DOSTime#dos_equals is deprecated. Use `==` instead.'
41
42
  self == other
42
43
  end