rubyzip 2.4.1 → 3.1.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/Changelog.md +457 -0
- data/LICENSE.md +24 -0
- data/README.md +138 -40
- data/Rakefile +15 -13
- data/lib/zip/central_directory.rb +169 -123
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +29 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +20 -14
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +10 -6
- data/lib/zip/decompressor.rb +4 -3
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +45 -5
- data/lib/zip/entry.rb +373 -249
- data/lib/zip/entry_set.rb +11 -9
- data/lib/zip/errors.rb +136 -16
- data/lib/zip/extra_field/aes.rb +45 -0
- data/lib/zip/extra_field/generic.rb +6 -13
- data/lib/zip/extra_field/ntfs.rb +6 -4
- data/lib/zip/extra_field/old_unix.rb +3 -1
- data/lib/zip/extra_field/universal_time.rb +3 -1
- data/lib/zip/extra_field/unix.rb +5 -3
- data/lib/zip/extra_field/unknown.rb +33 -0
- data/lib/zip/extra_field/zip64.rb +12 -5
- data/lib/zip/extra_field.rb +17 -22
- data/lib/zip/file.rb +166 -265
- data/lib/zip/file_split.rb +91 -0
- data/lib/zip/filesystem/dir.rb +86 -0
- data/lib/zip/filesystem/directory_iterator.rb +48 -0
- data/lib/zip/filesystem/file.rb +263 -0
- data/lib/zip/filesystem/file_stat.rb +110 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
- data/lib/zip/filesystem.rb +27 -596
- data/lib/zip/inflater.rb +11 -8
- data/lib/zip/input_stream.rb +76 -57
- data/lib/zip/ioextras/abstract_input_stream.rb +19 -13
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +7 -7
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +6 -3
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +55 -56
- data/lib/zip/pass_thru_compressor.rb +3 -1
- data/lib/zip/pass_thru_decompressor.rb +8 -5
- data/lib/zip/streamable_directory.rb +3 -1
- data/lib/zip/streamable_stream.rb +4 -1
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +25 -22
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +8 -3
- data/samples/example_filesystem.rb +3 -2
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +5 -3
- data/samples/qtzip.rb +7 -6
- data/samples/write_simple.rb +2 -1
- data/samples/zipfind.rb +1 -0
- metadata +87 -49
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/file.rb
CHANGED
@@ -1,132 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
require_relative 'file_split'
|
7
|
+
|
1
8
|
module Zip
|
2
|
-
#
|
3
|
-
# The most important methods are those
|
4
|
-
#
|
5
|
-
# the archive and methods such as get_input_stream and
|
6
|
-
# get_output_stream for reading from and writing entries to the
|
9
|
+
# Zip::File is modeled after java.util.zip.ZipFile from the Java SDK.
|
10
|
+
# The most important methods are those for accessing information about
|
11
|
+
# the entries in
|
12
|
+
# the archive and methods such as `get_input_stream` and
|
13
|
+
# `get_output_stream` for reading from and writing entries to the
|
7
14
|
# archive. The class includes a few convenience methods such as
|
8
|
-
#
|
9
|
-
#
|
15
|
+
# `extract` for extracting entries to the filesystem, and `remove`,
|
16
|
+
# `replace`, `rename` and `mkdir` for making simple modifications to
|
10
17
|
# the archive.
|
11
18
|
#
|
12
|
-
# Modifications to a zip archive are not committed until
|
13
|
-
#
|
14
|
-
# the pattern from File.open offering a simple way to
|
19
|
+
# Modifications to a zip archive are not committed until `commit` or
|
20
|
+
# `close` is called. The method `open` accepts a block following
|
21
|
+
# the pattern from ::File.open offering a simple way to
|
15
22
|
# automatically close the archive when the block returns.
|
16
23
|
#
|
17
|
-
# The following example opens zip archive
|
24
|
+
# The following example opens zip archive `my.zip`
|
18
25
|
# (creating it if it doesn't exist) and adds an entry
|
19
|
-
#
|
26
|
+
# `first.txt` and a directory entry `a_dir`
|
20
27
|
# to it.
|
21
28
|
#
|
22
|
-
#
|
29
|
+
# ```
|
30
|
+
# require 'zip'
|
23
31
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
32
|
+
# Zip::File.open('my.zip', create: true) do |zipfile|
|
33
|
+
# zipfile.get_output_stream('first.txt') { |f| f.puts 'Hello from Zip::File' }
|
34
|
+
# zipfile.mkdir('a_dir')
|
35
|
+
# end
|
36
|
+
# ```
|
29
37
|
#
|
30
|
-
# The next example reopens
|
31
|
-
#
|
38
|
+
# The next example reopens `my.zip`, writes the contents of
|
39
|
+
# `first.txt` to standard out and deletes the entry from
|
32
40
|
# the archive.
|
33
41
|
#
|
34
|
-
#
|
42
|
+
# ```
|
43
|
+
# require 'zip'
|
35
44
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# }
|
45
|
+
# Zip::File.open('my.zip', create: true) do |zipfile|
|
46
|
+
# puts zipfile.read('first.txt')
|
47
|
+
# zipfile.remove('first.txt')
|
48
|
+
# end
|
41
49
|
#
|
42
|
-
#
|
43
|
-
# interface for accessing the filesystem, ie. the File and Dir classes.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
ZIP64_EOCD_SIGNATURE = 0x06064b50
|
49
|
-
MAX_SEGMENT_SIZE = 3_221_225_472
|
50
|
-
MIN_SEGMENT_SIZE = 65_536
|
51
|
-
DATA_BUFFER_SIZE = 8192
|
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
|
50
|
+
# Zip::FileSystem offers an alternative API that emulates ruby's
|
51
|
+
# interface for accessing the filesystem, ie. the ::File and ::Dir classes.
|
52
|
+
class File
|
53
|
+
include Enumerable
|
54
|
+
extend Forwardable
|
55
|
+
extend FileSplit
|
59
56
|
|
57
|
+
IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze # :nodoc:
|
58
|
+
|
59
|
+
# The name of this zip archive.
|
60
60
|
attr_reader :name
|
61
61
|
|
62
62
|
# default -> false.
|
63
63
|
attr_accessor :restore_ownership
|
64
64
|
|
65
|
-
# default ->
|
65
|
+
# default -> true.
|
66
66
|
attr_accessor :restore_permissions
|
67
67
|
|
68
|
-
# default ->
|
68
|
+
# default -> true.
|
69
69
|
attr_accessor :restore_times
|
70
70
|
|
71
|
-
|
72
|
-
attr_accessor :comment
|
71
|
+
def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
|
73
72
|
|
74
|
-
# Opens a zip archive. Pass true
|
73
|
+
# Opens a zip archive. Pass create: true to create
|
75
74
|
# a new archive if it doesn't exist already.
|
76
|
-
def initialize(path_or_io,
|
77
|
-
|
75
|
+
def initialize(path_or_io, create: false, buffer: false,
|
76
|
+
restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
|
77
|
+
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
78
|
+
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
79
|
+
compression_level: ::Zip.default_compression)
|
78
80
|
super()
|
79
81
|
|
80
|
-
Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
|
81
|
-
|
82
|
-
options = DEFAULT_OPTIONS.merge(options)
|
83
82
|
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
84
|
-
@
|
85
|
-
@create = create || dep_create ? true : false # allow any truthy value to mean true
|
86
|
-
buffer ||= dep_buffer
|
83
|
+
@create = create ? true : false # allow any truthy value to mean true
|
87
84
|
|
88
|
-
|
89
|
-
# There is a file, which exists, that is associated with this zip.
|
90
|
-
@create = false
|
91
|
-
@file_permissions = ::File.stat(@name).mode
|
85
|
+
initialize_cdir(path_or_io, buffer: buffer)
|
92
86
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
read_from_stream(f)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
elsif buffer && path_or_io.size > 0
|
101
|
-
# This zip is probably a non-empty StringIO.
|
102
|
-
@create = false
|
103
|
-
read_from_stream(path_or_io)
|
104
|
-
elsif @create
|
105
|
-
# This zip is completely new/empty and is to be created.
|
106
|
-
@entry_set = EntrySet.new
|
107
|
-
elsif ::File.zero?(@name)
|
108
|
-
# A file exists, but it is empty.
|
109
|
-
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
110
|
-
else
|
111
|
-
# Everything is wrong.
|
112
|
-
raise Error, "File #{@name} not found"
|
113
|
-
end
|
114
|
-
|
115
|
-
@stored_entries = @entry_set.dup
|
116
|
-
@stored_comment = @comment
|
117
|
-
@restore_ownership = options[:restore_ownership]
|
118
|
-
@restore_permissions = options[:restore_permissions]
|
119
|
-
@restore_times = options[:restore_times]
|
87
|
+
@restore_ownership = restore_ownership
|
88
|
+
@restore_permissions = restore_permissions
|
89
|
+
@restore_times = restore_times
|
90
|
+
@compression_level = compression_level
|
120
91
|
end
|
121
92
|
|
122
93
|
class << self
|
123
94
|
# Similar to ::new. If a block is passed the Zip::File object is passed
|
124
95
|
# to the block and is automatically closed afterwards, just as with
|
125
96
|
# ruby's builtin File::open method.
|
126
|
-
def open(file_name,
|
127
|
-
|
97
|
+
def open(file_name, create: false,
|
98
|
+
restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
|
99
|
+
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
100
|
+
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
101
|
+
compression_level: ::Zip.default_compression)
|
102
|
+
zf = ::Zip::File.new(file_name, create: create,
|
103
|
+
restore_ownership: restore_ownership,
|
104
|
+
restore_permissions: restore_permissions,
|
105
|
+
restore_times: restore_times,
|
106
|
+
compression_level: compression_level)
|
128
107
|
|
129
|
-
zf = ::Zip::File.new(file_name, create: (dep_create || create), buffer: false, **options)
|
130
108
|
return zf unless block_given?
|
131
109
|
|
132
110
|
begin
|
@@ -136,31 +114,28 @@ module Zip
|
|
136
114
|
end
|
137
115
|
end
|
138
116
|
|
139
|
-
# Same as #open. But outputs data to a buffer instead of a file
|
140
|
-
def add_buffer
|
141
|
-
Zip.warn_about_v3_api('Zip::File.add_buffer')
|
142
|
-
|
143
|
-
io = ::StringIO.new
|
144
|
-
zf = ::Zip::File.new(io, true, true)
|
145
|
-
yield zf
|
146
|
-
zf.write_buffer(io)
|
147
|
-
end
|
148
|
-
|
149
117
|
# Like #open, but reads zip archive contents from a String or open IO
|
150
118
|
# stream, and outputs data to a buffer.
|
151
119
|
# (This can be used to extract data from a
|
152
120
|
# downloaded zip archive without first saving it to disk.)
|
153
|
-
def open_buffer(io,
|
121
|
+
def open_buffer(io = ::StringIO.new, create: false,
|
122
|
+
restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
|
123
|
+
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
124
|
+
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
125
|
+
compression_level: ::Zip.default_compression)
|
154
126
|
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
155
|
-
raise
|
127
|
+
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
128
|
+
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
156
129
|
end
|
157
130
|
|
158
131
|
io = ::StringIO.new(io) if io.kind_of?(::String)
|
159
132
|
|
160
|
-
|
161
|
-
|
133
|
+
zf = ::Zip::File.new(io, create: create, buffer: true,
|
134
|
+
restore_ownership: restore_ownership,
|
135
|
+
restore_permissions: restore_permissions,
|
136
|
+
restore_times: restore_times,
|
137
|
+
compression_level: compression_level)
|
162
138
|
|
163
|
-
zf = ::Zip::File.new(io, create: true, buffer: true, **options)
|
164
139
|
return zf unless block_given?
|
165
140
|
|
166
141
|
yield zf
|
@@ -184,88 +159,18 @@ module Zip
|
|
184
159
|
end
|
185
160
|
end
|
186
161
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
MAX_SEGMENT_SIZE
|
192
|
-
else
|
193
|
-
segment_size
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
198
|
-
unless partial_zip_file_name.nil?
|
199
|
-
partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
|
200
|
-
partial_zip_file_name + ::File.extname(zip_file_name))
|
201
|
-
end
|
202
|
-
partial_zip_file_name ||= zip_file_name
|
203
|
-
partial_zip_file_name
|
204
|
-
end
|
205
|
-
|
206
|
-
def get_segment_count_for_split(zip_file_size, segment_size)
|
207
|
-
(zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
|
208
|
-
end
|
209
|
-
|
210
|
-
def put_split_signature(szip_file, segment_size)
|
211
|
-
signature_packed = [SPLIT_SIGNATURE].pack('V')
|
212
|
-
szip_file << signature_packed
|
213
|
-
segment_size - signature_packed.size
|
214
|
-
end
|
215
|
-
|
216
|
-
#
|
217
|
-
# TODO: Make the code more understandable
|
218
|
-
#
|
219
|
-
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
220
|
-
ssegment_size = zip_file_size - zip_file.pos
|
221
|
-
ssegment_size = segment_size if ssegment_size > segment_size
|
222
|
-
szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
|
223
|
-
::File.open(szip_file_name, 'wb') do |szip_file|
|
224
|
-
if szip_file_index == 1
|
225
|
-
ssegment_size = put_split_signature(szip_file, segment_size)
|
226
|
-
end
|
227
|
-
chunk_bytes = 0
|
228
|
-
until ssegment_size == chunk_bytes || zip_file.eof?
|
229
|
-
segment_bytes_left = ssegment_size - chunk_bytes
|
230
|
-
buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
|
231
|
-
chunk = zip_file.read(buffer_size)
|
232
|
-
chunk_bytes += buffer_size
|
233
|
-
szip_file << chunk
|
234
|
-
# Info for track splitting
|
235
|
-
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# Splits an archive into parts with segment size
|
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)
|
244
|
-
raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
|
245
|
-
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
162
|
+
# Count the entries in a zip archive without reading the whole set of
|
163
|
+
# entry data into memory.
|
164
|
+
def count_entries(path_or_io)
|
165
|
+
cdir = ::Zip::CentralDirectory.new
|
246
166
|
|
247
|
-
if
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
zip_file_size = ::File.size(zip_file_name)
|
252
|
-
segment_size = get_segment_size_for_split(segment_size || dep_segment_size)
|
253
|
-
return if zip_file_size <= segment_size
|
254
|
-
|
255
|
-
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
256
|
-
# Checking for correct zip structure
|
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))
|
259
|
-
szip_file_index = 0
|
260
|
-
::File.open(zip_file_name, 'rb') do |zip_file|
|
261
|
-
until zip_file.eof?
|
262
|
-
szip_file_index += 1
|
263
|
-
save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
167
|
+
if path_or_io.kind_of?(String)
|
168
|
+
::File.open(path_or_io, 'rb') do |f|
|
169
|
+
cdir.count_entries(f)
|
264
170
|
end
|
171
|
+
else
|
172
|
+
cdir.count_entries(path_or_io)
|
265
173
|
end
|
266
|
-
delete_zip_file = delete_zip_file.nil? ? dep_delete_zip_file : delete_zip_file
|
267
|
-
::File.delete(zip_file_name) if delete_zip_file
|
268
|
-
szip_file_index
|
269
174
|
end
|
270
175
|
end
|
271
176
|
|
@@ -281,45 +186,30 @@ module Zip
|
|
281
186
|
# specified. If a block is passed the stream object is passed to the block and
|
282
187
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
283
188
|
# File.open method.
|
284
|
-
|
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,
|
189
|
+
def get_output_stream(entry, permissions: nil, comment: nil,
|
290
190
|
extra: nil, compressed_size: nil, crc: nil,
|
291
|
-
compression_method: 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
|
-
|
191
|
+
compression_method: nil, compression_level: nil,
|
192
|
+
size: nil, time: nil, &a_proc)
|
300
193
|
new_entry =
|
301
194
|
if entry.kind_of?(Entry)
|
302
195
|
entry
|
303
196
|
else
|
304
|
-
Entry.new(
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
size: (size || dep_size),
|
311
|
-
time: (time || dep_time))
|
197
|
+
Entry.new(
|
198
|
+
@name, entry.to_s, comment: comment, extra: extra,
|
199
|
+
compressed_size: compressed_size, crc: crc, size: size,
|
200
|
+
compression_method: compression_method,
|
201
|
+
compression_level: compression_level, time: time
|
202
|
+
)
|
312
203
|
end
|
313
204
|
if new_entry.directory?
|
314
205
|
raise ArgumentError,
|
315
206
|
"cannot open stream to directory entry - '#{new_entry}'"
|
316
207
|
end
|
317
|
-
new_entry.unix_perms =
|
208
|
+
new_entry.unix_perms = permissions
|
318
209
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
319
|
-
@
|
210
|
+
@cdir << zip_streamable_entry
|
320
211
|
zip_streamable_entry.get_output_stream(&a_proc)
|
321
212
|
end
|
322
|
-
# rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
323
213
|
|
324
214
|
# Returns the name of the zip archive
|
325
215
|
def to_s
|
@@ -335,31 +225,39 @@ module Zip
|
|
335
225
|
def add(entry, src_path, &continue_on_exists_proc)
|
336
226
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
337
227
|
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
338
|
-
new_entry = entry.kind_of?(::Zip::Entry)
|
228
|
+
new_entry = if entry.kind_of?(::Zip::Entry)
|
229
|
+
entry
|
230
|
+
else
|
231
|
+
::Zip::Entry.new(
|
232
|
+
@name, entry.to_s,
|
233
|
+
compression_level: @compression_level
|
234
|
+
)
|
235
|
+
end
|
339
236
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
340
|
-
|
341
|
-
@entry_set << new_entry
|
237
|
+
@cdir << new_entry
|
342
238
|
end
|
343
239
|
|
344
240
|
# Convenience method for adding the contents of a file to the archive
|
345
241
|
# in Stored format (uncompressed)
|
346
242
|
def add_stored(entry, src_path, &continue_on_exists_proc)
|
347
|
-
entry = ::Zip::Entry.new(
|
243
|
+
entry = ::Zip::Entry.new(
|
244
|
+
@name, entry.to_s, compression_method: ::Zip::Entry::STORED
|
245
|
+
)
|
348
246
|
add(entry, src_path, &continue_on_exists_proc)
|
349
247
|
end
|
350
248
|
|
351
249
|
# Removes the specified entry.
|
352
250
|
def remove(entry)
|
353
|
-
@
|
251
|
+
@cdir.delete(get_entry(entry))
|
354
252
|
end
|
355
253
|
|
356
254
|
# Renames the specified entry.
|
357
255
|
def rename(entry, new_name, &continue_on_exists_proc)
|
358
256
|
found_entry = get_entry(entry)
|
359
257
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
360
|
-
@
|
258
|
+
@cdir.delete(found_entry)
|
361
259
|
found_entry.name = new_name
|
362
|
-
@
|
260
|
+
@cdir << found_entry
|
363
261
|
end
|
364
262
|
|
365
263
|
# Replaces the specified entry with the contents of src_path (from
|
@@ -370,25 +268,16 @@ module Zip
|
|
370
268
|
add(entry, src_path)
|
371
269
|
end
|
372
270
|
|
373
|
-
# Extracts entry to file dest_path.
|
374
|
-
def extract(entry, dest_path, &block)
|
375
|
-
Zip.warn_about_v3_api('Zip::File#extract')
|
376
|
-
|
377
|
-
block ||= proc { ::Zip.on_exists_proc }
|
378
|
-
found_entry = get_entry(entry)
|
379
|
-
found_entry.extract(dest_path, &block)
|
380
|
-
end
|
381
|
-
|
382
271
|
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
383
272
|
# as the base location in the filesystem.
|
384
273
|
#
|
385
274
|
# NB: The caller is responsible for making sure `destination_directory` is
|
386
275
|
# safe, if it is passed.
|
387
|
-
def
|
276
|
+
def extract(entry, entry_path = nil, destination_directory: '.', &block)
|
388
277
|
block ||= proc { ::Zip.on_exists_proc }
|
389
278
|
found_entry = get_entry(entry)
|
390
279
|
entry_path ||= found_entry.name
|
391
|
-
found_entry.
|
280
|
+
found_entry.extract(entry_path, destination_directory: destination_directory, &block)
|
392
281
|
end
|
393
282
|
|
394
283
|
# Commits changes that has been made since the previous commit to
|
@@ -398,16 +287,15 @@ module Zip
|
|
398
287
|
|
399
288
|
on_success_replace do |tmp_file|
|
400
289
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
401
|
-
@
|
290
|
+
@cdir.each do |e|
|
402
291
|
e.write_to_zip_output_stream(zos)
|
403
|
-
e.dirty = false
|
404
292
|
e.clean_up
|
405
293
|
end
|
406
294
|
zos.comment = comment
|
407
295
|
end
|
408
296
|
true
|
409
297
|
end
|
410
|
-
|
298
|
+
initialize_cdir(@name)
|
411
299
|
end
|
412
300
|
|
413
301
|
# Write buffer write changes to buffer and return
|
@@ -415,7 +303,7 @@ module Zip
|
|
415
303
|
return io unless commit_required?
|
416
304
|
|
417
305
|
::Zip::OutputStream.write_buffer(io) do |zos|
|
418
|
-
@
|
306
|
+
@cdir.each { |e| e.write_to_zip_output_stream(zos) }
|
419
307
|
zos.comment = comment
|
420
308
|
end
|
421
309
|
end
|
@@ -428,16 +316,19 @@ module Zip
|
|
428
316
|
# Returns true if any changes has been made to this archive since
|
429
317
|
# the previous commit
|
430
318
|
def commit_required?
|
431
|
-
@
|
432
|
-
|
319
|
+
return true if @create || @cdir.dirty?
|
320
|
+
|
321
|
+
@cdir.each do |e|
|
322
|
+
return true if e.dirty?
|
433
323
|
end
|
434
|
-
|
324
|
+
|
325
|
+
false
|
435
326
|
end
|
436
327
|
|
437
328
|
# Searches for entry with the specified name. Returns nil if
|
438
329
|
# no entry is found. See also get_entry
|
439
330
|
def find_entry(entry_name)
|
440
|
-
selected_entry = @
|
331
|
+
selected_entry = @cdir.find_entry(entry_name)
|
441
332
|
return if selected_entry.nil?
|
442
333
|
|
443
334
|
selected_entry.restore_ownership = @restore_ownership
|
@@ -446,11 +337,6 @@ module Zip
|
|
446
337
|
selected_entry
|
447
338
|
end
|
448
339
|
|
449
|
-
# Searches for entries given a glob
|
450
|
-
def glob(*args, &block)
|
451
|
-
@entry_set.glob(*args, &block)
|
452
|
-
end
|
453
|
-
|
454
340
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
455
341
|
# if no entry is found.
|
456
342
|
def get_entry(entry)
|
@@ -466,33 +352,50 @@ module Zip
|
|
466
352
|
|
467
353
|
entry_name = entry_name.dup.to_s
|
468
354
|
entry_name << '/' unless entry_name.end_with?('/')
|
469
|
-
@
|
355
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
470
356
|
end
|
471
357
|
|
472
358
|
private
|
473
359
|
|
474
|
-
def
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
360
|
+
def initialize_cdir(path_or_io, buffer: false)
|
361
|
+
@cdir = ::Zip::CentralDirectory.new
|
362
|
+
|
363
|
+
if ::File.size?(@name.to_s)
|
364
|
+
# There is a file, which exists, that is associated with this zip.
|
365
|
+
@create = false
|
366
|
+
@file_permissions = ::File.stat(@name).mode
|
367
|
+
|
368
|
+
if buffer
|
369
|
+
# https://github.com/rubyzip/rubyzip/issues/119
|
370
|
+
path_or_io.binmode if path_or_io.respond_to?(:binmode)
|
371
|
+
@cdir.read_from_stream(path_or_io)
|
372
|
+
else
|
373
|
+
::File.open(@name, 'rb') do |f|
|
374
|
+
@cdir.read_from_stream(f)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
elsif buffer && path_or_io.size > 0
|
378
|
+
# This zip is probably a non-empty StringIO.
|
379
|
+
@create = false
|
380
|
+
@cdir.read_from_stream(path_or_io)
|
381
|
+
elsif !@create && ::File.empty?(@name)
|
382
|
+
# A file exists, but it is empty, and we've said we're
|
383
|
+
# NOT creating a new zip.
|
384
|
+
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
385
|
+
elsif !@create
|
386
|
+
# If we get here, and we're not creating a new zip, then
|
387
|
+
# everything is wrong.
|
388
|
+
raise Error, "File #{@name} not found"
|
482
389
|
end
|
483
|
-
new_entry.directory? && path_is_directory
|
484
390
|
end
|
485
391
|
|
486
392
|
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
393
|
+
return unless @cdir.include?(entry_name)
|
394
|
+
|
487
395
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
488
|
-
|
396
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
489
397
|
|
490
|
-
|
491
|
-
remove get_entry(entry_name)
|
492
|
-
else
|
493
|
-
raise ::Zip::EntryExistsError,
|
494
|
-
proc_name + " failed. Entry #{entry_name} already exists"
|
495
|
-
end
|
398
|
+
remove get_entry(entry_name)
|
496
399
|
end
|
497
400
|
|
498
401
|
def check_file(path)
|
@@ -502,14 +405,12 @@ module Zip
|
|
502
405
|
def on_success_replace
|
503
406
|
dirname, basename = ::File.split(name)
|
504
407
|
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
::File.chmod(@file_permissions, name) unless @create
|
509
|
-
end
|
510
|
-
ensure
|
511
|
-
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
408
|
+
if yield tmp_filename
|
409
|
+
::File.rename(tmp_filename, name)
|
410
|
+
::File.chmod(@file_permissions, name) unless @create
|
512
411
|
end
|
412
|
+
ensure
|
413
|
+
FileUtils.rm_f(tmp_filename)
|
513
414
|
end
|
514
415
|
end
|
515
416
|
end
|