rubyzip 1.2.4 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +60 -13
- data/lib/zip.rb +3 -1
- data/lib/zip/entry.rb +12 -0
- data/lib/zip/errors.rb +1 -0
- data/lib/zip/file.rb +7 -0
- data/lib/zip/version.rb +1 -1
- data/test/file_extract_test.rb +62 -0
- data/test/file_test.rb +20 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
|
4
|
+
data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9fa8a9f92c4789f06a1691eee6201918a753f1356eb6f2ef99fe9c777d46d661b7a8f96ce71b7a18406a4e1a4098f81b8a282babe2a7284ae0c8c04d03cf758
|
7
|
+
data.tar.gz: e4650b6f572ef4bcdb33cff4bd0c7ddbd399b04278bd644d11ea29defe057510706d15d0b8ac918df28280d13f4bdfec28526aaad258ff52136ce209f29a35dd
|
data/README.md
CHANGED
@@ -152,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved.
|
|
152
152
|
### Reading a Zip file
|
153
153
|
|
154
154
|
```ruby
|
155
|
+
MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this)
|
155
156
|
Zip::File.open('foo.zip') do |zip_file|
|
156
157
|
# Handle entries one by one
|
157
158
|
zip_file.each do |entry|
|
158
|
-
# Extract to file/directory/symlink
|
159
159
|
puts "Extracting #{entry.name}"
|
160
|
-
entry.
|
160
|
+
raise 'File too large when extracted' if entry.size > MAX_SIZE
|
161
|
+
|
162
|
+
# Extract to file or directory based on name in the archive
|
163
|
+
entry.extract
|
161
164
|
|
162
165
|
# Read into memory
|
163
166
|
content = entry.get_input_stream.read
|
@@ -165,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file|
|
|
165
168
|
|
166
169
|
# Find specific entry
|
167
170
|
entry = zip_file.glob('*.csv').first
|
171
|
+
raise 'File too large when extracted' if entry.size > MAX_SIZE
|
168
172
|
puts entry.get_input_stream.read
|
169
173
|
end
|
170
174
|
```
|
@@ -219,6 +223,8 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) }
|
|
219
223
|
|
220
224
|
## Configuration
|
221
225
|
|
226
|
+
### Existing Files
|
227
|
+
|
222
228
|
By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so:
|
223
229
|
|
224
230
|
```ruby
|
@@ -233,18 +239,63 @@ Additionally, if you want to configure rubyzip to overwrite existing files while
|
|
233
239
|
Zip.continue_on_exists_proc = true
|
234
240
|
```
|
235
241
|
|
242
|
+
### Non-ASCII Names
|
243
|
+
|
236
244
|
If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option:
|
237
245
|
|
238
246
|
```ruby
|
239
247
|
Zip.unicode_names = true
|
240
248
|
```
|
241
249
|
|
250
|
+
Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so:
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
Zip.force_entry_names_encoding = 'UTF-8'
|
254
|
+
```
|
255
|
+
|
256
|
+
Allowed encoding names are the same as accepted by `String#force_encoding`
|
257
|
+
|
258
|
+
### Date Validation
|
259
|
+
|
242
260
|
Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
|
243
261
|
|
244
262
|
```ruby
|
245
263
|
Zip.warn_invalid_date = false
|
246
264
|
```
|
247
265
|
|
266
|
+
### Size Validation
|
267
|
+
|
268
|
+
**This setting defaults to `false` in rubyzip 1.3 for backward compatibility, but it will default to `true` in rubyzip 2.0.**
|
269
|
+
|
270
|
+
If you set
|
271
|
+
```
|
272
|
+
Zip.validate_entry_sizes = true
|
273
|
+
```
|
274
|
+
then `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:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
MAX_FILE_SIZE = 10 * 1024**2 # 10MiB
|
278
|
+
MAX_FILES = 100
|
279
|
+
Zip::File.open('foo.zip') do |zip_file|
|
280
|
+
num_files = 0
|
281
|
+
zip_file.each do |entry|
|
282
|
+
num_files += 1 if entry.file?
|
283
|
+
raise 'Too many extracted files' if num_files > MAX_FILES
|
284
|
+
raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE
|
285
|
+
entry.extract
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with
|
291
|
+
```ruby
|
292
|
+
Zip.validate_entry_sizes = false
|
293
|
+
```
|
294
|
+
|
295
|
+
Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream.
|
296
|
+
|
297
|
+
### Default Compression
|
298
|
+
|
248
299
|
You can set the default compression level like so:
|
249
300
|
|
250
301
|
```ruby
|
@@ -253,13 +304,17 @@ Zip.default_compression = Zlib::DEFAULT_COMPRESSION
|
|
253
304
|
|
254
305
|
It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
|
255
306
|
|
256
|
-
|
307
|
+
### Zip64 Support
|
308
|
+
|
309
|
+
By default, Zip64 support is disabled for writing. To enable it do this:
|
257
310
|
|
258
311
|
```ruby
|
259
|
-
Zip.
|
312
|
+
Zip.write_zip64_support = true
|
260
313
|
```
|
261
314
|
|
262
|
-
|
315
|
+
_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
|
316
|
+
|
317
|
+
### Block Form
|
263
318
|
|
264
319
|
You can set multiple settings at the same time by using a block:
|
265
320
|
|
@@ -272,14 +327,6 @@ You can set multiple settings at the same time by using a block:
|
|
272
327
|
end
|
273
328
|
```
|
274
329
|
|
275
|
-
By default, Zip64 support is disabled for writing. To enable it do this:
|
276
|
-
|
277
|
-
```ruby
|
278
|
-
Zip.write_zip64_support = true
|
279
|
-
```
|
280
|
-
|
281
|
-
_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
|
282
|
-
|
283
330
|
## Developing
|
284
331
|
|
285
332
|
To run the test you need to do this:
|
data/lib/zip.rb
CHANGED
@@ -42,7 +42,8 @@ module Zip
|
|
42
42
|
:write_zip64_support,
|
43
43
|
:warn_invalid_date,
|
44
44
|
:case_insensitive_match,
|
45
|
-
:force_entry_names_encoding
|
45
|
+
:force_entry_names_encoding,
|
46
|
+
:validate_entry_sizes
|
46
47
|
|
47
48
|
def reset!
|
48
49
|
@_ran_once = false
|
@@ -54,6 +55,7 @@ module Zip
|
|
54
55
|
@write_zip64_support = false
|
55
56
|
@warn_invalid_date = true
|
56
57
|
@case_insensitive_match = false
|
58
|
+
@validate_entry_sizes = false
|
57
59
|
end
|
58
60
|
|
59
61
|
def setup
|
data/lib/zip/entry.rb
CHANGED
@@ -603,9 +603,21 @@ module Zip
|
|
603
603
|
get_input_stream do |is|
|
604
604
|
set_extra_attributes_on_path(dest_path)
|
605
605
|
|
606
|
+
bytes_written = 0
|
607
|
+
warned = false
|
606
608
|
buf = ''.dup
|
607
609
|
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
608
610
|
os << buf
|
611
|
+
bytes_written += buf.bytesize
|
612
|
+
if bytes_written > size && !warned
|
613
|
+
message = "Entry #{name} should be #{size}B but is larger when inflated"
|
614
|
+
if ::Zip.validate_entry_sizes
|
615
|
+
raise ::Zip::EntrySizeError, message
|
616
|
+
else
|
617
|
+
puts "WARNING: #{message}"
|
618
|
+
warned = true
|
619
|
+
end
|
620
|
+
end
|
609
621
|
end
|
610
622
|
end
|
611
623
|
end
|
data/lib/zip/errors.rb
CHANGED
data/lib/zip/file.rb
CHANGED
@@ -287,6 +287,13 @@ module Zip
|
|
287
287
|
@entry_set << new_entry
|
288
288
|
end
|
289
289
|
|
290
|
+
# Convenience method for adding the contents of a file to the archive
|
291
|
+
# in Stored format (uncompressed)
|
292
|
+
def add_stored(entry, src_path, &continue_on_exists_proc)
|
293
|
+
entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
|
294
|
+
add(entry, src_path, &continue_on_exists_proc)
|
295
|
+
end
|
296
|
+
|
290
297
|
# Removes the specified entry.
|
291
298
|
def remove(entry)
|
292
299
|
@entry_set.delete(get_entry(entry))
|
data/lib/zip/version.rb
CHANGED
data/test/file_extract_test.rb
CHANGED
@@ -10,6 +10,10 @@ class ZipFileExtractTest < MiniTest::Test
|
|
10
10
|
::File.delete(EXTRACTED_FILENAME) if ::File.exist?(EXTRACTED_FILENAME)
|
11
11
|
end
|
12
12
|
|
13
|
+
def teardown
|
14
|
+
::Zip.reset!
|
15
|
+
end
|
16
|
+
|
13
17
|
def test_extract
|
14
18
|
::Zip::File.open(TEST_ZIP.zip_name) do |zf|
|
15
19
|
zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME)
|
@@ -80,4 +84,62 @@ class ZipFileExtractTest < MiniTest::Test
|
|
80
84
|
end
|
81
85
|
assert(!File.exist?(outFile))
|
82
86
|
end
|
87
|
+
|
88
|
+
def test_extract_incorrect_size
|
89
|
+
# The uncompressed size fields in the zip file cannot be trusted. This makes
|
90
|
+
# it harder for callers to validate the sizes of the files they are
|
91
|
+
# extracting, which can lead to denial of service. See also
|
92
|
+
# https://en.wikipedia.org/wiki/Zip_bomb
|
93
|
+
Dir.mktmpdir do |tmp|
|
94
|
+
real_zip = File.join(tmp, 'real.zip')
|
95
|
+
fake_zip = File.join(tmp, 'fake.zip')
|
96
|
+
file_name = 'a'
|
97
|
+
true_size = 500_000
|
98
|
+
fake_size = 1
|
99
|
+
|
100
|
+
::Zip::File.open(real_zip, ::Zip::File::CREATE) do |zf|
|
101
|
+
zf.get_output_stream(file_name) do |os|
|
102
|
+
os.write 'a' * true_size
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
compressed_size = nil
|
107
|
+
::Zip::File.open(real_zip) do |zf|
|
108
|
+
a_entry = zf.find_entry(file_name)
|
109
|
+
compressed_size = a_entry.compressed_size
|
110
|
+
assert_equal true_size, a_entry.size
|
111
|
+
end
|
112
|
+
|
113
|
+
true_size_bytes = [compressed_size, true_size, file_name.size].pack('LLS')
|
114
|
+
fake_size_bytes = [compressed_size, fake_size, file_name.size].pack('LLS')
|
115
|
+
|
116
|
+
data = File.binread(real_zip)
|
117
|
+
assert data.include?(true_size_bytes)
|
118
|
+
data.gsub! true_size_bytes, fake_size_bytes
|
119
|
+
|
120
|
+
File.open(fake_zip, 'wb') do |file|
|
121
|
+
file.write data
|
122
|
+
end
|
123
|
+
|
124
|
+
Dir.chdir tmp do
|
125
|
+
::Zip::File.open(fake_zip) do |zf|
|
126
|
+
a_entry = zf.find_entry(file_name)
|
127
|
+
assert_equal fake_size, a_entry.size
|
128
|
+
|
129
|
+
::Zip.validate_entry_sizes = false
|
130
|
+
a_entry.extract
|
131
|
+
assert_equal true_size, File.size(file_name)
|
132
|
+
FileUtils.rm file_name
|
133
|
+
|
134
|
+
::Zip.validate_entry_sizes = true
|
135
|
+
error = assert_raises ::Zip::EntrySizeError do
|
136
|
+
a_entry.extract
|
137
|
+
end
|
138
|
+
assert_equal \
|
139
|
+
'Entry a should be 1B but is larger when inflated',
|
140
|
+
error.message
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
83
145
|
end
|
data/test/file_test.rb
CHANGED
@@ -204,6 +204,26 @@ class ZipFileTest < MiniTest::Test
|
|
204
204
|
zfRead.get_input_stream(entryName) { |zis| zis.read })
|
205
205
|
end
|
206
206
|
|
207
|
+
def test_add_stored
|
208
|
+
srcFile = 'test/data/file2.txt'
|
209
|
+
entryName = 'newEntryName.rb'
|
210
|
+
assert(::File.exist?(srcFile))
|
211
|
+
zf = ::Zip::File.new(EMPTY_FILENAME, ::Zip::File::CREATE)
|
212
|
+
zf.add_stored(entryName, srcFile)
|
213
|
+
zf.close
|
214
|
+
|
215
|
+
zfRead = ::Zip::File.new(EMPTY_FILENAME)
|
216
|
+
entry = zfRead.entries.first
|
217
|
+
assert_equal('', zfRead.comment)
|
218
|
+
assert_equal(1, zfRead.entries.length)
|
219
|
+
assert_equal(entryName, entry.name)
|
220
|
+
assert_equal(File.size(srcFile), entry.size)
|
221
|
+
assert_equal(entry.size, entry.compressed_size)
|
222
|
+
assert_equal(::Zip::Entry::STORED, entry.compression_method)
|
223
|
+
AssertEntry.assert_contents(srcFile,
|
224
|
+
zfRead.get_input_stream(entryName) { |zis| zis.read })
|
225
|
+
end
|
226
|
+
|
207
227
|
def test_recover_permissions_after_add_files_to_archive
|
208
228
|
srcZip = TEST_ZIP.zip_name
|
209
229
|
::File.chmod(0o664, srcZip)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyzip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Simonov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -211,7 +211,12 @@ files:
|
|
211
211
|
homepage: http://github.com/rubyzip/rubyzip
|
212
212
|
licenses:
|
213
213
|
- BSD 2-Clause
|
214
|
-
metadata:
|
214
|
+
metadata:
|
215
|
+
bug_tracker_uri: https://github.com/rubyzip/rubyzip/issues
|
216
|
+
changelog_uri: https://github.com/rubyzip/rubyzip/blob/v1.3.0/Changelog.md
|
217
|
+
documentation_uri: https://www.rubydoc.info/gems/rubyzip/1.3.0
|
218
|
+
source_code_uri: https://github.com/rubyzip/rubyzip/tree/v1.3.0
|
219
|
+
wiki_uri: https://github.com/rubyzip/rubyzip/wiki
|
215
220
|
post_install_message:
|
216
221
|
rdoc_options: []
|
217
222
|
require_paths:
|