rubyzip 3.2.2 → 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 +31 -0
- data/LICENSE.md +1 -1
- data/README.md +27 -7
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +32 -17
- data/lib/zip/crypto/decrypted_io.rb +5 -5
- data/lib/zip/crypto/traditional_encryption.rb +3 -1
- data/lib/zip/entry.rb +5 -6
- data/lib/zip/errors.rb +16 -0
- data/lib/zip/inflater.rb +5 -5
- data/lib/zip/input_stream.rb +25 -5
- data/lib/zip/ioextras/abstract_input_stream.rb +142 -60
- data/lib/zip/pass_thru_decompressor.rb +6 -6
- 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,34 @@
|
|
|
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
|
+
|
|
18
|
+
# 3.3.1 (2026-05-30)
|
|
19
|
+
|
|
20
|
+
- Reinstate default param for `InputStream#sysread`. [#663](https://github.com/rubyzip/rubyzip/issues/663)
|
|
21
|
+
|
|
22
|
+
# 3.3.0 (2026-05-02)
|
|
23
|
+
|
|
24
|
+
- Refactor `InputStream` and `AbstractInputStream`.
|
|
25
|
+
[#661](https://github.com/rubyzip/rubyzip/pull/661)
|
|
26
|
+
|
|
27
|
+
Tooling/internal:
|
|
28
|
+
|
|
29
|
+
- Update Actions to use checkout@v5.
|
|
30
|
+
- Add Ruby4.0 to the CI matrix. [#659](https://github.com/rubyzip/rubyzip/pull/659)
|
|
31
|
+
|
|
1
32
|
# 3.2.2 (2025-11-02)
|
|
2
33
|
|
|
3
34
|
- Fix reading EOCDs when header signatures are in an Entry payload. [#656](https://github.com/rubyzip/rubyzip/issues/656)
|
data/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
BSD 2-Clause License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2002-
|
|
3
|
+
Copyright (c) 2002-2026, The Rubyzip Developers
|
|
4
4
|
|
|
5
5
|
Redistribution and use in source and binary forms, with or without
|
|
6
6
|
modification, are permitted provided that the following conditions are met:
|
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
|
|
|
@@ -8,13 +8,13 @@ module Zip
|
|
|
8
8
|
@io = io
|
|
9
9
|
@decrypter = decrypter
|
|
10
10
|
@bytes_remaining = compressed_size
|
|
11
|
-
@buffer = +''
|
|
11
|
+
@buffer = +''.b
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def read(
|
|
15
|
-
return (
|
|
14
|
+
def read(maxlen = nil)
|
|
15
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
16
16
|
|
|
17
|
-
while
|
|
17
|
+
while maxlen.nil? || (@buffer.bytesize < maxlen)
|
|
18
18
|
break if input_finished?
|
|
19
19
|
|
|
20
20
|
@buffer << produce_input
|
|
@@ -22,7 +22,7 @@ module Zip
|
|
|
22
22
|
|
|
23
23
|
@decrypter.check_integrity!(@io) if input_finished?
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
@buffer.slice!(0...(maxlen || @buffer.bytesize))
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
@@ -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
|
|
@@ -751,21 +751,20 @@ module Zip
|
|
|
751
751
|
|
|
752
752
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
753
753
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
754
|
-
raise
|
|
754
|
+
raise DestinationExistsError, dest_path
|
|
755
755
|
end
|
|
756
756
|
|
|
757
757
|
::File.open(dest_path, 'wb') do |os|
|
|
758
758
|
get_input_stream do |is|
|
|
759
759
|
bytes_written = 0
|
|
760
760
|
warned = false
|
|
761
|
-
buf =
|
|
762
|
-
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
|
761
|
+
while (buf = is.sysread(Decompressor::CHUNK_SIZE))
|
|
763
762
|
os << buf
|
|
764
763
|
bytes_written += buf.bytesize
|
|
765
764
|
next unless bytes_written > size && !warned
|
|
766
765
|
|
|
767
|
-
error =
|
|
768
|
-
raise error if
|
|
766
|
+
error = EntrySizeError.new(self)
|
|
767
|
+
raise error if Zip.validate_entry_sizes
|
|
769
768
|
|
|
770
769
|
warn "WARNING: #{error.message}"
|
|
771
770
|
warned = true
|
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/inflater.rb
CHANGED
|
@@ -5,20 +5,20 @@ module Zip
|
|
|
5
5
|
def initialize(*args)
|
|
6
6
|
super
|
|
7
7
|
|
|
8
|
-
@buffer = +''
|
|
8
|
+
@buffer = +''.b
|
|
9
9
|
@zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def read(
|
|
13
|
-
return (
|
|
12
|
+
def read(maxlen = nil)
|
|
13
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
14
14
|
|
|
15
|
-
while
|
|
15
|
+
while maxlen.nil? || (@buffer.bytesize < maxlen)
|
|
16
16
|
break if input_finished?
|
|
17
17
|
|
|
18
18
|
@buffer << produce_input
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
@buffer.slice!(0...(maxlen || @buffer.bytesize))
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def eof?
|
data/lib/zip/input_stream.rb
CHANGED
|
@@ -89,9 +89,29 @@ module Zip
|
|
|
89
89
|
open_entry
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
# Modelled after IO#sysread.
|
|
93
|
+
#
|
|
94
|
+
# Reads up to maxlen bytes from the stream; returns a string
|
|
95
|
+
# (either a new string or the given out_string).
|
|
96
|
+
# Its encoding is the unchanged encoding of out_string, if out_string is
|
|
97
|
+
# given; ASCII-8BIT, otherwise. Output contains maxlen bytes from the
|
|
98
|
+
# stream, if available; otherwise contains all available bytes, if any
|
|
99
|
+
# available; otherwise is an empty string.
|
|
100
|
+
#
|
|
101
|
+
# This method should not be used with buffered input stream-reader methods,
|
|
102
|
+
# such as #read, #readline, #gets.
|
|
103
|
+
def sysread(maxlen = nil, out_string = nil)
|
|
104
|
+
# Remove the default value for maxlen for version 4.
|
|
105
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
106
|
+
|
|
107
|
+
output = produce_input(maxlen)
|
|
108
|
+
|
|
109
|
+
if out_string.nil?
|
|
110
|
+
output.force_encoding(Encoding::ASCII_8BIT)
|
|
111
|
+
else
|
|
112
|
+
encoding = out_string.encoding
|
|
113
|
+
out_string.replace(output).force_encoding(encoding)
|
|
114
|
+
end
|
|
95
115
|
end
|
|
96
116
|
|
|
97
117
|
# Returns the size of the current entry, or `nil` if there isn't one.
|
|
@@ -197,8 +217,8 @@ module Zip
|
|
|
197
217
|
decompressor_class.new(io, decompressed_size)
|
|
198
218
|
end
|
|
199
219
|
|
|
200
|
-
def produce_input # :nodoc:
|
|
201
|
-
@decompressor.read(
|
|
220
|
+
def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
|
|
221
|
+
@decompressor.read(maxlen)
|
|
202
222
|
end
|
|
203
223
|
|
|
204
224
|
def input_finished? # :nodoc:
|
|
@@ -3,126 +3,208 @@
|
|
|
3
3
|
module Zip
|
|
4
4
|
module IOExtras # :nodoc:
|
|
5
5
|
# Implements many of the convenience methods of IO
|
|
6
|
-
# such as gets, getc, readline and readlines
|
|
6
|
+
# such as gets, getc, read, readline and readlines
|
|
7
7
|
# depends on: input_finished?, produce_input and read
|
|
8
|
-
module AbstractInputStream
|
|
8
|
+
module AbstractInputStream
|
|
9
9
|
include Enumerable
|
|
10
10
|
include FakeIO
|
|
11
11
|
|
|
12
|
-
def initialize
|
|
12
|
+
def initialize # :nodoc:
|
|
13
13
|
super
|
|
14
14
|
@lineno = 0
|
|
15
15
|
@pos = 0
|
|
16
|
-
@output_buffer = +''
|
|
16
|
+
@output_buffer = +''.b
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Returns (or sets) the current line number in the decompressed
|
|
20
|
+
# (possibly decrypted) data stream. See the Line Number documentation
|
|
21
|
+
# for the IO class for more information.
|
|
19
22
|
attr_accessor :lineno
|
|
23
|
+
|
|
24
|
+
# Returns the current position (in bytes) in the decompressed (possibly
|
|
25
|
+
# decrypted) data stream.
|
|
20
26
|
attr_reader :pos
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
# Reads bytes from the stream decompressed (possibly decrypted) data
|
|
29
|
+
# stream. If `maxlen` is `nil`, reads all bytes; otherwise, reads up to
|
|
30
|
+
# `maxlen` bytes. If `maxlen` is zero, returns an empty string.
|
|
31
|
+
#
|
|
32
|
+
# Returns a string (either a new string or the given `out_string`)
|
|
33
|
+
# containing the bytes read. The string's encoding is the unchanged
|
|
34
|
+
# encoding of `out_string`, if `out_string` is given; `ASCII-8BIT`,
|
|
35
|
+
# otherwise.
|
|
36
|
+
def read(maxlen = nil, out_string = nil) # rubocop:disable Metrics/PerceivedComplexity
|
|
37
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
38
|
+
|
|
23
39
|
tbuf = if @output_buffer.bytesize > 0
|
|
24
|
-
if
|
|
25
|
-
@output_buffer.slice!(0,
|
|
40
|
+
if maxlen && maxlen <= @output_buffer.bytesize
|
|
41
|
+
@output_buffer.slice!(0, maxlen)
|
|
26
42
|
else
|
|
27
|
-
|
|
28
|
-
rbuf =
|
|
43
|
+
maxlen -= @output_buffer.bytesize if maxlen
|
|
44
|
+
rbuf = produce_input(maxlen)
|
|
29
45
|
out = @output_buffer
|
|
30
46
|
out << rbuf if rbuf
|
|
31
|
-
@output_buffer = ''
|
|
47
|
+
@output_buffer = +''.b
|
|
32
48
|
out
|
|
33
49
|
end
|
|
34
50
|
else
|
|
35
|
-
|
|
51
|
+
produce_input(maxlen)
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
if tbuf.nil? || tbuf.empty?
|
|
39
|
-
return nil if
|
|
55
|
+
return nil if maxlen&.positive?
|
|
40
56
|
|
|
41
57
|
return ''
|
|
42
58
|
end
|
|
43
59
|
|
|
44
60
|
@pos += tbuf.length
|
|
45
61
|
|
|
46
|
-
if
|
|
47
|
-
|
|
62
|
+
if out_string.nil?
|
|
63
|
+
tbuf.force_encoding(Encoding::ASCII_8BIT)
|
|
48
64
|
else
|
|
49
|
-
|
|
65
|
+
encoding = out_string.encoding
|
|
66
|
+
out_string.replace(tbuf).force_encoding(encoding)
|
|
50
67
|
end
|
|
51
|
-
buf
|
|
52
68
|
end
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
# Reads and returns all remaining lines from the stream. See the Line IO
|
|
71
|
+
# documentation in the IO class for more information.
|
|
72
|
+
#
|
|
73
|
+
# With no arguments given, returns lines as determined by line
|
|
74
|
+
# separator `$/`, or `nil` if none.
|
|
75
|
+
#
|
|
76
|
+
# With only string argument `sep` given, returns lines as
|
|
77
|
+
# determined by line separator `sep`, or `nil` if none. See the
|
|
78
|
+
# Line Separator documentation in the IO class for more information.
|
|
79
|
+
# The two special values for `sep` (`nil` and `""`) are honoured.
|
|
80
|
+
#
|
|
81
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
82
|
+
# in each line; see the Line Limit documentation in the IO class for more
|
|
83
|
+
# information.
|
|
84
|
+
#
|
|
85
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
86
|
+
#
|
|
87
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
88
|
+
# are to be omitted.
|
|
89
|
+
def readlines(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
90
|
+
each(sep, limit, chomp: chomp).to_a
|
|
58
91
|
end
|
|
59
92
|
|
|
60
|
-
|
|
61
|
-
|
|
93
|
+
# Reads and returns a line from the stream. See the Line IO
|
|
94
|
+
# documentation in the IO class for more information.
|
|
95
|
+
#
|
|
96
|
+
# With no arguments given, returns the next line as determined by line
|
|
97
|
+
# separator `$/`, or `nil` if none.
|
|
98
|
+
#
|
|
99
|
+
# With only string argument `sep` given, returns the next line as
|
|
100
|
+
# determined by line separator `sep`, or `nil` if none. See the
|
|
101
|
+
# Line Separator documentation in the IO class for more information.
|
|
102
|
+
# The two special values for `sep` (`nil` and `""`) are honoured.
|
|
103
|
+
#
|
|
104
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
105
|
+
# in the line; see the Line Limit documentation in the IO class for more
|
|
106
|
+
# information.
|
|
107
|
+
#
|
|
108
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
109
|
+
#
|
|
110
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
111
|
+
# are to be omitted.
|
|
112
|
+
def gets(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
113
|
+
if sep.nil?
|
|
114
|
+
return nil if eof?
|
|
115
|
+
|
|
116
|
+
@lineno = @lineno.next
|
|
117
|
+
return read(limit)
|
|
118
|
+
end
|
|
62
119
|
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
elsif
|
|
67
|
-
|
|
68
|
-
a_sep_string = $INPUT_RECORD_SEPARATOR
|
|
69
|
-
else
|
|
70
|
-
number_of_bytes = nil
|
|
71
|
-
a_sep_string = a_sep_string.to_str if a_sep_string
|
|
120
|
+
if sep.respond_to?(:to_int)
|
|
121
|
+
limit = sep.to_int
|
|
122
|
+
sep = $INPUT_RECORD_SEPARATOR
|
|
123
|
+
elsif sep&.empty?
|
|
124
|
+
sep = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}"
|
|
72
125
|
end
|
|
73
126
|
|
|
74
|
-
|
|
127
|
+
buffer_index = 0
|
|
128
|
+
while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
|
|
129
|
+
break if limit && @output_buffer.bytesize >= limit
|
|
75
130
|
|
|
76
|
-
|
|
131
|
+
if input_finished?
|
|
132
|
+
return nil if @output_buffer.empty?
|
|
77
133
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return @output_buffer.empty? ? nil : flush if input_finished?
|
|
134
|
+
@lineno = @lineno.next
|
|
135
|
+
@pos += @output_buffer.bytesize
|
|
136
|
+
return @output_buffer.slice!(0..)
|
|
137
|
+
end
|
|
83
138
|
|
|
139
|
+
buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
|
|
84
140
|
@output_buffer << produce_input
|
|
85
|
-
over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes
|
|
86
141
|
end
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@pos +=
|
|
92
|
-
@output_buffer.slice!(0
|
|
142
|
+
|
|
143
|
+
limit ||= @output_buffer.bytesize
|
|
144
|
+
cut_index = sep_index ? [sep_index + sep.bytesize, limit].min : limit
|
|
145
|
+
@lineno = @lineno.next
|
|
146
|
+
@pos += cut_index
|
|
147
|
+
chomp ? @output_buffer.slice!(0, cut_index).chomp(sep) : @output_buffer.slice!(0, cut_index)
|
|
93
148
|
end
|
|
94
149
|
|
|
95
|
-
def ungetc(byte)
|
|
150
|
+
def ungetc(byte) # :nodoc:
|
|
96
151
|
@output_buffer = byte.chr + @output_buffer
|
|
97
152
|
end
|
|
98
153
|
|
|
99
|
-
def flush
|
|
100
|
-
|
|
101
|
-
@output_buffer = +''
|
|
102
|
-
ret_val
|
|
154
|
+
def flush # :nodoc:
|
|
155
|
+
@output_buffer.slice!(0..)
|
|
103
156
|
end
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
# Reads a line as with #gets, but raises `EOFError` if already at
|
|
159
|
+
# end-of-stream.
|
|
160
|
+
#
|
|
161
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
162
|
+
# are to be omitted.
|
|
163
|
+
def readline(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
164
|
+
raise EOFError if eof?
|
|
108
165
|
|
|
109
|
-
|
|
166
|
+
gets(sep, limit, chomp: chomp)
|
|
110
167
|
end
|
|
111
168
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
# Calls the block with each remaining line read from the stream.
|
|
170
|
+
# Does nothing if already at end-of-stream. See the Line IO
|
|
171
|
+
# documentation in the IO class for more information.
|
|
172
|
+
#
|
|
173
|
+
# With no arguments given, reads lines as determined by line separator
|
|
174
|
+
# `$/`. With only string argument `sep` given, reads lines as determined
|
|
175
|
+
# by line separator `sep`. See the Line Separator documentation in the
|
|
176
|
+
# IO class for more information. The two special values for `sep`
|
|
177
|
+
# (`nil` and `""`) are honoured.
|
|
178
|
+
#
|
|
179
|
+
# With only integer argument `limit` given, limits the number of bytes
|
|
180
|
+
# in each line; see the Line Limit documentation in the IO class for
|
|
181
|
+
# more information.
|
|
182
|
+
#
|
|
183
|
+
# With arguments `sep` and `limit` given, combines the two behaviors.
|
|
184
|
+
#
|
|
185
|
+
# Optional keyword argument `chomp` specifies whether line separators
|
|
186
|
+
# are to be omitted.
|
|
187
|
+
#
|
|
188
|
+
# Returns an `Enumerator` if no block is given.
|
|
189
|
+
def each(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
|
|
190
|
+
return to_enum(:each, sep, limit, chomp: chomp) unless block_given?
|
|
191
|
+
|
|
192
|
+
while (line = gets(sep, limit, chomp: chomp))
|
|
193
|
+
yield line
|
|
194
|
+
end
|
|
116
195
|
end
|
|
117
196
|
|
|
118
|
-
alias each
|
|
197
|
+
alias each_line each
|
|
119
198
|
|
|
199
|
+
# Returns `true` if the stream is positioned at its end, `false`
|
|
200
|
+
# otherwise. See Position documentation in the IO class for more
|
|
201
|
+
# information.
|
|
120
202
|
def eof?
|
|
121
203
|
@output_buffer.empty? && input_finished?
|
|
122
204
|
end
|
|
123
205
|
|
|
124
206
|
# Alias for compatibility. Remove for version 4.
|
|
125
|
-
alias eof eof?
|
|
207
|
+
alias eof eof? # :nodoc:
|
|
126
208
|
end
|
|
127
209
|
end
|
|
128
210
|
end
|
|
@@ -7,15 +7,15 @@ module Zip
|
|
|
7
7
|
@read_so_far = 0
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def read(
|
|
11
|
-
return (
|
|
10
|
+
def read(maxlen = nil)
|
|
11
|
+
return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
|
|
12
12
|
|
|
13
|
-
if
|
|
14
|
-
|
|
13
|
+
if maxlen.nil? || (@read_so_far + maxlen) > decompressed_size
|
|
14
|
+
maxlen = decompressed_size - @read_so_far
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
@read_so_far +=
|
|
18
|
-
input_stream.read(
|
|
17
|
+
@read_so_far += maxlen
|
|
18
|
+
input_stream.read(maxlen)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def eof?
|
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: []
|