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