rubyzip 2.0.0 → 2.4.1

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -9
  3. data/Rakefile +3 -0
  4. data/lib/zip/central_directory.rb +9 -5
  5. data/lib/zip/constants.rb +52 -0
  6. data/lib/zip/crypto/decrypted_io.rb +40 -0
  7. data/lib/zip/crypto/traditional_encryption.rb +9 -9
  8. data/lib/zip/decompressor.rb +19 -1
  9. data/lib/zip/dos_time.rb +24 -12
  10. data/lib/zip/entry.rb +107 -49
  11. data/lib/zip/entry_set.rb +2 -0
  12. data/lib/zip/errors.rb +1 -0
  13. data/lib/zip/extra_field/generic.rb +10 -9
  14. data/lib/zip/extra_field/ntfs.rb +5 -1
  15. data/lib/zip/extra_field/old_unix.rb +3 -1
  16. data/lib/zip/extra_field/universal_time.rb +42 -12
  17. data/lib/zip/extra_field/unix.rb +3 -1
  18. data/lib/zip/extra_field/zip64.rb +5 -3
  19. data/lib/zip/extra_field.rb +11 -9
  20. data/lib/zip/file.rb +142 -65
  21. data/lib/zip/filesystem.rb +193 -177
  22. data/lib/zip/inflater.rb +24 -36
  23. data/lib/zip/input_stream.rb +50 -30
  24. data/lib/zip/ioextras/abstract_input_stream.rb +23 -12
  25. data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
  26. data/lib/zip/ioextras.rb +3 -3
  27. data/lib/zip/null_decompressor.rb +1 -9
  28. data/lib/zip/output_stream.rb +28 -12
  29. data/lib/zip/pass_thru_compressor.rb +2 -2
  30. data/lib/zip/pass_thru_decompressor.rb +13 -22
  31. data/lib/zip/streamable_directory.rb +3 -3
  32. data/lib/zip/streamable_stream.rb +6 -10
  33. data/lib/zip/version.rb +1 -1
  34. data/lib/zip.rb +22 -2
  35. data/samples/example.rb +2 -2
  36. data/samples/example_filesystem.rb +1 -1
  37. data/samples/gtk_ruby_zip.rb +19 -19
  38. data/samples/qtzip.rb +6 -6
  39. data/samples/write_simple.rb +2 -4
  40. data/samples/zipfind.rb +23 -22
  41. metadata +52 -31
data/lib/zip/file.rb CHANGED
@@ -49,26 +49,41 @@ module Zip
49
49
  MAX_SEGMENT_SIZE = 3_221_225_472
50
50
  MIN_SEGMENT_SIZE = 65_536
51
51
  DATA_BUFFER_SIZE = 8192
52
- IO_METHODS = [:tell, :seek, :read, :close]
52
+ IO_METHODS = [:tell, :seek, :read, :eof, :close]
53
+
54
+ DEFAULT_OPTIONS = {
55
+ restore_ownership: false,
56
+ restore_permissions: false,
57
+ restore_times: false
58
+ }.freeze
53
59
 
54
60
  attr_reader :name
55
61
 
56
- # default -> false
62
+ # default -> false.
57
63
  attr_accessor :restore_ownership
58
- # default -> false
64
+
65
+ # default -> false, but will be set to true in a future version.
59
66
  attr_accessor :restore_permissions
60
- # default -> true
67
+
68
+ # default -> false, but will be set to true in a future version.
61
69
  attr_accessor :restore_times
70
+
62
71
  # Returns the zip files comment, if it has one
63
72
  attr_accessor :comment
64
73
 
65
74
  # Opens a zip archive. Pass true as the second parameter to create
66
75
  # a new archive if it doesn't exist already.
67
- def initialize(path_or_io, create = false, buffer = false, options = {})
76
+ def initialize(path_or_io, dep_create = false, dep_buffer = false,
77
+ create: false, buffer: false, **options)
68
78
  super()
79
+
80
+ Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
81
+
82
+ options = DEFAULT_OPTIONS.merge(options)
69
83
  @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
70
84
  @comment = ''
71
- @create = create ? true : false # allow any truthy value to mean true
85
+ @create = create || dep_create ? true : false # allow any truthy value to mean true
86
+ buffer ||= dep_buffer
72
87
 
73
88
  if ::File.size?(@name.to_s)
74
89
  # There is a file, which exists, that is associated with this zip.
@@ -84,6 +99,7 @@ module Zip
84
99
  end
85
100
  elsif buffer && path_or_io.size > 0
86
101
  # This zip is probably a non-empty StringIO.
102
+ @create = false
87
103
  read_from_stream(path_or_io)
88
104
  elsif @create
89
105
  # This zip is completely new/empty and is to be created.
@@ -98,18 +114,21 @@ module Zip
98
114
 
99
115
  @stored_entries = @entry_set.dup
100
116
  @stored_comment = @comment
101
- @restore_ownership = options[:restore_ownership] || false
102
- @restore_permissions = options[:restore_permissions] || true
103
- @restore_times = options[:restore_times] || true
117
+ @restore_ownership = options[:restore_ownership]
118
+ @restore_permissions = options[:restore_permissions]
119
+ @restore_times = options[:restore_times]
104
120
  end
105
121
 
106
122
  class << self
107
- # Same as #new. If a block is passed the ZipFile object is passed
108
- # to the block and is automatically closed afterwards just as with
109
- # ruby's builtin File.open method.
110
- def open(file_name, create = false)
111
- zf = ::Zip::File.new(file_name, create)
123
+ # Similar to ::new. If a block is passed the Zip::File object is passed
124
+ # to the block and is automatically closed afterwards, just as with
125
+ # ruby's builtin File::open method.
126
+ def open(file_name, dep_create = false, create: false, **options)
127
+ Zip.warn_about_v3_api('Zip::File.open') if dep_create
128
+
129
+ zf = ::Zip::File.new(file_name, create: (dep_create || create), buffer: false, **options)
112
130
  return zf unless block_given?
131
+
113
132
  begin
114
133
  yield zf
115
134
  ensure
@@ -119,7 +138,9 @@ module Zip
119
138
 
120
139
  # Same as #open. But outputs data to a buffer instead of a file
121
140
  def add_buffer
122
- io = ::StringIO.new('')
141
+ Zip.warn_about_v3_api('Zip::File.add_buffer')
142
+
143
+ io = ::StringIO.new
123
144
  zf = ::Zip::File.new(io, true, true)
124
145
  yield zf
125
146
  zf.write_buffer(io)
@@ -129,18 +150,19 @@ module Zip
129
150
  # stream, and outputs data to a buffer.
130
151
  # (This can be used to extract data from a
131
152
  # downloaded zip archive without first saving it to disk.)
132
- def open_buffer(io, options = {})
133
- unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
153
+ def open_buffer(io, **options)
154
+ unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
134
155
  raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
135
156
  end
136
157
 
137
- io = ::StringIO.new(io) if io.is_a?(::String)
158
+ io = ::StringIO.new(io) if io.kind_of?(::String)
138
159
 
139
160
  # https://github.com/rubyzip/rubyzip/issues/119
140
161
  io.binmode if io.respond_to?(:binmode)
141
162
 
142
- zf = ::Zip::File.new(io, true, true, options)
163
+ zf = ::Zip::File.new(io, create: true, buffer: true, **options)
143
164
  return zf unless block_given?
165
+
144
166
  yield zf
145
167
 
146
168
  begin
@@ -156,9 +178,9 @@ module Zip
156
178
  # whereas ZipInputStream jumps through the entire archive accessing the
157
179
  # local entry headers (which contain the same information as the
158
180
  # central directory).
159
- def foreach(aZipFileName, &block)
160
- open(aZipFileName) do |zipFile|
161
- zipFile.each(&block)
181
+ def foreach(zip_file_name, &block)
182
+ ::Zip::File.open(zip_file_name) do |zip_file|
183
+ zip_file.each(&block)
162
184
  end
163
185
  end
164
186
 
@@ -216,16 +238,24 @@ module Zip
216
238
  end
217
239
 
218
240
  # Splits an archive into parts with segment size
219
- def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
241
+ def split(zip_file_name,
242
+ dep_segment_size = MAX_SEGMENT_SIZE, dep_delete_zip_file = true, dep_partial_zip_file_name = nil,
243
+ segment_size: MAX_SEGMENT_SIZE, delete_zip_file: nil, partial_zip_file_name: nil)
220
244
  raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
221
245
  raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
246
+
247
+ if dep_segment_size != MAX_SEGMENT_SIZE || !dep_delete_zip_file || dep_partial_zip_file_name
248
+ Zip.warn_about_v3_api('Zip::File.split')
249
+ end
250
+
222
251
  zip_file_size = ::File.size(zip_file_name)
223
- segment_size = get_segment_size_for_split(segment_size)
252
+ segment_size = get_segment_size_for_split(segment_size || dep_segment_size)
224
253
  return if zip_file_size <= segment_size
254
+
225
255
  segment_count = get_segment_count_for_split(zip_file_size, segment_size)
226
256
  # Checking for correct zip structure
227
- open(zip_file_name) {}
228
- partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
257
+ ::Zip::File.open(zip_file_name) {}
258
+ partial_zip_file_name = get_partial_zip_file_name(zip_file_name, (partial_zip_file_name || dep_partial_zip_file_name))
229
259
  szip_file_index = 0
230
260
  ::File.open(zip_file_name, 'rb') do |zip_file|
231
261
  until zip_file.eof?
@@ -233,6 +263,7 @@ module Zip
233
263
  save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
234
264
  end
235
265
  end
266
+ delete_zip_file = delete_zip_file.nil? ? dep_delete_zip_file : delete_zip_file
236
267
  ::File.delete(zip_file_name) if delete_zip_file
237
268
  szip_file_index
238
269
  end
@@ -241,8 +272,8 @@ module Zip
241
272
  # Returns an input stream to the specified entry. If a block is passed
242
273
  # the stream object is passed to the block and the stream is automatically
243
274
  # closed afterwards just as with ruby's builtin File.open method.
244
- def get_input_stream(entry, &aProc)
245
- get_entry(entry).get_input_stream(&aProc)
275
+ def get_input_stream(entry, &a_proc)
276
+ get_entry(entry).get_input_stream(&a_proc)
246
277
  end
247
278
 
248
279
  # Returns an output stream to the specified entry. If entry is not an instance
@@ -250,22 +281,45 @@ module Zip
250
281
  # specified. If a block is passed the stream object is passed to the block and
251
282
  # the stream is automatically closed afterwards just as with ruby's builtin
252
283
  # File.open method.
253
- def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
284
+ # rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
285
+ def get_output_stream(entry,
286
+ dep_permission_int = nil, dep_comment = nil,
287
+ dep_extra = nil, dep_compressed_size = nil, dep_crc = nil,
288
+ dep_compression_method = nil, dep_size = nil, dep_time = nil,
289
+ permission_int: nil, comment: nil,
290
+ extra: nil, compressed_size: nil, crc: nil,
291
+ compression_method: nil, size: nil, time: nil,
292
+ &a_proc)
293
+
294
+ unless dep_permission_int.nil? && dep_comment.nil? && dep_extra.nil? &&
295
+ dep_compressed_size.nil? && dep_crc.nil? && dep_compression_method.nil? &&
296
+ dep_size.nil? && dep_time.nil?
297
+ Zip.warn_about_v3_api('Zip::File#get_output_stream')
298
+ end
299
+
254
300
  new_entry =
255
301
  if entry.kind_of?(Entry)
256
302
  entry
257
303
  else
258
- Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time)
304
+ Entry.new(@name, entry.to_s,
305
+ comment: (comment || dep_comment),
306
+ extra: (extra || dep_extra),
307
+ compressed_size: (compressed_size || dep_compressed_size),
308
+ crc: (crc || dep_crc),
309
+ compression_method: (compression_method || dep_compression_method),
310
+ size: (size || dep_size),
311
+ time: (time || dep_time))
259
312
  end
260
313
  if new_entry.directory?
261
314
  raise ArgumentError,
262
315
  "cannot open stream to directory entry - '#{new_entry}'"
263
316
  end
264
- new_entry.unix_perms = permission_int
317
+ new_entry.unix_perms = (permission_int || dep_permission_int)
265
318
  zip_streamable_entry = StreamableStream.new(new_entry)
266
319
  @entry_set << zip_streamable_entry
267
- zip_streamable_entry.get_output_stream(&aProc)
320
+ zip_streamable_entry.get_output_stream(&a_proc)
268
321
  end
322
+ # rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
269
323
 
270
324
  # Returns the name of the zip archive
271
325
  def to_s
@@ -274,7 +328,7 @@ module Zip
274
328
 
275
329
  # Returns a string containing the contents of the specified entry
276
330
  def read(entry)
277
- get_input_stream(entry) { |is| is.read }
331
+ get_input_stream(entry, &:read)
278
332
  end
279
333
 
280
334
  # Convenience method for adding the contents of a file to the archive
@@ -301,32 +355,47 @@ module Zip
301
355
 
302
356
  # Renames the specified entry.
303
357
  def rename(entry, new_name, &continue_on_exists_proc)
304
- foundEntry = get_entry(entry)
358
+ found_entry = get_entry(entry)
305
359
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
306
- @entry_set.delete(foundEntry)
307
- foundEntry.name = new_name
308
- @entry_set << foundEntry
360
+ @entry_set.delete(found_entry)
361
+ found_entry.name = new_name
362
+ @entry_set << found_entry
309
363
  end
310
364
 
311
- # Replaces the specified entry with the contents of srcPath (from
365
+ # Replaces the specified entry with the contents of src_path (from
312
366
  # the file system).
313
- def replace(entry, srcPath)
314
- check_file(srcPath)
367
+ def replace(entry, src_path)
368
+ check_file(src_path)
315
369
  remove(entry)
316
- add(entry, srcPath)
370
+ add(entry, src_path)
317
371
  end
318
372
 
319
373
  # Extracts entry to file dest_path.
320
374
  def extract(entry, dest_path, &block)
375
+ Zip.warn_about_v3_api('Zip::File#extract')
376
+
321
377
  block ||= proc { ::Zip.on_exists_proc }
322
378
  found_entry = get_entry(entry)
323
379
  found_entry.extract(dest_path, &block)
324
380
  end
325
381
 
382
+ # Extracts `entry` to a file at `entry_path`, with `destination_directory`
383
+ # as the base location in the filesystem.
384
+ #
385
+ # NB: The caller is responsible for making sure `destination_directory` is
386
+ # safe, if it is passed.
387
+ def extract_v3(entry, entry_path = nil, destination_directory: '.', &block)
388
+ block ||= proc { ::Zip.on_exists_proc }
389
+ found_entry = get_entry(entry)
390
+ entry_path ||= found_entry.name
391
+ found_entry.extract_v3(entry_path, destination_directory: destination_directory, &block)
392
+ end
393
+
326
394
  # Commits changes that has been made since the previous commit to
327
395
  # the zip archive.
328
396
  def commit
329
- return if name.is_a?(StringIO) || !commit_required?
397
+ return if name.kind_of?(StringIO) || !commit_required?
398
+
330
399
  on_success_replace do |tmp_file|
331
400
  ::Zip::OutputStream.open(tmp_file) do |zos|
332
401
  @entry_set.each do |e|
@@ -342,7 +411,9 @@ module Zip
342
411
  end
343
412
 
344
413
  # Write buffer write changes to buffer and return
345
- def write_buffer(io = ::StringIO.new(''))
414
+ def write_buffer(io = ::StringIO.new)
415
+ return io unless commit_required?
416
+
346
417
  ::Zip::OutputStream.write_buffer(io) do |zos|
347
418
  @entry_set.each { |e| e.write_to_zip_output_stream(zos) }
348
419
  zos.comment = comment
@@ -366,7 +437,13 @@ module Zip
366
437
  # Searches for entry with the specified name. Returns nil if
367
438
  # no entry is found. See also get_entry
368
439
  def find_entry(entry_name)
369
- @entry_set.find_entry(entry_name)
440
+ selected_entry = @entry_set.find_entry(entry_name)
441
+ return if selected_entry.nil?
442
+
443
+ selected_entry.restore_ownership = @restore_ownership
444
+ selected_entry.restore_permissions = @restore_permissions
445
+ selected_entry.restore_times = @restore_times
446
+ selected_entry
370
447
  end
371
448
 
372
449
  # Searches for entries given a glob
@@ -378,43 +455,43 @@ module Zip
378
455
  # if no entry is found.
379
456
  def get_entry(entry)
380
457
  selected_entry = find_entry(entry)
381
- raise Errno::ENOENT, entry unless selected_entry
382
- selected_entry.restore_ownership = @restore_ownership
383
- selected_entry.restore_permissions = @restore_permissions
384
- selected_entry.restore_times = @restore_times
458
+ raise Errno::ENOENT, entry if selected_entry.nil?
459
+
385
460
  selected_entry
386
461
  end
387
462
 
388
463
  # Creates a directory
389
- def mkdir(entryName, permissionInt = 0o755)
390
- raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
391
- entryName = entryName.dup.to_s
392
- entryName << '/' unless entryName.end_with?('/')
393
- @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt)
464
+ def mkdir(entry_name, permission = 0o755)
465
+ raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
466
+
467
+ entry_name = entry_name.dup.to_s
468
+ entry_name << '/' unless entry_name.end_with?('/')
469
+ @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
394
470
  end
395
471
 
396
472
  private
397
473
 
398
- def directory?(newEntry, srcPath)
399
- srcPathIsDirectory = ::File.directory?(srcPath)
400
- if newEntry.directory? && !srcPathIsDirectory
474
+ def directory?(new_entry, src_path)
475
+ path_is_directory = ::File.directory?(src_path)
476
+ if new_entry.directory? && !path_is_directory
401
477
  raise ArgumentError,
402
- "entry name '#{newEntry}' indicates directory entry, but " \
403
- "'#{srcPath}' is not a directory"
404
- elsif !newEntry.directory? && srcPathIsDirectory
405
- newEntry.name += '/'
478
+ "entry name '#{new_entry}' indicates directory entry, but " \
479
+ "'#{src_path}' is not a directory"
480
+ elsif !new_entry.directory? && path_is_directory
481
+ new_entry.name += '/'
406
482
  end
407
- newEntry.directory? && srcPathIsDirectory
483
+ new_entry.directory? && path_is_directory
408
484
  end
409
485
 
410
- def check_entry_exists(entryName, continue_on_exists_proc, procedureName)
486
+ def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
411
487
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
412
- return unless @entry_set.include?(entryName)
488
+ return unless @entry_set.include?(entry_name)
489
+
413
490
  if continue_on_exists_proc.call
414
- remove get_entry(entryName)
491
+ remove get_entry(entry_name)
415
492
  else
416
493
  raise ::Zip::EntryExistsError,
417
- procedureName + " failed. Entry #{entryName} already exists"
494
+ proc_name + " failed. Entry #{entry_name} already exists"
418
495
  end
419
496
  end
420
497