rubyzip 1.2.0 → 2.3.0

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 (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