rubyzip 1.3.0 → 3.0.0.alpha
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 +368 -0
- data/README.md +123 -46
- data/Rakefile +13 -6
- data/lib/zip/central_directory.rb +166 -116
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +77 -21
- data/lib/zip/crypto/decrypted_io.rb +42 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -3
- data/lib/zip/crypto/traditional_encryption.rb +14 -12
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +10 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +53 -12
- data/lib/zip/entry.rb +306 -184
- data/lib/zip/entry_set.rb +11 -7
- data/lib/zip/errors.rb +115 -15
- data/lib/zip/extra_field/generic.rb +11 -17
- data/lib/zip/extra_field/ntfs.rb +8 -2
- data/lib/zip/extra_field/old_unix.rb +6 -2
- data/lib/zip/extra_field/universal_time.rb +45 -13
- data/lib/zip/extra_field/unix.rb +7 -3
- data/lib/zip/extra_field/unknown.rb +33 -0
- data/lib/zip/extra_field/zip64.rb +16 -7
- data/lib/zip/extra_field.rb +22 -26
- data/lib/zip/file.rb +196 -240
- data/lib/zip/file_split.rb +97 -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 +31 -584
- data/lib/zip/inflater.rb +27 -37
- data/lib/zip/input_stream.rb +67 -42
- data/lib/zip/ioextras/abstract_input_stream.rb +32 -16
- 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 +4 -10
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +58 -43
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +16 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +9 -10
- data/lib/zip/version.rb +3 -1
- data/lib/zip.rb +19 -4
- data/rubyzip.gemspec +38 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +3 -2
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +22 -20
- data/samples/qtzip.rb +12 -11
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +86 -179
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
- data/test/basic_zip_file_test.rb +0 -60
- data/test/case_sensitivity_test.rb +0 -69
- data/test/central_directory_entry_test.rb +0 -69
- data/test/central_directory_test.rb +0 -100
- data/test/crypto/null_encryption_test.rb +0 -57
- data/test/crypto/traditional_encryption_test.rb +0 -80
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +0 -46
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +0 -1504
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +0 -10
- data/test/data/path_traversal/jwilk/README.md +0 -5
- data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
- data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
- data/test/data/path_traversal/jwilk/relative0.zip +0 -0
- data/test/data/path_traversal/jwilk/relative2.zip +0 -0
- data/test/data/path_traversal/jwilk/symlink.zip +0 -0
- data/test/data/path_traversal/relative1.zip +0 -0
- data/test/data/path_traversal/tilde.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
- data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +0 -65
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -163
- data/test/entry_test.rb +0 -154
- data/test/errors_test.rb +0 -35
- data/test/extra_field_test.rb +0 -76
- data/test/file_extract_directory_test.rb +0 -54
- data/test/file_extract_test.rb +0 -145
- data/test/file_permissions_test.rb +0 -65
- data/test/file_split_test.rb +0 -57
- data/test/file_test.rb +0 -666
- data/test/filesystem/dir_iterator_test.rb +0 -58
- data/test/filesystem/directory_test.rb +0 -139
- data/test/filesystem/file_mutating_test.rb +0 -87
- data/test/filesystem/file_nonmutating_test.rb +0 -508
- data/test/filesystem/file_stat_test.rb +0 -64
- data/test/gentestfiles.rb +0 -126
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -182
- data/test/ioextras/abstract_input_stream_test.rb +0 -102
- data/test/ioextras/abstract_output_stream_test.rb +0 -106
- data/test/ioextras/fake_io_test.rb +0 -18
- data/test/local_entry_test.rb +0 -154
- data/test/output_stream_test.rb +0 -128
- data/test/pass_thru_compressor_test.rb +0 -30
- data/test/pass_thru_decompressor_test.rb +0 -14
- data/test/path_traversal_test.rb +0 -141
- data/test/samples/example_recursive_test.rb +0 -37
- data/test/settings_test.rb +0 -95
- data/test/test_helper.rb +0 -234
- data/test/unicode_file_names_and_comments_test.rb +0 -62
- data/test/zip64_full_test.rb +0 -51
- data/test/zip64_support_test.rb +0 -14
data/lib/zip/file.rb
CHANGED
@@ -1,115 +1,98 @@
|
|
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
|
-
|
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, :close]
|
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
|
54
|
+
|
55
|
+
IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze
|
53
56
|
|
54
57
|
attr_reader :name
|
55
58
|
|
56
|
-
# default -> false
|
59
|
+
# default -> false.
|
57
60
|
attr_accessor :restore_ownership
|
58
|
-
|
61
|
+
|
62
|
+
# default -> true.
|
59
63
|
attr_accessor :restore_permissions
|
60
|
-
|
64
|
+
|
65
|
+
# default -> true.
|
61
66
|
attr_accessor :restore_times
|
62
|
-
# Returns the zip files comment, if it has one
|
63
|
-
attr_accessor :comment
|
64
67
|
|
65
|
-
|
68
|
+
def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
|
69
|
+
|
70
|
+
# Opens a zip archive. Pass create: true to create
|
66
71
|
# a new archive if it doesn't exist already.
|
67
|
-
def initialize(path_or_io, create
|
72
|
+
def initialize(path_or_io, create: false, buffer: false, **options)
|
68
73
|
super()
|
74
|
+
options = DEFAULT_RESTORE_OPTIONS
|
75
|
+
.merge(compression_level: ::Zip.default_compression)
|
76
|
+
.merge(options)
|
69
77
|
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
70
|
-
@comment = ''
|
71
78
|
@create = create ? true : false # allow any truthy value to mean true
|
72
79
|
|
73
|
-
|
74
|
-
# There is a file, which exists, that is associated with this zip.
|
75
|
-
@create = false
|
76
|
-
@file_permissions = ::File.stat(@name).mode
|
77
|
-
|
78
|
-
if buffer
|
79
|
-
read_from_stream(path_or_io)
|
80
|
-
else
|
81
|
-
::File.open(@name, 'rb') do |f|
|
82
|
-
read_from_stream(f)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
elsif buffer && path_or_io.size > 0
|
86
|
-
# This zip is probably a non-empty StringIO.
|
87
|
-
read_from_stream(path_or_io)
|
88
|
-
elsif @create
|
89
|
-
# This zip is completely new/empty and is to be created.
|
90
|
-
@entry_set = EntrySet.new
|
91
|
-
elsif ::File.zero?(@name)
|
92
|
-
# A file exists, but it is empty.
|
93
|
-
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
94
|
-
else
|
95
|
-
# Everything is wrong.
|
96
|
-
raise Error, "File #{@name} not found"
|
97
|
-
end
|
80
|
+
initialize_cdir(path_or_io, buffer: buffer)
|
98
81
|
|
99
|
-
@
|
100
|
-
@
|
101
|
-
@
|
102
|
-
@
|
103
|
-
@restore_times = options[:restore_times] || true
|
82
|
+
@restore_ownership = options[:restore_ownership]
|
83
|
+
@restore_permissions = options[:restore_permissions]
|
84
|
+
@restore_times = options[:restore_times]
|
85
|
+
@compression_level = options[:compression_level]
|
104
86
|
end
|
105
87
|
|
106
88
|
class << self
|
107
|
-
#
|
108
|
-
# to the block and is automatically closed afterwards just as with
|
109
|
-
# ruby's builtin File
|
110
|
-
def open(file_name, create
|
111
|
-
zf = ::Zip::File.new(file_name, create)
|
89
|
+
# Similar to ::new. If a block is passed the Zip::File object is passed
|
90
|
+
# to the block and is automatically closed afterwards, just as with
|
91
|
+
# ruby's builtin File::open method.
|
92
|
+
def open(file_name, create: false, **options)
|
93
|
+
zf = ::Zip::File.new(file_name, create: create, **options)
|
112
94
|
return zf unless block_given?
|
95
|
+
|
113
96
|
begin
|
114
97
|
yield zf
|
115
98
|
ensure
|
@@ -117,30 +100,21 @@ module Zip
|
|
117
100
|
end
|
118
101
|
end
|
119
102
|
|
120
|
-
# Same as #open. But outputs data to a buffer instead of a file
|
121
|
-
def add_buffer
|
122
|
-
io = ::StringIO.new('')
|
123
|
-
zf = ::Zip::File.new(io, true, true)
|
124
|
-
yield zf
|
125
|
-
zf.write_buffer(io)
|
126
|
-
end
|
127
|
-
|
128
103
|
# Like #open, but reads zip archive contents from a String or open IO
|
129
104
|
# stream, and outputs data to a buffer.
|
130
105
|
# (This can be used to extract data from a
|
131
106
|
# downloaded zip archive without first saving it to disk.)
|
132
|
-
def open_buffer(io,
|
133
|
-
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.
|
134
|
-
raise
|
107
|
+
def open_buffer(io = ::StringIO.new, create: false, **options)
|
108
|
+
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
109
|
+
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
110
|
+
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
135
111
|
end
|
136
112
|
|
137
|
-
io = ::StringIO.new(io) if io.
|
138
|
-
|
139
|
-
# https://github.com/rubyzip/rubyzip/issues/119
|
140
|
-
io.binmode if io.respond_to?(:binmode)
|
113
|
+
io = ::StringIO.new(io) if io.kind_of?(::String)
|
141
114
|
|
142
|
-
zf = ::Zip::File.new(io,
|
115
|
+
zf = ::Zip::File.new(io, create: create, buffer: true, **options)
|
143
116
|
return zf unless block_given?
|
117
|
+
|
144
118
|
yield zf
|
145
119
|
|
146
120
|
begin
|
@@ -156,93 +130,32 @@ module Zip
|
|
156
130
|
# whereas ZipInputStream jumps through the entire archive accessing the
|
157
131
|
# local entry headers (which contain the same information as the
|
158
132
|
# central directory).
|
159
|
-
def foreach(
|
160
|
-
open(
|
161
|
-
|
133
|
+
def foreach(zip_file_name, &block)
|
134
|
+
::Zip::File.open(zip_file_name) do |zip_file|
|
135
|
+
zip_file.each(&block)
|
162
136
|
end
|
163
137
|
end
|
164
138
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
MAX_SEGMENT_SIZE
|
170
|
-
else
|
171
|
-
segment_size
|
172
|
-
end
|
173
|
-
end
|
139
|
+
# Count the entries in a zip archive without reading the whole set of
|
140
|
+
# entry data into memory.
|
141
|
+
def count_entries(path_or_io)
|
142
|
+
cdir = ::Zip::CentralDirectory.new
|
174
143
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
partial_zip_file_name + ::File.extname(zip_file_name))
|
179
|
-
end
|
180
|
-
partial_zip_file_name ||= zip_file_name
|
181
|
-
partial_zip_file_name
|
182
|
-
end
|
183
|
-
|
184
|
-
def get_segment_count_for_split(zip_file_size, segment_size)
|
185
|
-
(zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
|
186
|
-
end
|
187
|
-
|
188
|
-
def put_split_signature(szip_file, segment_size)
|
189
|
-
signature_packed = [SPLIT_SIGNATURE].pack('V')
|
190
|
-
szip_file << signature_packed
|
191
|
-
segment_size - signature_packed.size
|
192
|
-
end
|
193
|
-
|
194
|
-
#
|
195
|
-
# TODO: Make the code more understandable
|
196
|
-
#
|
197
|
-
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
198
|
-
ssegment_size = zip_file_size - zip_file.pos
|
199
|
-
ssegment_size = segment_size if ssegment_size > segment_size
|
200
|
-
szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
|
201
|
-
::File.open(szip_file_name, 'wb') do |szip_file|
|
202
|
-
if szip_file_index == 1
|
203
|
-
ssegment_size = put_split_signature(szip_file, segment_size)
|
204
|
-
end
|
205
|
-
chunk_bytes = 0
|
206
|
-
until ssegment_size == chunk_bytes || zip_file.eof?
|
207
|
-
segment_bytes_left = ssegment_size - chunk_bytes
|
208
|
-
buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
|
209
|
-
chunk = zip_file.read(buffer_size)
|
210
|
-
chunk_bytes += buffer_size
|
211
|
-
szip_file << chunk
|
212
|
-
# Info for track splitting
|
213
|
-
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
# Splits an archive into parts with segment size
|
219
|
-
def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
|
220
|
-
raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
|
221
|
-
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
222
|
-
zip_file_size = ::File.size(zip_file_name)
|
223
|
-
segment_size = get_segment_size_for_split(segment_size)
|
224
|
-
return if zip_file_size <= segment_size
|
225
|
-
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
226
|
-
# Checking for correct zip structure
|
227
|
-
open(zip_file_name) {}
|
228
|
-
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
229
|
-
szip_file_index = 0
|
230
|
-
::File.open(zip_file_name, 'rb') do |zip_file|
|
231
|
-
until zip_file.eof?
|
232
|
-
szip_file_index += 1
|
233
|
-
save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
144
|
+
if path_or_io.kind_of?(String)
|
145
|
+
::File.open(path_or_io, 'rb') do |f|
|
146
|
+
cdir.count_entries(f)
|
234
147
|
end
|
148
|
+
else
|
149
|
+
cdir.count_entries(path_or_io)
|
235
150
|
end
|
236
|
-
::File.delete(zip_file_name) if delete_zip_file
|
237
|
-
szip_file_index
|
238
151
|
end
|
239
152
|
end
|
240
153
|
|
241
154
|
# Returns an input stream to the specified entry. If a block is passed
|
242
155
|
# the stream object is passed to the block and the stream is automatically
|
243
156
|
# closed afterwards just as with ruby's builtin File.open method.
|
244
|
-
def get_input_stream(entry, &
|
245
|
-
get_entry(entry).get_input_stream(&
|
157
|
+
def get_input_stream(entry, &a_proc)
|
158
|
+
get_entry(entry).get_input_stream(&a_proc)
|
246
159
|
end
|
247
160
|
|
248
161
|
# Returns an output stream to the specified entry. If entry is not an instance
|
@@ -250,21 +163,30 @@ module Zip
|
|
250
163
|
# specified. If a block is passed the stream object is passed to the block and
|
251
164
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
252
165
|
# File.open method.
|
253
|
-
def get_output_stream(entry,
|
166
|
+
def get_output_stream(entry, permissions: nil, comment: nil,
|
167
|
+
extra: nil, compressed_size: nil, crc: nil,
|
168
|
+
compression_method: nil, compression_level: nil,
|
169
|
+
size: nil, time: nil, &a_proc)
|
170
|
+
|
254
171
|
new_entry =
|
255
172
|
if entry.kind_of?(Entry)
|
256
173
|
entry
|
257
174
|
else
|
258
|
-
Entry.new(
|
175
|
+
Entry.new(
|
176
|
+
@name, entry.to_s, comment: comment, extra: extra,
|
177
|
+
compressed_size: compressed_size, crc: crc, size: size,
|
178
|
+
compression_method: compression_method,
|
179
|
+
compression_level: compression_level, time: time
|
180
|
+
)
|
259
181
|
end
|
260
182
|
if new_entry.directory?
|
261
183
|
raise ArgumentError,
|
262
184
|
"cannot open stream to directory entry - '#{new_entry}'"
|
263
185
|
end
|
264
|
-
new_entry.unix_perms =
|
186
|
+
new_entry.unix_perms = permissions
|
265
187
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
266
|
-
@
|
267
|
-
zip_streamable_entry.get_output_stream(&
|
188
|
+
@cdir << zip_streamable_entry
|
189
|
+
zip_streamable_entry.get_output_stream(&a_proc)
|
268
190
|
end
|
269
191
|
|
270
192
|
# Returns the name of the zip archive
|
@@ -274,77 +196,92 @@ module Zip
|
|
274
196
|
|
275
197
|
# Returns a string containing the contents of the specified entry
|
276
198
|
def read(entry)
|
277
|
-
get_input_stream(entry
|
199
|
+
get_input_stream(entry, &:read)
|
278
200
|
end
|
279
201
|
|
280
202
|
# Convenience method for adding the contents of a file to the archive
|
281
203
|
def add(entry, src_path, &continue_on_exists_proc)
|
282
204
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
283
205
|
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
284
|
-
new_entry = entry.kind_of?(::Zip::Entry)
|
206
|
+
new_entry = if entry.kind_of?(::Zip::Entry)
|
207
|
+
entry
|
208
|
+
else
|
209
|
+
::Zip::Entry.new(
|
210
|
+
@name, entry.to_s,
|
211
|
+
compression_level: @compression_level
|
212
|
+
)
|
213
|
+
end
|
285
214
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
286
|
-
|
287
|
-
@entry_set << new_entry
|
215
|
+
@cdir << new_entry
|
288
216
|
end
|
289
217
|
|
290
218
|
# Convenience method for adding the contents of a file to the archive
|
291
219
|
# in Stored format (uncompressed)
|
292
220
|
def add_stored(entry, src_path, &continue_on_exists_proc)
|
293
|
-
entry = ::Zip::Entry.new(
|
221
|
+
entry = ::Zip::Entry.new(
|
222
|
+
@name, entry.to_s, compression_method: ::Zip::Entry::STORED
|
223
|
+
)
|
294
224
|
add(entry, src_path, &continue_on_exists_proc)
|
295
225
|
end
|
296
226
|
|
297
227
|
# Removes the specified entry.
|
298
228
|
def remove(entry)
|
299
|
-
@
|
229
|
+
@cdir.delete(get_entry(entry))
|
300
230
|
end
|
301
231
|
|
302
232
|
# Renames the specified entry.
|
303
233
|
def rename(entry, new_name, &continue_on_exists_proc)
|
304
|
-
|
234
|
+
found_entry = get_entry(entry)
|
305
235
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
306
|
-
@
|
307
|
-
|
308
|
-
@
|
236
|
+
@cdir.delete(found_entry)
|
237
|
+
found_entry.name = new_name
|
238
|
+
@cdir << found_entry
|
309
239
|
end
|
310
240
|
|
311
|
-
# Replaces the specified entry with the contents of
|
241
|
+
# Replaces the specified entry with the contents of src_path (from
|
312
242
|
# the file system).
|
313
|
-
def replace(entry,
|
314
|
-
check_file(
|
243
|
+
def replace(entry, src_path)
|
244
|
+
check_file(src_path)
|
315
245
|
remove(entry)
|
316
|
-
add(entry,
|
246
|
+
add(entry, src_path)
|
317
247
|
end
|
318
248
|
|
319
|
-
# Extracts entry to file
|
320
|
-
|
249
|
+
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
250
|
+
# as the base location in the filesystem.
|
251
|
+
#
|
252
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
253
|
+
# safe, if it is passed.
|
254
|
+
def extract(entry, entry_path = nil, destination_directory: '.', &block)
|
321
255
|
block ||= proc { ::Zip.on_exists_proc }
|
322
256
|
found_entry = get_entry(entry)
|
323
|
-
found_entry.
|
257
|
+
entry_path ||= found_entry.name
|
258
|
+
found_entry.extract(entry_path, destination_directory: destination_directory, &block)
|
324
259
|
end
|
325
260
|
|
326
261
|
# Commits changes that has been made since the previous commit to
|
327
262
|
# the zip archive.
|
328
263
|
def commit
|
329
|
-
return if name.
|
264
|
+
return if name.kind_of?(StringIO) || !commit_required?
|
265
|
+
|
330
266
|
on_success_replace do |tmp_file|
|
331
267
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
332
|
-
@
|
268
|
+
@cdir.each do |e|
|
333
269
|
e.write_to_zip_output_stream(zos)
|
334
|
-
e.dirty = false
|
335
270
|
e.clean_up
|
336
271
|
end
|
337
272
|
zos.comment = comment
|
338
273
|
end
|
339
274
|
true
|
340
275
|
end
|
341
|
-
|
276
|
+
initialize_cdir(@name)
|
342
277
|
end
|
343
278
|
|
344
279
|
# Write buffer write changes to buffer and return
|
345
|
-
def write_buffer(io = ::StringIO.new
|
280
|
+
def write_buffer(io = ::StringIO.new)
|
281
|
+
return unless commit_required?
|
282
|
+
|
346
283
|
::Zip::OutputStream.write_buffer(io) do |zos|
|
347
|
-
@
|
284
|
+
@cdir.each { |e| e.write_to_zip_output_stream(zos) }
|
348
285
|
zos.comment = comment
|
349
286
|
end
|
350
287
|
end
|
@@ -357,65 +294,86 @@ module Zip
|
|
357
294
|
# Returns true if any changes has been made to this archive since
|
358
295
|
# the previous commit
|
359
296
|
def commit_required?
|
360
|
-
@
|
361
|
-
|
297
|
+
return true if @create || @cdir.dirty?
|
298
|
+
|
299
|
+
@cdir.each do |e|
|
300
|
+
return true if e.dirty?
|
362
301
|
end
|
363
|
-
|
302
|
+
|
303
|
+
false
|
364
304
|
end
|
365
305
|
|
366
306
|
# Searches for entry with the specified name. Returns nil if
|
367
307
|
# no entry is found. See also get_entry
|
368
308
|
def find_entry(entry_name)
|
369
|
-
@
|
370
|
-
|
309
|
+
selected_entry = @cdir.find_entry(entry_name)
|
310
|
+
return if selected_entry.nil?
|
371
311
|
|
372
|
-
|
373
|
-
|
374
|
-
|
312
|
+
selected_entry.restore_ownership = @restore_ownership
|
313
|
+
selected_entry.restore_permissions = @restore_permissions
|
314
|
+
selected_entry.restore_times = @restore_times
|
315
|
+
selected_entry
|
375
316
|
end
|
376
317
|
|
377
318
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
378
319
|
# if no entry is found.
|
379
320
|
def get_entry(entry)
|
380
321
|
selected_entry = find_entry(entry)
|
381
|
-
raise Errno::ENOENT, entry
|
382
|
-
|
383
|
-
selected_entry.restore_permissions = @restore_permissions
|
384
|
-
selected_entry.restore_times = @restore_times
|
322
|
+
raise Errno::ENOENT, entry if selected_entry.nil?
|
323
|
+
|
385
324
|
selected_entry
|
386
325
|
end
|
387
326
|
|
388
327
|
# Creates a directory
|
389
|
-
def mkdir(
|
390
|
-
raise Errno::EEXIST, "File exists - #{
|
391
|
-
|
392
|
-
|
393
|
-
|
328
|
+
def mkdir(entry_name, permission = 0o755)
|
329
|
+
raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
|
330
|
+
|
331
|
+
entry_name = entry_name.dup.to_s
|
332
|
+
entry_name << '/' unless entry_name.end_with?('/')
|
333
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
394
334
|
end
|
395
335
|
|
396
336
|
private
|
397
337
|
|
398
|
-
def
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
338
|
+
def initialize_cdir(path_or_io, buffer: false)
|
339
|
+
@cdir = ::Zip::CentralDirectory.new
|
340
|
+
|
341
|
+
if ::File.size?(@name.to_s)
|
342
|
+
# There is a file, which exists, that is associated with this zip.
|
343
|
+
@create = false
|
344
|
+
@file_permissions = ::File.stat(@name).mode
|
345
|
+
|
346
|
+
if buffer
|
347
|
+
# https://github.com/rubyzip/rubyzip/issues/119
|
348
|
+
path_or_io.binmode if path_or_io.respond_to?(:binmode)
|
349
|
+
@cdir.read_from_stream(path_or_io)
|
350
|
+
else
|
351
|
+
::File.open(@name, 'rb') do |f|
|
352
|
+
@cdir.read_from_stream(f)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
elsif buffer && path_or_io.size > 0
|
356
|
+
# This zip is probably a non-empty StringIO.
|
357
|
+
@create = false
|
358
|
+
@cdir.read_from_stream(path_or_io)
|
359
|
+
elsif !@create && ::File.zero?(@name)
|
360
|
+
# A file exists, but it is empty, and we've said we're
|
361
|
+
# NOT creating a new zip.
|
362
|
+
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
363
|
+
elsif !@create
|
364
|
+
# If we get here, and we're not creating a new zip, then
|
365
|
+
# everything is wrong.
|
366
|
+
raise Error, "File #{@name} not found"
|
406
367
|
end
|
407
|
-
newEntry.directory? && srcPathIsDirectory
|
408
368
|
end
|
409
369
|
|
410
|
-
def check_entry_exists(
|
370
|
+
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
371
|
+
return unless @cdir.include?(entry_name)
|
372
|
+
|
411
373
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
else
|
416
|
-
raise ::Zip::EntryExistsError,
|
417
|
-
procedureName + " failed. Entry #{entryName} already exists"
|
418
|
-
end
|
374
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
375
|
+
|
376
|
+
remove get_entry(entry_name)
|
419
377
|
end
|
420
378
|
|
421
379
|
def check_file(path)
|
@@ -425,14 +383,12 @@ module Zip
|
|
425
383
|
def on_success_replace
|
426
384
|
dirname, basename = ::File.split(name)
|
427
385
|
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
::File.chmod(@file_permissions, name) unless @create
|
432
|
-
end
|
433
|
-
ensure
|
434
|
-
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
386
|
+
if yield tmp_filename
|
387
|
+
::File.rename(tmp_filename, name)
|
388
|
+
::File.chmod(@file_permissions, name) unless @create
|
435
389
|
end
|
390
|
+
ensure
|
391
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
436
392
|
end
|
437
393
|
end
|
438
394
|
end
|