rubyzip 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +89 -35
  3. data/lib/zip/central_directory.rb +3 -3
  4. data/lib/zip/compressor.rb +1 -2
  5. data/lib/zip/constants.rb +3 -3
  6. data/lib/zip/crypto/null_encryption.rb +2 -4
  7. data/lib/zip/decompressor.rb +1 -1
  8. data/lib/zip/dos_time.rb +1 -1
  9. data/lib/zip/entry.rb +67 -57
  10. data/lib/zip/entry_set.rb +3 -3
  11. data/lib/zip/errors.rb +1 -0
  12. data/lib/zip/extra_field/generic.rb +1 -1
  13. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  14. data/lib/zip/extra_field.rb +2 -2
  15. data/lib/zip/file.rb +47 -27
  16. data/lib/zip/filesystem.rb +17 -13
  17. data/lib/zip/inflater.rb +2 -2
  18. data/lib/zip/input_stream.rb +10 -7
  19. data/lib/zip/ioextras/abstract_input_stream.rb +1 -1
  20. data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
  21. data/lib/zip/output_stream.rb +5 -5
  22. data/lib/zip/pass_thru_decompressor.rb +1 -1
  23. data/lib/zip/streamable_stream.rb +1 -1
  24. data/lib/zip/version.rb +1 -1
  25. data/lib/zip.rb +11 -1
  26. data/samples/example_recursive.rb +14 -15
  27. data/samples/gtk_ruby_zip.rb +1 -1
  28. data/samples/qtzip.rb +1 -1
  29. data/samples/zipfind.rb +2 -2
  30. data/test/central_directory_entry_test.rb +1 -1
  31. data/test/data/gpbit3stored.zip +0 -0
  32. data/test/data/path_traversal/Makefile +10 -0
  33. data/test/data/path_traversal/jwilk/README.md +5 -0
  34. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  35. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  36. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  37. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  38. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  39. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  40. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  41. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  42. data/test/data/path_traversal/relative1.zip +0 -0
  43. data/test/data/path_traversal/tilde.zip +0 -0
  44. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  45. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  46. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  47. data/test/data/rubycode.zip +0 -0
  48. data/test/errors_test.rb +1 -0
  49. data/test/file_extract_test.rb +62 -0
  50. data/test/file_permissions_test.rb +11 -15
  51. data/test/file_test.rb +92 -9
  52. data/test/filesystem/dir_iterator_test.rb +1 -1
  53. data/test/filesystem/directory_test.rb +29 -11
  54. data/test/filesystem/file_mutating_test.rb +3 -4
  55. data/test/filesystem/file_nonmutating_test.rb +31 -31
  56. data/test/filesystem/file_stat_test.rb +4 -4
  57. data/test/gentestfiles.rb +13 -13
  58. data/test/input_stream_test.rb +6 -6
  59. data/test/ioextras/abstract_output_stream_test.rb +2 -2
  60. data/test/path_traversal_test.rb +141 -0
  61. data/test/test_helper.rb +2 -2
  62. data/test/unicode_file_names_and_comments_test.rb +12 -0
  63. data/test/zip64_full_test.rb +2 -2
  64. metadata +103 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1b7ea085db9b27be420d541113214a73ee044c9f
4
- data.tar.gz: ea28ff723bfd70f2e6f7a02f4fe28b805aa4a47e
2
+ SHA256:
3
+ metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
4
+ data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
5
5
  SHA512:
6
- metadata.gz: 121d7129aa37b72da82b32882401b7837c9cface1edf1b8bfb911cd20a55b2ecdacd308de9b7ed0cb6bc0b45d9d974faf28826efe191ddc36d3eb8431f8fe6f0
7
- data.tar.gz: b1ef9770bcd133de33f61642665d511a11aff21aba880cf1226df0c8e1b3602833679384d76bf6877b19567406f7bd9a60de8bd78aded3f303660fbb80b31bb7
6
+ metadata.gz: f9fa8a9f92c4789f06a1691eee6201918a753f1356eb6f2ef99fe9c777d46d661b7a8f96ce71b7a18406a4e1a4098f81b8a282babe2a7284ae0c8c04d03cf758
7
+ data.tar.gz: e4650b6f572ef4bcdb33cff4bd0c7ddbd399b04278bd644d11ea29defe057510706d15d0b8ac918df28280d13f4bdfec28526aaad258ff52136ce209f29a35dd
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # rubyzip
2
+
2
3
  [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip)
3
4
  [![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip)
4
5
  [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip)
@@ -19,9 +20,10 @@ gem 'zip-zip' # will load compatibility for old rubyzip API.
19
20
 
20
21
  ## Requirements
21
22
 
22
- * Ruby 1.9.2 or greater
23
+ - Ruby 1.9.2 or greater
23
24
 
24
25
  ## Installation
26
+
25
27
  Rubyzip is available on RubyGems:
26
28
 
27
29
  ```
@@ -52,14 +54,15 @@ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
52
54
  # Two arguments:
53
55
  # - The name of the file as it will appear in the archive
54
56
  # - The original file, including the path to find it
55
- zipfile.add(filename, folder + '/' + filename)
57
+ zipfile.add(filename, File.join(folder, filename))
56
58
  end
57
- zipfile.get_output_stream("myFile") { |os| os.write "myFile contains just this" }
59
+ zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
58
60
  end
59
61
  ```
60
62
 
61
63
  ### Zipping a directory recursively
62
- Copy from [here](https://github.com/rubyzip/rubyzip/blob/05916bf89181e1955118fd3ea059f18acac28cc8/samples/example_recursive.rb )
64
+
65
+ Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb)
63
66
 
64
67
  ```ruby
65
68
  require 'zip'
@@ -83,40 +86,37 @@ class ZipFileGenerator
83
86
 
84
87
  # Zip the input directory.
85
88
  def write
86
- entries = Dir.entries(@input_dir) - %w(. ..)
89
+ entries = Dir.entries(@input_dir) - %w[. ..]
87
90
 
88
- ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io|
89
- write_entries entries, '', io
91
+ ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
92
+ write_entries entries, '', zipfile
90
93
  end
91
94
  end
92
95
 
93
96
  private
94
97
 
95
98
  # A helper method to make the recursion work.
96
- def write_entries(entries, path, io)
99
+ def write_entries(entries, path, zipfile)
97
100
  entries.each do |e|
98
- zip_file_path = path == '' ? e : File.join(path, e)
99
- disk_file_path = File.join(@input_dir, zip_file_path)
100
- puts "Deflating #{disk_file_path}"
101
+ zipfile_path = path == '' ? e : File.join(path, e)
102
+ disk_file_path = File.join(@input_dir, zipfile_path)
101
103
 
102
104
  if File.directory? disk_file_path
103
- recursively_deflate_directory(disk_file_path, io, zip_file_path)
105
+ recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
104
106
  else
105
- put_into_archive(disk_file_path, io, zip_file_path)
107
+ put_into_archive(disk_file_path, zipfile, zipfile_path)
106
108
  end
107
109
  end
108
110
  end
109
111
 
110
- def recursively_deflate_directory(disk_file_path, io, zip_file_path)
111
- io.mkdir zip_file_path
112
- subdir = Dir.entries(disk_file_path) - %w(. ..)
113
- write_entries subdir, zip_file_path, io
112
+ def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
113
+ zipfile.mkdir zipfile_path
114
+ subdir = Dir.entries(disk_file_path) - %w[. ..]
115
+ write_entries subdir, zipfile_path, zipfile
114
116
  end
115
117
 
116
- def put_into_archive(disk_file_path, io, zip_file_path)
117
- io.get_output_stream(zip_file_path) do |f|
118
- f.write(File.open(disk_file_path, 'rb').read)
119
- end
118
+ def put_into_archive(disk_file_path, zipfile, zipfile_path)
119
+ zipfile.add(zipfile_path, disk_file_path)
120
120
  end
121
121
  end
122
122
  ```
@@ -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
  ```
@@ -175,9 +179,7 @@ end
175
179
 
176
180
  But there is one exception when it is not working - General Purpose Flag Bit 3.
177
181
 
178
- ```
179
- If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data
180
- ```
182
+ > If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data
181
183
 
182
184
  If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception.
183
185
 
@@ -221,7 +223,9 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) }
221
223
 
222
224
  ## Configuration
223
225
 
224
- 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:
226
+ ### Existing Files
227
+
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:
225
229
 
226
230
  ```ruby
227
231
  Zip.on_exists_proc = true
@@ -235,25 +239,83 @@ Additionally, if you want to configure rubyzip to overwrite existing files while
235
239
  Zip.continue_on_exists_proc = true
236
240
  ```
237
241
 
242
+ ### Non-ASCII Names
243
+
238
244
  If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option:
239
245
 
240
246
  ```ruby
241
247
  Zip.unicode_names = true
242
248
  ```
243
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
+
244
260
  Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
245
261
 
246
262
  ```ruby
247
263
  Zip.warn_invalid_date = false
248
264
  ```
249
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
+
250
299
  You can set the default compression level like so:
251
300
 
252
301
  ```ruby
253
302
  Zip.default_compression = Zlib::DEFAULT_COMPRESSION
254
303
  ```
304
+
255
305
  It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
256
306
 
307
+ ### Zip64 Support
308
+
309
+ By default, Zip64 support is disabled for writing. To enable it do this:
310
+
311
+ ```ruby
312
+ Zip.write_zip64_support = true
313
+ ```
314
+
315
+ _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
316
+
317
+ ### Block Form
318
+
257
319
  You can set multiple settings at the same time by using a block:
258
320
 
259
321
  ```ruby
@@ -265,14 +327,6 @@ You can set multiple settings at the same time by using a block:
265
327
  end
266
328
  ```
267
329
 
268
- By default, Zip64 support is disabled for writing. To enable it do this:
269
-
270
- ```ruby
271
- Zip.write_zip64_support = true
272
- ```
273
-
274
- _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
275
-
276
330
  ## Developing
277
331
 
278
332
  To run the test you need to do this:
@@ -96,7 +96,7 @@ module Zip
96
96
  @size_in_bytes = Entry.read_zip_64_long(buf)
97
97
  @cdir_offset = Entry.read_zip_64_long(buf)
98
98
  @zip_64_extensible = buf.slice!(0, buf.bytesize)
99
- raise Error, 'Zip consistency problem while reading eocd structure' unless buf.size == 0
99
+ raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
100
100
  end
101
101
 
102
102
  def read_e_o_c_d(buf) #:nodoc:
@@ -113,7 +113,7 @@ module Zip
113
113
  else
114
114
  buf.read(comment_length)
115
115
  end
116
- raise Error, 'Zip consistency problem while reading eocd structure' unless buf.size == 0
116
+ raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
117
117
  end
118
118
 
119
119
  def read_central_directory_entries(io) #:nodoc:
@@ -130,7 +130,7 @@ module Zip
130
130
 
131
131
  def read_from_stream(io) #:nodoc:
132
132
  buf = start_buf(io)
133
- if self.zip64_file?(buf)
133
+ if zip64_file?(buf)
134
134
  read_64_e_o_c_d(buf)
135
135
  else
136
136
  read_e_o_c_d(buf)
@@ -1,7 +1,6 @@
1
1
  module Zip
2
2
  class Compressor #:nodoc:all
3
- def finish
4
- end
3
+ def finish; end
5
4
  end
6
5
  end
7
6
 
data/lib/zip/constants.rb CHANGED
@@ -11,9 +11,9 @@ module Zip
11
11
  VERSION_NEEDED_TO_EXTRACT = 20
12
12
  VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
13
13
 
14
- FILE_TYPE_FILE = 010
15
- FILE_TYPE_DIR = 004
16
- FILE_TYPE_SYMLINK = 012
14
+ FILE_TYPE_FILE = 0o10
15
+ FILE_TYPE_DIR = 0o04
16
+ FILE_TYPE_SYMLINK = 0o12
17
17
 
18
18
  FSTYPE_FAT = 0
19
19
  FSTYPE_AMIGA = 1
@@ -24,8 +24,7 @@ module Zip
24
24
  ''
25
25
  end
26
26
 
27
- def reset!
28
- end
27
+ def reset!; end
29
28
  end
30
29
 
31
30
  class NullDecrypter < Decrypter
@@ -35,8 +34,7 @@ module Zip
35
34
  data
36
35
  end
37
36
 
38
- def reset!(_header)
39
- end
37
+ def reset!(_header); end
40
38
  end
41
39
  end
42
40
 
@@ -1,5 +1,5 @@
1
1
  module Zip
2
- class Decompressor #:nodoc:all
2
+ class Decompressor #:nodoc:all
3
3
  CHUNK_SIZE = 32_768
4
4
  def initialize(input_stream)
5
5
  super()
data/lib/zip/dos_time.rb CHANGED
@@ -19,7 +19,7 @@ module Zip
19
19
  end
20
20
 
21
21
  def to_binary_dos_date
22
- (day) +
22
+ day +
23
23
  (month << 5) +
24
24
  ((year - 1980) << 9)
25
25
  end
data/lib/zip/entry.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  module Zip
2
3
  class Entry
3
4
  STORED = 0
@@ -99,7 +100,7 @@ module Zip
99
100
  end
100
101
 
101
102
  # Dynamic checkers
102
- %w(directory file symlink).each do |k|
103
+ %w[directory file symlink].each do |k|
103
104
  define_method "#{k}?" do
104
105
  file_type_is?(k.to_sym)
105
106
  end
@@ -109,6 +110,17 @@ module Zip
109
110
  @name.end_with?('/')
110
111
  end
111
112
 
113
+ # Is the name a relative path, free of `..` patterns that could lead to
114
+ # path traversal attacks? This does NOT handle symlinks; if the path
115
+ # contains symlinks, this check is NOT enough to guarantee safety.
116
+ def name_safe?
117
+ cleanpath = Pathname.new(@name).cleanpath
118
+ return false unless cleanpath.relative?
119
+ root = ::File::SEPARATOR
120
+ naive_expanded_path = ::File.join(root, cleanpath.to_s)
121
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
122
+ end
123
+
112
124
  def local_entry_offset #:nodoc:all
113
125
  local_header_offset + @local_header_size
114
126
  end
@@ -147,14 +159,17 @@ module Zip
147
159
  end
148
160
 
149
161
  # Extracts entry to file dest_path (defaults to @name).
150
- def extract(dest_path = @name, &block)
151
- block ||= proc { ::Zip.on_exists_proc }
152
-
153
- if @name.squeeze('/') =~ /\.{2}(?:\/|\z)/
154
- puts "WARNING: skipped \"../\" path component(s) in #{@name}"
162
+ # NB: The caller is responsible for making sure dest_path is safe, if it
163
+ # is passed.
164
+ def extract(dest_path = nil, &block)
165
+ if dest_path.nil? && !name_safe?
166
+ puts "WARNING: skipped #{@name} as unsafe"
155
167
  return self
156
168
  end
157
169
 
170
+ dest_path ||= @name
171
+ block ||= proc { ::Zip.on_exists_proc }
172
+
158
173
  if directory? || file? || symlink?
159
174
  __send__("create_#{@ftype}", dest_path, &block)
160
175
  else
@@ -239,7 +254,10 @@ module Zip
239
254
  @name = io.read(@name_length)
240
255
  extra = io.read(@extra_length)
241
256
 
242
- @name.gsub!('\\', '/')
257
+ @name.tr!('\\', '/')
258
+ if ::Zip.force_entry_names_encoding
259
+ @name.force_encoding(::Zip.force_entry_names_encoding)
260
+ end
243
261
 
244
262
  if extra && extra.bytesize != @extra_length
245
263
  raise ::Zip::Error, 'Truncated local zip entry header'
@@ -258,13 +276,13 @@ module Zip
258
276
  zip64 = @extra['Zip64']
259
277
  [::Zip::LOCAL_ENTRY_SIGNATURE,
260
278
  @version_needed_to_extract, # version needed to extract
261
- @gp_flags, # @gp_flags ,
279
+ @gp_flags, # @gp_flags
262
280
  @compression_method,
263
- @time.to_binary_dos_time, # @last_mod_time ,
264
- @time.to_binary_dos_date, # @last_mod_date ,
281
+ @time.to_binary_dos_time, # @last_mod_time
282
+ @time.to_binary_dos_date, # @last_mod_date
265
283
  @crc,
266
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
267
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
284
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
285
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
268
286
  name_size,
269
287
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
270
288
  end
@@ -308,7 +326,7 @@ module Zip
308
326
  def set_ftype_from_c_dir_entry
309
327
  @ftype = case @fstype
310
328
  when ::Zip::FSTYPE_UNIX
311
- @unix_perms = (@external_file_attributes >> 16) & 07777
329
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
312
330
  case (@external_file_attributes >> 28)
313
331
  when ::Zip::FILE_TYPE_DIR
314
332
  :directory
@@ -364,6 +382,9 @@ module Zip
364
382
  check_c_dir_entry_signature
365
383
  set_time(@last_mod_date, @last_mod_time)
366
384
  @name = io.read(@name_length)
385
+ if ::Zip.force_entry_names_encoding
386
+ @name.force_encoding(::Zip.force_entry_names_encoding)
387
+ end
367
388
  read_c_dir_extra_field(io)
368
389
  @comment = io.read(@comment_length)
369
390
  check_c_dir_entry_comment_size
@@ -384,14 +405,14 @@ module Zip
384
405
  stat = file_stat(path)
385
406
  @unix_uid = stat.uid
386
407
  @unix_gid = stat.gid
387
- @unix_perms = stat.mode & 07777
408
+ @unix_perms = stat.mode & 0o7777
388
409
  end
389
410
 
390
411
  def set_unix_permissions_on_path(dest_path)
391
412
  # BUG: does not update timestamps into account
392
413
  # ignore setuid/setgid bits by default. honor if @restore_ownership
393
- unix_perms_mask = 01777
394
- unix_perms_mask = 07777 if @restore_ownership
414
+ unix_perms_mask = 0o1777
415
+ unix_perms_mask = 0o7777 if @restore_ownership
395
416
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
396
417
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
397
418
  # File::utimes()
@@ -412,21 +433,21 @@ module Zip
412
433
  @header_signature,
413
434
  @version, # version of encoding software
414
435
  @fstype, # filesystem type
415
- @version_needed_to_extract, # @versionNeededToExtract ,
416
- @gp_flags, # @gp_flags ,
436
+ @version_needed_to_extract, # @versionNeededToExtract
437
+ @gp_flags, # @gp_flags
417
438
  @compression_method,
418
- @time.to_binary_dos_time, # @last_mod_time ,
419
- @time.to_binary_dos_date, # @last_mod_date ,
439
+ @time.to_binary_dos_time, # @last_mod_time
440
+ @time.to_binary_dos_date, # @last_mod_date
420
441
  @crc,
421
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
422
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
442
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
443
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
423
444
  name_size,
424
445
  @extra ? @extra.c_dir_size : 0,
425
446
  comment_size,
426
- (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
447
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
427
448
  @internal_file_attributes, # file type (binary=0, text=1)
428
449
  @external_file_attributes, # native filesystem attributes
429
- (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
450
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
430
451
  @name,
431
452
  @extra,
432
453
  @comment
@@ -439,18 +460,18 @@ module Zip
439
460
  when ::Zip::FSTYPE_UNIX
440
461
  ft = case @ftype
441
462
  when :file
442
- @unix_perms ||= 0644
463
+ @unix_perms ||= 0o644
443
464
  ::Zip::FILE_TYPE_FILE
444
465
  when :directory
445
- @unix_perms ||= 0755
466
+ @unix_perms ||= 0o755
446
467
  ::Zip::FILE_TYPE_DIR
447
468
  when :symlink
448
- @unix_perms ||= 0755
469
+ @unix_perms ||= 0o755
449
470
  ::Zip::FILE_TYPE_SYMLINK
450
471
  end
451
472
 
452
473
  unless ft.nil?
453
- @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
474
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
454
475
  end
455
476
  end
456
477
 
@@ -464,7 +485,7 @@ module Zip
464
485
  def ==(other)
465
486
  return false unless other.class == self.class
466
487
  # Compares contents of local entry and exposed fields
467
- keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
488
+ keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
468
489
  other.__send__(k.to_sym) == __send__(k.to_sym)
469
490
  end
470
491
  keys_equal && time.dos_equals(other.time)
@@ -494,7 +515,7 @@ module Zip
494
515
  end
495
516
  else
496
517
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
497
- zis.instance_variable_set(:@internal, true)
518
+ zis.instance_variable_set(:@complete_entry, self)
498
519
  zis.get_next_entry
499
520
  if block_given?
500
521
  begin
@@ -582,9 +603,21 @@ module Zip
582
603
  get_input_stream do |is|
583
604
  set_extra_attributes_on_path(dest_path)
584
605
 
585
- buf = ''
606
+ bytes_written = 0
607
+ warned = false
608
+ buf = ''.dup
586
609
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
587
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
588
621
  end
589
622
  end
590
623
  end
@@ -607,32 +640,9 @@ module Zip
607
640
 
608
641
  # BUG: create_symlink() does not use &block
609
642
  def create_symlink(dest_path)
610
- stat = nil
611
- begin
612
- stat = ::File.lstat(dest_path)
613
- rescue Errno::ENOENT
614
- end
615
-
616
- io = get_input_stream
617
- linkto = io.read
618
-
619
- if stat
620
- if stat.symlink?
621
- if ::File.readlink(dest_path) == linkto
622
- return
623
- else
624
- raise ::Zip::DestinationFileExistsError,
625
- "Cannot create symlink '#{dest_path}'. " \
626
- 'A symlink already exists with that name'
627
- end
628
- else
629
- raise ::Zip::DestinationFileExistsError,
630
- "Cannot create symlink '#{dest_path}'. " \
631
- 'A file already exists with that name'
632
- end
633
- end
634
-
635
- ::File.symlink(linkto, dest_path)
643
+ # TODO: Symlinks pose security challenges. Symlink support temporarily
644
+ # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
645
+ puts "WARNING: skipped symlink #{dest_path}"
636
646
  end
637
647
 
638
648
  # apply missing data from the zip64 extra information field, if present
data/lib/zip/entry_set.rb CHANGED
@@ -5,7 +5,7 @@ module Zip
5
5
 
6
6
  def initialize(an_enumerable = [])
7
7
  super()
8
- @entry_set = {}
8
+ @entry_set = {}
9
9
  an_enumerable.each { |o| push(o) }
10
10
  end
11
11
 
@@ -33,9 +33,9 @@ module Zip
33
33
  entry if @entry_set.delete(to_key(entry))
34
34
  end
35
35
 
36
- def each(&block)
36
+ def each
37
37
  @entry_set = sorted_entries.dup.each do |_, value|
38
- block.call(value)
38
+ yield(value)
39
39
  end
40
40
  end
41
41
 
data/lib/zip/errors.rb CHANGED
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  module Zip
2
2
  class ExtraField::Generic
3
3
  def self.register_map
4
- if self.const_defined?(:HEADER_ID)
4
+ if const_defined?(:HEADER_ID)
5
5
  ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
6
  end
7
7
  end
@@ -6,8 +6,7 @@ module Zip
6
6
  HEADER_ID = ['9999'].pack('H*') # this ID is used by other libraries such as .NET's Ionic.zip
7
7
  register_map
8
8
 
9
- def initialize(_binstr = nil)
10
- end
9
+ def initialize(_binstr = nil); end
11
10
 
12
11
  def pack_for_local
13
12
  "\x00" * 16
@@ -8,7 +8,7 @@ module Zip
8
8
 
9
9
  def extra_field_type_exist(binstr, id, len, i)
10
10
  field_name = ID_MAP[id].name
11
- if self.member?(field_name)
11
+ if member?(field_name)
12
12
  self[field_name].merge(binstr[i, len + 4])
13
13
  else
14
14
  field_obj = ID_MAP[id].new(binstr[i, len + 4])
@@ -26,7 +26,7 @@ module Zip
26
26
  end
27
27
 
28
28
  def create_unknown_item
29
- s = ''
29
+ s = ''.dup
30
30
  class << s
31
31
  alias_method :to_c_dir_bin, :to_s
32
32
  alias_method :to_local_bin, :to_s