rubyzip 2.4.1 → 3.0.2
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 +436 -0
- data/LICENSE.md +24 -0
- data/README.md +126 -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 +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +43 -4
- data/lib/zip/entry.rb +352 -249
- 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 +167 -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 +51 -51
- data/lib/zip/ioextras/abstract_input_stream.rb +16 -11
- 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 +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 +23 -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 +85 -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 '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
|
-
|
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
|
+
include Enumerable
|
53
|
+
extend Forwardable
|
54
|
+
extend FileSplit
|
59
55
|
|
56
|
+
IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze # :nodoc:
|
57
|
+
|
58
|
+
# The name of this zip archive.
|
60
59
|
attr_reader :name
|
61
60
|
|
62
61
|
# default -> false.
|
63
62
|
attr_accessor :restore_ownership
|
64
63
|
|
65
|
-
# default ->
|
64
|
+
# default -> true.
|
66
65
|
attr_accessor :restore_permissions
|
67
66
|
|
68
|
-
# default ->
|
67
|
+
# default -> true.
|
69
68
|
attr_accessor :restore_times
|
70
69
|
|
71
|
-
|
72
|
-
attr_accessor :comment
|
70
|
+
def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
|
73
71
|
|
74
|
-
# Opens a zip archive. Pass true
|
72
|
+
# Opens a zip archive. Pass create: true to create
|
75
73
|
# a new archive if it doesn't exist already.
|
76
|
-
def initialize(path_or_io,
|
77
|
-
|
74
|
+
def initialize(path_or_io, create: false, buffer: false,
|
75
|
+
restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
|
76
|
+
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
77
|
+
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
78
|
+
compression_level: ::Zip.default_compression)
|
78
79
|
super()
|
79
80
|
|
80
|
-
Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
|
81
|
-
|
82
|
-
options = DEFAULT_OPTIONS.merge(options)
|
83
81
|
@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
|
82
|
+
@create = create ? true : false # allow any truthy value to mean true
|
87
83
|
|
88
|
-
|
89
|
-
# There is a file, which exists, that is associated with this zip.
|
90
|
-
@create = false
|
91
|
-
@file_permissions = ::File.stat(@name).mode
|
84
|
+
initialize_cdir(path_or_io, buffer: buffer)
|
92
85
|
|
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]
|
86
|
+
@restore_ownership = restore_ownership
|
87
|
+
@restore_permissions = restore_permissions
|
88
|
+
@restore_times = restore_times
|
89
|
+
@compression_level = compression_level
|
120
90
|
end
|
121
91
|
|
122
92
|
class << self
|
123
93
|
# Similar to ::new. If a block is passed the Zip::File object is passed
|
124
94
|
# to the block and is automatically closed afterwards, just as with
|
125
95
|
# ruby's builtin File::open method.
|
126
|
-
def open(file_name,
|
127
|
-
|
96
|
+
def open(file_name, create: false,
|
97
|
+
restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
|
98
|
+
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
99
|
+
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
100
|
+
compression_level: ::Zip.default_compression)
|
101
|
+
|
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,29 @@ 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)
|
126
|
+
|
154
127
|
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
155
|
-
raise
|
128
|
+
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
129
|
+
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
156
130
|
end
|
157
131
|
|
158
132
|
io = ::StringIO.new(io) if io.kind_of?(::String)
|
159
133
|
|
160
|
-
|
161
|
-
|
134
|
+
zf = ::Zip::File.new(io, create: create, buffer: true,
|
135
|
+
restore_ownership: restore_ownership,
|
136
|
+
restore_permissions: restore_permissions,
|
137
|
+
restore_times: restore_times,
|
138
|
+
compression_level: compression_level)
|
162
139
|
|
163
|
-
zf = ::Zip::File.new(io, create: true, buffer: true, **options)
|
164
140
|
return zf unless block_given?
|
165
141
|
|
166
142
|
yield zf
|
@@ -184,88 +160,18 @@ module Zip
|
|
184
160
|
end
|
185
161
|
end
|
186
162
|
|
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
|
163
|
+
# Count the entries in a zip archive without reading the whole set of
|
164
|
+
# entry data into memory.
|
165
|
+
def count_entries(path_or_io)
|
166
|
+
cdir = ::Zip::CentralDirectory.new
|
250
167
|
|
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)
|
168
|
+
if path_or_io.kind_of?(String)
|
169
|
+
::File.open(path_or_io, 'rb') do |f|
|
170
|
+
cdir.count_entries(f)
|
264
171
|
end
|
172
|
+
else
|
173
|
+
cdir.count_entries(path_or_io)
|
265
174
|
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
175
|
end
|
270
176
|
end
|
271
177
|
|
@@ -281,45 +187,31 @@ module Zip
|
|
281
187
|
# specified. If a block is passed the stream object is passed to the block and
|
282
188
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
283
189
|
# 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,
|
190
|
+
def get_output_stream(entry, permissions: nil, comment: nil,
|
290
191
|
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
|
192
|
+
compression_method: nil, compression_level: nil,
|
193
|
+
size: nil, time: nil, &a_proc)
|
299
194
|
|
300
195
|
new_entry =
|
301
196
|
if entry.kind_of?(Entry)
|
302
197
|
entry
|
303
198
|
else
|
304
|
-
Entry.new(
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
size: (size || dep_size),
|
311
|
-
time: (time || dep_time))
|
199
|
+
Entry.new(
|
200
|
+
@name, entry.to_s, comment: comment, extra: extra,
|
201
|
+
compressed_size: compressed_size, crc: crc, size: size,
|
202
|
+
compression_method: compression_method,
|
203
|
+
compression_level: compression_level, time: time
|
204
|
+
)
|
312
205
|
end
|
313
206
|
if new_entry.directory?
|
314
207
|
raise ArgumentError,
|
315
208
|
"cannot open stream to directory entry - '#{new_entry}'"
|
316
209
|
end
|
317
|
-
new_entry.unix_perms =
|
210
|
+
new_entry.unix_perms = permissions
|
318
211
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
319
|
-
@
|
212
|
+
@cdir << zip_streamable_entry
|
320
213
|
zip_streamable_entry.get_output_stream(&a_proc)
|
321
214
|
end
|
322
|
-
# rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
323
215
|
|
324
216
|
# Returns the name of the zip archive
|
325
217
|
def to_s
|
@@ -335,31 +227,39 @@ module Zip
|
|
335
227
|
def add(entry, src_path, &continue_on_exists_proc)
|
336
228
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
337
229
|
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
338
|
-
new_entry = entry.kind_of?(::Zip::Entry)
|
230
|
+
new_entry = if entry.kind_of?(::Zip::Entry)
|
231
|
+
entry
|
232
|
+
else
|
233
|
+
::Zip::Entry.new(
|
234
|
+
@name, entry.to_s,
|
235
|
+
compression_level: @compression_level
|
236
|
+
)
|
237
|
+
end
|
339
238
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
340
|
-
|
341
|
-
@entry_set << new_entry
|
239
|
+
@cdir << new_entry
|
342
240
|
end
|
343
241
|
|
344
242
|
# Convenience method for adding the contents of a file to the archive
|
345
243
|
# in Stored format (uncompressed)
|
346
244
|
def add_stored(entry, src_path, &continue_on_exists_proc)
|
347
|
-
entry = ::Zip::Entry.new(
|
245
|
+
entry = ::Zip::Entry.new(
|
246
|
+
@name, entry.to_s, compression_method: ::Zip::Entry::STORED
|
247
|
+
)
|
348
248
|
add(entry, src_path, &continue_on_exists_proc)
|
349
249
|
end
|
350
250
|
|
351
251
|
# Removes the specified entry.
|
352
252
|
def remove(entry)
|
353
|
-
@
|
253
|
+
@cdir.delete(get_entry(entry))
|
354
254
|
end
|
355
255
|
|
356
256
|
# Renames the specified entry.
|
357
257
|
def rename(entry, new_name, &continue_on_exists_proc)
|
358
258
|
found_entry = get_entry(entry)
|
359
259
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
360
|
-
@
|
260
|
+
@cdir.delete(found_entry)
|
361
261
|
found_entry.name = new_name
|
362
|
-
@
|
262
|
+
@cdir << found_entry
|
363
263
|
end
|
364
264
|
|
365
265
|
# Replaces the specified entry with the contents of src_path (from
|
@@ -370,25 +270,16 @@ module Zip
|
|
370
270
|
add(entry, src_path)
|
371
271
|
end
|
372
272
|
|
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
273
|
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
383
274
|
# as the base location in the filesystem.
|
384
275
|
#
|
385
276
|
# NB: The caller is responsible for making sure `destination_directory` is
|
386
277
|
# safe, if it is passed.
|
387
|
-
def
|
278
|
+
def extract(entry, entry_path = nil, destination_directory: '.', &block)
|
388
279
|
block ||= proc { ::Zip.on_exists_proc }
|
389
280
|
found_entry = get_entry(entry)
|
390
281
|
entry_path ||= found_entry.name
|
391
|
-
found_entry.
|
282
|
+
found_entry.extract(entry_path, destination_directory: destination_directory, &block)
|
392
283
|
end
|
393
284
|
|
394
285
|
# Commits changes that has been made since the previous commit to
|
@@ -398,16 +289,15 @@ module Zip
|
|
398
289
|
|
399
290
|
on_success_replace do |tmp_file|
|
400
291
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
401
|
-
@
|
292
|
+
@cdir.each do |e|
|
402
293
|
e.write_to_zip_output_stream(zos)
|
403
|
-
e.dirty = false
|
404
294
|
e.clean_up
|
405
295
|
end
|
406
296
|
zos.comment = comment
|
407
297
|
end
|
408
298
|
true
|
409
299
|
end
|
410
|
-
|
300
|
+
initialize_cdir(@name)
|
411
301
|
end
|
412
302
|
|
413
303
|
# Write buffer write changes to buffer and return
|
@@ -415,7 +305,7 @@ module Zip
|
|
415
305
|
return io unless commit_required?
|
416
306
|
|
417
307
|
::Zip::OutputStream.write_buffer(io) do |zos|
|
418
|
-
@
|
308
|
+
@cdir.each { |e| e.write_to_zip_output_stream(zos) }
|
419
309
|
zos.comment = comment
|
420
310
|
end
|
421
311
|
end
|
@@ -428,16 +318,19 @@ module Zip
|
|
428
318
|
# Returns true if any changes has been made to this archive since
|
429
319
|
# the previous commit
|
430
320
|
def commit_required?
|
431
|
-
@
|
432
|
-
|
321
|
+
return true if @create || @cdir.dirty?
|
322
|
+
|
323
|
+
@cdir.each do |e|
|
324
|
+
return true if e.dirty?
|
433
325
|
end
|
434
|
-
|
326
|
+
|
327
|
+
false
|
435
328
|
end
|
436
329
|
|
437
330
|
# Searches for entry with the specified name. Returns nil if
|
438
331
|
# no entry is found. See also get_entry
|
439
332
|
def find_entry(entry_name)
|
440
|
-
selected_entry = @
|
333
|
+
selected_entry = @cdir.find_entry(entry_name)
|
441
334
|
return if selected_entry.nil?
|
442
335
|
|
443
336
|
selected_entry.restore_ownership = @restore_ownership
|
@@ -446,11 +339,6 @@ module Zip
|
|
446
339
|
selected_entry
|
447
340
|
end
|
448
341
|
|
449
|
-
# Searches for entries given a glob
|
450
|
-
def glob(*args, &block)
|
451
|
-
@entry_set.glob(*args, &block)
|
452
|
-
end
|
453
|
-
|
454
342
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
455
343
|
# if no entry is found.
|
456
344
|
def get_entry(entry)
|
@@ -466,33 +354,50 @@ module Zip
|
|
466
354
|
|
467
355
|
entry_name = entry_name.dup.to_s
|
468
356
|
entry_name << '/' unless entry_name.end_with?('/')
|
469
|
-
@
|
357
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
470
358
|
end
|
471
359
|
|
472
360
|
private
|
473
361
|
|
474
|
-
def
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
362
|
+
def initialize_cdir(path_or_io, buffer: false)
|
363
|
+
@cdir = ::Zip::CentralDirectory.new
|
364
|
+
|
365
|
+
if ::File.size?(@name.to_s)
|
366
|
+
# There is a file, which exists, that is associated with this zip.
|
367
|
+
@create = false
|
368
|
+
@file_permissions = ::File.stat(@name).mode
|
369
|
+
|
370
|
+
if buffer
|
371
|
+
# https://github.com/rubyzip/rubyzip/issues/119
|
372
|
+
path_or_io.binmode if path_or_io.respond_to?(:binmode)
|
373
|
+
@cdir.read_from_stream(path_or_io)
|
374
|
+
else
|
375
|
+
::File.open(@name, 'rb') do |f|
|
376
|
+
@cdir.read_from_stream(f)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
elsif buffer && path_or_io.size > 0
|
380
|
+
# This zip is probably a non-empty StringIO.
|
381
|
+
@create = false
|
382
|
+
@cdir.read_from_stream(path_or_io)
|
383
|
+
elsif !@create && ::File.empty?(@name)
|
384
|
+
# A file exists, but it is empty, and we've said we're
|
385
|
+
# NOT creating a new zip.
|
386
|
+
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
387
|
+
elsif !@create
|
388
|
+
# If we get here, and we're not creating a new zip, then
|
389
|
+
# everything is wrong.
|
390
|
+
raise Error, "File #{@name} not found"
|
482
391
|
end
|
483
|
-
new_entry.directory? && path_is_directory
|
484
392
|
end
|
485
393
|
|
486
394
|
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
395
|
+
return unless @cdir.include?(entry_name)
|
396
|
+
|
487
397
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
488
|
-
|
398
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
489
399
|
|
490
|
-
|
491
|
-
remove get_entry(entry_name)
|
492
|
-
else
|
493
|
-
raise ::Zip::EntryExistsError,
|
494
|
-
proc_name + " failed. Entry #{entry_name} already exists"
|
495
|
-
end
|
400
|
+
remove get_entry(entry_name)
|
496
401
|
end
|
497
402
|
|
498
403
|
def check_file(path)
|
@@ -502,14 +407,12 @@ module Zip
|
|
502
407
|
def on_success_replace
|
503
408
|
dirname, basename = ::File.split(name)
|
504
409
|
::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)
|
410
|
+
if yield tmp_filename
|
411
|
+
::File.rename(tmp_filename, name)
|
412
|
+
::File.chmod(@file_permissions, name) unless @create
|
512
413
|
end
|
414
|
+
ensure
|
415
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
513
416
|
end
|
514
417
|
end
|
515
418
|
end
|