rubyzip 1.1.7 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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