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
data/lib/zip/file.rb CHANGED
@@ -64,25 +64,38 @@ module Zip
64
64
 
65
65
  # Opens a zip archive. Pass true as the second parameter to create
66
66
  # a new archive if it doesn't exist already.
67
- def initialize(file_name, create = false, buffer = false, options = {})
67
+ def initialize(path_or_io, create = false, buffer = false, options = {})
68
68
  super()
69
- @name = file_name
69
+ @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
70
70
  @comment = ''
71
71
  @create = create ? true : false # allow any truthy value to mean true
72
- case
73
- when !buffer && ::File.size?(file_name)
72
+
73
+ if ::File.size?(@name.to_s)
74
+ # There is a file, which exists, that is associated with this zip.
74
75
  @create = false
75
- @file_permissions = ::File.stat(file_name).mode
76
- ::File.open(name, 'rb') do |f|
77
- read_from_stream(f)
76
+ @file_permissions = ::File.stat(@name).mode
77
+
78
+ if buffer
79
+ read_from_stream(path_or_io)
80
+ else
81
+ ::File.open(@name, 'rb') do |f|
82
+ read_from_stream(f)
83
+ end
78
84
  end
79
- when @create
85
+ elsif buffer && path_or_io.size > 0
86
+ # This zip is probably a non-empty StringIO.
87
+ read_from_stream(path_or_io)
88
+ elsif @create
89
+ # This zip is completely new/empty and is to be created.
80
90
  @entry_set = EntrySet.new
81
- when ::File.zero?(file_name)
82
- raise Error, "File #{file_name} has zero size. Did you mean to pass the create flag?"
91
+ elsif ::File.zero?(@name)
92
+ # A file exists, but it is empty.
93
+ raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
83
94
  else
84
- raise Error, "File #{file_name} not found"
95
+ # Everything is wrong.
96
+ raise Error, "File #{@name} not found"
85
97
  end
98
+
86
99
  @stored_entries = @entry_set.dup
87
100
  @stored_comment = @comment
88
101
  @restore_ownership = options[:restore_ownership] || false
@@ -120,17 +133,16 @@ module Zip
120
133
  unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
121
134
  raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
122
135
  end
123
- if io.is_a?(::String)
124
- require 'stringio'
125
- io = ::StringIO.new(io)
126
- elsif io.respond_to?(:binmode)
127
- # https://github.com/rubyzip/rubyzip/issues/119
128
- io.binmode
129
- end
136
+
137
+ io = ::StringIO.new(io) if io.is_a?(::String)
138
+
139
+ # https://github.com/rubyzip/rubyzip/issues/119
140
+ io.binmode if io.respond_to?(:binmode)
141
+
130
142
  zf = ::Zip::File.new(io, true, true, options)
131
- zf.read_from_stream(io)
132
143
  return zf unless block_given?
133
144
  yield zf
145
+
134
146
  begin
135
147
  zf.write_buffer(io)
136
148
  rescue IOError => e
@@ -151,10 +163,9 @@ module Zip
151
163
  end
152
164
 
153
165
  def get_segment_size_for_split(segment_size)
154
- case
155
- when MIN_SEGMENT_SIZE > segment_size
166
+ if MIN_SEGMENT_SIZE > segment_size
156
167
  MIN_SEGMENT_SIZE
157
- when MAX_SEGMENT_SIZE < segment_size
168
+ elsif MAX_SEGMENT_SIZE < segment_size
158
169
  MAX_SEGMENT_SIZE
159
170
  else
160
171
  segment_size
@@ -162,8 +173,10 @@ module Zip
162
173
  end
163
174
 
164
175
  def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
165
- partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
166
- partial_zip_file_name + ::File.extname(zip_file_name)) unless partial_zip_file_name.nil?
176
+ unless partial_zip_file_name.nil?
177
+ partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
178
+ partial_zip_file_name + ::File.extname(zip_file_name))
179
+ end
167
180
  partial_zip_file_name ||= zip_file_name
168
181
  partial_zip_file_name
169
182
  end
@@ -237,7 +250,7 @@ module Zip
237
250
  # specified. If a block is passed the stream object is passed to the block and
238
251
  # the stream is automatically closed afterwards just as with ruby's builtin
239
252
  # File.open method.
240
- def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
253
+ def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
241
254
  new_entry =
242
255
  if entry.kind_of?(Entry)
243
256
  entry
@@ -274,6 +287,13 @@ module Zip
274
287
  @entry_set << new_entry
275
288
  end
276
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
+
277
297
  # Removes the specified entry.
278
298
  def remove(entry)
279
299
  @entry_set.delete(get_entry(entry))
@@ -306,7 +326,7 @@ module Zip
306
326
  # Commits changes that has been made since the previous commit to
307
327
  # the zip archive.
308
328
  def commit
309
- return unless commit_required?
329
+ return if name.is_a?(StringIO) || !commit_required?
310
330
  on_success_replace do |tmp_file|
311
331
  ::Zip::OutputStream.open(tmp_file) do |zos|
312
332
  @entry_set.each do |e|
@@ -366,7 +386,7 @@ module Zip
366
386
  end
367
387
 
368
388
  # Creates a directory
369
- def mkdir(entryName, permissionInt = 0755)
389
+ def mkdir(entryName, permissionInt = 0o755)
370
390
  raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
371
391
  entryName = entryName.dup.to_s
372
392
  entryName << '/' unless entryName.end_with?('/')
@@ -142,9 +142,9 @@ module Zip
142
142
 
143
143
  def ftype
144
144
  if file?
145
- return 'file'
145
+ 'file'
146
146
  elsif directory?
147
- return 'directory'
147
+ 'directory'
148
148
  else
149
149
  raise StandardError, 'Unknown file type'
150
150
  end
@@ -198,30 +198,30 @@ module Zip
198
198
  alias grpowned? exists?
199
199
 
200
200
  def readable?(fileName)
201
- unix_mode_cmp(fileName, 0444)
201
+ unix_mode_cmp(fileName, 0o444)
202
202
  end
203
203
  alias readable_real? readable?
204
204
 
205
205
  def writable?(fileName)
206
- unix_mode_cmp(fileName, 0222)
206
+ unix_mode_cmp(fileName, 0o222)
207
207
  end
208
208
  alias writable_real? writable?
209
209
 
210
210
  def executable?(fileName)
211
- unix_mode_cmp(fileName, 0111)
211
+ unix_mode_cmp(fileName, 0o111)
212
212
  end
213
213
  alias executable_real? executable?
214
214
 
215
215
  def setuid?(fileName)
216
- unix_mode_cmp(fileName, 04000)
216
+ unix_mode_cmp(fileName, 0o4000)
217
217
  end
218
218
 
219
219
  def setgid?(fileName)
220
- unix_mode_cmp(fileName, 02000)
220
+ unix_mode_cmp(fileName, 0o2000)
221
221
  end
222
222
 
223
223
  def sticky?(fileName)
224
- unix_mode_cmp(fileName, 01000)
224
+ unix_mode_cmp(fileName, 0o1000)
225
225
  end
226
226
 
227
227
  def umask(*args)
@@ -237,8 +237,8 @@ module Zip
237
237
  expand_path(fileName) == '/' || (!entry.nil? && entry.directory?)
238
238
  end
239
239
 
240
- def open(fileName, openMode = 'r', permissionInt = 0644, &block)
241
- openMode.gsub!('b', '') # ignore b option
240
+ def open(fileName, openMode = 'r', permissionInt = 0o644, &block)
241
+ openMode.delete!('b') # ignore b option
242
242
  case openMode
243
243
  when 'r'
244
244
  @mappedZip.get_input_stream(fileName, &block)
@@ -260,7 +260,7 @@ module Zip
260
260
  # Returns nil for not found and nil for directories
261
261
  def size?(fileName)
262
262
  entry = @mappedZip.find_entry(fileName)
263
- (entry.nil? || entry.directory?) ? nil : entry.size
263
+ entry.nil? || entry.directory? ? nil : entry.size
264
264
  end
265
265
 
266
266
  def chown(ownerInt, groupInt, *filenames)
@@ -498,7 +498,7 @@ module Zip
498
498
  alias rmdir delete
499
499
  alias unlink delete
500
500
 
501
- def mkdir(entryName, permissionInt = 0755)
501
+ def mkdir(entryName, permissionInt = 0o755)
502
502
  @mappedZip.mkdir(entryName, permissionInt)
503
503
  end
504
504
 
@@ -573,6 +573,10 @@ module Zip
573
573
  @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc)
574
574
  end
575
575
 
576
+ def glob(pattern, *flags, &block)
577
+ @zipFile.glob(expand_to_entry(pattern), *flags, &block)
578
+ end
579
+
576
580
  def read(fileName)
577
581
  @zipFile.read(expand_to_entry(fileName))
578
582
  end
@@ -586,7 +590,7 @@ module Zip
586
590
  &continueOnExistsProc)
587
591
  end
588
592
 
589
- def mkdir(fileName, permissionInt = 0755)
593
+ def mkdir(fileName, permissionInt = 0o755)
590
594
  @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
591
595
  end
592
596
 
data/lib/zip/inflater.rb CHANGED
@@ -3,9 +3,9 @@ module Zip
3
3
  def initialize(input_stream, decrypter = NullDecrypter.new)
4
4
  super(input_stream)
5
5
  @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
6
- @output_buffer = ''
6
+ @output_buffer = ''.dup
7
7
  @has_returned_empty_string = false
8
- @decrypter = decrypter
8
+ @decrypter = decrypter
9
9
  end
10
10
 
11
11
  def sysread(number_of_bytes = nil, buf = '')
@@ -129,23 +129,26 @@ module Zip
129
129
  end
130
130
  if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \
131
131
  && @current_entry.compressed_size == 0 \
132
- && @current_entry.size == 0 && !@internal
132
+ && @current_entry.size == 0 && !@complete_entry
133
133
  raise GPFBit3Error,
134
134
  'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \
135
135
  'Please use ::Zip::File instead of ::Zip::InputStream'
136
136
  end
137
- @decompressor = get_decompressor
137
+ @decompressor = get_decompressor
138
138
  flush
139
139
  @current_entry
140
140
  end
141
141
 
142
142
  def get_decompressor
143
- case
144
- when @current_entry.nil?
143
+ if @current_entry.nil?
145
144
  ::Zip::NullDecompressor
146
- when @current_entry.compression_method == ::Zip::Entry::STORED
147
- ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size)
148
- when @current_entry.compression_method == ::Zip::Entry::DEFLATED
145
+ elsif @current_entry.compression_method == ::Zip::Entry::STORED
146
+ if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
147
+ ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size)
148
+ else
149
+ ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size)
150
+ end
151
+ elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED
149
152
  header = @archive_io.read(@decrypter.header_bytesize)
150
153
  @decrypter.reset!(header)
151
154
  ::Zip::Inflater.new(@archive_io, @decrypter)
@@ -33,7 +33,7 @@ module Zip
33
33
  sysread(number_of_bytes, buf)
34
34
  end
35
35
 
36
- if tbuf.nil? || tbuf.length == 0
36
+ if tbuf.nil? || tbuf.empty?
37
37
  return nil if number_of_bytes
38
38
  return ''
39
39
  end
@@ -15,7 +15,7 @@ module Zip
15
15
  end
16
16
 
17
17
  def printf(a_format_string, *params)
18
- self << sprintf(a_format_string, *params)
18
+ self << format(a_format_string, *params)
19
19
  end
20
20
 
21
21
  def putc(an_object)
@@ -87,11 +87,11 @@ module Zip
87
87
  # +entry+ can be a ZipEntry object or a string.
88
88
  def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression)
89
89
  raise Error, 'zip stream is closed' if @closed
90
- if entry_name.kind_of?(Entry)
91
- new_entry = entry_name
92
- else
93
- new_entry = Entry.new(@file_name, entry_name.to_s)
94
- end
90
+ new_entry = if entry_name.kind_of?(Entry)
91
+ entry_name
92
+ else
93
+ Entry.new(@file_name, entry_name.to_s)
94
+ end
95
95
  new_entry.comment = comment unless comment.nil?
96
96
  unless extra.nil?
97
97
  new_entry.extra = extra.is_a?(ExtraField) ? extra : ExtraField.new(extra.to_s)
@@ -1,5 +1,5 @@
1
1
  module Zip
2
- class PassThruDecompressor < Decompressor #:nodoc:all
2
+ class PassThruDecompressor < Decompressor #:nodoc:all
3
3
  def initialize(input_stream, chars_to_read)
4
4
  super(input_stream)
5
5
  @chars_to_read = chars_to_read
@@ -5,7 +5,7 @@ module Zip
5
5
  dirname = if zipfile.is_a?(::String)
6
6
  ::File.dirname(zipfile)
7
7
  else
8
- '.'
8
+ nil
9
9
  end
10
10
  @temp_file = Tempfile.new(::File.basename(name), dirname)
11
11
  @temp_file.binmode
data/lib/zip/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Zip
2
- VERSION = '1.2.1'
2
+ VERSION = '1.3.0'
3
3
  end
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
@@ -19,37 +19,36 @@ class ZipFileGenerator
19
19
 
20
20
  # Zip the input directory.
21
21
  def write
22
- entries = Dir.entries(@input_dir) - %w(. ..)
22
+ entries = Dir.entries(@input_dir) - %w[. ..]
23
23
 
24
- ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io|
25
- write_entries entries, '', io
24
+ ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
25
+ write_entries entries, '', zipfile
26
26
  end
27
27
  end
28
28
 
29
29
  private
30
30
 
31
31
  # A helper method to make the recursion work.
32
- def write_entries(entries, path, io)
32
+ def write_entries(entries, path, zipfile)
33
33
  entries.each do |e|
34
- zip_file_path = path == '' ? e : File.join(path, e)
35
- disk_file_path = File.join(@input_dir, zip_file_path)
36
- puts "Deflating #{disk_file_path}"
34
+ zipfile_path = path == '' ? e : File.join(path, e)
35
+ disk_file_path = File.join(@input_dir, zipfile_path)
37
36
 
38
37
  if File.directory? disk_file_path
39
- recursively_deflate_directory(disk_file_path, io, zip_file_path)
38
+ recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
40
39
  else
41
- put_into_archive(disk_file_path, io, zip_file_path)
40
+ put_into_archive(disk_file_path, zipfile, zipfile_path)
42
41
  end
43
42
  end
44
43
  end
45
44
 
46
- def recursively_deflate_directory(disk_file_path, io, zip_file_path)
47
- io.mkdir zip_file_path
48
- subdir = Dir.entries(disk_file_path) - %w(. ..)
49
- write_entries subdir, zip_file_path, io
45
+ def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
46
+ zipfile.mkdir zipfile_path
47
+ subdir = Dir.entries(disk_file_path) - %w[. ..]
48
+ write_entries subdir, zipfile_path, zipfile
50
49
  end
51
50
 
52
- def put_into_archive(disk_file_path, io, zip_file_path)
53
- io.add(zip_file_path, disk_file_path)
51
+ def put_into_archive(disk_file_path, zipfile, zipfile_path)
52
+ zipfile.add(zipfile_path, disk_file_path)
54
53
  end
55
54
  end
@@ -31,7 +31,7 @@ class MainApp < Gtk::Window
31
31
  sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
32
32
  box.pack_start(sw, true, true, 0)
33
33
 
34
- @clist = Gtk::CList.new(%w(Name Size Compression))
34
+ @clist = Gtk::CList.new(%w[Name Size Compression])
35
35
  @clist.set_selection_mode(Gtk::SELECTION_BROWSE)
36
36
  @clist.set_column_width(0, 120)
37
37
  @clist.set_column_width(1, 120)
data/samples/qtzip.rb CHANGED
@@ -65,7 +65,7 @@ class ZipDialog < ZipDialogUI
65
65
  end
66
66
  puts "selected_items.size = #{selected_items.size}"
67
67
  puts "unselected_items.size = #{unselected_items.size}"
68
- items = selected_items.size > 0 ? selected_items : unselected_items
68
+ items = !selected_items.empty? ? selected_items : unselected_items
69
69
  puts "items.size = #{items.size}"
70
70
 
71
71
  d = Qt::FileDialog.get_existing_directory(nil, self)
data/samples/zipfind.rb CHANGED
@@ -31,7 +31,7 @@ module Zip
31
31
  end
32
32
  end
33
33
 
34
- if __FILE__ == $0
34
+ if $0 == __FILE__
35
35
  module ZipFindConsoleRunner
36
36
  PATH_ARG_INDEX = 0
37
37
  FILENAME_PATTERN_ARG_INDEX = 1
@@ -47,7 +47,7 @@ if __FILE__ == $0
47
47
  end
48
48
 
49
49
  def self.check_args(args)
50
- if (args.size != 3)
50
+ if args.size != 3
51
51
  usage
52
52
  exit
53
53
  end
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class ZipCentralDirectoryEntryTest < MiniTest::Test
4
4
  def test_read_from_stream
5
- File.open('test/data/testDirectory.bin', 'rb') do |file|
5
+ File.open('test/data/testDirectory.bin', 'rb') do |file|
6
6
  entry = ::Zip::Entry.read_c_dir_entry(file)
7
7
 
8
8
  assert_equal('longAscii.txt', entry.name)
Binary file
@@ -0,0 +1,10 @@
1
+ # Based on 'relative2' in https://github.com/jwilk/path-traversal-samples,
2
+ # but create the local `tmp` folder before adding the symlink. Otherwise
3
+ # we may bail out before we get to trying to create the file.
4
+ all: relative1.zip
5
+ relative1.zip:
6
+ rm -f $(@)
7
+ mkdir -p -m 755 tmp/tmp
8
+ umask 022 && echo moo > moo
9
+ cd tmp && zip -X ../$(@) tmp tmp/../../moo
10
+ rm -rf tmp moo
@@ -0,0 +1,5 @@
1
+ # Path Traversal Samples
2
+
3
+ Copied from https://github.com/jwilk/path-traversal-samples on 2018-08-26.
4
+
5
+ License: MIT
Binary file
@@ -0,0 +1,3 @@
1
+ # Path Traversal Samples
2
+
3
+ Copied from https://github.com/tuzovakaoff/zip_path_traversal on 2018-08-25.
Binary file
data/test/errors_test.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+
2
3
  require 'test_helper'
3
4
 
4
5
  class ErrorsTest < MiniTest::Test
@@ -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