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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 971ff85e75ec367c6d983a2c5fdcb24f8c5d1a4a450068b6b925a2df735cd1aa
4
- data.tar.gz: 665b1e4f89f765cd9138222b08ac7759ee8e3ad4ad6e7c68171f2afb523b2f9f
3
+ metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
4
+ data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
5
5
  SHA512:
6
- metadata.gz: 4322b88da024f5625fa1824c80a8ee8b07e9970baea5937b5a11004d21038a590aeb07c4beb2296d639fb67c1146f2ace736bef7faffb87afcc065538df37afb
7
- data.tar.gz: 77ba210a9a4d66b35fa43a75ac72126c0df5fb65d04e79c50e2168698aeae66a8cf19c27fb86ccb39a6670a3e079bda953c22fd9917ed7a7b0f33c83753e3107
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.extract(dest_file)
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
- 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:
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.force_entry_names_encoding = 'UTF-8'
312
+ Zip.write_zip64_support = true
260
313
  ```
261
314
 
262
- Allowed encoding names are the same as accepted by `String#force_encoding`
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
@@ -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
@@ -4,6 +4,7 @@ module Zip
4
4
  class DestinationFileExistsError < Error; end
5
5
  class CompressionMethodError < Error; end
6
6
  class EntryNameError < Error; end
7
+ class EntrySizeError < Error; end
7
8
  class InternalError < Error; end
8
9
  class GPFBit3Error < Error; end
9
10
 
@@ -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))
@@ -1,3 +1,3 @@
1
1
  module Zip
2
- VERSION = '1.2.4'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -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
@@ -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.2.4
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-06 00:00:00.000000000 Z
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: