rubyzip 1.1.7 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +143 -54
  3. data/Rakefile +3 -4
  4. data/lib/zip/central_directory.rb +8 -8
  5. data/lib/zip/compressor.rb +1 -2
  6. data/lib/zip/constants.rb +5 -5
  7. data/lib/zip/crypto/null_encryption.rb +4 -6
  8. data/lib/zip/crypto/traditional_encryption.rb +5 -5
  9. data/lib/zip/decompressor.rb +3 -3
  10. data/lib/zip/deflater.rb +8 -6
  11. data/lib/zip/dos_time.rb +5 -6
  12. data/lib/zip/entry.rb +132 -128
  13. data/lib/zip/entry_set.rb +14 -14
  14. data/lib/zip/errors.rb +2 -0
  15. data/lib/zip/extra_field/generic.rb +8 -8
  16. data/lib/zip/extra_field/ntfs.rb +14 -16
  17. data/lib/zip/extra_field/old_unix.rb +9 -10
  18. data/lib/zip/extra_field/universal_time.rb +14 -14
  19. data/lib/zip/extra_field/unix.rb +8 -9
  20. data/lib/zip/extra_field/zip64.rb +12 -11
  21. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  22. data/lib/zip/extra_field.rb +8 -8
  23. data/lib/zip/file.rb +88 -81
  24. data/lib/zip/filesystem.rb +144 -143
  25. data/lib/zip/inflater.rb +5 -5
  26. data/lib/zip/input_stream.rb +22 -13
  27. data/lib/zip/ioextras/abstract_input_stream.rb +6 -10
  28. data/lib/zip/ioextras/abstract_output_stream.rb +3 -5
  29. data/lib/zip/ioextras.rb +1 -3
  30. data/lib/zip/null_compressor.rb +2 -2
  31. data/lib/zip/null_decompressor.rb +3 -3
  32. data/lib/zip/null_input_stream.rb +0 -0
  33. data/lib/zip/output_stream.rb +13 -14
  34. data/lib/zip/pass_thru_compressor.rb +4 -4
  35. data/lib/zip/pass_thru_decompressor.rb +3 -4
  36. data/lib/zip/streamable_directory.rb +2 -2
  37. data/lib/zip/streamable_stream.rb +3 -3
  38. data/lib/zip/version.rb +1 -1
  39. data/lib/zip.rb +13 -5
  40. data/samples/example.rb +29 -39
  41. data/samples/example_filesystem.rb +16 -18
  42. data/samples/example_recursive.rb +31 -25
  43. data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +23 -25
  44. data/samples/qtzip.rb +18 -27
  45. data/samples/write_simple.rb +12 -13
  46. data/samples/zipfind.rb +26 -34
  47. data/test/basic_zip_file_test.rb +11 -15
  48. data/test/case_sensitivity_test.rb +69 -0
  49. data/test/central_directory_entry_test.rb +32 -36
  50. data/test/central_directory_test.rb +46 -50
  51. data/test/crypto/null_encryption_test.rb +8 -4
  52. data/test/crypto/traditional_encryption_test.rb +5 -5
  53. data/test/data/gpbit3stored.zip +0 -0
  54. data/test/data/notzippedruby.rb +1 -1
  55. data/test/data/oddExtraField.zip +0 -0
  56. data/test/data/path_traversal/Makefile +10 -0
  57. data/test/data/path_traversal/jwilk/README.md +5 -0
  58. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  59. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  60. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  61. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  62. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  63. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  64. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  65. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  66. data/test/data/path_traversal/relative1.zip +0 -0
  67. data/test/data/path_traversal/tilde.zip +0 -0
  68. data/test/data/path_traversal/tuzovakaoff/README.md +3 -0
  69. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  70. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  71. data/test/data/rubycode.zip +0 -0
  72. data/test/data/test.xls +0 -0
  73. data/test/deflater_test.rb +10 -12
  74. data/test/encryption_test.rb +2 -2
  75. data/test/entry_set_test.rb +50 -25
  76. data/test/entry_test.rb +76 -87
  77. data/test/errors_test.rb +1 -2
  78. data/test/extra_field_test.rb +19 -21
  79. data/test/file_extract_directory_test.rb +12 -14
  80. data/test/file_extract_test.rb +94 -39
  81. data/test/file_permissions_test.rb +65 -0
  82. data/test/file_split_test.rb +24 -27
  83. data/test/file_test.rb +286 -179
  84. data/test/filesystem/dir_iterator_test.rb +13 -17
  85. data/test/filesystem/directory_test.rb +101 -93
  86. data/test/filesystem/file_mutating_test.rb +52 -65
  87. data/test/filesystem/file_nonmutating_test.rb +223 -229
  88. data/test/filesystem/file_stat_test.rb +17 -19
  89. data/test/gentestfiles.rb +54 -62
  90. data/test/inflater_test.rb +1 -1
  91. data/test/input_stream_test.rb +52 -40
  92. data/test/ioextras/abstract_input_stream_test.rb +22 -23
  93. data/test/ioextras/abstract_output_stream_test.rb +33 -33
  94. data/test/ioextras/fake_io_test.rb +1 -1
  95. data/test/local_entry_test.rb +36 -38
  96. data/test/output_stream_test.rb +20 -21
  97. data/test/pass_thru_compressor_test.rb +5 -6
  98. data/test/pass_thru_decompressor_test.rb +0 -1
  99. data/test/path_traversal_test.rb +141 -0
  100. data/test/samples/example_recursive_test.rb +37 -0
  101. data/test/settings_test.rb +18 -15
  102. data/test/test_helper.rb +52 -46
  103. data/test/unicode_file_names_and_comments_test.rb +17 -7
  104. data/test/zip64_full_test.rb +10 -12
  105. data/test/zip64_support_test.rb +0 -1
  106. metadata +100 -66
@@ -12,7 +12,7 @@ module Zip
12
12
  class NullEncrypter < Encrypter
13
13
  include NullEncryption
14
14
 
15
- def header(mtime)
15
+ def header(_mtime)
16
16
  ''
17
17
  end
18
18
 
@@ -20,12 +20,11 @@ module Zip
20
20
  data
21
21
  end
22
22
 
23
- def data_descriptor(crc32, compressed_size, uncomprssed_size)
23
+ def data_descriptor(_crc32, _compressed_size, _uncomprssed_size)
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
 
@@ -26,7 +26,7 @@ module Zip
26
26
 
27
27
  def update_keys(n)
28
28
  @key0 = ~Zlib.crc32(n, ~@key0)
29
- @key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff
29
+ @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff
30
30
  @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
31
31
  end
32
32
 
@@ -46,15 +46,15 @@ module Zip
46
46
  end
47
47
  header << (mtime.to_binary_dos_time & 0xff)
48
48
  header << (mtime.to_binary_dos_time >> 8)
49
- end.map{|x| encode x}.pack("C*")
49
+ end.map { |x| encode x }.pack('C*')
50
50
  end
51
51
 
52
52
  def encrypt(data)
53
- data.unpack("C*").map{|x| encode x}.pack("C*")
53
+ data.unpack('C*').map { |x| encode x }.pack('C*')
54
54
  end
55
55
 
56
56
  def data_descriptor(crc32, compressed_size, uncomprssed_size)
57
- [0x08074b50, crc32, compressed_size, uncomprssed_size].pack("VVVV")
57
+ [0x08074b50, crc32, compressed_size, uncomprssed_size].pack('VVVV')
58
58
  end
59
59
 
60
60
  def reset!
@@ -74,7 +74,7 @@ module Zip
74
74
  include TraditionalEncryption
75
75
 
76
76
  def decrypt(data)
77
- data.unpack("C*").map{|x| decode x}.pack("C*")
77
+ data.unpack('C*').map { |x| decode x }.pack('C*')
78
78
  end
79
79
 
80
80
  def reset!(header)
@@ -1,9 +1,9 @@
1
1
  module Zip
2
- class Decompressor #:nodoc:all
3
- CHUNK_SIZE = 32768
2
+ class Decompressor #:nodoc:all
3
+ CHUNK_SIZE = 32_768
4
4
  def initialize(input_stream)
5
5
  super()
6
- @input_stream=input_stream
6
+ @input_stream = input_stream
7
7
  end
8
8
  end
9
9
  end
data/lib/zip/deflater.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Zip
2
2
  class Deflater < Compressor #:nodoc:all
3
-
4
3
  def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new)
5
4
  super()
6
5
  @output_stream = output_stream
@@ -8,18 +7,21 @@ module Zip
8
7
  @size = 0
9
8
  @crc = ::Zlib.crc32
10
9
  @encrypter = encrypter
11
- @buffer_stream = ::StringIO.new('')
12
10
  end
13
11
 
14
- def << (data)
12
+ def <<(data)
15
13
  val = data.to_s
16
- @crc = Zlib::crc32(val, @crc)
14
+ @crc = Zlib.crc32(val, @crc)
17
15
  @size += val.bytesize
18
- @buffer_stream << @zlib_deflater.deflate(data)
16
+ buffer = @zlib_deflater.deflate(data)
17
+ if buffer.empty?
18
+ @output_stream
19
+ else
20
+ @output_stream << @encrypter.encrypt(buffer)
21
+ end
19
22
  end
20
23
 
21
24
  def finish
22
- @output_stream << @encrypter.encrypt(@buffer_stream.string)
23
25
  @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished?
24
26
  end
25
27
 
data/lib/zip/dos_time.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  module Zip
2
2
  class DOSTime < Time #:nodoc:all
3
-
4
- #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
3
+ # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
5
4
 
6
5
  # Register CX, the Time:
7
6
  # Bits 0-4 2 second increments (0-29)
@@ -14,20 +13,20 @@ module Zip
14
13
  # bits 9-15 year (four digit year minus 1980)
15
14
 
16
15
  def to_binary_dos_time
17
- (sec/2) +
16
+ (sec / 2) +
18
17
  (min << 5) +
19
18
  (hour << 11)
20
19
  end
21
20
 
22
21
  def to_binary_dos_date
23
- (day) +
22
+ day +
24
23
  (month << 5) +
25
24
  ((year - 1980) << 9)
26
25
  end
27
26
 
28
27
  # Dos time is only stored with two seconds accuracy
29
28
  def dos_equals(other)
30
- to_i/2 == other.to_i/2
29
+ to_i / 2 == other.to_i / 2
31
30
  end
32
31
 
33
32
  def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
@@ -38,7 +37,7 @@ module Zip
38
37
  month = (0b111100000 & binaryDosDate) >> 5
39
38
  year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
40
39
  begin
41
- self.local(year, month, day, hour, minute, second)
40
+ local(year, month, day, hour, minute, second)
42
41
  end
43
42
  end
44
43
  end
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,
@@ -39,15 +41,14 @@ module Zip
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
+ raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
51
52
  end
52
53
 
53
54
  def initialize(*args)
@@ -68,7 +69,7 @@ module Zip
68
69
  @time = args[8] || ::Zip::DOSTime.now
69
70
 
70
71
  @ftype = name_is_directory? ? :directory : :file
71
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField === @extra
72
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
72
73
  end
73
74
 
74
75
  def time
@@ -83,7 +84,7 @@ module Zip
83
84
  end
84
85
  end
85
86
 
86
- alias :mtime :time
87
+ alias mtime time
87
88
 
88
89
  def time=(value)
89
90
  unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
@@ -94,12 +95,12 @@ module Zip
94
95
  end
95
96
 
96
97
  def file_type_is?(type)
97
- raise InternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
98
+ raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
98
99
  @ftype == type
99
100
  end
100
101
 
101
102
  # Dynamic checkers
102
- %w(directory file symlink).each do |k|
103
+ %w[directory file symlink].each do |k|
103
104
  define_method "#{k}?" do
104
105
  file_type_is?(k.to_sym)
105
106
  end
@@ -109,6 +110,17 @@ module Zip
109
110
  @name.end_with?('/')
110
111
  end
111
112
 
113
+ # Is the name a relative path, free of `..` patterns that could lead to
114
+ # path traversal attacks? This does NOT handle symlinks; if the path
115
+ # contains symlinks, this check is NOT enough to guarantee safety.
116
+ def name_safe?
117
+ cleanpath = Pathname.new(@name).cleanpath
118
+ return false unless cleanpath.relative?
119
+ root = ::File::SEPARATOR
120
+ naive_expanded_path = ::File.join(root, cleanpath.to_s)
121
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
122
+ end
123
+
112
124
  def local_entry_offset #:nodoc:all
113
125
  local_header_offset + @local_header_size
114
126
  end
@@ -143,17 +155,25 @@ module Zip
143
155
  end
144
156
 
145
157
  def next_header_offset #:nodoc:all
146
- local_entry_offset + self.compressed_size + data_descriptor_size
158
+ local_entry_offset + compressed_size + data_descriptor_size
147
159
  end
148
160
 
149
161
  # Extracts entry to file dest_path (defaults to @name).
150
- def extract(dest_path = @name, &block)
162
+ # NB: The caller is responsible for making sure dest_path is safe, if it
163
+ # is passed.
164
+ def extract(dest_path = nil, &block)
165
+ if dest_path.nil? && !name_safe?
166
+ puts "WARNING: skipped #{@name} as unsafe"
167
+ return self
168
+ end
169
+
170
+ dest_path ||= @name
151
171
  block ||= proc { ::Zip.on_exists_proc }
152
172
 
153
173
  if directory? || file? || symlink?
154
- self.__send__("create_#{@ftype}", dest_path, &block)
174
+ __send__("create_#{@ftype}", dest_path, &block)
155
175
  else
156
- raise RuntimeError, "unknown file type #{self.inspect}"
176
+ raise "unknown file type #{inspect}"
157
177
  end
158
178
 
159
179
  self
@@ -163,8 +183,6 @@ module Zip
163
183
  @name
164
184
  end
165
185
 
166
- protected
167
-
168
186
  class << self
169
187
  def read_zip_short(io) # :nodoc:
170
188
  io.read(2).unpack('v')[0]
@@ -179,11 +197,11 @@ module Zip
179
197
  end
180
198
 
181
199
  def read_c_dir_entry(io) #:nodoc:all
182
- path = if io.is_a?(::IO)
183
- io.path
184
- else
185
- io
186
- end
200
+ path = if io.respond_to?(:path)
201
+ io.path
202
+ else
203
+ io
204
+ end
187
205
  entry = new(path)
188
206
  entry.read_c_dir_entry(io)
189
207
  entry
@@ -192,13 +210,12 @@ module Zip
192
210
  end
193
211
 
194
212
  def read_local_entry(io)
195
- entry = self.new(io)
213
+ entry = new(io)
196
214
  entry.read_local_entry(io)
197
215
  entry
198
216
  rescue Error
199
217
  nil
200
218
  end
201
-
202
219
  end
203
220
 
204
221
  public
@@ -221,10 +238,10 @@ module Zip
221
238
  def read_local_entry(io) #:nodoc:all
222
239
  @local_header_offset = io.tell
223
240
 
224
- static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
241
+ static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
225
242
 
226
243
  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"
244
+ raise Error, 'Premature end of file. Not enough data for zip entry local header'
228
245
  end
229
246
 
230
247
  unpack_local_entry(static_sized_fields_buf)
@@ -237,13 +254,16 @@ module Zip
237
254
  @name = io.read(@name_length)
238
255
  extra = io.read(@extra_length)
239
256
 
240
- @name.gsub!('\\', '/')
257
+ @name.tr!('\\', '/')
258
+ if ::Zip.force_entry_names_encoding
259
+ @name.force_encoding(::Zip.force_entry_names_encoding)
260
+ end
241
261
 
242
262
  if extra && extra.bytesize != @extra_length
243
- raise ::Zip::Error, "Truncated local zip entry header"
263
+ raise ::Zip::Error, 'Truncated local zip entry header'
244
264
  else
245
- if ::Zip::ExtraField === @extra
246
- @extra.merge(extra)
265
+ if @extra.is_a?(::Zip::ExtraField)
266
+ @extra.merge(extra) if extra
247
267
  else
248
268
  @extra = ::Zip::ExtraField.new(extra)
249
269
  end
@@ -256,13 +276,13 @@ module Zip
256
276
  zip64 = @extra['Zip64']
257
277
  [::Zip::LOCAL_ENTRY_SIGNATURE,
258
278
  @version_needed_to_extract, # version needed to extract
259
- @gp_flags, # @gp_flags ,
279
+ @gp_flags, # @gp_flags
260
280
  @compression_method,
261
- @time.to_binary_dos_time, # @last_mod_time ,
262
- @time.to_binary_dos_date, # @last_mod_date ,
281
+ @time.to_binary_dos_time, # @last_mod_time
282
+ @time.to_binary_dos_date, # @last_mod_date
263
283
  @crc,
264
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
265
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
284
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
285
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
266
286
  name_size,
267
287
  @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
268
288
  end
@@ -306,7 +326,7 @@ module Zip
306
326
  def set_ftype_from_c_dir_entry
307
327
  @ftype = case @fstype
308
328
  when ::Zip::FSTYPE_UNIX
309
- @unix_perms = (@external_file_attributes >> 16) & 07777
329
+ @unix_perms = (@external_file_attributes >> 16) & 0o7777
310
330
  case (@external_file_attributes >> 28)
311
331
  when ::Zip::FILE_TYPE_DIR
312
332
  :directory
@@ -315,8 +335,8 @@ module Zip
315
335
  when ::Zip::FILE_TYPE_SYMLINK
316
336
  :symlink
317
337
  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
338
+ # best case guess for whether it is a file or not
339
+ # Otherwise this would be set to unknown and that entry would never be able to extracted
320
340
  if name_is_directory?
321
341
  :directory
322
342
  else
@@ -333,21 +353,18 @@ module Zip
333
353
  end
334
354
 
335
355
  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
356
+ return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
357
+ raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
339
358
  end
340
359
 
341
360
  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
361
+ return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
362
+ raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
345
363
  end
346
364
 
347
365
  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
366
+ return if @comment && @comment.bytesize == @comment_length
367
+ raise ::Zip::Error, 'Truncated cdir zip entry header'
351
368
  end
352
369
 
353
370
  def read_c_dir_extra_field(io)
@@ -364,7 +381,10 @@ module Zip
364
381
  unpack_c_dir_entry(static_sized_fields_buf)
365
382
  check_c_dir_entry_signature
366
383
  set_time(@last_mod_date, @last_mod_time)
367
- @name = io.read(@name_length).tr('\\', '/')
384
+ @name = io.read(@name_length)
385
+ if ::Zip.force_entry_names_encoding
386
+ @name.force_encoding(::Zip.force_entry_names_encoding)
387
+ end
368
388
  read_c_dir_extra_field(io)
369
389
  @comment = io.read(@comment_length)
370
390
  check_c_dir_entry_comment_size
@@ -374,33 +394,32 @@ module Zip
374
394
 
375
395
  def file_stat(path) # :nodoc:
376
396
  if @follow_symlinks
377
- ::File::stat(path)
397
+ ::File.stat(path)
378
398
  else
379
- ::File::lstat(path)
399
+ ::File.lstat(path)
380
400
  end
381
401
  end
382
402
 
383
403
  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
404
+ return if Zip::RUNNING_ON_WINDOWS
405
+ stat = file_stat(path)
406
+ @unix_uid = stat.uid
407
+ @unix_gid = stat.gid
408
+ @unix_perms = stat.mode & 0o7777
390
409
  end
391
410
 
392
411
  def set_unix_permissions_on_path(dest_path)
393
412
  # BUG: does not update timestamps into account
394
413
  # ignore setuid/setgid bits by default. honor if @restore_ownership
395
- unix_perms_mask = 01777
396
- unix_perms_mask = 07777 if @restore_ownership
414
+ unix_perms_mask = 0o1777
415
+ unix_perms_mask = 0o7777 if @restore_ownership
397
416
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
398
417
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
399
418
  # File::utimes()
400
419
  end
401
420
 
402
421
  def set_extra_attributes_on_path(dest_path) # :nodoc:
403
- return unless (file? || directory?)
422
+ return unless file? || directory?
404
423
 
405
424
  case @fstype
406
425
  when ::Zip::FSTYPE_UNIX
@@ -414,21 +433,21 @@ module Zip
414
433
  @header_signature,
415
434
  @version, # version of encoding software
416
435
  @fstype, # filesystem type
417
- @version_needed_to_extract, # @versionNeededToExtract ,
418
- @gp_flags, # @gp_flags ,
436
+ @version_needed_to_extract, # @versionNeededToExtract
437
+ @gp_flags, # @gp_flags
419
438
  @compression_method,
420
- @time.to_binary_dos_time, # @last_mod_time ,
421
- @time.to_binary_dos_date, # @last_mod_date ,
439
+ @time.to_binary_dos_time, # @last_mod_time
440
+ @time.to_binary_dos_date, # @last_mod_date
422
441
  @crc,
423
- (zip64 && zip64.compressed_size) ? 0xFFFFFFFF : @compressed_size,
424
- (zip64 && zip64.original_size) ? 0xFFFFFFFF : @size,
442
+ zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
443
+ zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
425
444
  name_size,
426
445
  @extra ? @extra.c_dir_size : 0,
427
446
  comment_size,
428
- (zip64 && zip64.disk_start_number) ? 0xFFFF : 0, # disk number start
447
+ zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
429
448
  @internal_file_attributes, # file type (binary=0, text=1)
430
449
  @external_file_attributes, # native filesystem attributes
431
- (zip64 && zip64.relative_header_offset) ? 0xFFFFFFFF : @local_header_offset,
450
+ zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
432
451
  @name,
433
452
  @extra,
434
453
  @comment
@@ -441,18 +460,18 @@ module Zip
441
460
  when ::Zip::FSTYPE_UNIX
442
461
  ft = case @ftype
443
462
  when :file
444
- @unix_perms ||= 0644
463
+ @unix_perms ||= 0o644
445
464
  ::Zip::FILE_TYPE_FILE
446
465
  when :directory
447
- @unix_perms ||= 0755
466
+ @unix_perms ||= 0o755
448
467
  ::Zip::FILE_TYPE_DIR
449
468
  when :symlink
450
- @unix_perms ||= 0755
469
+ @unix_perms ||= 0o755
451
470
  ::Zip::FILE_TYPE_SYMLINK
452
471
  end
453
472
 
454
473
  unless ft.nil?
455
- @external_file_attributes = (ft << 12 | (@unix_perms & 07777)) << 16
474
+ @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
456
475
  end
457
476
  end
458
477
 
@@ -466,14 +485,14 @@ module Zip
466
485
  def ==(other)
467
486
  return false unless other.class == self.class
468
487
  # 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)
488
+ keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
489
+ other.__send__(k.to_sym) == __send__(k.to_sym)
471
490
  end
472
- keys_equal && self.time.dos_equals(other.time)
491
+ keys_equal && time.dos_equals(other.time)
473
492
  end
474
493
 
475
- def <=> (other)
476
- self.to_s <=> other.to_s
494
+ def <=>(other)
495
+ to_s <=> other.to_s
477
496
  end
478
497
 
479
498
  # Returns an IO like object for the given ZipEntry.
@@ -496,6 +515,7 @@ module Zip
496
515
  end
497
516
  else
498
517
  zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
518
+ zis.instance_variable_set(:@complete_entry, self)
499
519
  zis.get_next_entry
500
520
  if block_given?
501
521
  begin
@@ -515,8 +535,8 @@ module Zip
515
535
  when 'file'
516
536
  if name_is_directory?
517
537
  raise ArgumentError,
518
- "entry name '#{newEntry}' indicates directory entry, but "+
519
- "'#{src_path}' is not a directory"
538
+ "entry name '#{newEntry}' indicates directory entry, but " \
539
+ "'#{src_path}' is not a directory"
520
540
  end
521
541
  :file
522
542
  when 'directory'
@@ -525,12 +545,12 @@ module Zip
525
545
  when 'link'
526
546
  if name_is_directory?
527
547
  raise ArgumentError,
528
- "entry name '#{newEntry}' indicates directory entry, but "+
529
- "'#{src_path}' is not a directory"
548
+ "entry name '#{newEntry}' indicates directory entry, but " \
549
+ "'#{src_path}' is not a directory"
530
550
  end
531
551
  :symlink
532
552
  else
533
- raise RuntimeError, "unknown file type: #{src_path.inspect} #{stat.inspect}"
553
+ raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
534
554
  end
535
555
 
536
556
  @filepath = src_path
@@ -541,7 +561,7 @@ module Zip
541
561
  if @ftype == :directory
542
562
  zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
543
563
  elsif @filepath
544
- zip_output_stream.put_next_entry(self, nil, nil, self.compression_method || ::Zip::Entry::DEFLATED)
564
+ zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
545
565
  get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
546
566
  else
547
567
  zip_output_stream.copy_raw_entry(self)
@@ -551,14 +571,14 @@ module Zip
551
571
  def parent_as_string
552
572
  entry_name = name.chomp('/')
553
573
  slash_index = entry_name.rindex('/')
554
- slash_index ? entry_name.slice(0, slash_index+1) : nil
574
+ slash_index ? entry_name.slice(0, slash_index + 1) : nil
555
575
  end
556
576
 
557
577
  def get_raw_input_stream(&block)
558
- if @zipfile.is_a?(::IO) || @zipfile.is_a?(::StringIO)
578
+ if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
559
579
  yield @zipfile
560
580
  else
561
- ::File.open(@zipfile, "rb", &block)
581
+ ::File.open(@zipfile, 'rb', &block)
562
582
  end
563
583
  end
564
584
 
@@ -571,21 +591,33 @@ module Zip
571
591
  def set_time(binary_dos_date, binary_dos_time)
572
592
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
573
593
  rescue ArgumentError
574
- puts "Invalid date/time in zip entry" if ::Zip.warn_invalid_date
594
+ warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
575
595
  end
576
596
 
577
- def create_file(dest_path, continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
597
+ def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
578
598
  if ::File.exist?(dest_path) && !yield(self, dest_path)
579
599
  raise ::Zip::DestinationFileExistsError,
580
600
  "Destination '#{dest_path}' already exists"
581
601
  end
582
- ::File.open(dest_path, "wb") do |os|
602
+ ::File.open(dest_path, 'wb') do |os|
583
603
  get_input_stream do |is|
584
604
  set_extra_attributes_on_path(dest_path)
585
605
 
586
- buf = ''
587
- while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
606
+ bytes_written = 0
607
+ warned = false
608
+ buf = ''.dup
609
+ while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
588
610
  os << buf
611
+ bytes_written += buf.bytesize
612
+ if bytes_written > size && !warned
613
+ message = "Entry #{name} should be #{size}B but is larger when inflated"
614
+ if ::Zip.validate_entry_sizes
615
+ raise ::Zip::EntrySizeError, message
616
+ else
617
+ puts "WARNING: #{message}"
618
+ warned = true
619
+ end
620
+ end
589
621
  end
590
622
  end
591
623
  end
@@ -595,11 +627,11 @@ module Zip
595
627
  return if ::File.directory?(dest_path)
596
628
  if ::File.exist?(dest_path)
597
629
  if block_given? && yield(self, dest_path)
598
- ::FileUtils::rm_f dest_path
630
+ ::FileUtils.rm_f dest_path
599
631
  else
600
632
  raise ::Zip::DestinationFileExistsError,
601
- "Cannot create directory '#{dest_path}'. "+
602
- "A file already exists with that name"
633
+ "Cannot create directory '#{dest_path}'. " \
634
+ 'A file already exists with that name'
603
635
  end
604
636
  end
605
637
  ::FileUtils.mkdir_p(dest_path)
@@ -608,43 +640,19 @@ module Zip
608
640
 
609
641
  # BUG: create_symlink() does not use &block
610
642
  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)
643
+ # TODO: Symlinks pose security challenges. Symlink support temporarily
644
+ # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
645
+ puts "WARNING: skipped symlink #{dest_path}"
637
646
  end
638
647
 
639
648
  # apply missing data from the zip64 extra information field, if present
640
649
  # (required when file sizes exceed 2**32, but can be used for all files)
641
650
  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
651
+ return if @extra['Zip64'].nil?
652
+ if for_local_header
653
+ @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
654
+ else
655
+ @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
648
656
  end
649
657
  end
650
658
 
@@ -656,10 +664,7 @@ module Zip
656
664
  def prep_zip64_extra(for_local_header) #:nodoc:all
657
665
  return unless ::Zip.write_zip64_support
658
666
  need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
659
- unless for_local_header
660
- need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
661
- end
662
-
667
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
663
668
  if need_zip64
664
669
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
665
670
  @extra.delete('Zip64Placeholder')
@@ -687,7 +692,6 @@ module Zip
687
692
  end
688
693
  end
689
694
  end
690
-
691
695
  end
692
696
  end
693
697