rubyzip 1.1.7 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +137 -54
  3. data/Rakefile +6 -4
  4. data/lib/zip/central_directory.rb +17 -13
  5. data/lib/zip/compressor.rb +1 -2
  6. data/lib/zip/constants.rb +57 -5
  7. data/lib/zip/crypto/decrypted_io.rb +40 -0
  8. data/lib/zip/crypto/null_encryption.rb +4 -6
  9. data/lib/zip/crypto/traditional_encryption.rb +14 -14
  10. data/lib/zip/decompressor.rb +22 -4
  11. data/lib/zip/deflater.rb +8 -6
  12. data/lib/zip/dos_time.rb +17 -13
  13. data/lib/zip/entry.rb +171 -148
  14. data/lib/zip/entry_set.rb +16 -14
  15. data/lib/zip/errors.rb +3 -0
  16. data/lib/zip/extra_field/generic.rb +14 -13
  17. data/lib/zip/extra_field/ntfs.rb +18 -16
  18. data/lib/zip/extra_field/old_unix.rb +12 -11
  19. data/lib/zip/extra_field/universal_time.rb +46 -16
  20. data/lib/zip/extra_field/unix.rb +10 -9
  21. data/lib/zip/extra_field/zip64.rb +15 -12
  22. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  23. data/lib/zip/extra_field.rb +18 -16
  24. data/lib/zip/file.rb +147 -115
  25. data/lib/zip/filesystem.rb +289 -272
  26. data/lib/zip/inflater.rb +24 -36
  27. data/lib/zip/input_stream.rb +44 -28
  28. data/lib/zip/ioextras/abstract_input_stream.rb +24 -17
  29. data/lib/zip/ioextras/abstract_output_stream.rb +4 -6
  30. data/lib/zip/ioextras.rb +2 -4
  31. data/lib/zip/null_compressor.rb +2 -2
  32. data/lib/zip/null_decompressor.rb +3 -11
  33. data/lib/zip/null_input_stream.rb +0 -0
  34. data/lib/zip/output_stream.rb +25 -17
  35. data/lib/zip/pass_thru_compressor.rb +6 -6
  36. data/lib/zip/pass_thru_decompressor.rb +14 -24
  37. data/lib/zip/streamable_directory.rb +3 -3
  38. data/lib/zip/streamable_stream.rb +7 -11
  39. data/lib/zip/version.rb +1 -1
  40. data/lib/zip.rb +15 -6
  41. data/samples/example.rb +29 -39
  42. data/samples/example_filesystem.rb +16 -18
  43. data/samples/example_recursive.rb +31 -25
  44. data/samples/gtk_ruby_zip.rb +84 -0
  45. data/samples/qtzip.rb +23 -32
  46. data/samples/write_simple.rb +10 -13
  47. data/samples/zipfind.rb +33 -40
  48. metadata +50 -141
  49. data/samples/gtkRubyzip.rb +0 -86
  50. data/test/basic_zip_file_test.rb +0 -64
  51. data/test/central_directory_entry_test.rb +0 -73
  52. data/test/central_directory_test.rb +0 -104
  53. data/test/crypto/null_encryption_test.rb +0 -53
  54. data/test/crypto/traditional_encryption_test.rb +0 -80
  55. data/test/data/WarnInvalidDate.zip +0 -0
  56. data/test/data/file1.txt +0 -46
  57. data/test/data/file1.txt.deflatedData +0 -0
  58. data/test/data/file2.txt +0 -1504
  59. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  60. data/test/data/globTest/foo.txt +0 -0
  61. data/test/data/globTest/food.txt +0 -0
  62. data/test/data/globTest.zip +0 -0
  63. data/test/data/mimetype +0 -1
  64. data/test/data/notzippedruby.rb +0 -7
  65. data/test/data/ntfs.zip +0 -0
  66. data/test/data/rubycode.zip +0 -0
  67. data/test/data/rubycode2.zip +0 -0
  68. data/test/data/testDirectory.bin +0 -0
  69. data/test/data/zip64-sample.zip +0 -0
  70. data/test/data/zipWithDirs.zip +0 -0
  71. data/test/data/zipWithEncryption.zip +0 -0
  72. data/test/deflater_test.rb +0 -67
  73. data/test/encryption_test.rb +0 -42
  74. data/test/entry_set_test.rb +0 -138
  75. data/test/entry_test.rb +0 -165
  76. data/test/errors_test.rb +0 -36
  77. data/test/extra_field_test.rb +0 -78
  78. data/test/file_extract_directory_test.rb +0 -56
  79. data/test/file_extract_test.rb +0 -90
  80. data/test/file_split_test.rb +0 -60
  81. data/test/file_test.rb +0 -559
  82. data/test/filesystem/dir_iterator_test.rb +0 -62
  83. data/test/filesystem/directory_test.rb +0 -131
  84. data/test/filesystem/file_mutating_test.rb +0 -100
  85. data/test/filesystem/file_nonmutating_test.rb +0 -514
  86. data/test/filesystem/file_stat_test.rb +0 -66
  87. data/test/gentestfiles.rb +0 -134
  88. data/test/inflater_test.rb +0 -14
  89. data/test/input_stream_test.rb +0 -170
  90. data/test/ioextras/abstract_input_stream_test.rb +0 -103
  91. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  92. data/test/ioextras/fake_io_test.rb +0 -18
  93. data/test/local_entry_test.rb +0 -156
  94. data/test/output_stream_test.rb +0 -129
  95. data/test/pass_thru_compressor_test.rb +0 -31
  96. data/test/pass_thru_decompressor_test.rb +0 -15
  97. data/test/settings_test.rb +0 -92
  98. data/test/test_helper.rb +0 -228
  99. data/test/unicode_file_names_and_comments_test.rb +0 -52
  100. data/test/zip64_full_test.rb +0 -53
  101. data/test/zip64_support_test.rb +0 -15
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
@@ -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,
@@ -32,22 +34,22 @@ module Zip
32
34
  end
33
35
  @follow_symlinks = false
34
36
 
35
- @restore_times = true
37
+ @restore_times = false
36
38
  @restore_permissions = false
37
39
  @restore_ownership = false
38
40
  # BUG: need an extra field to support uid/gid's
39
41
  @unix_uid = nil
40
42
  @unix_gid = nil
41
43
  @unix_perms = nil
42
- #@posix_acl = nil
43
- #@ntfs_acl = nil
44
+ # @posix_acl = nil
45
+ # @ntfs_acl = nil
44
46
  @dirty = false
45
47
  end
46
48
 
47
49
  def check_name(name)
48
- if name.start_with?('/')
49
- raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
50
- end
50
+ return unless name.start_with?('/')
51
+
52
+ raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
51
53
  end
52
54
 
53
55
  def initialize(*args)
@@ -68,7 +70,15 @@ module Zip
68
70
  @time = args[8] || ::Zip::DOSTime.now
69
71
 
70
72
  @ftype = name_is_directory? ? :directory : :file
71
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField === @extra
73
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
74
+ end
75
+
76
+ def encrypted?
77
+ gp_flags & 1 == 1
78
+ end
79
+
80
+ def incomplete?
81
+ gp_flags & 8 == 8
72
82
  end
73
83
 
74
84
  def time
@@ -83,23 +93,24 @@ module Zip
83
93
  end
84
94
  end
85
95
 
86
- alias :mtime :time
96
+ alias mtime time
87
97
 
88
98
  def time=(value)
89
99
  unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
90
100
  @extra.create('UniversalTime')
91
101
  end
92
102
  (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
93
- @time = value
103
+ @time = value
94
104
  end
95
105
 
96
106
  def file_type_is?(type)
97
- raise InternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
107
+ raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
108
+
98
109
  @ftype == type
99
110
  end
100
111
 
101
112
  # Dynamic checkers
102
- %w(directory file symlink).each do |k|
113
+ %w[directory file symlink].each do |k|
103
114
  define_method "#{k}?" do
104
115
  file_type_is?(k.to_sym)
105
116
  end
@@ -109,6 +120,18 @@ module Zip
109
120
  @name.end_with?('/')
110
121
  end
111
122
 
123
+ # Is the name a relative path, free of `..` patterns that could lead to
124
+ # path traversal attacks? This does NOT handle symlinks; if the path
125
+ # contains symlinks, this check is NOT enough to guarantee safety.
126
+ def name_safe?
127
+ cleanpath = Pathname.new(@name).cleanpath
128
+ return false unless cleanpath.relative?
129
+
130
+ root = ::File::SEPARATOR
131
+ naive_expanded_path = ::File.join(root, cleanpath.to_s)
132
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
133
+ end
134
+
112
135
  def local_entry_offset #:nodoc:all
113
136
  local_header_offset + @local_header_size
114
137
  end
@@ -133,6 +156,7 @@ module Zip
133
156
  # that we didn't change the header size (and thus clobber file data or something)
134
157
  def verify_local_header_size!
135
158
  return if @local_header_size.nil?
159
+
136
160
  new_size = calculate_local_header_size
137
161
  raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
138
162
  end
@@ -143,19 +167,24 @@ module Zip
143
167
  end
144
168
 
145
169
  def next_header_offset #:nodoc:all
146
- local_entry_offset + self.compressed_size + data_descriptor_size
170
+ local_entry_offset + compressed_size + data_descriptor_size
147
171
  end
148
172
 
149
173
  # Extracts entry to file dest_path (defaults to @name).
150
- def extract(dest_path = @name, &block)
174
+ # NB: The caller is responsible for making sure dest_path is safe, if it
175
+ # is passed.
176
+ def extract(dest_path = nil, &block)
177
+ if dest_path.nil? && !name_safe?
178
+ warn "WARNING: skipped '#{@name}' as unsafe."
179
+ return self
180
+ end
181
+
182
+ dest_path ||= @name
151
183
  block ||= proc { ::Zip.on_exists_proc }
152
184
 
153
- if directory? || file? || symlink?
154
- self.__send__("create_#{@ftype}", dest_path, &block)
155
- else
156
- raise RuntimeError, "unknown file type #{self.inspect}"
157
- end
185
+ raise "unknown file type #{inspect}" unless directory? || file? || symlink?
158
186
 
187
+ __send__("create_#{@ftype}", dest_path, &block)
159
188
  self
160
189
  end
161
190
 
@@ -163,27 +192,25 @@ module Zip
163
192
  @name
164
193
  end
165
194
 
166
- protected
167
-
168
195
  class << self
169
196
  def read_zip_short(io) # :nodoc:
170
- io.read(2).unpack('v')[0]
197
+ io.read(2).unpack1('v')
171
198
  end
172
199
 
173
200
  def read_zip_long(io) # :nodoc:
174
- io.read(4).unpack('V')[0]
201
+ io.read(4).unpack1('V')
175
202
  end
176
203
 
177
204
  def read_zip_64_long(io) # :nodoc:
178
- io.read(8).unpack('Q<')[0]
205
+ io.read(8).unpack1('Q<')
179
206
  end
180
207
 
181
208
  def read_c_dir_entry(io) #:nodoc:all
182
- path = if io.is_a?(::IO)
183
- io.path
184
- else
185
- io
186
- end
209
+ path = if io.respond_to?(:path)
210
+ io.path
211
+ else
212
+ io
213
+ end
187
214
  entry = new(path)
188
215
  entry.read_c_dir_entry(io)
189
216
  entry
@@ -192,17 +219,14 @@ module Zip
192
219
  end
193
220
 
194
221
  def read_local_entry(io)
195
- entry = self.new(io)
222
+ entry = new(io)
196
223
  entry.read_local_entry(io)
197
224
  entry
198
225
  rescue Error
199
226
  nil
200
227
  end
201
-
202
228
  end
203
229
 
204
- public
205
-
206
230
  def unpack_local_entry(buf)
207
231
  @header_signature,
208
232
  @version,
@@ -221,10 +245,10 @@ module Zip
221
245
  def read_local_entry(io) #:nodoc:all
222
246
  @local_header_offset = io.tell
223
247
 
224
- static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
248
+ static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
225
249
 
226
250
  unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
227
- raise Error, "Premature end of file. Not enough data for zip entry local header"
251
+ raise Error, 'Premature end of file. Not enough data for zip entry local header'
228
252
  end
229
253
 
230
254
  unpack_local_entry(static_sized_fields_buf)
@@ -232,22 +256,27 @@ module Zip
232
256
  unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
233
257
  raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
234
258
  end
259
+
235
260
  set_time(@last_mod_date, @last_mod_time)
236
261
 
237
262
  @name = io.read(@name_length)
238
263
  extra = io.read(@extra_length)
239
264
 
240
- @name.gsub!('\\', '/')
265
+ @name.tr!('\\', '/')
266
+ if ::Zip.force_entry_names_encoding
267
+ @name.force_encoding(::Zip.force_entry_names_encoding)
268
+ end
241
269
 
242
270
  if extra && extra.bytesize != @extra_length
243
- raise ::Zip::Error, "Truncated local zip entry header"
271
+ raise ::Zip::Error, 'Truncated local zip entry header'
272
+ end
273
+
274
+ if @extra.kind_of?(::Zip::ExtraField)
275
+ @extra.merge(extra) if extra
244
276
  else
245
- if ::Zip::ExtraField === @extra
246
- @extra.merge(extra)
247
- else
248
- @extra = ::Zip::ExtraField.new(extra)
249
- end
277
+ @extra = ::Zip::ExtraField.new(extra)
250
278
  end
279
+
251
280
  parse_zip64_extra(true)
252
281
  @local_header_size = calculate_local_header_size
253
282
  end
@@ -256,13 +285,13 @@ module Zip
256
285
  zip64 = @extra['Zip64']
257
286
  [::Zip::LOCAL_ENTRY_SIGNATURE,
258
287
  @version_needed_to_extract, # version needed to extract
259
- @gp_flags, # @gp_flags ,
288
+ @gp_flags, # @gp_flags
260
289
  @compression_method,
261
- @time.to_binary_dos_time, # @last_mod_time ,
262
- @time.to_binary_dos_date, # @last_mod_date ,
290
+ @time.to_binary_dos_time, # @last_mod_time
291
+ @time.to_binary_dos_date, # @last_mod_date
263
292
  @crc,
264
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
265
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
293
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
294
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
266
295
  name_size,
267
296
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
268
297
  end
@@ -306,7 +335,7 @@ module Zip
306
335
  def set_ftype_from_c_dir_entry
307
336
  @ftype = case @fstype
308
337
  when ::Zip::FSTYPE_UNIX
309
- @unix_perms = (@external_file_attributes >> 16) & 07777
338
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
310
339
  case (@external_file_attributes >> 28)
311
340
  when ::Zip::FILE_TYPE_DIR
312
341
  :directory
@@ -315,8 +344,8 @@ module Zip
315
344
  when ::Zip::FILE_TYPE_SYMLINK
316
345
  :symlink
317
346
  else
318
- #best case guess for whether it is a file or not
319
- #Otherwise this would be set to unknown and that entry would never be able to extracted
347
+ # best case guess for whether it is a file or not
348
+ # Otherwise this would be set to unknown and that entry would never be able to extracted
320
349
  if name_is_directory?
321
350
  :directory
322
351
  else
@@ -333,25 +362,25 @@ module Zip
333
362
  end
334
363
 
335
364
  def check_c_dir_entry_static_header_length(buf)
336
- unless buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
337
- raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
338
- end
365
+ return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
366
+
367
+ raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
339
368
  end
340
369
 
341
370
  def check_c_dir_entry_signature
342
- unless header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
343
- raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
344
- end
371
+ return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
372
+
373
+ raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
345
374
  end
346
375
 
347
376
  def check_c_dir_entry_comment_size
348
- unless @comment && @comment.bytesize == @comment_length
349
- raise ::Zip::Error, "Truncated cdir zip entry header"
350
- end
377
+ return if @comment && @comment.bytesize == @comment_length
378
+
379
+ raise ::Zip::Error, 'Truncated cdir zip entry header'
351
380
  end
352
381
 
353
382
  def read_c_dir_extra_field(io)
354
- if @extra.is_a?(::Zip::ExtraField)
383
+ if @extra.kind_of?(::Zip::ExtraField)
355
384
  @extra.merge(io.read(@extra_length))
356
385
  else
357
386
  @extra = ::Zip::ExtraField.new(io.read(@extra_length))
@@ -364,7 +393,10 @@ module Zip
364
393
  unpack_c_dir_entry(static_sized_fields_buf)
365
394
  check_c_dir_entry_signature
366
395
  set_time(@last_mod_date, @last_mod_time)
367
- @name = io.read(@name_length).tr('\\', '/')
396
+ @name = io.read(@name_length)
397
+ if ::Zip.force_entry_names_encoding
398
+ @name.force_encoding(::Zip.force_entry_names_encoding)
399
+ end
368
400
  read_c_dir_extra_field(io)
369
401
  @comment = io.read(@comment_length)
370
402
  check_c_dir_entry_comment_size
@@ -374,37 +406,41 @@ module Zip
374
406
 
375
407
  def file_stat(path) # :nodoc:
376
408
  if @follow_symlinks
377
- ::File::stat(path)
409
+ ::File.stat(path)
378
410
  else
379
- ::File::lstat(path)
411
+ ::File.lstat(path)
380
412
  end
381
413
  end
382
414
 
383
415
  def get_extra_attributes_from_path(path) # :nodoc:
384
- unless Zip::RUNNING_ON_WINDOWS
385
- stat = file_stat(path)
386
- @unix_uid = stat.uid
387
- @unix_gid = stat.gid
388
- @unix_perms = stat.mode & 07777
389
- end
416
+ return if Zip::RUNNING_ON_WINDOWS
417
+
418
+ stat = file_stat(path)
419
+ @unix_uid = stat.uid
420
+ @unix_gid = stat.gid
421
+ @unix_perms = stat.mode & 0o7777
422
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
390
423
  end
391
424
 
392
- def set_unix_permissions_on_path(dest_path)
393
- # BUG: does not update timestamps into account
425
+ def set_unix_attributes_on_path(dest_path)
394
426
  # ignore setuid/setgid bits by default. honor if @restore_ownership
395
- unix_perms_mask = 01777
396
- unix_perms_mask = 07777 if @restore_ownership
427
+ unix_perms_mask = 0o1777
428
+ unix_perms_mask = 0o7777 if @restore_ownership
397
429
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
398
430
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
399
- # File::utimes()
431
+
432
+ # Restore the timestamp on a file. This will either have come from the
433
+ # original source file that was copied into the archive, or from the
434
+ # creation date of the archive if there was no original source file.
435
+ ::FileUtils.touch(dest_path, mtime: time) if @restore_times
400
436
  end
401
437
 
402
438
  def set_extra_attributes_on_path(dest_path) # :nodoc:
403
- return unless (file? || directory?)
439
+ return unless file? || directory?
404
440
 
405
441
  case @fstype
406
442
  when ::Zip::FSTYPE_UNIX
407
- set_unix_permissions_on_path(dest_path)
443
+ set_unix_attributes_on_path(dest_path)
408
444
  end
409
445
  end
410
446
 
@@ -414,21 +450,21 @@ module Zip
414
450
  @header_signature,
415
451
  @version, # version of encoding software
416
452
  @fstype, # filesystem type
417
- @version_needed_to_extract, # @versionNeededToExtract ,
418
- @gp_flags, # @gp_flags ,
453
+ @version_needed_to_extract, # @versionNeededToExtract
454
+ @gp_flags, # @gp_flags
419
455
  @compression_method,
420
- @time.to_binary_dos_time, # @last_mod_time ,
421
- @time.to_binary_dos_date, # @last_mod_date ,
456
+ @time.to_binary_dos_time, # @last_mod_time
457
+ @time.to_binary_dos_date, # @last_mod_date
422
458
  @crc,
423
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
424
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
459
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
460
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
425
461
  name_size,
426
462
  @extra ? @extra.c_dir_size : 0,
427
463
  comment_size,
428
- (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
464
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
429
465
  @internal_file_attributes, # file type (binary=0, text=1)
430
466
  @external_file_attributes, # native filesystem attributes
431
- (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
467
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
432
468
  @name,
433
469
  @extra,
434
470
  @comment
@@ -441,18 +477,18 @@ module Zip
441
477
  when ::Zip::FSTYPE_UNIX
442
478
  ft = case @ftype
443
479
  when :file
444
- @unix_perms ||= 0644
480
+ @unix_perms ||= 0o644
445
481
  ::Zip::FILE_TYPE_FILE
446
482
  when :directory
447
- @unix_perms ||= 0755
483
+ @unix_perms ||= 0o755
448
484
  ::Zip::FILE_TYPE_DIR
449
485
  when :symlink
450
- @unix_perms ||= 0755
486
+ @unix_perms ||= 0o755
451
487
  ::Zip::FILE_TYPE_SYMLINK
452
488
  end
453
489
 
454
490
  unless ft.nil?
455
- @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
491
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
456
492
  end
457
493
  end
458
494
 
@@ -465,15 +501,16 @@ module Zip
465
501
 
466
502
  def ==(other)
467
503
  return false unless other.class == self.class
504
+
468
505
  # Compares contents of local entry and exposed fields
469
- keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
470
- other.__send__(k.to_sym) == self.__send__(k.to_sym)
506
+ keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
507
+ other.__send__(k.to_sym) == __send__(k.to_sym)
471
508
  end
472
- keys_equal && self.time.dos_equals(other.time)
509
+ keys_equal && time.dos_equals(other.time)
473
510
  end
474
511
 
475
- def <=> (other)
476
- self.to_s <=> other.to_s
512
+ def <=>(other)
513
+ to_s <=> other.to_s
477
514
  end
478
515
 
479
516
  # Returns an IO like object for the given ZipEntry.
@@ -496,6 +533,7 @@ module Zip
496
533
  end
497
534
  else
498
535
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
536
+ zis.instance_variable_set(:@complete_entry, self)
499
537
  zis.get_next_entry
500
538
  if block_given?
501
539
  begin
@@ -515,8 +553,8 @@ module Zip
515
553
  when 'file'
516
554
  if name_is_directory?
517
555
  raise ArgumentError,
518
- "entry name '#{newEntry}' indicates directory entry, but "+
519
- "'#{src_path}' is not a directory"
556
+ "entry name '#{newEntry}' indicates directory entry, but " \
557
+ "'#{src_path}' is not a directory"
520
558
  end
521
559
  :file
522
560
  when 'directory'
@@ -525,12 +563,12 @@ module Zip
525
563
  when 'link'
526
564
  if name_is_directory?
527
565
  raise ArgumentError,
528
- "entry name '#{newEntry}' indicates directory entry, but "+
529
- "'#{src_path}' is not a directory"
566
+ "entry name '#{newEntry}' indicates directory entry, but " \
567
+ "'#{src_path}' is not a directory"
530
568
  end
531
569
  :symlink
532
570
  else
533
- raise RuntimeError, "unknown file type: #{src_path.inspect} #{stat.inspect}"
571
+ raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
534
572
  end
535
573
 
536
574
  @filepath = src_path
@@ -541,7 +579,7 @@ module Zip
541
579
  if @ftype == :directory
542
580
  zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
543
581
  elsif @filepath
544
- zip_output_stream.put_next_entry(self, nil, nil, self.compression_method || ::Zip::Entry::DEFLATED)
582
+ zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
545
583
  get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
546
584
  else
547
585
  zip_output_stream.copy_raw_entry(self)
@@ -551,14 +589,14 @@ module Zip
551
589
  def parent_as_string
552
590
  entry_name = name.chomp('/')
553
591
  slash_index = entry_name.rindex('/')
554
- slash_index ? entry_name.slice(0, slash_index+1) : nil
592
+ slash_index ? entry_name.slice(0, slash_index + 1) : nil
555
593
  end
556
594
 
557
595
  def get_raw_input_stream(&block)
558
- if @zipfile.is_a?(::IO) || @zipfile.is_a?(::StringIO)
596
+ if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
559
597
  yield @zipfile
560
598
  else
561
- ::File.open(@zipfile, "rb", &block)
599
+ ::File.open(@zipfile, 'rb', &block)
562
600
  end
563
601
  end
564
602
 
@@ -571,35 +609,46 @@ module Zip
571
609
  def set_time(binary_dos_date, binary_dos_time)
572
610
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
573
611
  rescue ArgumentError
574
- puts "Invalid date/time in zip entry" if ::Zip.warn_invalid_date
612
+ warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
575
613
  end
576
614
 
577
- def create_file(dest_path, continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
615
+ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
578
616
  if ::File.exist?(dest_path) && !yield(self, dest_path)
579
617
  raise ::Zip::DestinationFileExistsError,
580
618
  "Destination '#{dest_path}' already exists"
581
619
  end
582
- ::File.open(dest_path, "wb") do |os|
620
+ ::File.open(dest_path, 'wb') do |os|
583
621
  get_input_stream do |is|
584
- set_extra_attributes_on_path(dest_path)
585
-
586
- buf = ''
587
- while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
622
+ bytes_written = 0
623
+ warned = false
624
+ buf = +''
625
+ while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
588
626
  os << buf
627
+ bytes_written += buf.bytesize
628
+ next unless bytes_written > size && !warned
629
+
630
+ message = "entry '#{name}' should be #{size}B, but is larger when inflated."
631
+ raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes
632
+
633
+ warn "WARNING: #{message}"
634
+ warned = true
589
635
  end
590
636
  end
591
637
  end
638
+
639
+ set_extra_attributes_on_path(dest_path)
592
640
  end
593
641
 
594
642
  def create_directory(dest_path)
595
643
  return if ::File.directory?(dest_path)
644
+
596
645
  if ::File.exist?(dest_path)
597
646
  if block_given? && yield(self, dest_path)
598
- ::FileUtils::rm_f dest_path
647
+ ::FileUtils.rm_f dest_path
599
648
  else
600
649
  raise ::Zip::DestinationFileExistsError,
601
- "Cannot create directory '#{dest_path}'. "+
602
- "A file already exists with that name"
650
+ "Cannot create directory '#{dest_path}'. " \
651
+ 'A file already exists with that name'
603
652
  end
604
653
  end
605
654
  ::FileUtils.mkdir_p(dest_path)
@@ -608,43 +657,20 @@ module Zip
608
657
 
609
658
  # BUG: create_symlink() does not use &block
610
659
  def create_symlink(dest_path)
611
- stat = nil
612
- begin
613
- stat = ::File.lstat(dest_path)
614
- rescue Errno::ENOENT
615
- end
616
-
617
- io = get_input_stream
618
- linkto = io.read
619
-
620
- if stat
621
- if stat.symlink?
622
- if ::File.readlink(dest_path) == linkto
623
- return
624
- else
625
- raise ::Zip::DestinationFileExistsError,
626
- "Cannot create symlink '#{dest_path}'. "+
627
- "A symlink already exists with that name"
628
- end
629
- else
630
- raise ::Zip::DestinationFileExistsError,
631
- "Cannot create symlink '#{dest_path}'. "+
632
- "A file already exists with that name"
633
- end
634
- end
635
-
636
- ::File.symlink(linkto, dest_path)
660
+ # TODO: Symlinks pose security challenges. Symlink support temporarily
661
+ # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
662
+ warn "WARNING: skipped symlink '#{dest_path}'."
637
663
  end
638
664
 
639
665
  # apply missing data from the zip64 extra information field, if present
640
666
  # (required when file sizes exceed 2**32, but can be used for all files)
641
667
  def parse_zip64_extra(for_local_header) #:nodoc:all
642
- if zip64 = @extra['Zip64']
643
- if for_local_header
644
- @size, @compressed_size = zip64.parse(@size, @compressed_size)
645
- else
646
- @size, @compressed_size, @local_header_offset = zip64.parse(@size, @compressed_size, @local_header_offset)
647
- end
668
+ return if @extra['Zip64'].nil?
669
+
670
+ if for_local_header
671
+ @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
672
+ else
673
+ @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
648
674
  end
649
675
  end
650
676
 
@@ -655,11 +681,9 @@ module Zip
655
681
  # create a zip64 extra information field if we need one
656
682
  def prep_zip64_extra(for_local_header) #:nodoc:all
657
683
  return unless ::Zip.write_zip64_support
658
- need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
659
- unless for_local_header
660
- need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
661
- end
662
684
 
685
+ need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
686
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
663
687
  if need_zip64
664
688
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
665
689
  @extra.delete('Zip64Placeholder')
@@ -687,7 +711,6 @@ module Zip
687
711
  end
688
712
  end
689
713
  end
690
-
691
714
  end
692
715
  end
693
716