rubyzip 1.0.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +6 -14
- data/README.md +231 -46
- data/Rakefile +13 -5
- data/TODO +0 -1
- data/lib/zip/central_directory.rb +64 -29
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +59 -5
- data/lib/zip/crypto/decrypted_io.rb +40 -0
- data/lib/zip/crypto/encryption.rb +11 -0
- data/lib/zip/crypto/null_encryption.rb +43 -0
- data/lib/zip/crypto/traditional_encryption.rb +99 -0
- data/lib/zip/decompressor.rb +22 -4
- data/lib/zip/deflater.rb +11 -6
- data/lib/zip/dos_time.rb +27 -16
- data/lib/zip/entry.rb +299 -163
- data/lib/zip/entry_set.rb +22 -20
- data/lib/zip/errors.rb +17 -6
- data/lib/zip/extra_field/generic.rb +15 -14
- data/lib/zip/extra_field/ntfs.rb +94 -0
- data/lib/zip/extra_field/old_unix.rb +46 -0
- data/lib/zip/extra_field/universal_time.rb +46 -16
- data/lib/zip/extra_field/unix.rb +10 -9
- data/lib/zip/extra_field/zip64.rb +46 -6
- data/lib/zip/extra_field/zip64_placeholder.rb +15 -0
- data/lib/zip/extra_field.rb +32 -18
- data/lib/zip/file.rb +260 -160
- data/lib/zip/filesystem.rb +297 -276
- data/lib/zip/inflater.rb +23 -34
- data/lib/zip/input_stream.rb +130 -82
- data/lib/zip/ioextras/abstract_input_stream.rb +36 -22
- data/lib/zip/ioextras/abstract_output_stream.rb +4 -6
- data/lib/zip/ioextras.rb +4 -6
- data/lib/zip/null_compressor.rb +2 -2
- data/lib/zip/null_decompressor.rb +4 -12
- data/lib/zip/null_input_stream.rb +2 -1
- data/lib/zip/output_stream.rb +75 -45
- data/lib/zip/pass_thru_compressor.rb +6 -6
- data/lib/zip/pass_thru_decompressor.rb +14 -24
- data/lib/zip/streamable_directory.rb +3 -3
- data/lib/zip/streamable_stream.rb +21 -16
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +42 -3
- data/samples/example.rb +30 -40
- data/samples/example_filesystem.rb +16 -18
- data/samples/example_recursive.rb +33 -28
- data/samples/gtk_ruby_zip.rb +84 -0
- data/samples/qtzip.rb +25 -34
- data/samples/write_simple.rb +10 -13
- data/samples/zipfind.rb +38 -45
- metadata +129 -28
- data/NEWS +0 -182
- data/samples/gtkRubyzip.rb +0 -86
data/lib/zip/file.rb
CHANGED
|
@@ -43,90 +43,133 @@ module Zip
|
|
|
43
43
|
# interface for accessing the filesystem, ie. the File and Dir classes.
|
|
44
44
|
|
|
45
45
|
class File < CentralDirectory
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
SPLIT_SIGNATURE = 0x08074b50
|
|
46
|
+
CREATE = true
|
|
47
|
+
SPLIT_SIGNATURE = 0x08074b50
|
|
49
48
|
ZIP64_EOCD_SIGNATURE = 0x06064b50
|
|
50
|
-
MAX_SEGMENT_SIZE
|
|
51
|
-
MIN_SEGMENT_SIZE
|
|
52
|
-
DATA_BUFFER_SIZE
|
|
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
|
|
53
59
|
|
|
54
60
|
attr_reader :name
|
|
55
61
|
|
|
56
|
-
# default -> false
|
|
62
|
+
# default -> false.
|
|
57
63
|
attr_accessor :restore_ownership
|
|
58
|
-
|
|
64
|
+
|
|
65
|
+
# default -> false, but will be set to true in a future version.
|
|
59
66
|
attr_accessor :restore_permissions
|
|
60
|
-
|
|
67
|
+
|
|
68
|
+
# default -> false, but will be set to true in a future version.
|
|
61
69
|
attr_accessor :restore_times
|
|
70
|
+
|
|
62
71
|
# Returns the zip files comment, if it has one
|
|
63
72
|
attr_accessor :comment
|
|
64
73
|
|
|
65
74
|
# Opens a zip archive. Pass true as the second parameter to create
|
|
66
75
|
# a new archive if it doesn't exist already.
|
|
67
|
-
def initialize(
|
|
76
|
+
def initialize(path_or_io, dep_create = false, dep_buffer = false,
|
|
77
|
+
create: false, buffer: false, **options)
|
|
68
78
|
super()
|
|
69
|
-
|
|
79
|
+
|
|
80
|
+
Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
|
|
81
|
+
|
|
82
|
+
options = DEFAULT_OPTIONS.merge(options)
|
|
83
|
+
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
|
70
84
|
@comment = ''
|
|
71
|
-
@create
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
@create = create || dep_create ? true : false # allow any truthy value to mean true
|
|
86
|
+
buffer ||= dep_buffer
|
|
87
|
+
|
|
88
|
+
if ::File.size?(@name.to_s)
|
|
89
|
+
# There is a file, which exists, that is associated with this zip.
|
|
90
|
+
@create = false
|
|
91
|
+
@file_permissions = ::File.stat(@name).mode
|
|
92
|
+
|
|
93
|
+
if buffer
|
|
94
|
+
read_from_stream(path_or_io)
|
|
95
|
+
else
|
|
96
|
+
::File.open(@name, 'rb') do |f|
|
|
97
|
+
read_from_stream(f)
|
|
98
|
+
end
|
|
77
99
|
end
|
|
78
|
-
|
|
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.
|
|
79
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?"
|
|
80
110
|
else
|
|
81
|
-
|
|
111
|
+
# Everything is wrong.
|
|
112
|
+
raise Error, "File #{@name} not found"
|
|
82
113
|
end
|
|
83
|
-
|
|
84
|
-
@
|
|
85
|
-
@
|
|
86
|
-
@
|
|
87
|
-
@
|
|
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
120
|
end
|
|
89
121
|
|
|
90
122
|
class << self
|
|
91
|
-
#
|
|
92
|
-
# to the block and is automatically closed afterwards just as with
|
|
93
|
-
# ruby's builtin File
|
|
94
|
-
def open(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
zf
|
|
123
|
+
# Similar to ::new. If a block is passed the Zip::File object is passed
|
|
124
|
+
# to the block and is automatically closed afterwards, just as with
|
|
125
|
+
# ruby's builtin File::open method.
|
|
126
|
+
def open(file_name, dep_create = false, create: false, **options)
|
|
127
|
+
Zip.warn_about_v3_api('Zip::File.open') if dep_create
|
|
128
|
+
|
|
129
|
+
zf = ::Zip::File.new(file_name, create: (dep_create || create), buffer: false, **options)
|
|
130
|
+
return zf unless block_given?
|
|
131
|
+
|
|
132
|
+
begin
|
|
133
|
+
yield zf
|
|
134
|
+
ensure
|
|
135
|
+
zf.close
|
|
104
136
|
end
|
|
105
137
|
end
|
|
106
138
|
|
|
107
139
|
# Same as #open. But outputs data to a buffer instead of a file
|
|
108
140
|
def add_buffer
|
|
109
|
-
|
|
141
|
+
Zip.warn_about_v3_api('Zip::File.add_buffer')
|
|
142
|
+
|
|
143
|
+
io = ::StringIO.new
|
|
144
|
+
zf = ::Zip::File.new(io, true, true)
|
|
110
145
|
yield zf
|
|
111
|
-
zf.write_buffer
|
|
146
|
+
zf.write_buffer(io)
|
|
112
147
|
end
|
|
113
148
|
|
|
114
149
|
# Like #open, but reads zip archive contents from a String or open IO
|
|
115
150
|
# stream, and outputs data to a buffer.
|
|
116
|
-
# (This can be used to extract data from a
|
|
151
|
+
# (This can be used to extract data from a
|
|
117
152
|
# downloaded zip archive without first saving it to disk.)
|
|
118
|
-
def open_buffer(io)
|
|
119
|
-
unless io.
|
|
120
|
-
raise "Zip::
|
|
121
|
-
end
|
|
122
|
-
zf = ::Zip::File.new('', true, true)
|
|
123
|
-
if io.is_a(::String)
|
|
124
|
-
require 'stringio'
|
|
125
|
-
io = ::StringIO.new(io)
|
|
153
|
+
def open_buffer(io, **options)
|
|
154
|
+
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
|
155
|
+
raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
|
126
156
|
end
|
|
127
|
-
|
|
157
|
+
|
|
158
|
+
io = ::StringIO.new(io) if io.kind_of?(::String)
|
|
159
|
+
|
|
160
|
+
# https://github.com/rubyzip/rubyzip/issues/119
|
|
161
|
+
io.binmode if io.respond_to?(:binmode)
|
|
162
|
+
|
|
163
|
+
zf = ::Zip::File.new(io, create: true, buffer: true, **options)
|
|
164
|
+
return zf unless block_given?
|
|
165
|
+
|
|
128
166
|
yield zf
|
|
129
|
-
|
|
167
|
+
|
|
168
|
+
begin
|
|
169
|
+
zf.write_buffer(io)
|
|
170
|
+
rescue IOError => e
|
|
171
|
+
raise unless e.message == 'not opened for writing'
|
|
172
|
+
end
|
|
130
173
|
end
|
|
131
174
|
|
|
132
175
|
# Iterates over the contents of the ZipFile. This is more efficient
|
|
@@ -135,17 +178,16 @@ module Zip
|
|
|
135
178
|
# whereas ZipInputStream jumps through the entire archive accessing the
|
|
136
179
|
# local entry headers (which contain the same information as the
|
|
137
180
|
# central directory).
|
|
138
|
-
def foreach(
|
|
139
|
-
open(
|
|
140
|
-
|
|
181
|
+
def foreach(zip_file_name, &block)
|
|
182
|
+
::Zip::File.open(zip_file_name) do |zip_file|
|
|
183
|
+
zip_file.each(&block)
|
|
141
184
|
end
|
|
142
185
|
end
|
|
143
186
|
|
|
144
187
|
def get_segment_size_for_split(segment_size)
|
|
145
|
-
|
|
146
|
-
when MIN_SEGMENT_SIZE > segment_size
|
|
188
|
+
if MIN_SEGMENT_SIZE > segment_size
|
|
147
189
|
MIN_SEGMENT_SIZE
|
|
148
|
-
|
|
190
|
+
elsif MAX_SEGMENT_SIZE < segment_size
|
|
149
191
|
MAX_SEGMENT_SIZE
|
|
150
192
|
else
|
|
151
193
|
segment_size
|
|
@@ -153,8 +195,10 @@ module Zip
|
|
|
153
195
|
end
|
|
154
196
|
|
|
155
197
|
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
|
156
|
-
partial_zip_file_name
|
|
157
|
-
|
|
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
|
|
158
202
|
partial_zip_file_name ||= zip_file_name
|
|
159
203
|
partial_zip_file_name
|
|
160
204
|
end
|
|
@@ -173,9 +217,9 @@ module Zip
|
|
|
173
217
|
# TODO: Make the code more understandable
|
|
174
218
|
#
|
|
175
219
|
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
|
176
|
-
ssegment_size
|
|
177
|
-
ssegment_size
|
|
178
|
-
szip_file_name = "#{partial_zip_file_name}.#{'%03d'
|
|
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)}"
|
|
179
223
|
::File.open(szip_file_name, 'wb') do |szip_file|
|
|
180
224
|
if szip_file_index == 1
|
|
181
225
|
ssegment_size = put_split_signature(szip_file, segment_size)
|
|
@@ -185,7 +229,7 @@ module Zip
|
|
|
185
229
|
segment_bytes_left = ssegment_size - chunk_bytes
|
|
186
230
|
buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
|
|
187
231
|
chunk = zip_file.read(buffer_size)
|
|
188
|
-
chunk_bytes
|
|
232
|
+
chunk_bytes += buffer_size
|
|
189
233
|
szip_file << chunk
|
|
190
234
|
# Info for track splitting
|
|
191
235
|
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
|
@@ -194,16 +238,24 @@ module Zip
|
|
|
194
238
|
end
|
|
195
239
|
|
|
196
240
|
# Splits an archive into parts with segment size
|
|
197
|
-
def split(zip_file_name,
|
|
198
|
-
|
|
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)
|
|
199
245
|
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
|
246
|
+
|
|
247
|
+
if dep_segment_size != MAX_SEGMENT_SIZE || !dep_delete_zip_file || dep_partial_zip_file_name
|
|
248
|
+
Zip.warn_about_v3_api('Zip::File.split')
|
|
249
|
+
end
|
|
250
|
+
|
|
200
251
|
zip_file_size = ::File.size(zip_file_name)
|
|
201
|
-
segment_size = get_segment_size_for_split(segment_size)
|
|
252
|
+
segment_size = get_segment_size_for_split(segment_size || dep_segment_size)
|
|
202
253
|
return if zip_file_size <= segment_size
|
|
254
|
+
|
|
203
255
|
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
|
204
256
|
# Checking for correct zip structure
|
|
205
|
-
|
|
206
|
-
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
|
257
|
+
::Zip::File.open(zip_file_name) {}
|
|
258
|
+
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, (partial_zip_file_name || dep_partial_zip_file_name))
|
|
207
259
|
szip_file_index = 0
|
|
208
260
|
::File.open(zip_file_name, 'rb') do |zip_file|
|
|
209
261
|
until zip_file.eof?
|
|
@@ -211,33 +263,63 @@ module Zip
|
|
|
211
263
|
save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
|
212
264
|
end
|
|
213
265
|
end
|
|
266
|
+
delete_zip_file = delete_zip_file.nil? ? dep_delete_zip_file : delete_zip_file
|
|
214
267
|
::File.delete(zip_file_name) if delete_zip_file
|
|
215
268
|
szip_file_index
|
|
216
269
|
end
|
|
217
270
|
end
|
|
218
271
|
|
|
219
|
-
|
|
220
272
|
# Returns an input stream to the specified entry. If a block is passed
|
|
221
273
|
# the stream object is passed to the block and the stream is automatically
|
|
222
274
|
# closed afterwards just as with ruby's builtin File.open method.
|
|
223
|
-
def get_input_stream(entry, &
|
|
224
|
-
get_entry(entry).get_input_stream(&
|
|
275
|
+
def get_input_stream(entry, &a_proc)
|
|
276
|
+
get_entry(entry).get_input_stream(&a_proc)
|
|
225
277
|
end
|
|
226
278
|
|
|
227
|
-
# Returns an output stream to the specified entry. If
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
279
|
+
# Returns an output stream to the specified entry. If entry is not an instance
|
|
280
|
+
# of Zip::Entry, a new Zip::Entry will be initialized using the arguments
|
|
281
|
+
# specified. If a block is passed the stream object is passed to the block and
|
|
282
|
+
# the stream is automatically closed afterwards just as with ruby's builtin
|
|
283
|
+
# File.open method.
|
|
284
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
285
|
+
def get_output_stream(entry,
|
|
286
|
+
dep_permission_int = nil, dep_comment = nil,
|
|
287
|
+
dep_extra = nil, dep_compressed_size = nil, dep_crc = nil,
|
|
288
|
+
dep_compression_method = nil, dep_size = nil, dep_time = nil,
|
|
289
|
+
permission_int: nil, comment: nil,
|
|
290
|
+
extra: nil, compressed_size: nil, crc: nil,
|
|
291
|
+
compression_method: nil, size: nil, time: nil,
|
|
292
|
+
&a_proc)
|
|
293
|
+
|
|
294
|
+
unless dep_permission_int.nil? && dep_comment.nil? && dep_extra.nil? &&
|
|
295
|
+
dep_compressed_size.nil? && dep_crc.nil? && dep_compression_method.nil? &&
|
|
296
|
+
dep_size.nil? && dep_time.nil?
|
|
297
|
+
Zip.warn_about_v3_api('Zip::File#get_output_stream')
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
new_entry =
|
|
301
|
+
if entry.kind_of?(Entry)
|
|
302
|
+
entry
|
|
303
|
+
else
|
|
304
|
+
Entry.new(@name, entry.to_s,
|
|
305
|
+
comment: (comment || dep_comment),
|
|
306
|
+
extra: (extra || dep_extra),
|
|
307
|
+
compressed_size: (compressed_size || dep_compressed_size),
|
|
308
|
+
crc: (crc || dep_crc),
|
|
309
|
+
compression_method: (compression_method || dep_compression_method),
|
|
310
|
+
size: (size || dep_size),
|
|
311
|
+
time: (time || dep_time))
|
|
312
|
+
end
|
|
313
|
+
if new_entry.directory?
|
|
233
314
|
raise ArgumentError,
|
|
234
|
-
"cannot open stream to directory entry - '#{
|
|
315
|
+
"cannot open stream to directory entry - '#{new_entry}'"
|
|
235
316
|
end
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
@entry_set <<
|
|
239
|
-
|
|
317
|
+
new_entry.unix_perms = (permission_int || dep_permission_int)
|
|
318
|
+
zip_streamable_entry = StreamableStream.new(new_entry)
|
|
319
|
+
@entry_set << zip_streamable_entry
|
|
320
|
+
zip_streamable_entry.get_output_stream(&a_proc)
|
|
240
321
|
end
|
|
322
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
241
323
|
|
|
242
324
|
# Returns the name of the zip archive
|
|
243
325
|
def to_s
|
|
@@ -246,16 +328,24 @@ module Zip
|
|
|
246
328
|
|
|
247
329
|
# Returns a string containing the contents of the specified entry
|
|
248
330
|
def read(entry)
|
|
249
|
-
get_input_stream(entry
|
|
331
|
+
get_input_stream(entry, &:read)
|
|
250
332
|
end
|
|
251
333
|
|
|
252
334
|
# Convenience method for adding the contents of a file to the archive
|
|
253
|
-
def add(entry,
|
|
254
|
-
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
|
255
|
-
check_entry_exists(entry, continue_on_exists_proc,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
335
|
+
def add(entry, src_path, &continue_on_exists_proc)
|
|
336
|
+
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
|
337
|
+
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
|
338
|
+
new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
|
|
339
|
+
new_entry.gather_fileinfo_from_srcpath(src_path)
|
|
340
|
+
new_entry.dirty = true
|
|
341
|
+
@entry_set << new_entry
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Convenience method for adding the contents of a file to the archive
|
|
345
|
+
# in Stored format (uncompressed)
|
|
346
|
+
def add_stored(entry, src_path, &continue_on_exists_proc)
|
|
347
|
+
entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
|
|
348
|
+
add(entry, src_path, &continue_on_exists_proc)
|
|
259
349
|
end
|
|
260
350
|
|
|
261
351
|
# Removes the specified entry.
|
|
@@ -265,56 +355,69 @@ module Zip
|
|
|
265
355
|
|
|
266
356
|
# Renames the specified entry.
|
|
267
357
|
def rename(entry, new_name, &continue_on_exists_proc)
|
|
268
|
-
|
|
358
|
+
found_entry = get_entry(entry)
|
|
269
359
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
|
270
|
-
@entry_set.delete(
|
|
271
|
-
|
|
272
|
-
@entry_set <<
|
|
360
|
+
@entry_set.delete(found_entry)
|
|
361
|
+
found_entry.name = new_name
|
|
362
|
+
@entry_set << found_entry
|
|
273
363
|
end
|
|
274
364
|
|
|
275
|
-
# Replaces the specified entry with the contents of
|
|
365
|
+
# Replaces the specified entry with the contents of src_path (from
|
|
276
366
|
# the file system).
|
|
277
|
-
def replace(entry,
|
|
278
|
-
check_file(
|
|
367
|
+
def replace(entry, src_path)
|
|
368
|
+
check_file(src_path)
|
|
279
369
|
remove(entry)
|
|
280
|
-
add(entry,
|
|
370
|
+
add(entry, src_path)
|
|
281
371
|
end
|
|
282
372
|
|
|
283
373
|
# Extracts entry to file dest_path.
|
|
284
374
|
def extract(entry, dest_path, &block)
|
|
285
|
-
|
|
375
|
+
Zip.warn_about_v3_api('Zip::File#extract')
|
|
376
|
+
|
|
377
|
+
block ||= proc { ::Zip.on_exists_proc }
|
|
286
378
|
found_entry = get_entry(entry)
|
|
287
379
|
found_entry.extract(dest_path, &block)
|
|
288
380
|
end
|
|
289
381
|
|
|
382
|
+
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
|
383
|
+
# as the base location in the filesystem.
|
|
384
|
+
#
|
|
385
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
|
386
|
+
# safe, if it is passed.
|
|
387
|
+
def extract_v3(entry, entry_path = nil, destination_directory: '.', &block)
|
|
388
|
+
block ||= proc { ::Zip.on_exists_proc }
|
|
389
|
+
found_entry = get_entry(entry)
|
|
390
|
+
entry_path ||= found_entry.name
|
|
391
|
+
found_entry.extract_v3(entry_path, destination_directory: destination_directory, &block)
|
|
392
|
+
end
|
|
393
|
+
|
|
290
394
|
# Commits changes that has been made since the previous commit to
|
|
291
395
|
# the zip archive.
|
|
292
396
|
def commit
|
|
293
|
-
return if !commit_required?
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
OutputStream.open(
|
|
297
|
-
|
|
|
298
|
-
|
|
299
|
-
@entry_set.each {
|
|
300
|
-
|e|
|
|
397
|
+
return if name.kind_of?(StringIO) || !commit_required?
|
|
398
|
+
|
|
399
|
+
on_success_replace do |tmp_file|
|
|
400
|
+
::Zip::OutputStream.open(tmp_file) do |zos|
|
|
401
|
+
@entry_set.each do |e|
|
|
301
402
|
e.write_to_zip_output_stream(zos)
|
|
302
403
|
e.dirty = false
|
|
303
|
-
|
|
404
|
+
e.clean_up
|
|
405
|
+
end
|
|
304
406
|
zos.comment = comment
|
|
305
|
-
|
|
407
|
+
end
|
|
306
408
|
true
|
|
307
|
-
|
|
409
|
+
end
|
|
308
410
|
initialize(name)
|
|
309
411
|
end
|
|
310
412
|
|
|
311
413
|
# Write buffer write changes to buffer and return
|
|
312
|
-
def write_buffer
|
|
313
|
-
|
|
414
|
+
def write_buffer(io = ::StringIO.new)
|
|
415
|
+
return io unless commit_required?
|
|
416
|
+
|
|
417
|
+
::Zip::OutputStream.write_buffer(io) do |zos|
|
|
314
418
|
@entry_set.each { |e| e.write_to_zip_output_stream(zos) }
|
|
315
419
|
zos.comment = comment
|
|
316
420
|
end
|
|
317
|
-
return buffer
|
|
318
421
|
end
|
|
319
422
|
|
|
320
423
|
# Closes the zip file committing any changes that has been made.
|
|
@@ -328,13 +431,19 @@ module Zip
|
|
|
328
431
|
@entry_set.each do |e|
|
|
329
432
|
return true if e.dirty
|
|
330
433
|
end
|
|
331
|
-
@comment != @
|
|
434
|
+
@comment != @stored_comment || @entry_set != @stored_entries || @create
|
|
332
435
|
end
|
|
333
436
|
|
|
334
437
|
# Searches for entry with the specified name. Returns nil if
|
|
335
438
|
# no entry is found. See also get_entry
|
|
336
439
|
def find_entry(entry_name)
|
|
337
|
-
@entry_set.find_entry(entry_name)
|
|
440
|
+
selected_entry = @entry_set.find_entry(entry_name)
|
|
441
|
+
return if selected_entry.nil?
|
|
442
|
+
|
|
443
|
+
selected_entry.restore_ownership = @restore_ownership
|
|
444
|
+
selected_entry.restore_permissions = @restore_permissions
|
|
445
|
+
selected_entry.restore_times = @restore_times
|
|
446
|
+
selected_entry
|
|
338
447
|
end
|
|
339
448
|
|
|
340
449
|
# Searches for entries given a glob
|
|
@@ -345,73 +454,64 @@ module Zip
|
|
|
345
454
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
|
346
455
|
# if no entry is found.
|
|
347
456
|
def get_entry(entry)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
selectedEntry.restore_ownership = @restore_ownership
|
|
353
|
-
selectedEntry.restore_permissions = @restore_permissions
|
|
354
|
-
selectedEntry.restore_times = @restore_times
|
|
355
|
-
selectedEntry
|
|
457
|
+
selected_entry = find_entry(entry)
|
|
458
|
+
raise Errno::ENOENT, entry if selected_entry.nil?
|
|
459
|
+
|
|
460
|
+
selected_entry
|
|
356
461
|
end
|
|
357
462
|
|
|
358
463
|
# Creates a directory
|
|
359
|
-
def mkdir(
|
|
360
|
-
if find_entry(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
@entry_set << StreamableDirectory.new(@name, entryName, nil, permissionInt)
|
|
464
|
+
def mkdir(entry_name, permission = 0o755)
|
|
465
|
+
raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
|
|
466
|
+
|
|
467
|
+
entry_name = entry_name.dup.to_s
|
|
468
|
+
entry_name << '/' unless entry_name.end_with?('/')
|
|
469
|
+
@entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
|
366
470
|
end
|
|
367
471
|
|
|
368
472
|
private
|
|
369
473
|
|
|
370
|
-
def
|
|
371
|
-
|
|
372
|
-
if
|
|
474
|
+
def directory?(new_entry, src_path)
|
|
475
|
+
path_is_directory = ::File.directory?(src_path)
|
|
476
|
+
if new_entry.directory? && !path_is_directory
|
|
373
477
|
raise ArgumentError,
|
|
374
|
-
"entry name '#{
|
|
375
|
-
|
|
376
|
-
elsif !
|
|
377
|
-
|
|
478
|
+
"entry name '#{new_entry}' indicates directory entry, but " \
|
|
479
|
+
"'#{src_path}' is not a directory"
|
|
480
|
+
elsif !new_entry.directory? && path_is_directory
|
|
481
|
+
new_entry.name += '/'
|
|
378
482
|
end
|
|
379
|
-
|
|
483
|
+
new_entry.directory? && path_is_directory
|
|
380
484
|
end
|
|
381
485
|
|
|
382
|
-
def check_entry_exists(
|
|
486
|
+
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
|
383
487
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
488
|
+
return unless @entry_set.include?(entry_name)
|
|
489
|
+
|
|
490
|
+
if continue_on_exists_proc.call
|
|
491
|
+
remove get_entry(entry_name)
|
|
492
|
+
else
|
|
493
|
+
raise ::Zip::EntryExistsError,
|
|
494
|
+
proc_name + " failed. Entry #{entry_name} already exists"
|
|
391
495
|
end
|
|
392
496
|
end
|
|
393
497
|
|
|
394
498
|
def check_file(path)
|
|
395
|
-
unless ::File.readable?(path)
|
|
396
|
-
raise Errno::ENOENT, path
|
|
397
|
-
end
|
|
499
|
+
raise Errno::ENOENT, path unless ::File.readable?(path)
|
|
398
500
|
end
|
|
399
501
|
|
|
400
|
-
def on_success_replace
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
502
|
+
def on_success_replace
|
|
503
|
+
dirname, basename = ::File.split(name)
|
|
504
|
+
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
|
505
|
+
begin
|
|
506
|
+
if yield tmp_filename
|
|
507
|
+
::File.rename(tmp_filename, name)
|
|
508
|
+
::File.chmod(@file_permissions, name) unless @create
|
|
509
|
+
end
|
|
510
|
+
ensure
|
|
511
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
|
512
|
+
end
|
|
406
513
|
end
|
|
407
514
|
end
|
|
408
|
-
|
|
409
|
-
def get_tempfile
|
|
410
|
-
tempFile = Tempfile.new(::File.basename(name), ::File.dirname(name))
|
|
411
|
-
tempFile.binmode
|
|
412
|
-
tempFile
|
|
413
|
-
end
|
|
414
|
-
|
|
415
515
|
end
|
|
416
516
|
end
|
|
417
517
|
|