rubyzip 1.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +89 -43
  3. data/Rakefile +3 -0
  4. data/lib/zip.rb +14 -3
  5. data/lib/zip/central_directory.rb +12 -8
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +55 -3
  8. data/lib/zip/crypto/decrypted_io.rb +40 -0
  9. data/lib/zip/crypto/null_encryption.rb +2 -4
  10. data/lib/zip/crypto/traditional_encryption.rb +9 -9
  11. data/lib/zip/decompressor.rb +20 -2
  12. data/lib/zip/dos_time.rb +13 -8
  13. data/lib/zip/entry.rb +115 -80
  14. data/lib/zip/entry_set.rb +6 -4
  15. data/lib/zip/errors.rb +2 -0
  16. data/lib/zip/extra_field.rb +12 -10
  17. data/lib/zip/extra_field/generic.rb +10 -9
  18. data/lib/zip/extra_field/ntfs.rb +4 -0
  19. data/lib/zip/extra_field/old_unix.rb +3 -1
  20. data/lib/zip/extra_field/universal_time.rb +42 -12
  21. data/lib/zip/extra_field/unix.rb +3 -1
  22. data/lib/zip/extra_field/zip64.rb +4 -2
  23. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  24. data/lib/zip/file.rb +136 -100
  25. data/lib/zip/filesystem.rb +199 -179
  26. data/lib/zip/inflater.rb +24 -36
  27. data/lib/zip/input_stream.rb +37 -27
  28. data/lib/zip/ioextras.rb +1 -1
  29. data/lib/zip/ioextras/abstract_input_stream.rb +20 -9
  30. data/lib/zip/ioextras/abstract_output_stream.rb +4 -4
  31. data/lib/zip/null_decompressor.rb +1 -9
  32. data/lib/zip/output_stream.rb +19 -10
  33. data/lib/zip/pass_thru_compressor.rb +2 -2
  34. data/lib/zip/pass_thru_decompressor.rb +14 -23
  35. data/lib/zip/streamable_directory.rb +3 -3
  36. data/lib/zip/streamable_stream.rb +6 -10
  37. data/lib/zip/version.rb +1 -1
  38. data/samples/example.rb +2 -2
  39. data/samples/example_filesystem.rb +1 -1
  40. data/samples/example_recursive.rb +15 -18
  41. data/samples/gtk_ruby_zip.rb +20 -20
  42. data/samples/qtzip.rb +7 -7
  43. data/samples/write_simple.rb +2 -4
  44. data/samples/zipfind.rb +23 -22
  45. metadata +41 -130
  46. data/test/basic_zip_file_test.rb +0 -60
  47. data/test/case_sensitivity_test.rb +0 -69
  48. data/test/central_directory_entry_test.rb +0 -69
  49. data/test/central_directory_test.rb +0 -100
  50. data/test/crypto/null_encryption_test.rb +0 -53
  51. data/test/crypto/traditional_encryption_test.rb +0 -80
  52. data/test/data/WarnInvalidDate.zip +0 -0
  53. data/test/data/file1.txt +0 -46
  54. data/test/data/file1.txt.deflatedData +0 -0
  55. data/test/data/file2.txt +0 -1504
  56. data/test/data/globTest.zip +0 -0
  57. data/test/data/globTest/foo.txt +0 -0
  58. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  59. data/test/data/globTest/food.txt +0 -0
  60. data/test/data/mimetype +0 -1
  61. data/test/data/notzippedruby.rb +0 -7
  62. data/test/data/ntfs.zip +0 -0
  63. data/test/data/oddExtraField.zip +0 -0
  64. data/test/data/rubycode.zip +0 -0
  65. data/test/data/rubycode2.zip +0 -0
  66. data/test/data/test.xls +0 -0
  67. data/test/data/testDirectory.bin +0 -0
  68. data/test/data/zip64-sample.zip +0 -0
  69. data/test/data/zipWithDirs.zip +0 -0
  70. data/test/data/zipWithEncryption.zip +0 -0
  71. data/test/deflater_test.rb +0 -65
  72. data/test/encryption_test.rb +0 -42
  73. data/test/entry_set_test.rb +0 -152
  74. data/test/entry_test.rb +0 -163
  75. data/test/errors_test.rb +0 -34
  76. data/test/extra_field_test.rb +0 -76
  77. data/test/file_extract_directory_test.rb +0 -54
  78. data/test/file_extract_test.rb +0 -83
  79. data/test/file_permissions_test.rb +0 -69
  80. data/test/file_split_test.rb +0 -57
  81. data/test/file_test.rb +0 -563
  82. data/test/filesystem/dir_iterator_test.rb +0 -58
  83. data/test/filesystem/directory_test.rb +0 -121
  84. data/test/filesystem/file_mutating_test.rb +0 -88
  85. data/test/filesystem/file_nonmutating_test.rb +0 -508
  86. data/test/filesystem/file_stat_test.rb +0 -64
  87. data/test/gentestfiles.rb +0 -122
  88. data/test/inflater_test.rb +0 -14
  89. data/test/input_stream_test.rb +0 -182
  90. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  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 -154
  94. data/test/output_stream_test.rb +0 -128
  95. data/test/pass_thru_compressor_test.rb +0 -30
  96. data/test/pass_thru_decompressor_test.rb +0 -14
  97. data/test/samples/example_recursive_test.rb +0 -37
  98. data/test/settings_test.rb +0 -95
  99. data/test/test_helper.rb +0 -221
  100. data/test/unicode_file_names_and_comments_test.rb +0 -50
  101. data/test/zip64_full_test.rb +0 -51
  102. data/test/zip64_support_test.rb +0 -14
@@ -0,0 +1,40 @@
1
+ module Zip
2
+ class DecryptedIo #:nodoc:all
3
+ CHUNK_SIZE = 32_768
4
+
5
+ def initialize(io, decrypter)
6
+ @io = io
7
+ @decrypter = decrypter
8
+ end
9
+
10
+ def read(length = nil, outbuf = +'')
11
+ return (length.nil? || length.zero? ? '' : nil) if eof
12
+
13
+ while length.nil? || (buffer.bytesize < length)
14
+ break if input_finished?
15
+
16
+ buffer << produce_input
17
+ end
18
+
19
+ outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize)))
20
+ end
21
+
22
+ private
23
+
24
+ def eof
25
+ buffer.empty? && input_finished?
26
+ end
27
+
28
+ def buffer
29
+ @buffer ||= +''
30
+ end
31
+
32
+ def input_finished?
33
+ @io.eof
34
+ end
35
+
36
+ def produce_input
37
+ @decrypter.decrypt(@io.read(CHUNK_SIZE))
38
+ end
39
+ end
40
+ end
@@ -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
 
@@ -24,8 +24,8 @@ module Zip
24
24
  end
25
25
  end
26
26
 
27
- def update_keys(n)
28
- @key0 = ~Zlib.crc32(n, ~@key0)
27
+ def update_keys(num)
28
+ @key0 = ~Zlib.crc32(num, ~@key0)
29
29
  @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff
30
30
  @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
31
31
  end
@@ -63,10 +63,10 @@ module Zip
63
63
 
64
64
  private
65
65
 
66
- def encode(n)
66
+ def encode(num)
67
67
  t = decrypt_byte
68
- update_keys(n.chr)
69
- t ^ n
68
+ update_keys(num.chr)
69
+ t ^ num
70
70
  end
71
71
  end
72
72
 
@@ -86,10 +86,10 @@ module Zip
86
86
 
87
87
  private
88
88
 
89
- def decode(n)
90
- n ^= decrypt_byte
91
- update_keys(n.chr)
92
- n
89
+ def decode(num)
90
+ num ^= decrypt_byte
91
+ update_keys(num.chr)
92
+ num
93
93
  end
94
94
  end
95
95
  end
@@ -1,9 +1,27 @@
1
1
  module Zip
2
- class Decompressor #:nodoc:all
2
+ class Decompressor #:nodoc:all
3
3
  CHUNK_SIZE = 32_768
4
- def initialize(input_stream)
4
+
5
+ def self.decompressor_classes
6
+ @decompressor_classes ||= {}
7
+ end
8
+
9
+ def self.register(compression_method, decompressor_class)
10
+ decompressor_classes[compression_method] = decompressor_class
11
+ end
12
+
13
+ def self.find_by_compression_method(compression_method)
14
+ decompressor_classes[compression_method]
15
+ end
16
+
17
+ attr_reader :input_stream
18
+ attr_reader :decompressed_size
19
+
20
+ def initialize(input_stream, decompressed_size = nil)
5
21
  super()
22
+
6
23
  @input_stream = input_stream
24
+ @decompressed_size = decompressed_size
7
25
  end
8
26
  end
9
27
  end
@@ -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
@@ -29,13 +29,18 @@ module Zip
29
29
  to_i / 2 == other.to_i / 2
30
30
  end
31
31
 
32
- def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
33
- second = 2 * (0b11111 & binaryDosTime)
34
- minute = (0b11111100000 & binaryDosTime) >> 5
35
- hour = (0b1111100000000000 & binaryDosTime) >> 11
36
- day = (0b11111 & binaryDosDate)
37
- month = (0b111100000 & binaryDosDate) >> 5
38
- year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
32
+ # Create a DOSTime instance from a vanilla Time instance.
33
+ def self.from_time(time)
34
+ local(time.year, time.month, time.day, time.hour, time.min, time.sec)
35
+ end
36
+
37
+ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time)
38
+ second = 2 * (0b11111 & bin_dos_time)
39
+ minute = (0b11111100000 & bin_dos_time) >> 5
40
+ hour = (0b1111100000000000 & bin_dos_time) >> 11
41
+ day = (0b11111 & bin_dos_date)
42
+ month = (0b111100000 & bin_dos_date) >> 5
43
+ year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980
39
44
  begin
40
45
  local(year, month, day, hour, minute, second)
41
46
  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,
@@ -32,7 +34,7 @@ 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
@@ -46,6 +48,7 @@ module Zip
46
48
 
47
49
  def check_name(name)
48
50
  return unless name.start_with?('/')
51
+
49
52
  raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
50
53
  end
51
54
 
@@ -67,7 +70,15 @@ module Zip
67
70
  @time = args[8] || ::Zip::DOSTime.now
68
71
 
69
72
  @ftype = name_is_directory? ? :directory : :file
70
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
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
71
82
  end
72
83
 
73
84
  def time
@@ -89,16 +100,17 @@ module Zip
89
100
  @extra.create('UniversalTime')
90
101
  end
91
102
  (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
92
- @time = value
103
+ @time = value
93
104
  end
94
105
 
95
106
  def file_type_is?(type)
96
107
  raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
108
+
97
109
  @ftype == type
98
110
  end
99
111
 
100
112
  # Dynamic checkers
101
- %w(directory file symlink).each do |k|
113
+ %w[directory file symlink].each do |k|
102
114
  define_method "#{k}?" do
103
115
  file_type_is?(k.to_sym)
104
116
  end
@@ -108,6 +120,18 @@ module Zip
108
120
  @name.end_with?('/')
109
121
  end
110
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
+
111
135
  def local_entry_offset #:nodoc:all
112
136
  local_header_offset + @local_header_size
113
137
  end
@@ -132,6 +156,7 @@ module Zip
132
156
  # that we didn't change the header size (and thus clobber file data or something)
133
157
  def verify_local_header_size!
134
158
  return if @local_header_size.nil?
159
+
135
160
  new_size = calculate_local_header_size
136
161
  raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
137
162
  end
@@ -146,15 +171,20 @@ module Zip
146
171
  end
147
172
 
148
173
  # Extracts entry to file dest_path (defaults to @name).
149
- 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
150
183
  block ||= proc { ::Zip.on_exists_proc }
151
184
 
152
- if directory? || file? || symlink?
153
- __send__("create_#{@ftype}", dest_path, &block)
154
- else
155
- raise "unknown file type #{inspect}"
156
- end
185
+ raise "unknown file type #{inspect}" unless directory? || file? || symlink?
157
186
 
187
+ __send__("create_#{@ftype}", dest_path, &block)
158
188
  self
159
189
  end
160
190
 
@@ -164,15 +194,15 @@ module Zip
164
194
 
165
195
  class << self
166
196
  def read_zip_short(io) # :nodoc:
167
- io.read(2).unpack('v')[0]
197
+ io.read(2).unpack1('v')
168
198
  end
169
199
 
170
200
  def read_zip_long(io) # :nodoc:
171
- io.read(4).unpack('V')[0]
201
+ io.read(4).unpack1('V')
172
202
  end
173
203
 
174
204
  def read_zip_64_long(io) # :nodoc:
175
- io.read(8).unpack('Q<')[0]
205
+ io.read(8).unpack1('Q<')
176
206
  end
177
207
 
178
208
  def read_c_dir_entry(io) #:nodoc:all
@@ -197,8 +227,6 @@ module Zip
197
227
  end
198
228
  end
199
229
 
200
- public
201
-
202
230
  def unpack_local_entry(buf)
203
231
  @header_signature,
204
232
  @version,
@@ -228,22 +256,27 @@ module Zip
228
256
  unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
229
257
  raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
230
258
  end
259
+
231
260
  set_time(@last_mod_date, @last_mod_time)
232
261
 
233
262
  @name = io.read(@name_length)
234
263
  extra = io.read(@extra_length)
235
264
 
236
- @name.gsub!('\\', '/')
265
+ @name.tr!('\\', '/')
266
+ if ::Zip.force_entry_names_encoding
267
+ @name.force_encoding(::Zip.force_entry_names_encoding)
268
+ end
237
269
 
238
270
  if extra && extra.bytesize != @extra_length
239
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
240
276
  else
241
- if @extra.is_a?(::Zip::ExtraField)
242
- @extra.merge(extra) if extra
243
- else
244
- @extra = ::Zip::ExtraField.new(extra)
245
- end
277
+ @extra = ::Zip::ExtraField.new(extra)
246
278
  end
279
+
247
280
  parse_zip64_extra(true)
248
281
  @local_header_size = calculate_local_header_size
249
282
  end
@@ -252,13 +285,13 @@ module Zip
252
285
  zip64 = @extra['Zip64']
253
286
  [::Zip::LOCAL_ENTRY_SIGNATURE,
254
287
  @version_needed_to_extract, # version needed to extract
255
- @gp_flags, # @gp_flags ,
288
+ @gp_flags, # @gp_flags
256
289
  @compression_method,
257
- @time.to_binary_dos_time, # @last_mod_time ,
258
- @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
259
292
  @crc,
260
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
261
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
293
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
294
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
262
295
  name_size,
263
296
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
264
297
  end
@@ -302,7 +335,7 @@ module Zip
302
335
  def set_ftype_from_c_dir_entry
303
336
  @ftype = case @fstype
304
337
  when ::Zip::FSTYPE_UNIX
305
- @unix_perms = (@external_file_attributes >> 16) & 07777
338
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
306
339
  case (@external_file_attributes >> 28)
307
340
  when ::Zip::FILE_TYPE_DIR
308
341
  :directory
@@ -330,21 +363,24 @@ module Zip
330
363
 
331
364
  def check_c_dir_entry_static_header_length(buf)
332
365
  return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
366
+
333
367
  raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
334
368
  end
335
369
 
336
370
  def check_c_dir_entry_signature
337
371
  return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
372
+
338
373
  raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
339
374
  end
340
375
 
341
376
  def check_c_dir_entry_comment_size
342
377
  return if @comment && @comment.bytesize == @comment_length
378
+
343
379
  raise ::Zip::Error, 'Truncated cdir zip entry header'
344
380
  end
345
381
 
346
382
  def read_c_dir_extra_field(io)
347
- if @extra.is_a?(::Zip::ExtraField)
383
+ if @extra.kind_of?(::Zip::ExtraField)
348
384
  @extra.merge(io.read(@extra_length))
349
385
  else
350
386
  @extra = ::Zip::ExtraField.new(io.read(@extra_length))
@@ -357,7 +393,10 @@ module Zip
357
393
  unpack_c_dir_entry(static_sized_fields_buf)
358
394
  check_c_dir_entry_signature
359
395
  set_time(@last_mod_date, @last_mod_time)
360
- @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
361
400
  read_c_dir_extra_field(io)
362
401
  @comment = io.read(@comment_length)
363
402
  check_c_dir_entry_comment_size
@@ -375,20 +414,25 @@ module Zip
375
414
 
376
415
  def get_extra_attributes_from_path(path) # :nodoc:
377
416
  return if Zip::RUNNING_ON_WINDOWS
417
+
378
418
  stat = file_stat(path)
379
419
  @unix_uid = stat.uid
380
420
  @unix_gid = stat.gid
381
- @unix_perms = stat.mode & 07777
421
+ @unix_perms = stat.mode & 0o7777
422
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
382
423
  end
383
424
 
384
- def set_unix_permissions_on_path(dest_path)
385
- # BUG: does not update timestamps into account
425
+ def set_unix_attributes_on_path(dest_path)
386
426
  # ignore setuid/setgid bits by default. honor if @restore_ownership
387
- unix_perms_mask = 01777
388
- unix_perms_mask = 07777 if @restore_ownership
427
+ unix_perms_mask = 0o1777
428
+ unix_perms_mask = 0o7777 if @restore_ownership
389
429
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
390
430
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
391
- # 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
392
436
  end
393
437
 
394
438
  def set_extra_attributes_on_path(dest_path) # :nodoc:
@@ -396,7 +440,7 @@ module Zip
396
440
 
397
441
  case @fstype
398
442
  when ::Zip::FSTYPE_UNIX
399
- set_unix_permissions_on_path(dest_path)
443
+ set_unix_attributes_on_path(dest_path)
400
444
  end
401
445
  end
402
446
 
@@ -406,21 +450,21 @@ module Zip
406
450
  @header_signature,
407
451
  @version, # version of encoding software
408
452
  @fstype, # filesystem type
409
- @version_needed_to_extract, # @versionNeededToExtract ,
410
- @gp_flags, # @gp_flags ,
453
+ @version_needed_to_extract, # @versionNeededToExtract
454
+ @gp_flags, # @gp_flags
411
455
  @compression_method,
412
- @time.to_binary_dos_time, # @last_mod_time ,
413
- @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
414
458
  @crc,
415
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
416
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
459
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
460
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
417
461
  name_size,
418
462
  @extra ? @extra.c_dir_size : 0,
419
463
  comment_size,
420
- (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
464
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
421
465
  @internal_file_attributes, # file type (binary=0, text=1)
422
466
  @external_file_attributes, # native filesystem attributes
423
- (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
467
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
424
468
  @name,
425
469
  @extra,
426
470
  @comment
@@ -433,18 +477,18 @@ module Zip
433
477
  when ::Zip::FSTYPE_UNIX
434
478
  ft = case @ftype
435
479
  when :file
436
- @unix_perms ||= 0644
480
+ @unix_perms ||= 0o644
437
481
  ::Zip::FILE_TYPE_FILE
438
482
  when :directory
439
- @unix_perms ||= 0755
483
+ @unix_perms ||= 0o755
440
484
  ::Zip::FILE_TYPE_DIR
441
485
  when :symlink
442
- @unix_perms ||= 0755
486
+ @unix_perms ||= 0o755
443
487
  ::Zip::FILE_TYPE_SYMLINK
444
488
  end
445
489
 
446
490
  unless ft.nil?
447
- @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
491
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
448
492
  end
449
493
  end
450
494
 
@@ -457,8 +501,9 @@ module Zip
457
501
 
458
502
  def ==(other)
459
503
  return false unless other.class == self.class
504
+
460
505
  # Compares contents of local entry and exposed fields
461
- keys_equal = %w(compression_method crc compressed_size size name extra filepath).all? do |k|
506
+ keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
462
507
  other.__send__(k.to_sym) == __send__(k.to_sym)
463
508
  end
464
509
  keys_equal && time.dos_equals(other.time)
@@ -488,7 +533,7 @@ module Zip
488
533
  end
489
534
  else
490
535
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
491
- zis.instance_variable_set(:@internal, true)
536
+ zis.instance_variable_set(:@complete_entry, self)
492
537
  zis.get_next_entry
493
538
  if block_given?
494
539
  begin
@@ -564,7 +609,7 @@ module Zip
564
609
  def set_time(binary_dos_date, binary_dos_time)
565
610
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
566
611
  rescue ArgumentError
567
- warn '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
568
613
  end
569
614
 
570
615
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
@@ -574,18 +619,29 @@ module Zip
574
619
  end
575
620
  ::File.open(dest_path, 'wb') do |os|
576
621
  get_input_stream do |is|
577
- set_extra_attributes_on_path(dest_path)
578
-
579
- buf = ''
622
+ bytes_written = 0
623
+ warned = false
624
+ buf = +''
580
625
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
581
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
582
635
  end
583
636
  end
584
637
  end
638
+
639
+ set_extra_attributes_on_path(dest_path)
585
640
  end
586
641
 
587
642
  def create_directory(dest_path)
588
643
  return if ::File.directory?(dest_path)
644
+
589
645
  if ::File.exist?(dest_path)
590
646
  if block_given? && yield(self, dest_path)
591
647
  ::FileUtils.rm_f dest_path
@@ -601,38 +657,16 @@ module Zip
601
657
 
602
658
  # BUG: create_symlink() does not use &block
603
659
  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)
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}'."
630
663
  end
631
664
 
632
665
  # apply missing data from the zip64 extra information field, if present
633
666
  # (required when file sizes exceed 2**32, but can be used for all files)
634
667
  def parse_zip64_extra(for_local_header) #:nodoc:all
635
668
  return if @extra['Zip64'].nil?
669
+
636
670
  if for_local_header
637
671
  @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
638
672
  else
@@ -647,6 +681,7 @@ module Zip
647
681
  # create a zip64 extra information field if we need one
648
682
  def prep_zip64_extra(for_local_header) #:nodoc:all
649
683
  return unless ::Zip.write_zip64_support
684
+
650
685
  need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
651
686
  need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
652
687
  if need_zip64