rubyzip 3.3.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +17 -0
- data/README.md +27 -7
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +32 -17
- data/lib/zip/crypto/traditional_encryption.rb +3 -1
- data/lib/zip/entry.rb +1 -1
- data/lib/zip/errors.rb +16 -0
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +3 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31692c3a3abbfc979783a52acfad553e88440ef25ef3375e2e7f2355407999ef
|
|
4
|
+
data.tar.gz: 16f79be412b8048c8db41fb4a9807b6743adf390f80d2ee73580eb98cf5c7073
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 614763745e03176e441f401d739c79bd4df37483d7d3d9fd0821a8119906048f2b3ebbbd9604653e273e7c115e41176b1d3a3ea3f5199d6701cda2f9b30bb777
|
|
7
|
+
data.tar.gz: e704da45a03de72a67e108a037aac218bb397f1a625f50ca8ea6dbce842bb1a312de6679662bc4154afbb9229f4f5136f3aac981d651615dbde6c7b9a66dade7
|
data/Changelog.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# 3.4.0 (2026-06-14)
|
|
2
|
+
|
|
3
|
+
- Prevent entries from being extracted outside specified directory. [#664](https://github.com/rubyzip/rubyzip/issues/664). Thanks to @connorshea for additional reporting on this.
|
|
4
|
+
- Use `SecureRandom` in place of insecure `Random`.
|
|
5
|
+
- Stop reading the central directory on first error.
|
|
6
|
+
- Add a check on number of declared entries in a zip file. Thanks to @connorshea for reporting this.
|
|
7
|
+
- Add note to README re reporting security issues privately.
|
|
8
|
+
- Add lib/rubyzip.rb for Bundler auto-require. [#660](https://github.com/rubyzip/rubyzip/pull/660)
|
|
9
|
+
|
|
10
|
+
Tooling/internal:
|
|
11
|
+
|
|
12
|
+
- Replace the test Excel spreadsheet fixture.
|
|
13
|
+
- Use `assert_silent` shorthand when expecting no output from a test.
|
|
14
|
+
- Clean up CentralDirectory instance variables.
|
|
15
|
+
- Add Ruby 4.0 to the Windows CI and update CI matrix in the README.
|
|
16
|
+
- List ZIP docs that we store here and link to online versions. [#657](https://github.com/rubyzip/rubyzip/issues/657)
|
|
17
|
+
|
|
1
18
|
# 3.3.1 (2026-05-30)
|
|
2
19
|
|
|
3
20
|
- Reinstate default param for `InputStream#sysread`. [#663](https://github.com/rubyzip/rubyzip/issues/663)
|
data/README.md
CHANGED
|
@@ -11,6 +11,10 @@ Rubyzip is a ruby library for reading and writing zip files.
|
|
|
11
11
|
|
|
12
12
|
## Important notes
|
|
13
13
|
|
|
14
|
+
### Reporting security issues with this library
|
|
15
|
+
|
|
16
|
+
If you think you have found a security issue with this library, please don't submit a public issue or PR. Email me directly at hainesr@gmail.com with as much information as you can provide - steps for replication are particularly helpful if you can - and we'll get it sorted ASAP. Thank you.
|
|
17
|
+
|
|
14
18
|
### Updating to version 3.0
|
|
15
19
|
|
|
16
20
|
The public API of some classes has been modernized to use named parameters for optional arguments. Please check your usage of the following Rubyzip classes:
|
|
@@ -40,9 +44,12 @@ gem install rubyzip
|
|
|
40
44
|
Or in your Gemfile:
|
|
41
45
|
|
|
42
46
|
```ruby
|
|
43
|
-
gem 'rubyzip'
|
|
47
|
+
gem 'rubyzip', require: 'zip' # For versions before 3.4.
|
|
48
|
+
gem 'rubyzip' # For version 3.4 and after.
|
|
44
49
|
```
|
|
45
50
|
|
|
51
|
+
From version 3.4 onwards, you can `require` either 'zip' or 'rubyzip' to use this library. Before version 3.4 you need to `require 'zip'` explicitly.
|
|
52
|
+
|
|
46
53
|
## Usage
|
|
47
54
|
|
|
48
55
|
### Basic zip archive creation
|
|
@@ -363,7 +370,19 @@ Some zip files might have an invalid date format, which will raise a warning. Yo
|
|
|
363
370
|
Zip.warn_invalid_date = false
|
|
364
371
|
```
|
|
365
372
|
|
|
366
|
-
###
|
|
373
|
+
### Validating Declared Number of Entries
|
|
374
|
+
|
|
375
|
+
When reading a zip file it is potentially dangerous to trust what it tells you about how many entries it contains. A malformed zip file could claim a high number of entries in an attempt to waste internal resources, or crash the processing application.
|
|
376
|
+
|
|
377
|
+
By default rubyzip will warn if the number of declared entries is impossible based on the actual size of the Central Directory headers. You can set this check to raise an error:
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
Zip.validate_declared_number_of_entries = true
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
It is likely that the default behaviour for this check will be changed to raise an error in version 4.
|
|
384
|
+
|
|
385
|
+
### Entry Size Validation
|
|
367
386
|
|
|
368
387
|
By default (in rubyzip >= 2.0), rubyzip's `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like:
|
|
369
388
|
|
|
@@ -445,11 +464,12 @@ Rubyzip 2.4 is known to work on MRI 2.4 to 3.4 on Linux and Mac, and JRuby and T
|
|
|
445
464
|
|
|
446
465
|
Please see the table below for what we think the current situation is. Note: an empty cell means "unknown", not "does not work".
|
|
447
466
|
|
|
448
|
-
| OS/Ruby | 3.0 | 3.1 | 3.2 | 3.3 | 3.4 | Head | JRuby 10.0.1.0 | JRuby Head | Truffleruby
|
|
449
|
-
|
|
450
|
-
|Ubuntu 24.04| CI | CI | CI | CI | CI | ci | CI | ci | CI | ci |
|
|
451
|
-
|Mac OS
|
|
452
|
-
|Windows Server 2022| CI | | | |
|
|
467
|
+
| OS/Ruby | 3.0 | 3.1 | 3.2 | 3.3 | 3.4 | 4.0 | Head | JRuby 10.0.1.0 | JRuby Head | Truffleruby 34.0.1 | Truffleruby Head |
|
|
468
|
+
|---------|-----|-----|-----|-----|-----|-----|------|----------------|------------|-------------------|------------------|
|
|
469
|
+
|Ubuntu 24.04| CI | CI | CI | CI | CI | CI | ci | CI | ci | CI | ci |
|
|
470
|
+
|Mac OS 15.7.7| CI | x | x | CI | CI | CI | ci | x | | x | |
|
|
471
|
+
|Windows Server 2022| CI | | | | | | | | | | |
|
|
472
|
+
|Windows Server 2025| | | | CI | | CI | CI mswin</br>CI ucrt | | | | |
|
|
453
473
|
|
|
454
474
|
Key: `CI` - tested in CI, should work; `ci` - tested in CI, might fail; `x` - known working; `o` - known failing.
|
|
455
475
|
|
data/lib/rubyzip.rb
ADDED
|
@@ -114,18 +114,20 @@ module Zip
|
|
|
114
114
|
|
|
115
115
|
def unpack_64_e_o_c_d(buffer) # :nodoc:
|
|
116
116
|
_, # ZIP64_END_OF_CD_SIG. We know we have this at this point.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
size_of_zip64_e_o_c_d,
|
|
118
|
+
_version_made_by,
|
|
119
|
+
_version_needed_for_extract,
|
|
120
|
+
_number_of_this_disk,
|
|
121
|
+
_number_of_disk_with_start_of_cdir,
|
|
122
|
+
_total_number_of_entries_in_cdir_on_this_disk,
|
|
123
123
|
@size,
|
|
124
|
-
|
|
124
|
+
size_in_bytes,
|
|
125
125
|
@cdir_offset = buffer.unpack('VQ<vvVVQ<Q<Q<Q<')
|
|
126
126
|
|
|
127
|
+
validate_size!(@size, size_in_bytes)
|
|
128
|
+
|
|
127
129
|
zip64_extensible_data_size =
|
|
128
|
-
|
|
130
|
+
size_of_zip64_e_o_c_d - ZIP64_STATIC_EOCD_SIZE + 12
|
|
129
131
|
@zip64_extensible_data = if zip64_extensible_data_size.zero?
|
|
130
132
|
''
|
|
131
133
|
else
|
|
@@ -145,25 +147,29 @@ module Zip
|
|
|
145
147
|
|
|
146
148
|
# Unpack the EOCD and return a boolean indicating whether this header is
|
|
147
149
|
# complete without needing Zip64 extensions.
|
|
148
|
-
def unpack_e_o_c_d(buffer) # :nodoc:
|
|
150
|
+
def unpack_e_o_c_d(buffer) # :nodoc:
|
|
149
151
|
_, # END_OF_CD_SIG. We know we have this at this point.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
number_of_this_disk,
|
|
153
|
+
number_of_disk_with_start_of_cdir,
|
|
154
|
+
total_number_of_entries_in_cdir_on_this_disk,
|
|
153
155
|
@size,
|
|
154
|
-
|
|
156
|
+
size_in_bytes,
|
|
155
157
|
@cdir_offset,
|
|
156
158
|
comment_length = buffer.unpack('VvvvvVVv')
|
|
157
159
|
|
|
160
|
+
complete = !([number_of_this_disk, number_of_disk_with_start_of_cdir,
|
|
161
|
+
total_number_of_entries_in_cdir_on_this_disk, @size].any?(0xFFFF) ||
|
|
162
|
+
size_in_bytes == 0xFFFFFFFF || @cdir_offset == 0xFFFFFFFF)
|
|
163
|
+
|
|
164
|
+
validate_size!(@size, size_in_bytes) if complete
|
|
165
|
+
|
|
158
166
|
@comment = if comment_length.positive?
|
|
159
167
|
buffer.slice(STATIC_EOCD_SIZE, comment_length)
|
|
160
168
|
else
|
|
161
169
|
''
|
|
162
170
|
end
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
@total_number_of_entries_in_cdir_on_this_disk, @size].any?(0xFFFF) ||
|
|
166
|
-
@size_in_bytes == 0xFFFFFFFF || @cdir_offset == 0xFFFFFFFF)
|
|
172
|
+
complete
|
|
167
173
|
end
|
|
168
174
|
|
|
169
175
|
def read_central_directory_entries(io) # :nodoc:
|
|
@@ -180,7 +186,7 @@ module Zip
|
|
|
180
186
|
@entry_set = EntrySet.new
|
|
181
187
|
@size.times do
|
|
182
188
|
entry = Entry.read_c_dir_entry(io)
|
|
183
|
-
|
|
189
|
+
break unless entry
|
|
184
190
|
|
|
185
191
|
offset = if entry.zip64?
|
|
186
192
|
entry.extra[:zip64].relative_header_offset
|
|
@@ -258,6 +264,15 @@ module Zip
|
|
|
258
264
|
|
|
259
265
|
[io.tell, io.read]
|
|
260
266
|
end
|
|
267
|
+
|
|
268
|
+
def validate_size!(size, size_in_bytes)
|
|
269
|
+
return if size * CDIR_ENTRY_STATIC_HEADER_LENGTH <= size_in_bytes
|
|
270
|
+
|
|
271
|
+
error = EntryNumberMismatchError.new(size, size_in_bytes)
|
|
272
|
+
raise error if Zip.validate_declared_number_of_entries
|
|
273
|
+
|
|
274
|
+
warn error.message
|
|
275
|
+
end
|
|
261
276
|
end
|
|
262
277
|
end
|
|
263
278
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
3
5
|
module Zip
|
|
4
6
|
module TraditionalEncryption # :nodoc:
|
|
5
7
|
def initialize(password)
|
|
@@ -44,7 +46,7 @@ module Zip
|
|
|
44
46
|
def header(mtime)
|
|
45
47
|
[].tap do |header|
|
|
46
48
|
(header_bytesize - 2).times do
|
|
47
|
-
header <<
|
|
49
|
+
header << SecureRandom.rand(0..255)
|
|
48
50
|
end
|
|
49
51
|
header << (mtime.to_binary_dos_time & 0xff)
|
|
50
52
|
header << (mtime.to_binary_dos_time >> 8)
|
data/lib/zip/entry.rb
CHANGED
|
@@ -290,7 +290,7 @@ module Zip
|
|
|
290
290
|
dest_dir = ::File.absolute_path(destination_directory || '.')
|
|
291
291
|
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
|
292
292
|
|
|
293
|
-
unless extract_path.start_with?(dest_dir)
|
|
293
|
+
unless extract_path.start_with?(dest_dir + ::File::SEPARATOR) || dest_dir == ::File::SEPARATOR
|
|
294
294
|
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
|
295
295
|
return self
|
|
296
296
|
end
|
data/lib/zip/errors.rb
CHANGED
|
@@ -109,6 +109,22 @@ module Zip
|
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
class EntryNumberMismatchError < Error
|
|
113
|
+
# Create a new EntryNumberMismatchError with the specified size and size in bytes.
|
|
114
|
+
def initialize(size, size_in_bytes)
|
|
115
|
+
super()
|
|
116
|
+
@size = size
|
|
117
|
+
@size_in_bytes = size_in_bytes
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The message returned by this error.
|
|
121
|
+
def message
|
|
122
|
+
"Zip consistency problem: an impossibly high number of entries (#{@size}) " \
|
|
123
|
+
'is declared in this file, compared to the size of the central directory ' \
|
|
124
|
+
"(#{@size_in_bytes} bytes)."
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
112
128
|
# Error raised if a split archive is read. Rubyzip does not support reading
|
|
113
129
|
# split archives.
|
|
114
130
|
class SplitArchiveError < Error
|
data/lib/zip/version.rb
CHANGED
data/lib/zip.rb
CHANGED
|
@@ -54,7 +54,8 @@ module Zip
|
|
|
54
54
|
:warn_invalid_date,
|
|
55
55
|
:case_insensitive_match,
|
|
56
56
|
:force_entry_names_encoding,
|
|
57
|
-
:validate_entry_sizes
|
|
57
|
+
:validate_entry_sizes,
|
|
58
|
+
:validate_declared_number_of_entries
|
|
58
59
|
|
|
59
60
|
DEFAULT_RESTORE_OPTIONS = {
|
|
60
61
|
restore_ownership: false,
|
|
@@ -78,6 +79,7 @@ module Zip
|
|
|
78
79
|
@case_insensitive_match = false
|
|
79
80
|
@force_entry_names_encoding = nil
|
|
80
81
|
@validate_entry_sizes = true
|
|
82
|
+
@validate_declared_number_of_entries = false # Set this to `true` in v4.0.0?
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
# Set options for RubyZip in one block.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubyzip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Haines
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- LICENSE.md
|
|
136
136
|
- README.md
|
|
137
137
|
- Rakefile
|
|
138
|
+
- lib/rubyzip.rb
|
|
138
139
|
- lib/zip.rb
|
|
139
140
|
- lib/zip/central_directory.rb
|
|
140
141
|
- lib/zip/compressor.rb
|
|
@@ -195,9 +196,9 @@ licenses:
|
|
|
195
196
|
- BSD-2-Clause
|
|
196
197
|
metadata:
|
|
197
198
|
bug_tracker_uri: https://github.com/rubyzip/rubyzip/issues
|
|
198
|
-
changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.
|
|
199
|
-
documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.
|
|
200
|
-
source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.
|
|
199
|
+
changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.4.0/Changelog.md
|
|
200
|
+
documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.4.0
|
|
201
|
+
source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.4.0
|
|
201
202
|
wiki_uri: https://github.com/rubyzip/rubyzip/wiki
|
|
202
203
|
rubygems_mfa_required: 'true'
|
|
203
204
|
rdoc_options: []
|