rubyzip 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +95 -43
  3. data/lib/zip.rb +11 -1
  4. data/lib/zip/central_directory.rb +3 -3
  5. data/lib/zip/compressor.rb +1 -2
  6. data/lib/zip/constants.rb +3 -3
  7. data/lib/zip/crypto/null_encryption.rb +2 -4
  8. data/lib/zip/decompressor.rb +1 -1
  9. data/lib/zip/dos_time.rb +1 -1
  10. data/lib/zip/entry.rb +70 -54
  11. data/lib/zip/entry_set.rb +4 -4
  12. data/lib/zip/errors.rb +1 -0
  13. data/lib/zip/extra_field.rb +2 -2
  14. data/lib/zip/extra_field/generic.rb +1 -1
  15. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  16. data/lib/zip/file.rb +62 -51
  17. data/lib/zip/filesystem.rb +17 -13
  18. data/lib/zip/inflater.rb +2 -2
  19. data/lib/zip/input_stream.rb +10 -7
  20. data/lib/zip/ioextras/abstract_input_stream.rb +1 -1
  21. data/lib/zip/ioextras/abstract_output_stream.rb +3 -3
  22. data/lib/zip/output_stream.rb +5 -5
  23. data/lib/zip/pass_thru_decompressor.rb +1 -1
  24. data/lib/zip/streamable_stream.rb +1 -1
  25. data/lib/zip/version.rb +1 -1
  26. data/samples/example_recursive.rb +15 -18
  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 +2 -2
  31. data/test/crypto/null_encryption_test.rb +6 -2
  32. data/test/data/gpbit3stored.zip +0 -0
  33. data/test/data/path_traversal/Makefile +10 -0
  34. data/test/data/path_traversal/jwilk/README.md +5 -0
  35. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  36. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  37. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  38. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  39. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  40. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  41. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  42. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  43. data/test/data/path_traversal/relative1.zip +0 -0
  44. data/test/data/path_traversal/tilde.zip +0 -0
  45. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  46. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  47. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  48. data/test/data/rubycode.zip +0 -0
  49. data/test/entry_set_test.rb +13 -2
  50. data/test/entry_test.rb +3 -12
  51. data/test/errors_test.rb +1 -0
  52. data/test/file_extract_test.rb +62 -0
  53. data/test/file_permissions_test.rb +39 -43
  54. data/test/file_test.rb +115 -12
  55. data/test/filesystem/dir_iterator_test.rb +1 -1
  56. data/test/filesystem/directory_test.rb +29 -11
  57. data/test/filesystem/file_mutating_test.rb +3 -4
  58. data/test/filesystem/file_nonmutating_test.rb +34 -34
  59. data/test/filesystem/file_stat_test.rb +5 -5
  60. data/test/gentestfiles.rb +17 -13
  61. data/test/input_stream_test.rb +10 -10
  62. data/test/ioextras/abstract_input_stream_test.rb +1 -1
  63. data/test/ioextras/abstract_output_stream_test.rb +2 -2
  64. data/test/ioextras/fake_io_test.rb +1 -1
  65. data/test/local_entry_test.rb +1 -1
  66. data/test/path_traversal_test.rb +141 -0
  67. data/test/test_helper.rb +16 -3
  68. data/test/unicode_file_names_and_comments_test.rb +12 -0
  69. data/test/zip64_full_test.rb +2 -2
  70. metadata +103 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4af5fd26173e16759a74f190b293f84cc02d605b
4
- data.tar.gz: 05666235e929fdb9bd6abe6807d3bf9583e65244
2
+ SHA256:
3
+ metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
4
+ data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
5
5
  SHA512:
6
- metadata.gz: 2f4b82040f81e517ddb65660bea4a3368ec5e150bd094051d0ae3a515184c2eef55e32bcf5f49877bd432b3331a809402977e1c4b2a045695c8d2f45ab86f43c
7
- data.tar.gz: 31fdb0e8c66d737d341161d02faf343786d0a0d1beca058cff4532886a2cfe268b738c6307b042c22a3186909953cd6879656a7660ef607eea906f407f1aada1
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,30 +54,29 @@ 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
- require 'rubygems'
66
68
  require 'zip'
69
+
67
70
  # This is a simple example which uses rubyzip to
68
71
  # recursively generate a zip file from the contents of
69
72
  # a specified directory. The directory itself is not
70
73
  # included in the archive, rather just its contents.
71
74
  #
72
75
  # Usage:
73
- # require /path/to/the/ZipFileGenerator/Class
74
- # directoryToZip = "/tmp/input"
75
- # outputFile = "/tmp/out.zip"
76
- # zf = ZipFileGenerator.new(directoryToZip, outputFile)
77
- # zf.write()
78
-
76
+ # directory_to_zip = "/tmp/input"
77
+ # output_file = "/tmp/out.zip"
78
+ # zf = ZipFileGenerator.new(directory_to_zip, output_file)
79
+ # zf.write()
79
80
  class ZipFileGenerator
80
81
  # Initialize with the directory to zip and the location of the output archive.
81
82
  def initialize(input_dir, output_file)
@@ -85,40 +86,37 @@ class ZipFileGenerator
85
86
 
86
87
  # Zip the input directory.
87
88
  def write
88
- entries = Dir.entries(@input_dir) - %w(. ..)
89
+ entries = Dir.entries(@input_dir) - %w[. ..]
89
90
 
90
- ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io|
91
- write_entries entries, '', io
91
+ ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
92
+ write_entries entries, '', zipfile
92
93
  end
93
94
  end
94
95
 
95
96
  private
96
97
 
97
98
  # A helper method to make the recursion work.
98
- def write_entries(entries, path, io)
99
+ def write_entries(entries, path, zipfile)
99
100
  entries.each do |e|
100
- zip_file_path = path == '' ? e : File.join(path, e)
101
- disk_file_path = File.join(@input_dir, zip_file_path)
102
- puts "Deflating #{disk_file_path}"
101
+ zipfile_path = path == '' ? e : File.join(path, e)
102
+ disk_file_path = File.join(@input_dir, zipfile_path)
103
103
 
104
104
  if File.directory? disk_file_path
105
- recursively_deflate_directory(disk_file_path, io, zip_file_path)
105
+ recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
106
106
  else
107
- put_into_archive(disk_file_path, io, zip_file_path)
107
+ put_into_archive(disk_file_path, zipfile, zipfile_path)
108
108
  end
109
109
  end
110
110
  end
111
111
 
112
- def recursively_deflate_directory(disk_file_path, io, zip_file_path)
113
- io.mkdir zip_file_path
114
- subdir = Dir.entries(disk_file_path) - %w(. ..)
115
- 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
116
116
  end
117
117
 
118
- def put_into_archive(disk_file_path, io, zip_file_path)
119
- io.get_output_stream(zip_file_path) do |f|
120
- f.puts(File.open(disk_file_path, 'rb').read)
121
- end
118
+ def put_into_archive(disk_file_path, zipfile, zipfile_path)
119
+ zipfile.add(zipfile_path, disk_file_path)
122
120
  end
123
121
  end
124
122
  ```
@@ -154,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved.
154
152
  ### Reading a Zip file
155
153
 
156
154
  ```ruby
155
+ MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this)
157
156
  Zip::File.open('foo.zip') do |zip_file|
158
157
  # Handle entries one by one
159
158
  zip_file.each do |entry|
160
- # Extract to file/directory/symlink
161
159
  puts "Extracting #{entry.name}"
162
- 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
163
164
 
164
165
  # Read into memory
165
166
  content = entry.get_input_stream.read
@@ -167,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file|
167
168
 
168
169
  # Find specific entry
169
170
  entry = zip_file.glob('*.csv').first
171
+ raise 'File too large when extracted' if entry.size > MAX_SIZE
170
172
  puts entry.get_input_stream.read
171
173
  end
172
174
  ```
@@ -177,9 +179,7 @@ end
177
179
 
178
180
  But there is one exception when it is not working - General Purpose Flag Bit 3.
179
181
 
180
- ```
181
- 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
182
- ```
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
183
183
 
184
184
  If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception.
185
185
 
@@ -218,12 +218,14 @@ buffer = Zip::OutputStream.write_buffer do |out|
218
218
  out.write rels.to_xml(:indent => 0).gsub("\n","")
219
219
  end
220
220
 
221
- File.open(new_path, "w") {|f| f.write(buffer.string) }
221
+ File.open(new_path, "wb") {|f| f.write(buffer.string) }
222
222
  ```
223
223
 
224
224
  ## Configuration
225
225
 
226
- 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:
227
229
 
228
230
  ```ruby
229
231
  Zip.on_exists_proc = true
@@ -237,25 +239,83 @@ Additionally, if you want to configure rubyzip to overwrite existing files while
237
239
  Zip.continue_on_exists_proc = true
238
240
  ```
239
241
 
242
+ ### Non-ASCII Names
243
+
240
244
  If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option:
241
245
 
242
246
  ```ruby
243
247
  Zip.unicode_names = true
244
248
  ```
245
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
+
246
260
  Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
247
261
 
248
262
  ```ruby
249
263
  Zip.warn_invalid_date = false
250
264
  ```
251
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
+
252
299
  You can set the default compression level like so:
253
300
 
254
301
  ```ruby
255
302
  Zip.default_compression = Zlib::DEFAULT_COMPRESSION
256
303
  ```
304
+
257
305
  It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
258
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
+
259
319
  You can set multiple settings at the same time by using a block:
260
320
 
261
321
  ```ruby
@@ -267,14 +327,6 @@ You can set multiple settings at the same time by using a block:
267
327
  end
268
328
  ```
269
329
 
270
- By default, Zip64 support is disabled for writing. To enable it do this:
271
-
272
- ```ruby
273
- Zip.write_zip64_support = true
274
- ```
275
-
276
- _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
277
-
278
330
  ## Developing
279
331
 
280
332
  To run the test you need to do this:
data/lib/zip.rb CHANGED
@@ -34,7 +34,16 @@ require 'zip/errors'
34
34
 
35
35
  module Zip
36
36
  extend self
37
- attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc, :sort_entries, :default_compression, :write_zip64_support, :warn_invalid_date, :case_insensitive_match
37
+ attr_accessor :unicode_names,
38
+ :on_exists_proc,
39
+ :continue_on_exists_proc,
40
+ :sort_entries,
41
+ :default_compression,
42
+ :write_zip64_support,
43
+ :warn_invalid_date,
44
+ :case_insensitive_match,
45
+ :force_entry_names_encoding,
46
+ :validate_entry_sizes
38
47
 
39
48
  def reset!
40
49
  @_ran_once = false
@@ -46,6 +55,7 @@ module Zip
46
55
  @write_zip64_support = false
47
56
  @warn_invalid_date = true
48
57
  @case_insensitive_match = false
58
+ @validate_entry_sizes = false
49
59
  end
50
60
 
51
61
  def setup
@@ -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
 
@@ -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()
@@ -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
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  module Zip
2
3
  class Entry
3
4
  STORED = 0
@@ -7,6 +8,7 @@ module Zip
7
8
 
8
9
  attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
9
10
  :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
11
+ :internal_file_attributes,
10
12
  :gp_flags, :header_signature, :follow_symlinks,
11
13
  :restore_times, :restore_permissions, :restore_ownership,
12
14
  :unix_uid, :unix_gid, :unix_perms,
@@ -98,7 +100,7 @@ module Zip
98
100
  end
99
101
 
100
102
  # Dynamic checkers
101
- %w(directory file symlink).each do |k|
103
+ %w[directory file symlink].each do |k|
102
104
  define_method "#{k}?" do
103
105
  file_type_is?(k.to_sym)
104
106
  end
@@ -108,6 +110,17 @@ module Zip
108
110
  @name.end_with?('/')
109
111
  end
110
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
+
111
124
  def local_entry_offset #:nodoc:all
112
125
  local_header_offset + @local_header_size
113
126
  end
@@ -146,7 +159,15 @@ module Zip
146
159
  end
147
160
 
148
161
  # Extracts entry to file dest_path (defaults to @name).
149
- def extract(dest_path = @name, &block)
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"
167
+ return self
168
+ end
169
+
170
+ dest_path ||= @name
150
171
  block ||= proc { ::Zip.on_exists_proc }
151
172
 
152
173
  if directory? || file? || symlink?
@@ -233,7 +254,10 @@ module Zip
233
254
  @name = io.read(@name_length)
234
255
  extra = io.read(@extra_length)
235
256
 
236
- @name.gsub!('\\', '/')
257
+ @name.tr!('\\', '/')
258
+ if ::Zip.force_entry_names_encoding
259
+ @name.force_encoding(::Zip.force_entry_names_encoding)
260
+ end
237
261
 
238
262
  if extra && extra.bytesize != @extra_length
239
263
  raise ::Zip::Error, 'Truncated local zip entry header'
@@ -252,13 +276,13 @@ module Zip
252
276
  zip64 = @extra['Zip64']
253
277
  [::Zip::LOCAL_ENTRY_SIGNATURE,
254
278
  @version_needed_to_extract, # version needed to extract
255
- @gp_flags, # @gp_flags ,
279
+ @gp_flags, # @gp_flags
256
280
  @compression_method,
257
- @time.to_binary_dos_time, # @last_mod_time ,
258
- @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
259
283
  @crc,
260
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
261
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
284
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
285
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
262
286
  name_size,
263
287
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
264
288
  end
@@ -302,7 +326,7 @@ module Zip
302
326
  def set_ftype_from_c_dir_entry
303
327
  @ftype = case @fstype
304
328
  when ::Zip::FSTYPE_UNIX
305
- @unix_perms = (@external_file_attributes >> 16) & 07777
329
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
306
330
  case (@external_file_attributes >> 28)
307
331
  when ::Zip::FILE_TYPE_DIR
308
332
  :directory
@@ -357,7 +381,10 @@ module Zip
357
381
  unpack_c_dir_entry(static_sized_fields_buf)
358
382
  check_c_dir_entry_signature
359
383
  set_time(@last_mod_date, @last_mod_time)
360
- @name = io.read(@name_length).tr('\\', '/')
384
+ @name = io.read(@name_length)
385
+ if ::Zip.force_entry_names_encoding
386
+ @name.force_encoding(::Zip.force_entry_names_encoding)
387
+ end
361
388
  read_c_dir_extra_field(io)
362
389
  @comment = io.read(@comment_length)
363
390
  check_c_dir_entry_comment_size
@@ -378,14 +405,14 @@ module Zip
378
405
  stat = file_stat(path)
379
406
  @unix_uid = stat.uid
380
407
  @unix_gid = stat.gid
381
- @unix_perms = stat.mode & 07777
408
+ @unix_perms = stat.mode & 0o7777
382
409
  end
383
410
 
384
411
  def set_unix_permissions_on_path(dest_path)
385
412
  # BUG: does not update timestamps into account
386
413
  # ignore setuid/setgid bits by default. honor if @restore_ownership
387
- unix_perms_mask = 01777
388
- unix_perms_mask = 07777 if @restore_ownership
414
+ unix_perms_mask = 0o1777
415
+ unix_perms_mask = 0o7777 if @restore_ownership
389
416
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
390
417
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
391
418
  # File::utimes()
@@ -406,21 +433,21 @@ module Zip
406
433
  @header_signature,
407
434
  @version, # version of encoding software
408
435
  @fstype, # filesystem type
409
- @version_needed_to_extract, # @versionNeededToExtract ,
410
- @gp_flags, # @gp_flags ,
436
+ @version_needed_to_extract, # @versionNeededToExtract
437
+ @gp_flags, # @gp_flags
411
438
  @compression_method,
412
- @time.to_binary_dos_time, # @last_mod_time ,
413
- @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
414
441
  @crc,
415
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
416
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
442
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
443
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
417
444
  name_size,
418
445
  @extra ? @extra.c_dir_size : 0,
419
446
  comment_size,
420
- (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
447
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
421
448
  @internal_file_attributes, # file type (binary=0, text=1)
422
449
  @external_file_attributes, # native filesystem attributes
423
- (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
450
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
424
451
  @name,
425
452
  @extra,
426
453
  @comment
@@ -433,18 +460,18 @@ module Zip
433
460
  when ::Zip::FSTYPE_UNIX
434
461
  ft = case @ftype
435
462
  when :file
436
- @unix_perms ||= 0644
463
+ @unix_perms ||= 0o644
437
464
  ::Zip::FILE_TYPE_FILE
438
465
  when :directory
439
- @unix_perms ||= 0755
466
+ @unix_perms ||= 0o755
440
467
  ::Zip::FILE_TYPE_DIR
441
468
  when :symlink
442
- @unix_perms ||= 0755
469
+ @unix_perms ||= 0o755
443
470
  ::Zip::FILE_TYPE_SYMLINK
444
471
  end
445
472
 
446
473
  unless ft.nil?
447
- @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
474
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
448
475
  end
449
476
  end
450
477
 
@@ -458,7 +485,7 @@ module Zip
458
485
  def ==(other)
459
486
  return false unless other.class == self.class
460
487
  # Compares contents of local entry and exposed fields
461
- 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|
462
489
  other.__send__(k.to_sym) == __send__(k.to_sym)
463
490
  end
464
491
  keys_equal && time.dos_equals(other.time)
@@ -488,7 +515,7 @@ module Zip
488
515
  end
489
516
  else
490
517
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
491
- zis.instance_variable_set(:@internal, true)
518
+ zis.instance_variable_set(:@complete_entry, self)
492
519
  zis.get_next_entry
493
520
  if block_given?
494
521
  begin
@@ -576,9 +603,21 @@ module Zip
576
603
  get_input_stream do |is|
577
604
  set_extra_attributes_on_path(dest_path)
578
605
 
579
- buf = ''
606
+ bytes_written = 0
607
+ warned = false
608
+ buf = ''.dup
580
609
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
581
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
582
621
  end
583
622
  end
584
623
  end
@@ -601,32 +640,9 @@ module Zip
601
640
 
602
641
  # BUG: create_symlink() does not use &block
603
642
  def create_symlink(dest_path)
604
- stat = nil
605
- begin
606
- stat = ::File.lstat(dest_path)
607
- rescue Errno::ENOENT
608
- end
609
-
610
- io = get_input_stream
611
- linkto = io.read
612
-
613
- if stat
614
- if stat.symlink?
615
- if ::File.readlink(dest_path) == linkto
616
- return
617
- else
618
- raise ::Zip::DestinationFileExistsError,
619
- "Cannot create symlink '#{dest_path}'. " \
620
- 'A symlink already exists with that name'
621
- end
622
- else
623
- raise ::Zip::DestinationFileExistsError,
624
- "Cannot create symlink '#{dest_path}'. " \
625
- 'A file already exists with that name'
626
- end
627
- end
628
-
629
- ::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}"
630
646
  end
631
647
 
632
648
  # apply missing data from the zip64 extra information field, if present