rubyzip 1.1.7 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubyzip might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +87 -45
  3. data/Rakefile +3 -4
  4. data/lib/zip.rb +11 -5
  5. data/lib/zip/central_directory.rb +8 -8
  6. data/lib/zip/compressor.rb +1 -2
  7. data/lib/zip/constants.rb +5 -5
  8. data/lib/zip/crypto/null_encryption.rb +4 -6
  9. data/lib/zip/crypto/traditional_encryption.rb +5 -5
  10. data/lib/zip/decompressor.rb +3 -3
  11. data/lib/zip/deflater.rb +8 -6
  12. data/lib/zip/dos_time.rb +5 -6
  13. data/lib/zip/entry.rb +120 -128
  14. data/lib/zip/entry_set.rb +14 -14
  15. data/lib/zip/errors.rb +1 -0
  16. data/lib/zip/extra_field.rb +8 -8
  17. data/lib/zip/extra_field/generic.rb +8 -8
  18. data/lib/zip/extra_field/ntfs.rb +14 -16
  19. data/lib/zip/extra_field/old_unix.rb +9 -10
  20. data/lib/zip/extra_field/universal_time.rb +14 -14
  21. data/lib/zip/extra_field/unix.rb +8 -9
  22. data/lib/zip/extra_field/zip64.rb +12 -11
  23. data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
  24. data/lib/zip/file.rb +81 -81
  25. data/lib/zip/filesystem.rb +144 -143
  26. data/lib/zip/inflater.rb +5 -5
  27. data/lib/zip/input_stream.rb +22 -13
  28. data/lib/zip/ioextras.rb +1 -3
  29. data/lib/zip/ioextras/abstract_input_stream.rb +6 -10
  30. data/lib/zip/ioextras/abstract_output_stream.rb +3 -5
  31. data/lib/zip/null_compressor.rb +2 -2
  32. data/lib/zip/null_decompressor.rb +3 -3
  33. data/lib/zip/null_input_stream.rb +0 -0
  34. data/lib/zip/output_stream.rb +13 -14
  35. data/lib/zip/pass_thru_compressor.rb +4 -4
  36. data/lib/zip/pass_thru_decompressor.rb +3 -4
  37. data/lib/zip/streamable_directory.rb +2 -2
  38. data/lib/zip/streamable_stream.rb +3 -3
  39. data/lib/zip/version.rb +1 -1
  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 +33 -40
  81. data/test/file_permissions_test.rb +65 -0
  82. data/test/file_split_test.rb +24 -27
  83. data/test/file_test.rb +266 -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 +94 -65
@@ -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
@@ -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
 
@@ -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
@@ -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,20 +591,20 @@ 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
+ buf = ''.dup
607
+ while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
588
608
  os << buf
589
609
  end
590
610
  end
@@ -595,11 +615,11 @@ module Zip
595
615
  return if ::File.directory?(dest_path)
596
616
  if ::File.exist?(dest_path)
597
617
  if block_given? && yield(self, dest_path)
598
- ::FileUtils::rm_f dest_path
618
+ ::FileUtils.rm_f dest_path
599
619
  else
600
620
  raise ::Zip::DestinationFileExistsError,
601
- "Cannot create directory '#{dest_path}'. "+
602
- "A file already exists with that name"
621
+ "Cannot create directory '#{dest_path}'. " \
622
+ 'A file already exists with that name'
603
623
  end
604
624
  end
605
625
  ::FileUtils.mkdir_p(dest_path)
@@ -608,43 +628,19 @@ module Zip
608
628
 
609
629
  # BUG: create_symlink() does not use &block
610
630
  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)
631
+ # TODO: Symlinks pose security challenges. Symlink support temporarily
632
+ # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
633
+ puts "WARNING: skipped symlink #{dest_path}"
637
634
  end
638
635
 
639
636
  # apply missing data from the zip64 extra information field, if present
640
637
  # (required when file sizes exceed 2**32, but can be used for all files)
641
638
  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
639
+ return if @extra['Zip64'].nil?
640
+ if for_local_header
641
+ @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
642
+ else
643
+ @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
648
644
  end
649
645
  end
650
646
 
@@ -656,10 +652,7 @@ module Zip
656
652
  def prep_zip64_extra(for_local_header) #:nodoc:all
657
653
  return unless ::Zip.write_zip64_support
658
654
  need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
659
- unless for_local_header
660
- need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
661
- end
662
-
655
+ need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
663
656
  if need_zip64
664
657
  @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
665
658
  @extra.delete('Zip64Placeholder')
@@ -687,7 +680,6 @@ module Zip
687
680
  end
688
681
  end
689
682
  end
690
-
691
683
  end
692
684
  end
693
685