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.
- checksums.yaml +4 -4
- data/README.md +17 -9
- data/Rakefile +3 -0
- data/lib/zip/central_directory.rb +9 -5
- data/lib/zip/constants.rb +52 -0
- data/lib/zip/crypto/decrypted_io.rb +40 -0
- data/lib/zip/crypto/traditional_encryption.rb +9 -9
- data/lib/zip/decompressor.rb +19 -1
- data/lib/zip/dos_time.rb +24 -12
- data/lib/zip/entry.rb +107 -49
- data/lib/zip/entry_set.rb +2 -0
- data/lib/zip/errors.rb +1 -0
- data/lib/zip/extra_field/generic.rb +10 -9
- data/lib/zip/extra_field/ntfs.rb +5 -1
- data/lib/zip/extra_field/old_unix.rb +3 -1
- data/lib/zip/extra_field/universal_time.rb +42 -12
- data/lib/zip/extra_field/unix.rb +3 -1
- data/lib/zip/extra_field/zip64.rb +5 -3
- data/lib/zip/extra_field.rb +11 -9
- data/lib/zip/file.rb +142 -65
- data/lib/zip/filesystem.rb +193 -177
- data/lib/zip/inflater.rb +24 -36
- data/lib/zip/input_stream.rb +50 -30
- data/lib/zip/ioextras/abstract_input_stream.rb +23 -12
- data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
- data/lib/zip/ioextras.rb +3 -3
- data/lib/zip/null_decompressor.rb +1 -9
- data/lib/zip/output_stream.rb +28 -12
- data/lib/zip/pass_thru_compressor.rb +2 -2
- data/lib/zip/pass_thru_decompressor.rb +13 -22
- data/lib/zip/streamable_directory.rb +3 -3
- data/lib/zip/streamable_stream.rb +6 -10
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +22 -2
- data/samples/example.rb +2 -2
- data/samples/example_filesystem.rb +1 -1
- data/samples/gtk_ruby_zip.rb +19 -19
- data/samples/qtzip.rb +6 -6
- data/samples/write_simple.rb +2 -4
- data/samples/zipfind.rb +23 -22
- 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
|
-
|
64
|
+
|
65
|
+
# default -> false, but will be set to true in a future version.
|
59
66
|
attr_accessor :restore_permissions
|
60
|
-
|
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,
|
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]
|
102
|
-
@restore_permissions = options[:restore_permissions]
|
103
|
-
@restore_times = options[:restore_times]
|
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
|
-
#
|
108
|
-
# to the block and is automatically closed afterwards just as with
|
109
|
-
# ruby's builtin File
|
110
|
-
def open(file_name,
|
111
|
-
|
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
|
-
|
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.
|
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.
|
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(
|
160
|
-
open(
|
161
|
-
|
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,
|
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, &
|
245
|
-
get_entry(entry).get_input_stream(&
|
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
|
-
|
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,
|
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(&
|
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
|
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
|
-
|
358
|
+
found_entry = get_entry(entry)
|
305
359
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
306
|
-
@entry_set.delete(
|
307
|
-
|
308
|
-
@entry_set <<
|
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
|
365
|
+
# Replaces the specified entry with the contents of src_path (from
|
312
366
|
# the file system).
|
313
|
-
def replace(entry,
|
314
|
-
check_file(
|
367
|
+
def replace(entry, src_path)
|
368
|
+
check_file(src_path)
|
315
369
|
remove(entry)
|
316
|
-
add(entry,
|
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.
|
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
|
382
|
-
|
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(
|
390
|
-
raise Errno::EEXIST, "File exists - #{
|
391
|
-
|
392
|
-
|
393
|
-
|
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?(
|
399
|
-
|
400
|
-
if
|
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 '#{
|
403
|
-
"'#{
|
404
|
-
elsif !
|
405
|
-
|
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
|
-
|
483
|
+
new_entry.directory? && path_is_directory
|
408
484
|
end
|
409
485
|
|
410
|
-
def check_entry_exists(
|
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?(
|
488
|
+
return unless @entry_set.include?(entry_name)
|
489
|
+
|
413
490
|
if continue_on_exists_proc.call
|
414
|
-
remove get_entry(
|
491
|
+
remove get_entry(entry_name)
|
415
492
|
else
|
416
493
|
raise ::Zip::EntryExistsError,
|
417
|
-
|
494
|
+
proc_name + " failed. Entry #{entry_name} already exists"
|
418
495
|
end
|
419
496
|
end
|
420
497
|
|