rubyzip 1.3.0 → 3.4.0
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 +516 -0
- data/LICENSE.md +24 -0
- data/README.md +213 -51
- data/Rakefile +17 -12
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +196 -123
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +81 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +46 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +22 -16
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +65 -13
- data/lib/zip/entry.rb +433 -249
- data/lib/zip/entry_set.rb +13 -9
- data/lib/zip/errors.rb +152 -15
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +18 -18
- data/lib/zip/extra_field/ntfs.rb +10 -4
- 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 +35 -0
- data/lib/zip/extra_field/zip64.rb +23 -7
- data/lib/zip/extra_field.rb +31 -27
- data/lib/zip/file.rb +225 -241
- 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 +32 -585
- data/lib/zip/inflater.rb +28 -37
- data/lib/zip/input_stream.rb +113 -54
- data/lib/zip/ioextras/abstract_input_stream.rb +154 -55
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +8 -8
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +7 -12
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +69 -50
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +17 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +10 -11
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +32 -7
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +4 -3
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +23 -21
- data/samples/qtzip.rb +13 -12
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +85 -177
- 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,116 @@
|
|
|
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, :close]
|
|
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
|
|
53
56
|
|
|
57
|
+
IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze # :nodoc:
|
|
58
|
+
|
|
59
|
+
# The name of this zip archive.
|
|
54
60
|
attr_reader :name
|
|
55
61
|
|
|
56
|
-
# default -> false
|
|
62
|
+
# default -> false.
|
|
57
63
|
attr_accessor :restore_ownership
|
|
58
|
-
|
|
64
|
+
|
|
65
|
+
# default -> true.
|
|
59
66
|
attr_accessor :restore_permissions
|
|
60
|
-
|
|
67
|
+
|
|
68
|
+
# default -> true.
|
|
61
69
|
attr_accessor :restore_times
|
|
62
|
-
# Returns the zip files comment, if it has one
|
|
63
|
-
attr_accessor :comment
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
|
|
72
|
+
|
|
73
|
+
# Opens a zip archive. Pass create: true to create
|
|
66
74
|
# a new archive if it doesn't exist already.
|
|
67
|
-
def initialize(path_or_io, create
|
|
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)
|
|
68
81
|
super()
|
|
82
|
+
|
|
69
83
|
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
|
70
|
-
@comment = ''
|
|
71
84
|
@create = create ? true : false # allow any truthy value to mean true
|
|
72
85
|
|
|
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
|
|
86
|
+
initialize_cdir(path_or_io, buffer: buffer)
|
|
98
87
|
|
|
99
|
-
@
|
|
100
|
-
@
|
|
101
|
-
@
|
|
102
|
-
@
|
|
103
|
-
@
|
|
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
|
|
104
93
|
end
|
|
105
94
|
|
|
106
95
|
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
|
-
|
|
96
|
+
# Similar to ::new. If a block is passed the Zip::File object is passed
|
|
97
|
+
# to the block and is automatically closed afterwards, just as with
|
|
98
|
+
# ruby's builtin File::open method.
|
|
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)
|
|
111
|
+
|
|
112
112
|
return zf unless block_given?
|
|
113
|
+
|
|
113
114
|
begin
|
|
114
115
|
yield zf
|
|
115
116
|
ensure
|
|
@@ -117,30 +118,32 @@ module Zip
|
|
|
117
118
|
end
|
|
118
119
|
end
|
|
119
120
|
|
|
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
121
|
# Like #open, but reads zip archive contents from a String or open IO
|
|
129
122
|
# stream, and outputs data to a buffer.
|
|
130
123
|
# (This can be used to extract data from a
|
|
131
124
|
# downloaded zip archive without first saving it to disk.)
|
|
132
|
-
def open_buffer(io,
|
|
133
|
-
|
|
134
|
-
|
|
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)
|
|
131
|
+
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
|
132
|
+
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
|
133
|
+
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
|
135
134
|
end
|
|
136
135
|
|
|
137
|
-
io = ::StringIO.new(io) if io.
|
|
136
|
+
io = ::StringIO.new(io) if io.kind_of?(::String)
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
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)
|
|
141
144
|
|
|
142
|
-
zf = ::Zip::File.new(io, true, true, options)
|
|
143
145
|
return zf unless block_given?
|
|
146
|
+
|
|
144
147
|
yield zf
|
|
145
148
|
|
|
146
149
|
begin
|
|
@@ -156,93 +159,32 @@ module Zip
|
|
|
156
159
|
# whereas ZipInputStream jumps through the entire archive accessing the
|
|
157
160
|
# local entry headers (which contain the same information as the
|
|
158
161
|
# central directory).
|
|
159
|
-
def foreach(
|
|
160
|
-
open(
|
|
161
|
-
|
|
162
|
+
def foreach(zip_file_name, &block)
|
|
163
|
+
::Zip::File.open(zip_file_name) do |zip_file|
|
|
164
|
+
zip_file.each(&block)
|
|
162
165
|
end
|
|
163
166
|
end
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
MAX_SEGMENT_SIZE
|
|
170
|
-
else
|
|
171
|
-
segment_size
|
|
172
|
-
end
|
|
173
|
-
end
|
|
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
|
|
174
172
|
|
|
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)
|
|
173
|
+
if path_or_io.kind_of?(String)
|
|
174
|
+
::File.open(path_or_io, 'rb') do |f|
|
|
175
|
+
cdir.count_entries(f)
|
|
234
176
|
end
|
|
177
|
+
else
|
|
178
|
+
cdir.count_entries(path_or_io)
|
|
235
179
|
end
|
|
236
|
-
::File.delete(zip_file_name) if delete_zip_file
|
|
237
|
-
szip_file_index
|
|
238
180
|
end
|
|
239
181
|
end
|
|
240
182
|
|
|
241
183
|
# Returns an input stream to the specified entry. If a block is passed
|
|
242
184
|
# the stream object is passed to the block and the stream is automatically
|
|
243
185
|
# closed afterwards just as with ruby's builtin File.open method.
|
|
244
|
-
def get_input_stream(entry, &
|
|
245
|
-
get_entry(entry).get_input_stream(&
|
|
186
|
+
def get_input_stream(entry, &a_proc)
|
|
187
|
+
get_entry(entry).get_input_stream(&a_proc)
|
|
246
188
|
end
|
|
247
189
|
|
|
248
190
|
# Returns an output stream to the specified entry. If entry is not an instance
|
|
@@ -250,21 +192,29 @@ module Zip
|
|
|
250
192
|
# specified. If a block is passed the stream object is passed to the block and
|
|
251
193
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
|
252
194
|
# File.open method.
|
|
253
|
-
def get_output_stream(entry,
|
|
195
|
+
def get_output_stream(entry, permissions: nil, comment: nil,
|
|
196
|
+
extra: nil, compressed_size: nil, crc: nil,
|
|
197
|
+
compression_method: nil, compression_level: nil,
|
|
198
|
+
size: nil, time: nil, &a_proc)
|
|
254
199
|
new_entry =
|
|
255
200
|
if entry.kind_of?(Entry)
|
|
256
201
|
entry
|
|
257
202
|
else
|
|
258
|
-
Entry.new(
|
|
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
|
+
)
|
|
259
209
|
end
|
|
260
210
|
if new_entry.directory?
|
|
261
211
|
raise ArgumentError,
|
|
262
212
|
"cannot open stream to directory entry - '#{new_entry}'"
|
|
263
213
|
end
|
|
264
|
-
new_entry.unix_perms =
|
|
214
|
+
new_entry.unix_perms = permissions
|
|
265
215
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
|
266
|
-
@
|
|
267
|
-
zip_streamable_entry.get_output_stream(&
|
|
216
|
+
@cdir << zip_streamable_entry
|
|
217
|
+
zip_streamable_entry.get_output_stream(&a_proc)
|
|
268
218
|
end
|
|
269
219
|
|
|
270
220
|
# Returns the name of the zip archive
|
|
@@ -274,77 +224,92 @@ module Zip
|
|
|
274
224
|
|
|
275
225
|
# Returns a string containing the contents of the specified entry
|
|
276
226
|
def read(entry)
|
|
277
|
-
get_input_stream(entry
|
|
227
|
+
get_input_stream(entry, &:read)
|
|
278
228
|
end
|
|
279
229
|
|
|
280
230
|
# Convenience method for adding the contents of a file to the archive
|
|
281
231
|
def add(entry, src_path, &continue_on_exists_proc)
|
|
282
232
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
|
283
233
|
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
|
284
|
-
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
|
|
285
242
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
|
286
|
-
|
|
287
|
-
@entry_set << new_entry
|
|
243
|
+
@cdir << new_entry
|
|
288
244
|
end
|
|
289
245
|
|
|
290
246
|
# Convenience method for adding the contents of a file to the archive
|
|
291
247
|
# in Stored format (uncompressed)
|
|
292
248
|
def add_stored(entry, src_path, &continue_on_exists_proc)
|
|
293
|
-
entry = ::Zip::Entry.new(
|
|
249
|
+
entry = ::Zip::Entry.new(
|
|
250
|
+
@name, entry.to_s, compression_method: ::Zip::Entry::STORED
|
|
251
|
+
)
|
|
294
252
|
add(entry, src_path, &continue_on_exists_proc)
|
|
295
253
|
end
|
|
296
254
|
|
|
297
255
|
# Removes the specified entry.
|
|
298
256
|
def remove(entry)
|
|
299
|
-
@
|
|
257
|
+
@cdir.delete(get_entry(entry))
|
|
300
258
|
end
|
|
301
259
|
|
|
302
260
|
# Renames the specified entry.
|
|
303
261
|
def rename(entry, new_name, &continue_on_exists_proc)
|
|
304
|
-
|
|
262
|
+
found_entry = get_entry(entry)
|
|
305
263
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
|
306
|
-
@
|
|
307
|
-
|
|
308
|
-
@
|
|
264
|
+
@cdir.delete(found_entry)
|
|
265
|
+
found_entry.name = new_name
|
|
266
|
+
@cdir << found_entry
|
|
309
267
|
end
|
|
310
268
|
|
|
311
|
-
# Replaces the specified entry with the contents of
|
|
269
|
+
# Replaces the specified entry with the contents of src_path (from
|
|
312
270
|
# the file system).
|
|
313
|
-
def replace(entry,
|
|
314
|
-
check_file(
|
|
271
|
+
def replace(entry, src_path)
|
|
272
|
+
check_file(src_path)
|
|
315
273
|
remove(entry)
|
|
316
|
-
add(entry,
|
|
274
|
+
add(entry, src_path)
|
|
317
275
|
end
|
|
318
276
|
|
|
319
|
-
# Extracts entry to file
|
|
320
|
-
|
|
277
|
+
# Extracts `entry` to a file at `entry_path`, with `destination_directory`
|
|
278
|
+
# as the base location in the filesystem.
|
|
279
|
+
#
|
|
280
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
|
281
|
+
# safe, if it is passed.
|
|
282
|
+
def extract(entry, entry_path = nil, destination_directory: '.', &block)
|
|
321
283
|
block ||= proc { ::Zip.on_exists_proc }
|
|
322
284
|
found_entry = get_entry(entry)
|
|
323
|
-
found_entry.
|
|
285
|
+
entry_path ||= found_entry.name
|
|
286
|
+
found_entry.extract(entry_path, destination_directory: destination_directory, &block)
|
|
324
287
|
end
|
|
325
288
|
|
|
326
289
|
# Commits changes that has been made since the previous commit to
|
|
327
290
|
# the zip archive.
|
|
328
291
|
def commit
|
|
329
|
-
return if name.
|
|
292
|
+
return if name.kind_of?(StringIO) || !commit_required?
|
|
293
|
+
|
|
330
294
|
on_success_replace do |tmp_file|
|
|
331
|
-
::Zip::OutputStream.open(tmp_file) do |zos|
|
|
332
|
-
@
|
|
295
|
+
::Zip::OutputStream.open(tmp_file, suppress_extra_fields: @suppress_extra_fields) do |zos|
|
|
296
|
+
@cdir.each do |e|
|
|
333
297
|
e.write_to_zip_output_stream(zos)
|
|
334
|
-
e.dirty = false
|
|
335
298
|
e.clean_up
|
|
336
299
|
end
|
|
337
300
|
zos.comment = comment
|
|
338
301
|
end
|
|
339
302
|
true
|
|
340
303
|
end
|
|
341
|
-
|
|
304
|
+
initialize_cdir(@name)
|
|
342
305
|
end
|
|
343
306
|
|
|
344
307
|
# Write buffer write changes to buffer and return
|
|
345
|
-
def write_buffer(io = ::StringIO.new
|
|
346
|
-
|
|
347
|
-
|
|
308
|
+
def write_buffer(io = ::StringIO.new)
|
|
309
|
+
return io unless commit_required?
|
|
310
|
+
|
|
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) }
|
|
348
313
|
zos.comment = comment
|
|
349
314
|
end
|
|
350
315
|
end
|
|
@@ -357,65 +322,86 @@ module Zip
|
|
|
357
322
|
# Returns true if any changes has been made to this archive since
|
|
358
323
|
# the previous commit
|
|
359
324
|
def commit_required?
|
|
360
|
-
@
|
|
361
|
-
|
|
325
|
+
return true if @create || @cdir.dirty?
|
|
326
|
+
|
|
327
|
+
@cdir.each do |e|
|
|
328
|
+
return true if e.dirty?
|
|
362
329
|
end
|
|
363
|
-
|
|
330
|
+
|
|
331
|
+
false
|
|
364
332
|
end
|
|
365
333
|
|
|
366
334
|
# Searches for entry with the specified name. Returns nil if
|
|
367
335
|
# no entry is found. See also get_entry
|
|
368
336
|
def find_entry(entry_name)
|
|
369
|
-
@
|
|
370
|
-
|
|
337
|
+
selected_entry = @cdir.find_entry(entry_name)
|
|
338
|
+
return if selected_entry.nil?
|
|
371
339
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
340
|
+
selected_entry.restore_ownership = @restore_ownership
|
|
341
|
+
selected_entry.restore_permissions = @restore_permissions
|
|
342
|
+
selected_entry.restore_times = @restore_times
|
|
343
|
+
selected_entry
|
|
375
344
|
end
|
|
376
345
|
|
|
377
346
|
# Searches for an entry just as find_entry, but throws Errno::ENOENT
|
|
378
347
|
# if no entry is found.
|
|
379
348
|
def get_entry(entry)
|
|
380
349
|
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
|
|
350
|
+
raise Errno::ENOENT, entry if selected_entry.nil?
|
|
351
|
+
|
|
385
352
|
selected_entry
|
|
386
353
|
end
|
|
387
354
|
|
|
388
355
|
# Creates a directory
|
|
389
|
-
def mkdir(
|
|
390
|
-
raise Errno::EEXIST, "File exists - #{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
356
|
+
def mkdir(entry_name, permission = 0o755)
|
|
357
|
+
raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
|
|
358
|
+
|
|
359
|
+
entry_name = entry_name.dup.to_s
|
|
360
|
+
entry_name << '/' unless entry_name.end_with?('/')
|
|
361
|
+
@cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
|
394
362
|
end
|
|
395
363
|
|
|
396
364
|
private
|
|
397
365
|
|
|
398
|
-
def
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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"
|
|
406
395
|
end
|
|
407
|
-
newEntry.directory? && srcPathIsDirectory
|
|
408
396
|
end
|
|
409
397
|
|
|
410
|
-
def check_entry_exists(
|
|
398
|
+
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
|
399
|
+
return unless @cdir.include?(entry_name)
|
|
400
|
+
|
|
411
401
|
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
|
|
402
|
+
raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
|
|
403
|
+
|
|
404
|
+
remove get_entry(entry_name)
|
|
419
405
|
end
|
|
420
406
|
|
|
421
407
|
def check_file(path)
|
|
@@ -425,14 +411,12 @@ module Zip
|
|
|
425
411
|
def on_success_replace
|
|
426
412
|
dirname, basename = ::File.split(name)
|
|
427
413
|
::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)
|
|
414
|
+
if yield tmp_filename
|
|
415
|
+
::File.rename(tmp_filename, name)
|
|
416
|
+
::File.chmod(@file_permissions, name) unless @create
|
|
435
417
|
end
|
|
418
|
+
ensure
|
|
419
|
+
FileUtils.rm_f(tmp_filename)
|
|
436
420
|
end
|
|
437
421
|
end
|
|
438
422
|
end
|