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