rubyzip 2.3.2 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +485 -0
- data/LICENSE.md +24 -0
- data/README.md +192 -44
- data/Rakefile +15 -13
- data/lib/zip/central_directory.rb +179 -125
- 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 +53 -6
- data/lib/zip/entry.rb +404 -238
- 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 +185 -226
- 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 +10 -7
- data/lib/zip/input_stream.rb +76 -44
- data/lib/zip/ioextras/abstract_input_stream.rb +18 -12
- 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 +58 -48
- 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 -3
- 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 +81 -49
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/entry.rb
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pathname'
|
|
4
|
+
|
|
5
|
+
require_relative 'constants'
|
|
6
|
+
require_relative 'dirtyable'
|
|
7
|
+
|
|
2
8
|
module Zip
|
|
9
|
+
# Zip::Entry represents an entry in a Zip archive.
|
|
3
10
|
class Entry
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
include Dirtyable
|
|
12
|
+
|
|
13
|
+
# Constant used to specify that the entry is stored (i.e., not compressed).
|
|
14
|
+
STORED = ::Zip::COMPRESSION_METHOD_STORE
|
|
15
|
+
|
|
16
|
+
# Constant used to specify that the entry is deflated (i.e., compressed).
|
|
17
|
+
DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE
|
|
18
|
+
|
|
6
19
|
# Language encoding flag (EFS) bit
|
|
7
|
-
EFS = 0b100000000000
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
EFS = 0b100000000000 # :nodoc:
|
|
21
|
+
|
|
22
|
+
# Compression level flags (used as part of the gp flags).
|
|
23
|
+
COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110 # :nodoc:
|
|
24
|
+
COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 # :nodoc:
|
|
25
|
+
COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 # :nodoc:
|
|
26
|
+
|
|
27
|
+
attr_accessor :comment, :compressed_size, :follow_symlinks, :name,
|
|
28
|
+
:restore_ownership, :restore_permissions, :restore_times,
|
|
29
|
+
:unix_gid, :unix_perms, :unix_uid
|
|
30
|
+
|
|
31
|
+
attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags,
|
|
32
|
+
:internal_file_attributes, :local_header_offset # :nodoc:
|
|
33
|
+
|
|
34
|
+
attr_reader :extra, :compression_level, :filepath # :nodoc:
|
|
35
|
+
|
|
36
|
+
attr_writer :size # :nodoc:
|
|
37
|
+
|
|
38
|
+
mark_dirty :comment=, :compressed_size=, :external_file_attributes=,
|
|
39
|
+
:fstype=, :gp_flags=, :name=, :size=,
|
|
40
|
+
:unix_gid=, :unix_perms=, :unix_uid=
|
|
41
|
+
|
|
42
|
+
def set_default_vars_values # :nodoc:
|
|
19
43
|
@local_header_offset = 0
|
|
20
44
|
@local_header_size = nil # not known until local entry is created or read
|
|
21
45
|
@internal_file_attributes = 1
|
|
@@ -34,178 +58,257 @@ module Zip
|
|
|
34
58
|
end
|
|
35
59
|
@follow_symlinks = false
|
|
36
60
|
|
|
37
|
-
@restore_times =
|
|
38
|
-
@restore_permissions =
|
|
39
|
-
@restore_ownership =
|
|
61
|
+
@restore_times = DEFAULT_RESTORE_OPTIONS[:restore_times]
|
|
62
|
+
@restore_permissions = DEFAULT_RESTORE_OPTIONS[:restore_permissions]
|
|
63
|
+
@restore_ownership = DEFAULT_RESTORE_OPTIONS[:restore_ownership]
|
|
40
64
|
# BUG: need an extra field to support uid/gid's
|
|
41
65
|
@unix_uid = nil
|
|
42
66
|
@unix_gid = nil
|
|
43
67
|
@unix_perms = nil
|
|
44
|
-
# @posix_acl = nil
|
|
45
|
-
# @ntfs_acl = nil
|
|
46
|
-
@dirty = false
|
|
47
68
|
end
|
|
48
69
|
|
|
49
|
-
def check_name(name)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
|
70
|
+
def check_name(name) # :nodoc:
|
|
71
|
+
raise EntryNameError, name if name.start_with?('/')
|
|
72
|
+
raise EntryNameError if name.length > 65_535
|
|
53
73
|
end
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
# Create a new Zip::Entry.
|
|
76
|
+
def initialize(
|
|
77
|
+
zipfile = '', name = '',
|
|
78
|
+
comment: '', size: nil, compressed_size: 0, crc: 0,
|
|
79
|
+
compression_method: DEFLATED,
|
|
80
|
+
compression_level: ::Zip.default_compression,
|
|
81
|
+
time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new
|
|
82
|
+
)
|
|
83
|
+
super()
|
|
84
|
+
@name = name
|
|
85
|
+
check_name(@name)
|
|
58
86
|
|
|
59
87
|
set_default_vars_values
|
|
60
88
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
|
61
89
|
|
|
62
|
-
@zipfile =
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
@
|
|
66
|
-
@compressed_size =
|
|
67
|
-
@crc =
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
@zipfile = zipfile
|
|
91
|
+
@comment = comment || ''
|
|
92
|
+
@compression_method = compression_method || DEFLATED
|
|
93
|
+
@compression_level = compression_level || ::Zip.default_compression
|
|
94
|
+
@compressed_size = compressed_size || 0
|
|
95
|
+
@crc = crc || 0
|
|
96
|
+
@size = size
|
|
97
|
+
@time = case time
|
|
98
|
+
when ::Zip::DOSTime
|
|
99
|
+
time
|
|
100
|
+
when Time
|
|
101
|
+
::Zip::DOSTime.from_time(time)
|
|
102
|
+
else
|
|
103
|
+
::Zip::DOSTime.now
|
|
104
|
+
end
|
|
105
|
+
@extra =
|
|
106
|
+
extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s)
|
|
107
|
+
|
|
108
|
+
set_compression_level_flags
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Is this entry encrypted?
|
|
76
112
|
def encrypted?
|
|
77
113
|
gp_flags & 1 == 1
|
|
78
114
|
end
|
|
79
115
|
|
|
80
|
-
def incomplete?
|
|
81
|
-
gp_flags & 8 == 8
|
|
116
|
+
def incomplete? # :nodoc:
|
|
117
|
+
(gp_flags & 8 == 8) && (crc == 0 || size == 0 || compressed_size == 0)
|
|
82
118
|
end
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
# The uncompressed size of the entry.
|
|
121
|
+
def size
|
|
122
|
+
@size || 0
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get a timestamp component of this entry.
|
|
126
|
+
#
|
|
127
|
+
# Returns modification time by default.
|
|
128
|
+
def time(component: :mtime)
|
|
129
|
+
time =
|
|
130
|
+
if @extra[:universaltime]
|
|
131
|
+
@extra[:universaltime].send(component)
|
|
132
|
+
elsif @extra[:ntfs]
|
|
133
|
+
@extra[:ntfs].send(component)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Standard time field in central directory has local time
|
|
137
|
+
# under archive creator. Then, we can't get timezone.
|
|
138
|
+
time || (@time if component == :mtime)
|
|
94
139
|
end
|
|
95
140
|
|
|
96
141
|
alias mtime time
|
|
97
142
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
# Get the last access time of this entry, if available.
|
|
144
|
+
def atime
|
|
145
|
+
time(component: :atime)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Get the creation time of this entry, if available.
|
|
149
|
+
def ctime
|
|
150
|
+
time(component: :ctime)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Set a timestamp component of this entry.
|
|
154
|
+
#
|
|
155
|
+
# Sets modification time by default.
|
|
156
|
+
def time=(value, component: :mtime)
|
|
157
|
+
@dirty = true
|
|
158
|
+
unless @extra.member?(:universaltime) || @extra.member?(:ntfs)
|
|
159
|
+
@extra.create(:universaltime)
|
|
101
160
|
end
|
|
102
|
-
|
|
103
|
-
|
|
161
|
+
|
|
162
|
+
value = DOSTime.from_time(value)
|
|
163
|
+
comp = "#{component}=" unless component.to_s.end_with?('=')
|
|
164
|
+
(@extra[:universaltime] || @extra[:ntfs]).send(comp, value)
|
|
165
|
+
@time = value if component == :mtime
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
alias mtime= time=
|
|
169
|
+
|
|
170
|
+
# Set the last access time of this entry.
|
|
171
|
+
def atime=(value)
|
|
172
|
+
send(:time=, value, component: :atime)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Set the creation time of this entry.
|
|
176
|
+
def ctime=(value)
|
|
177
|
+
send(:time=, value, component: :ctime)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Does this entry return time fields with accurate timezone information?
|
|
181
|
+
def absolute_time?
|
|
182
|
+
@extra.member?(:universaltime) || @extra.member?(:ntfs)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Return the compression method for this entry.
|
|
186
|
+
#
|
|
187
|
+
# Returns STORED if the entry is a directory or if the compression
|
|
188
|
+
# level is 0.
|
|
189
|
+
def compression_method
|
|
190
|
+
return STORED if ftype == :directory || @compression_level == 0
|
|
191
|
+
|
|
192
|
+
@compression_method
|
|
104
193
|
end
|
|
105
194
|
|
|
106
|
-
|
|
107
|
-
|
|
195
|
+
# Set the compression method for this entry.
|
|
196
|
+
def compression_method=(method)
|
|
197
|
+
@dirty = true
|
|
198
|
+
@compression_method = (ftype == :directory ? STORED : method)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Does this entry use the ZIP64 extensions?
|
|
202
|
+
def zip64?
|
|
203
|
+
!@extra[:zip64].nil?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Is this entry encrypted with AES encryption?
|
|
207
|
+
def aes?
|
|
208
|
+
!@extra[:aes].nil?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def file_type_is?(type) # :nodoc:
|
|
212
|
+
ftype == type
|
|
213
|
+
end
|
|
108
214
|
|
|
109
|
-
|
|
215
|
+
def ftype # :nodoc:
|
|
216
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
|
110
217
|
end
|
|
111
218
|
|
|
112
219
|
# Dynamic checkers
|
|
113
220
|
%w[directory file symlink].each do |k|
|
|
114
|
-
define_method "#{k}?" do
|
|
221
|
+
define_method :"#{k}?" do
|
|
115
222
|
file_type_is?(k.to_sym)
|
|
116
223
|
end
|
|
117
224
|
end
|
|
118
225
|
|
|
119
|
-
def name_is_directory?
|
|
226
|
+
def name_is_directory? # :nodoc:
|
|
120
227
|
@name.end_with?('/')
|
|
121
228
|
end
|
|
122
229
|
|
|
123
230
|
# Is the name a relative path, free of `..` patterns that could lead to
|
|
124
231
|
# path traversal attacks? This does NOT handle symlinks; if the path
|
|
125
232
|
# contains symlinks, this check is NOT enough to guarantee safety.
|
|
126
|
-
def name_safe?
|
|
233
|
+
def name_safe? # :nodoc:
|
|
127
234
|
cleanpath = Pathname.new(@name).cleanpath
|
|
128
235
|
return false unless cleanpath.relative?
|
|
129
236
|
|
|
130
237
|
root = ::File::SEPARATOR
|
|
131
|
-
|
|
132
|
-
|
|
238
|
+
naive = Regexp.escape(::File.join(root, cleanpath.to_s))
|
|
239
|
+
# Allow for Windows drive mappings at the root.
|
|
240
|
+
::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i)
|
|
133
241
|
end
|
|
134
242
|
|
|
135
|
-
def local_entry_offset
|
|
243
|
+
def local_entry_offset # :nodoc:
|
|
136
244
|
local_header_offset + @local_header_size
|
|
137
245
|
end
|
|
138
246
|
|
|
139
|
-
def name_size
|
|
247
|
+
def name_size # :nodoc:
|
|
140
248
|
@name ? @name.bytesize : 0
|
|
141
249
|
end
|
|
142
250
|
|
|
143
|
-
def extra_size
|
|
251
|
+
def extra_size # :nodoc:
|
|
144
252
|
@extra ? @extra.local_size : 0
|
|
145
253
|
end
|
|
146
254
|
|
|
147
|
-
def comment_size
|
|
255
|
+
def comment_size # :nodoc:
|
|
148
256
|
@comment ? @comment.bytesize : 0
|
|
149
257
|
end
|
|
150
258
|
|
|
151
|
-
def calculate_local_header_size
|
|
259
|
+
def calculate_local_header_size # :nodoc:
|
|
152
260
|
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
|
|
153
261
|
end
|
|
154
262
|
|
|
155
263
|
# check before rewriting an entry (after file sizes are known)
|
|
156
264
|
# that we didn't change the header size (and thus clobber file data or something)
|
|
157
|
-
def verify_local_header_size!
|
|
265
|
+
def verify_local_header_size! # :nodoc:
|
|
158
266
|
return if @local_header_size.nil?
|
|
159
267
|
|
|
160
268
|
new_size = calculate_local_header_size
|
|
161
|
-
|
|
269
|
+
return unless @local_header_size != new_size
|
|
270
|
+
|
|
271
|
+
raise Error,
|
|
272
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
|
162
273
|
end
|
|
163
274
|
|
|
164
|
-
def cdir_header_size
|
|
275
|
+
def cdir_header_size # :nodoc:
|
|
165
276
|
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
|
166
277
|
(@extra ? @extra.c_dir_size : 0) + comment_size
|
|
167
278
|
end
|
|
168
279
|
|
|
169
|
-
def next_header_offset
|
|
170
|
-
local_entry_offset + compressed_size
|
|
280
|
+
def next_header_offset # :nodoc:
|
|
281
|
+
local_entry_offset + compressed_size
|
|
171
282
|
end
|
|
172
283
|
|
|
173
|
-
# Extracts entry to file
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
284
|
+
# Extracts this entry to a file at `entry_path`, with
|
|
285
|
+
# `destination_directory` as the base location in the filesystem.
|
|
286
|
+
#
|
|
287
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
|
288
|
+
# safe, if it is passed.
|
|
289
|
+
def extract(entry_path = @name, destination_directory: '.', &block)
|
|
290
|
+
dest_dir = ::File.absolute_path(destination_directory || '.')
|
|
291
|
+
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
|
292
|
+
|
|
293
|
+
unless extract_path.start_with?(dest_dir)
|
|
294
|
+
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
|
179
295
|
return self
|
|
180
296
|
end
|
|
181
297
|
|
|
182
|
-
dest_path ||= @name
|
|
183
298
|
block ||= proc { ::Zip.on_exists_proc }
|
|
184
299
|
|
|
185
300
|
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
|
186
301
|
|
|
187
|
-
__send__("create_#{
|
|
302
|
+
__send__(:"create_#{ftype}", extract_path, &block)
|
|
188
303
|
self
|
|
189
304
|
end
|
|
190
305
|
|
|
191
|
-
def to_s
|
|
306
|
+
def to_s # :nodoc:
|
|
192
307
|
@name
|
|
193
308
|
end
|
|
194
309
|
|
|
195
310
|
class << self
|
|
196
|
-
def
|
|
197
|
-
io.read(2).unpack1('v')
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def read_zip_long(io) # :nodoc:
|
|
201
|
-
io.read(4).unpack1('V')
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def read_zip_64_long(io) # :nodoc:
|
|
205
|
-
io.read(8).unpack1('Q<')
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def read_c_dir_entry(io) #:nodoc:all
|
|
311
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
209
312
|
path = if io.respond_to?(:path)
|
|
210
313
|
io.path
|
|
211
314
|
else
|
|
@@ -218,16 +321,18 @@ module Zip
|
|
|
218
321
|
nil
|
|
219
322
|
end
|
|
220
323
|
|
|
221
|
-
def read_local_entry(io)
|
|
324
|
+
def read_local_entry(io) # :nodoc:
|
|
222
325
|
entry = new(io)
|
|
223
326
|
entry.read_local_entry(io)
|
|
224
327
|
entry
|
|
328
|
+
rescue SplitArchiveError
|
|
329
|
+
raise
|
|
225
330
|
rescue Error
|
|
226
331
|
nil
|
|
227
332
|
end
|
|
228
333
|
end
|
|
229
334
|
|
|
230
|
-
def unpack_local_entry(buf)
|
|
335
|
+
def unpack_local_entry(buf) # :nodoc:
|
|
231
336
|
@header_signature,
|
|
232
337
|
@version,
|
|
233
338
|
@fstype,
|
|
@@ -242,63 +347,76 @@ module Zip
|
|
|
242
347
|
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
|
243
348
|
end
|
|
244
349
|
|
|
245
|
-
def read_local_entry(io)
|
|
246
|
-
@
|
|
350
|
+
def read_local_entry(io) # :nodoc:
|
|
351
|
+
@dirty = false # No changes at this point.
|
|
352
|
+
current_offset = io.tell
|
|
247
353
|
|
|
248
|
-
|
|
354
|
+
read_local_header_fields(io)
|
|
249
355
|
|
|
250
|
-
|
|
251
|
-
raise
|
|
252
|
-
end
|
|
356
|
+
if @header_signature == SPLIT_FILE_SIGNATURE
|
|
357
|
+
raise SplitArchiveError if current_offset.zero?
|
|
253
358
|
|
|
254
|
-
|
|
359
|
+
# Rewind, skipping the data descriptor, then try to read the local header again.
|
|
360
|
+
current_offset += 16
|
|
361
|
+
io.seek(current_offset)
|
|
362
|
+
read_local_header_fields(io)
|
|
363
|
+
end
|
|
255
364
|
|
|
256
|
-
unless @header_signature ==
|
|
257
|
-
raise
|
|
365
|
+
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
|
366
|
+
raise Error, "Zip local header magic not found at location '#{current_offset}'"
|
|
258
367
|
end
|
|
259
368
|
|
|
369
|
+
@local_header_offset = current_offset
|
|
370
|
+
|
|
260
371
|
set_time(@last_mod_date, @last_mod_time)
|
|
261
372
|
|
|
262
373
|
@name = io.read(@name_length)
|
|
263
|
-
extra = io.read(@extra_length)
|
|
264
|
-
|
|
265
|
-
@name.tr!('\\', '/')
|
|
266
374
|
if ::Zip.force_entry_names_encoding
|
|
267
375
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
268
376
|
end
|
|
377
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
378
|
+
|
|
379
|
+
# We need to do this here because `initialize` has so many side-effects.
|
|
380
|
+
# :-(
|
|
381
|
+
@ftype = name_is_directory? ? :directory : :file
|
|
269
382
|
|
|
383
|
+
extra = io.read(@extra_length)
|
|
270
384
|
if extra && extra.bytesize != @extra_length
|
|
271
385
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
|
272
386
|
end
|
|
273
387
|
|
|
274
|
-
|
|
275
|
-
@extra.merge(extra) if extra
|
|
276
|
-
else
|
|
277
|
-
@extra = ::Zip::ExtraField.new(extra)
|
|
278
|
-
end
|
|
279
|
-
|
|
388
|
+
read_extra_field(extra, local: true)
|
|
280
389
|
parse_zip64_extra(true)
|
|
390
|
+
parse_aes_extra
|
|
281
391
|
@local_header_size = calculate_local_header_size
|
|
282
392
|
end
|
|
283
393
|
|
|
284
|
-
def pack_local_entry
|
|
285
|
-
zip64 = @extra[
|
|
394
|
+
def pack_local_entry # :nodoc:
|
|
395
|
+
zip64 = @extra[:zip64]
|
|
286
396
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
|
287
397
|
@version_needed_to_extract, # version needed to extract
|
|
288
398
|
@gp_flags, # @gp_flags
|
|
289
|
-
|
|
399
|
+
compression_method,
|
|
290
400
|
@time.to_binary_dos_time, # @last_mod_time
|
|
291
401
|
@time.to_binary_dos_date, # @last_mod_date
|
|
292
402
|
@crc,
|
|
293
403
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
294
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
404
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
295
405
|
name_size,
|
|
296
406
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
|
297
407
|
end
|
|
298
408
|
|
|
299
|
-
def write_local_entry(io, rewrite
|
|
300
|
-
|
|
301
|
-
|
|
409
|
+
def write_local_entry(io, suppress_extra_fields: false, rewrite: false) # :nodoc:
|
|
410
|
+
prep_local_zip64_extra
|
|
411
|
+
|
|
412
|
+
# If we are rewriting the local header, then we verify that we haven't changed
|
|
413
|
+
# its size. At this point we have to keep extra fields if they are present.
|
|
414
|
+
if rewrite
|
|
415
|
+
verify_local_header_size!
|
|
416
|
+
elsif suppress_extra_fields
|
|
417
|
+
@extra.suppress_fields!(suppress_extra_fields)
|
|
418
|
+
end
|
|
419
|
+
|
|
302
420
|
@local_header_offset = io.tell
|
|
303
421
|
|
|
304
422
|
io << pack_local_entry
|
|
@@ -308,7 +426,7 @@ module Zip
|
|
|
308
426
|
@local_header_size = io.tell - @local_header_offset
|
|
309
427
|
end
|
|
310
428
|
|
|
311
|
-
def unpack_c_dir_entry(buf)
|
|
429
|
+
def unpack_c_dir_entry(buf) # :nodoc:
|
|
312
430
|
@header_signature,
|
|
313
431
|
@version, # version of encoding software
|
|
314
432
|
@fstype, # filesystem type
|
|
@@ -326,13 +444,10 @@ module Zip
|
|
|
326
444
|
_, # diskNumberStart
|
|
327
445
|
@internal_file_attributes,
|
|
328
446
|
@external_file_attributes,
|
|
329
|
-
@local_header_offset
|
|
330
|
-
@name,
|
|
331
|
-
@extra,
|
|
332
|
-
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
447
|
+
@local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
333
448
|
end
|
|
334
449
|
|
|
335
|
-
def set_ftype_from_c_dir_entry
|
|
450
|
+
def set_ftype_from_c_dir_entry # :nodoc:
|
|
336
451
|
@ftype = case @fstype
|
|
337
452
|
when ::Zip::FSTYPE_UNIX
|
|
338
453
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
|
@@ -344,8 +459,9 @@ module Zip
|
|
|
344
459
|
when ::Zip::FILE_TYPE_SYMLINK
|
|
345
460
|
:symlink
|
|
346
461
|
else
|
|
347
|
-
#
|
|
348
|
-
# Otherwise this would be set to unknown and that
|
|
462
|
+
# Best case guess for whether it is a file or not.
|
|
463
|
+
# Otherwise this would be set to unknown and that
|
|
464
|
+
# entry would never be able to be extracted.
|
|
349
465
|
if name_is_directory?
|
|
350
466
|
:directory
|
|
351
467
|
else
|
|
@@ -361,47 +477,52 @@ module Zip
|
|
|
361
477
|
end
|
|
362
478
|
end
|
|
363
479
|
|
|
364
|
-
def check_c_dir_entry_static_header_length(buf)
|
|
365
|
-
return
|
|
480
|
+
def check_c_dir_entry_static_header_length(buf) # :nodoc:
|
|
481
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
|
366
482
|
|
|
367
483
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
|
368
484
|
end
|
|
369
485
|
|
|
370
|
-
def check_c_dir_entry_signature
|
|
371
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
486
|
+
def check_c_dir_entry_signature # :nodoc:
|
|
487
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
372
488
|
|
|
373
489
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
|
374
490
|
end
|
|
375
491
|
|
|
376
|
-
def check_c_dir_entry_comment_size
|
|
492
|
+
def check_c_dir_entry_comment_size # :nodoc:
|
|
377
493
|
return if @comment && @comment.bytesize == @comment_length
|
|
378
494
|
|
|
379
495
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
|
380
496
|
end
|
|
381
497
|
|
|
382
|
-
def
|
|
498
|
+
def read_extra_field(buf, local: false) # :nodoc:
|
|
383
499
|
if @extra.kind_of?(::Zip::ExtraField)
|
|
384
|
-
@extra.merge(
|
|
500
|
+
@extra.merge(buf, local: local) if buf
|
|
385
501
|
else
|
|
386
|
-
@extra = ::Zip::ExtraField.new(
|
|
502
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
|
387
503
|
end
|
|
388
504
|
end
|
|
389
505
|
|
|
390
|
-
def read_c_dir_entry(io)
|
|
506
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
507
|
+
@dirty = false # No changes at this point.
|
|
391
508
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
392
509
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
393
510
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
394
511
|
check_c_dir_entry_signature
|
|
395
512
|
set_time(@last_mod_date, @last_mod_time)
|
|
513
|
+
|
|
396
514
|
@name = io.read(@name_length)
|
|
397
515
|
if ::Zip.force_entry_names_encoding
|
|
398
516
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
399
517
|
end
|
|
400
|
-
|
|
518
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
519
|
+
|
|
520
|
+
read_extra_field(io.read(@extra_length))
|
|
401
521
|
@comment = io.read(@comment_length)
|
|
402
522
|
check_c_dir_entry_comment_size
|
|
403
523
|
set_ftype_from_c_dir_entry
|
|
404
524
|
parse_zip64_extra(false)
|
|
525
|
+
parse_aes_extra
|
|
405
526
|
end
|
|
406
527
|
|
|
407
528
|
def file_stat(path) # :nodoc:
|
|
@@ -413,27 +534,27 @@ module Zip
|
|
|
413
534
|
end
|
|
414
535
|
|
|
415
536
|
def get_extra_attributes_from_path(path) # :nodoc:
|
|
416
|
-
|
|
537
|
+
stat = file_stat(path)
|
|
538
|
+
@time = DOSTime.from_time(stat.mtime)
|
|
539
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
|
417
540
|
|
|
418
|
-
stat = file_stat(path)
|
|
419
541
|
@unix_uid = stat.uid
|
|
420
542
|
@unix_gid = stat.gid
|
|
421
543
|
@unix_perms = stat.mode & 0o7777
|
|
422
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
|
423
544
|
end
|
|
424
545
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
unix_perms_mask = 0o7777
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
|
546
|
+
# rubocop:disable Style/GuardClause
|
|
547
|
+
def set_unix_attributes_on_path(dest_path) # :nodoc:
|
|
548
|
+
# Ignore setuid/setgid bits by default. Honour if @restore_ownership.
|
|
549
|
+
unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777)
|
|
550
|
+
if @restore_permissions && @unix_perms
|
|
551
|
+
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path)
|
|
552
|
+
end
|
|
553
|
+
if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
|
554
|
+
::FileUtils.chown(@unix_uid, @unix_gid, dest_path)
|
|
555
|
+
end
|
|
436
556
|
end
|
|
557
|
+
# rubocop:enable Style/GuardClause
|
|
437
558
|
|
|
438
559
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
|
439
560
|
return unless file? || directory?
|
|
@@ -442,40 +563,43 @@ module Zip
|
|
|
442
563
|
when ::Zip::FSTYPE_UNIX
|
|
443
564
|
set_unix_attributes_on_path(dest_path)
|
|
444
565
|
end
|
|
566
|
+
|
|
567
|
+
# Restore the timestamp on a file. This will either have come from the
|
|
568
|
+
# original source file that was copied into the archive, or from the
|
|
569
|
+
# creation date of the archive if there was no original source file.
|
|
570
|
+
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
|
445
571
|
end
|
|
446
572
|
|
|
447
|
-
def pack_c_dir_entry
|
|
448
|
-
zip64 = @extra[
|
|
573
|
+
def pack_c_dir_entry # :nodoc:
|
|
574
|
+
zip64 = @extra[:zip64]
|
|
449
575
|
[
|
|
450
576
|
@header_signature,
|
|
451
577
|
@version, # version of encoding software
|
|
452
578
|
@fstype, # filesystem type
|
|
453
579
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
454
580
|
@gp_flags, # @gp_flags
|
|
455
|
-
|
|
581
|
+
compression_method,
|
|
456
582
|
@time.to_binary_dos_time, # @last_mod_time
|
|
457
583
|
@time.to_binary_dos_date, # @last_mod_date
|
|
458
584
|
@crc,
|
|
459
585
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
460
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
586
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
461
587
|
name_size,
|
|
462
588
|
@extra ? @extra.c_dir_size : 0,
|
|
463
589
|
comment_size,
|
|
464
590
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
|
465
591
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
466
592
|
@external_file_attributes, # native filesystem attributes
|
|
467
|
-
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
468
|
-
@name,
|
|
469
|
-
@extra,
|
|
470
|
-
@comment
|
|
593
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
471
594
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
472
595
|
end
|
|
473
596
|
|
|
474
|
-
def write_c_dir_entry(io)
|
|
475
|
-
|
|
597
|
+
def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
|
|
598
|
+
prep_cdir_zip64_extra
|
|
599
|
+
|
|
476
600
|
case @fstype
|
|
477
601
|
when ::Zip::FSTYPE_UNIX
|
|
478
|
-
ft = case
|
|
602
|
+
ft = case ftype
|
|
479
603
|
when :file
|
|
480
604
|
@unix_perms ||= 0o644
|
|
481
605
|
::Zip::FILE_TYPE_FILE
|
|
@@ -488,10 +612,11 @@ module Zip
|
|
|
488
612
|
end
|
|
489
613
|
|
|
490
614
|
unless ft.nil?
|
|
491
|
-
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
|
615
|
+
@external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16
|
|
492
616
|
end
|
|
493
617
|
end
|
|
494
618
|
|
|
619
|
+
@extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
|
|
495
620
|
io << pack_c_dir_entry
|
|
496
621
|
|
|
497
622
|
io << @name
|
|
@@ -499,43 +624,42 @@ module Zip
|
|
|
499
624
|
io << @comment
|
|
500
625
|
end
|
|
501
626
|
|
|
502
|
-
def ==(other)
|
|
627
|
+
def ==(other) # :nodoc:
|
|
503
628
|
return false unless other.class == self.class
|
|
504
629
|
|
|
505
630
|
# Compares contents of local entry and exposed fields
|
|
506
|
-
|
|
631
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
|
507
632
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
|
508
633
|
end
|
|
509
|
-
keys_equal && time.dos_equals(other.time)
|
|
510
634
|
end
|
|
511
635
|
|
|
512
|
-
def <=>(other)
|
|
636
|
+
def <=>(other) # :nodoc:
|
|
513
637
|
to_s <=> other.to_s
|
|
514
638
|
end
|
|
515
639
|
|
|
516
640
|
# Returns an IO like object for the given ZipEntry.
|
|
517
641
|
# Warning: may behave weird with symlinks.
|
|
518
642
|
def get_input_stream(&block)
|
|
519
|
-
if
|
|
520
|
-
yield ::Zip::NullInputStream if
|
|
643
|
+
if ftype == :directory
|
|
644
|
+
yield ::Zip::NullInputStream if block
|
|
521
645
|
::Zip::NullInputStream
|
|
522
646
|
elsif @filepath
|
|
523
|
-
case
|
|
647
|
+
case ftype
|
|
524
648
|
when :file
|
|
525
649
|
::File.open(@filepath, 'rb', &block)
|
|
526
650
|
when :symlink
|
|
527
651
|
linkpath = ::File.readlink(@filepath)
|
|
528
652
|
stringio = ::StringIO.new(linkpath)
|
|
529
|
-
yield(stringio) if
|
|
653
|
+
yield(stringio) if block
|
|
530
654
|
stringio
|
|
531
655
|
else
|
|
532
|
-
raise "unknown @file_type #{
|
|
656
|
+
raise "unknown @file_type #{ftype}"
|
|
533
657
|
end
|
|
534
658
|
else
|
|
535
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
|
659
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
|
536
660
|
zis.instance_variable_set(:@complete_entry, self)
|
|
537
661
|
zis.get_next_entry
|
|
538
|
-
if
|
|
662
|
+
if block
|
|
539
663
|
begin
|
|
540
664
|
yield(zis)
|
|
541
665
|
ensure
|
|
@@ -553,8 +677,8 @@ module Zip
|
|
|
553
677
|
when 'file'
|
|
554
678
|
if name_is_directory?
|
|
555
679
|
raise ArgumentError,
|
|
556
|
-
"entry name '#{
|
|
557
|
-
|
|
680
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
681
|
+
"'#{src_path}' is not a directory"
|
|
558
682
|
end
|
|
559
683
|
:file
|
|
560
684
|
when 'directory'
|
|
@@ -563,8 +687,8 @@ module Zip
|
|
|
563
687
|
when 'link'
|
|
564
688
|
if name_is_directory?
|
|
565
689
|
raise ArgumentError,
|
|
566
|
-
"entry name '#{
|
|
567
|
-
|
|
690
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
691
|
+
"'#{src_path}' is not a directory"
|
|
568
692
|
end
|
|
569
693
|
:symlink
|
|
570
694
|
else
|
|
@@ -572,27 +696,30 @@ module Zip
|
|
|
572
696
|
end
|
|
573
697
|
|
|
574
698
|
@filepath = src_path
|
|
699
|
+
@size = stat.size
|
|
575
700
|
get_extra_attributes_from_path(@filepath)
|
|
576
701
|
end
|
|
577
702
|
|
|
578
|
-
def write_to_zip_output_stream(zip_output_stream)
|
|
579
|
-
if
|
|
580
|
-
zip_output_stream.put_next_entry(self
|
|
703
|
+
def write_to_zip_output_stream(zip_output_stream) # :nodoc:
|
|
704
|
+
if ftype == :directory
|
|
705
|
+
zip_output_stream.put_next_entry(self)
|
|
581
706
|
elsif @filepath
|
|
582
|
-
zip_output_stream.put_next_entry(self
|
|
583
|
-
get_input_stream
|
|
707
|
+
zip_output_stream.put_next_entry(self)
|
|
708
|
+
get_input_stream do |is|
|
|
709
|
+
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
|
710
|
+
end
|
|
584
711
|
else
|
|
585
712
|
zip_output_stream.copy_raw_entry(self)
|
|
586
713
|
end
|
|
587
714
|
end
|
|
588
715
|
|
|
589
|
-
def parent_as_string
|
|
716
|
+
def parent_as_string # :nodoc:
|
|
590
717
|
entry_name = name.chomp('/')
|
|
591
718
|
slash_index = entry_name.rindex('/')
|
|
592
719
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
|
593
720
|
end
|
|
594
721
|
|
|
595
|
-
def get_raw_input_stream(&block)
|
|
722
|
+
def get_raw_input_stream(&block) # :nodoc:
|
|
596
723
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
|
597
724
|
yield @zipfile
|
|
598
725
|
else
|
|
@@ -600,12 +727,22 @@ module Zip
|
|
|
600
727
|
end
|
|
601
728
|
end
|
|
602
729
|
|
|
603
|
-
def clean_up
|
|
604
|
-
#
|
|
730
|
+
def clean_up # :nodoc:
|
|
731
|
+
@dirty = false # Any changes are written at this point.
|
|
605
732
|
end
|
|
606
733
|
|
|
607
734
|
private
|
|
608
735
|
|
|
736
|
+
def read_local_header_fields(io) # :nodoc:
|
|
737
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
|
738
|
+
|
|
739
|
+
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
|
740
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
unpack_local_entry(static_sized_fields_buf)
|
|
744
|
+
end
|
|
745
|
+
|
|
609
746
|
def set_time(binary_dos_date, binary_dos_time)
|
|
610
747
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
|
611
748
|
rescue ArgumentError
|
|
@@ -614,9 +751,9 @@ module Zip
|
|
|
614
751
|
|
|
615
752
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
616
753
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
617
|
-
raise ::Zip::
|
|
618
|
-
"Destination '#{dest_path}' already exists"
|
|
754
|
+
raise ::Zip::DestinationExistsError, dest_path
|
|
619
755
|
end
|
|
756
|
+
|
|
620
757
|
::File.open(dest_path, 'wb') do |os|
|
|
621
758
|
get_input_stream do |is|
|
|
622
759
|
bytes_written = 0
|
|
@@ -627,10 +764,10 @@ module Zip
|
|
|
627
764
|
bytes_written += buf.bytesize
|
|
628
765
|
next unless bytes_written > size && !warned
|
|
629
766
|
|
|
630
|
-
|
|
631
|
-
raise
|
|
767
|
+
error = ::Zip::EntrySizeError.new(self)
|
|
768
|
+
raise error if ::Zip.validate_entry_sizes
|
|
632
769
|
|
|
633
|
-
warn "WARNING: #{message}"
|
|
770
|
+
warn "WARNING: #{error.message}"
|
|
634
771
|
warned = true
|
|
635
772
|
end
|
|
636
773
|
end
|
|
@@ -643,14 +780,11 @@ module Zip
|
|
|
643
780
|
return if ::File.directory?(dest_path)
|
|
644
781
|
|
|
645
782
|
if ::File.exist?(dest_path)
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
raise ::Zip::DestinationFileExistsError,
|
|
650
|
-
"Cannot create directory '#{dest_path}'. " \
|
|
651
|
-
'A file already exists with that name'
|
|
652
|
-
end
|
|
783
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
|
784
|
+
|
|
785
|
+
::FileUtils.rm_f dest_path
|
|
653
786
|
end
|
|
787
|
+
|
|
654
788
|
::FileUtils.mkdir_p(dest_path)
|
|
655
789
|
set_extra_attributes_on_path(dest_path)
|
|
656
790
|
end
|
|
@@ -664,53 +798,85 @@ module Zip
|
|
|
664
798
|
|
|
665
799
|
# apply missing data from the zip64 extra information field, if present
|
|
666
800
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
667
|
-
def parse_zip64_extra(for_local_header)
|
|
668
|
-
return
|
|
801
|
+
def parse_zip64_extra(for_local_header) # :nodoc:
|
|
802
|
+
return unless zip64?
|
|
669
803
|
|
|
670
804
|
if for_local_header
|
|
671
|
-
@size, @compressed_size = @extra[
|
|
805
|
+
@size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
|
|
672
806
|
else
|
|
673
|
-
@size, @compressed_size, @local_header_offset = @extra[
|
|
807
|
+
@size, @compressed_size, @local_header_offset = @extra[:zip64].parse(
|
|
808
|
+
@size, @compressed_size, @local_header_offset
|
|
809
|
+
)
|
|
674
810
|
end
|
|
675
811
|
end
|
|
676
812
|
|
|
677
|
-
def
|
|
678
|
-
|
|
813
|
+
def parse_aes_extra # :nodoc:
|
|
814
|
+
return unless aes?
|
|
815
|
+
|
|
816
|
+
if @extra[:aes].vendor_id != 'AE'
|
|
817
|
+
raise Error, "Unsupported encryption method #{@extra[:aes].vendor_id}"
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
unless ::Zip::AESEncryption::VERSIONS.include? @extra[:aes].vendor_version
|
|
821
|
+
raise Error, "Unsupported encryption style #{@extra[:aes].vendor_version}"
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
@compression_method = @extra[:aes].compression_method if ftype != :directory
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
|
828
|
+
# indicate compression level. This seems to be mainly cosmetic but they are
|
|
829
|
+
# generally set by other tools - including in docx files. It is these flags
|
|
830
|
+
# that are used by commandline tools (and elsewhere) to give an indication
|
|
831
|
+
# of how compressed a file is. See the PKWARE APPNOTE for more information:
|
|
832
|
+
# https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
833
|
+
#
|
|
834
|
+
# It's safe to simply OR these flags here as compression_level is read only.
|
|
835
|
+
def set_compression_level_flags
|
|
836
|
+
return unless compression_method == DEFLATED
|
|
837
|
+
|
|
838
|
+
case @compression_level
|
|
839
|
+
when 1
|
|
840
|
+
@gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG
|
|
841
|
+
when 2
|
|
842
|
+
@gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG
|
|
843
|
+
when 8, 9
|
|
844
|
+
@gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG
|
|
845
|
+
end
|
|
679
846
|
end
|
|
680
847
|
|
|
681
|
-
#
|
|
682
|
-
def
|
|
848
|
+
# rubocop:disable Style/GuardClause
|
|
849
|
+
def prep_local_zip64_extra
|
|
683
850
|
return unless ::Zip.write_zip64_support
|
|
851
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
|
684
852
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if
|
|
853
|
+
# Might not know size here, so need ZIP64 just in case.
|
|
854
|
+
# If we already have a ZIP64 extra (placeholder) then we must fill it in.
|
|
855
|
+
if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
|
688
856
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
689
|
-
@extra.
|
|
690
|
-
zip64 = @extra.create('Zip64')
|
|
691
|
-
if for_local_header
|
|
692
|
-
# local header always includes size and compressed size
|
|
693
|
-
zip64.original_size = @size
|
|
694
|
-
zip64.compressed_size = @compressed_size
|
|
695
|
-
else
|
|
696
|
-
# central directory entry entries include whichever fields are necessary
|
|
697
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
|
698
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
699
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
700
|
-
end
|
|
701
|
-
else
|
|
702
|
-
@extra.delete('Zip64')
|
|
857
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
703
858
|
|
|
704
|
-
#
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
859
|
+
# Local header always includes size and compressed size.
|
|
860
|
+
zip64.original_size = @size || 0
|
|
861
|
+
zip64.compressed_size = @compressed_size
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
def prep_cdir_zip64_extra
|
|
866
|
+
return unless ::Zip.write_zip64_support
|
|
867
|
+
|
|
868
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
|
869
|
+
@local_header_offset >= 0xFFFFFFFF
|
|
870
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
871
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
872
|
+
|
|
873
|
+
# Central directory entry entries include whichever fields are necessary.
|
|
874
|
+
zip64.original_size = @size if @size && @size >= 0xFFFFFFFF
|
|
875
|
+
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
876
|
+
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
712
877
|
end
|
|
713
878
|
end
|
|
879
|
+
# rubocop:enable Style/GuardClause
|
|
714
880
|
end
|
|
715
881
|
end
|
|
716
882
|
|