rubyzip 2.1.0 → 3.6.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 +538 -0
- data/LICENSE.md +24 -0
- data/README.md +235 -48
- 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 +123 -0
- data/lib/zip/crypto/decrypted_io.rb +52 -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 +79 -14
- data/lib/zip/entry.rb +430 -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 +17 -17
- data/lib/zip/extra_field/ntfs.rb +10 -4
- data/lib/zip/extra_field/old_unix.rb +8 -4
- data/lib/zip/extra_field/universal_time.rb +6 -1
- data/lib/zip/extra_field/unix.rb +9 -5
- 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 +211 -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 +12 -9
- 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 +76 -51
- 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 +9 -5
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +31 -5
- 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 +84 -28
- 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,169 +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
|
-
raise
|
|
70
|
+
def check_name(name) # :nodoc:
|
|
71
|
+
raise EntryNameError, name if name.start_with?('/')
|
|
72
|
+
raise EntryNameError if name.length > 65_535
|
|
52
73
|
end
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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)
|
|
57
86
|
|
|
58
87
|
set_default_vars_values
|
|
59
88
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
|
60
89
|
|
|
61
|
-
@zipfile =
|
|
62
|
-
@
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
@compressed_size =
|
|
66
|
-
@crc =
|
|
67
|
-
@
|
|
68
|
-
@
|
|
69
|
-
|
|
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?
|
|
112
|
+
def encrypted?
|
|
113
|
+
gp_flags & 1 == 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def incomplete? # :nodoc:
|
|
117
|
+
(gp_flags & 8 == 8) && (crc == 0 || size == 0 || compressed_size == 0)
|
|
118
|
+
end
|
|
119
|
+
|
|
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
|
|
70
135
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
139
|
end
|
|
74
140
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
else
|
|
81
|
-
# Standard time field in central directory has local time
|
|
82
|
-
# under archive creator. Then, we can't get timezone.
|
|
83
|
-
@time
|
|
84
|
-
end
|
|
141
|
+
alias mtime time
|
|
142
|
+
|
|
143
|
+
# Get the last access time of this entry, if available.
|
|
144
|
+
def atime
|
|
145
|
+
time(component: :atime)
|
|
85
146
|
end
|
|
86
147
|
|
|
87
|
-
|
|
148
|
+
# Get the creation time of this entry, if available.
|
|
149
|
+
def ctime
|
|
150
|
+
time(component: :ctime)
|
|
151
|
+
end
|
|
88
152
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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)
|
|
92
160
|
end
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Set the compression method for this entry.
|
|
196
|
+
def compression_method=(method)
|
|
197
|
+
@dirty = true
|
|
198
|
+
@compression_method = (ftype == :directory ? STORED : method)
|
|
95
199
|
end
|
|
96
200
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
214
|
+
|
|
215
|
+
def ftype # :nodoc:
|
|
216
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
|
100
217
|
end
|
|
101
218
|
|
|
102
219
|
# Dynamic checkers
|
|
103
220
|
%w[directory file symlink].each do |k|
|
|
104
|
-
define_method "#{k}?" do
|
|
221
|
+
define_method :"#{k}?" do
|
|
105
222
|
file_type_is?(k.to_sym)
|
|
106
223
|
end
|
|
107
224
|
end
|
|
108
225
|
|
|
109
|
-
def name_is_directory?
|
|
226
|
+
def name_is_directory? # :nodoc:
|
|
110
227
|
@name.end_with?('/')
|
|
111
228
|
end
|
|
112
229
|
|
|
113
230
|
# Is the name a relative path, free of `..` patterns that could lead to
|
|
114
231
|
# path traversal attacks? This does NOT handle symlinks; if the path
|
|
115
232
|
# contains symlinks, this check is NOT enough to guarantee safety.
|
|
116
|
-
def name_safe?
|
|
233
|
+
def name_safe? # :nodoc:
|
|
117
234
|
cleanpath = Pathname.new(@name).cleanpath
|
|
118
235
|
return false unless cleanpath.relative?
|
|
236
|
+
|
|
119
237
|
root = ::File::SEPARATOR
|
|
120
|
-
|
|
121
|
-
|
|
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)
|
|
122
241
|
end
|
|
123
242
|
|
|
124
|
-
def local_entry_offset
|
|
243
|
+
def local_entry_offset # :nodoc:
|
|
125
244
|
local_header_offset + @local_header_size
|
|
126
245
|
end
|
|
127
246
|
|
|
128
|
-
def name_size
|
|
247
|
+
def name_size # :nodoc:
|
|
129
248
|
@name ? @name.bytesize : 0
|
|
130
249
|
end
|
|
131
250
|
|
|
132
|
-
def extra_size
|
|
251
|
+
def extra_size # :nodoc:
|
|
133
252
|
@extra ? @extra.local_size : 0
|
|
134
253
|
end
|
|
135
254
|
|
|
136
|
-
def comment_size
|
|
255
|
+
def comment_size # :nodoc:
|
|
137
256
|
@comment ? @comment.bytesize : 0
|
|
138
257
|
end
|
|
139
258
|
|
|
140
|
-
def calculate_local_header_size
|
|
259
|
+
def calculate_local_header_size # :nodoc:
|
|
141
260
|
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
|
|
142
261
|
end
|
|
143
262
|
|
|
144
263
|
# check before rewriting an entry (after file sizes are known)
|
|
145
264
|
# that we didn't change the header size (and thus clobber file data or something)
|
|
146
|
-
def verify_local_header_size!
|
|
265
|
+
def verify_local_header_size! # :nodoc:
|
|
147
266
|
return if @local_header_size.nil?
|
|
267
|
+
|
|
148
268
|
new_size = calculate_local_header_size
|
|
149
|
-
|
|
269
|
+
return unless @local_header_size != new_size
|
|
270
|
+
|
|
271
|
+
raise Error,
|
|
272
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
|
150
273
|
end
|
|
151
274
|
|
|
152
|
-
def cdir_header_size
|
|
275
|
+
def cdir_header_size # :nodoc:
|
|
153
276
|
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
|
154
277
|
(@extra ? @extra.c_dir_size : 0) + comment_size
|
|
155
278
|
end
|
|
156
279
|
|
|
157
|
-
def next_header_offset
|
|
158
|
-
local_entry_offset + compressed_size
|
|
280
|
+
def next_header_offset # :nodoc:
|
|
281
|
+
local_entry_offset + compressed_size
|
|
159
282
|
end
|
|
160
283
|
|
|
161
|
-
# Extracts entry to file
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 + ::File::SEPARATOR) || dest_dir == ::File::SEPARATOR
|
|
294
|
+
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
|
167
295
|
return self
|
|
168
296
|
end
|
|
169
297
|
|
|
170
|
-
dest_path ||= @name
|
|
171
298
|
block ||= proc { ::Zip.on_exists_proc }
|
|
172
299
|
|
|
173
|
-
|
|
174
|
-
__send__("create_#{@ftype}", dest_path, &block)
|
|
175
|
-
else
|
|
176
|
-
raise "unknown file type #{inspect}"
|
|
177
|
-
end
|
|
300
|
+
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
|
178
301
|
|
|
302
|
+
__send__(:"create_#{ftype}", extract_path, &block)
|
|
179
303
|
self
|
|
180
304
|
end
|
|
181
305
|
|
|
182
|
-
def to_s
|
|
306
|
+
def to_s # :nodoc:
|
|
183
307
|
@name
|
|
184
308
|
end
|
|
185
309
|
|
|
186
310
|
class << self
|
|
187
|
-
def
|
|
188
|
-
io.read(2).unpack('v')[0]
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def read_zip_long(io) # :nodoc:
|
|
192
|
-
io.read(4).unpack('V')[0]
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def read_zip_64_long(io) # :nodoc:
|
|
196
|
-
io.read(8).unpack('Q<')[0]
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def read_c_dir_entry(io) #:nodoc:all
|
|
311
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
200
312
|
path = if io.respond_to?(:path)
|
|
201
313
|
io.path
|
|
202
314
|
else
|
|
@@ -209,18 +321,18 @@ module Zip
|
|
|
209
321
|
nil
|
|
210
322
|
end
|
|
211
323
|
|
|
212
|
-
def read_local_entry(io)
|
|
324
|
+
def read_local_entry(io) # :nodoc:
|
|
213
325
|
entry = new(io)
|
|
214
326
|
entry.read_local_entry(io)
|
|
215
327
|
entry
|
|
328
|
+
rescue SplitArchiveError
|
|
329
|
+
raise
|
|
216
330
|
rescue Error
|
|
217
331
|
nil
|
|
218
332
|
end
|
|
219
333
|
end
|
|
220
334
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def unpack_local_entry(buf)
|
|
335
|
+
def unpack_local_entry(buf) # :nodoc:
|
|
224
336
|
@header_signature,
|
|
225
337
|
@version,
|
|
226
338
|
@fstype,
|
|
@@ -235,61 +347,76 @@ module Zip
|
|
|
235
347
|
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
|
236
348
|
end
|
|
237
349
|
|
|
238
|
-
def read_local_entry(io)
|
|
239
|
-
@
|
|
350
|
+
def read_local_entry(io) # :nodoc:
|
|
351
|
+
@dirty = false # No changes at this point.
|
|
352
|
+
current_offset = io.tell
|
|
240
353
|
|
|
241
|
-
|
|
354
|
+
read_local_header_fields(io)
|
|
242
355
|
|
|
243
|
-
|
|
244
|
-
raise
|
|
245
|
-
end
|
|
356
|
+
if @header_signature == SPLIT_FILE_SIGNATURE
|
|
357
|
+
raise SplitArchiveError if current_offset.zero?
|
|
246
358
|
|
|
247
|
-
|
|
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
|
|
248
364
|
|
|
249
|
-
unless @header_signature ==
|
|
250
|
-
raise
|
|
365
|
+
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
|
366
|
+
raise Error, "Zip local header magic not found at location '#{current_offset}'"
|
|
251
367
|
end
|
|
368
|
+
|
|
369
|
+
@local_header_offset = current_offset
|
|
370
|
+
|
|
252
371
|
set_time(@last_mod_date, @last_mod_time)
|
|
253
372
|
|
|
254
373
|
@name = io.read(@name_length)
|
|
255
|
-
extra = io.read(@extra_length)
|
|
256
|
-
|
|
257
|
-
@name.tr!('\\', '/')
|
|
258
374
|
if ::Zip.force_entry_names_encoding
|
|
259
375
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
260
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
|
|
261
382
|
|
|
383
|
+
extra = io.read(@extra_length)
|
|
262
384
|
if extra && extra.bytesize != @extra_length
|
|
263
385
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
|
264
|
-
else
|
|
265
|
-
if @extra.is_a?(::Zip::ExtraField)
|
|
266
|
-
@extra.merge(extra) if extra
|
|
267
|
-
else
|
|
268
|
-
@extra = ::Zip::ExtraField.new(extra)
|
|
269
|
-
end
|
|
270
386
|
end
|
|
387
|
+
|
|
388
|
+
read_extra_field(extra, local: true)
|
|
271
389
|
parse_zip64_extra(true)
|
|
390
|
+
parse_aes_extra
|
|
272
391
|
@local_header_size = calculate_local_header_size
|
|
273
392
|
end
|
|
274
393
|
|
|
275
|
-
def pack_local_entry
|
|
276
|
-
zip64 = @extra[
|
|
394
|
+
def pack_local_entry # :nodoc:
|
|
395
|
+
zip64 = @extra[:zip64]
|
|
277
396
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
|
278
397
|
@version_needed_to_extract, # version needed to extract
|
|
279
398
|
@gp_flags, # @gp_flags
|
|
280
|
-
|
|
399
|
+
compression_method,
|
|
281
400
|
@time.to_binary_dos_time, # @last_mod_time
|
|
282
401
|
@time.to_binary_dos_date, # @last_mod_date
|
|
283
402
|
@crc,
|
|
284
403
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
285
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
404
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
286
405
|
name_size,
|
|
287
406
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
|
288
407
|
end
|
|
289
408
|
|
|
290
|
-
def write_local_entry(io, rewrite
|
|
291
|
-
|
|
292
|
-
|
|
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
|
+
|
|
293
420
|
@local_header_offset = io.tell
|
|
294
421
|
|
|
295
422
|
io << pack_local_entry
|
|
@@ -299,7 +426,7 @@ module Zip
|
|
|
299
426
|
@local_header_size = io.tell - @local_header_offset
|
|
300
427
|
end
|
|
301
428
|
|
|
302
|
-
def unpack_c_dir_entry(buf)
|
|
429
|
+
def unpack_c_dir_entry(buf) # :nodoc:
|
|
303
430
|
@header_signature,
|
|
304
431
|
@version, # version of encoding software
|
|
305
432
|
@fstype, # filesystem type
|
|
@@ -317,13 +444,10 @@ module Zip
|
|
|
317
444
|
_, # diskNumberStart
|
|
318
445
|
@internal_file_attributes,
|
|
319
446
|
@external_file_attributes,
|
|
320
|
-
@local_header_offset
|
|
321
|
-
@name,
|
|
322
|
-
@extra,
|
|
323
|
-
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
447
|
+
@local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
324
448
|
end
|
|
325
449
|
|
|
326
|
-
def set_ftype_from_c_dir_entry
|
|
450
|
+
def set_ftype_from_c_dir_entry # :nodoc:
|
|
327
451
|
@ftype = case @fstype
|
|
328
452
|
when ::Zip::FSTYPE_UNIX
|
|
329
453
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
|
@@ -335,8 +459,9 @@ module Zip
|
|
|
335
459
|
when ::Zip::FILE_TYPE_SYMLINK
|
|
336
460
|
:symlink
|
|
337
461
|
else
|
|
338
|
-
#
|
|
339
|
-
# 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.
|
|
340
465
|
if name_is_directory?
|
|
341
466
|
:directory
|
|
342
467
|
else
|
|
@@ -352,44 +477,52 @@ module Zip
|
|
|
352
477
|
end
|
|
353
478
|
end
|
|
354
479
|
|
|
355
|
-
def check_c_dir_entry_static_header_length(buf)
|
|
356
|
-
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
|
|
482
|
+
|
|
357
483
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
|
358
484
|
end
|
|
359
485
|
|
|
360
|
-
def check_c_dir_entry_signature
|
|
361
|
-
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
|
|
488
|
+
|
|
362
489
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
|
363
490
|
end
|
|
364
491
|
|
|
365
|
-
def check_c_dir_entry_comment_size
|
|
492
|
+
def check_c_dir_entry_comment_size # :nodoc:
|
|
366
493
|
return if @comment && @comment.bytesize == @comment_length
|
|
494
|
+
|
|
367
495
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
|
368
496
|
end
|
|
369
497
|
|
|
370
|
-
def
|
|
371
|
-
if @extra.
|
|
372
|
-
@extra.merge(
|
|
498
|
+
def read_extra_field(buf, local: false) # :nodoc:
|
|
499
|
+
if @extra.kind_of?(::Zip::ExtraField)
|
|
500
|
+
@extra.merge(buf, local: local) if buf
|
|
373
501
|
else
|
|
374
|
-
@extra = ::Zip::ExtraField.new(
|
|
502
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
|
375
503
|
end
|
|
376
504
|
end
|
|
377
505
|
|
|
378
|
-
def read_c_dir_entry(io)
|
|
506
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
507
|
+
@dirty = false # No changes at this point.
|
|
379
508
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
380
509
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
381
510
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
382
511
|
check_c_dir_entry_signature
|
|
383
512
|
set_time(@last_mod_date, @last_mod_time)
|
|
513
|
+
|
|
384
514
|
@name = io.read(@name_length)
|
|
385
515
|
if ::Zip.force_entry_names_encoding
|
|
386
516
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
387
517
|
end
|
|
388
|
-
|
|
518
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
519
|
+
|
|
520
|
+
read_extra_field(io.read(@extra_length))
|
|
389
521
|
@comment = io.read(@comment_length)
|
|
390
522
|
check_c_dir_entry_comment_size
|
|
391
523
|
set_ftype_from_c_dir_entry
|
|
392
524
|
parse_zip64_extra(false)
|
|
525
|
+
parse_aes_extra
|
|
393
526
|
end
|
|
394
527
|
|
|
395
528
|
def file_stat(path) # :nodoc:
|
|
@@ -401,26 +534,27 @@ module Zip
|
|
|
401
534
|
end
|
|
402
535
|
|
|
403
536
|
def get_extra_attributes_from_path(path) # :nodoc:
|
|
404
|
-
|
|
405
|
-
|
|
537
|
+
stat = file_stat(path)
|
|
538
|
+
@time = DOSTime.from_time(stat.mtime)
|
|
539
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
|
540
|
+
|
|
406
541
|
@unix_uid = stat.uid
|
|
407
542
|
@unix_gid = stat.gid
|
|
408
543
|
@unix_perms = stat.mode & 0o7777
|
|
409
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
|
410
544
|
end
|
|
411
545
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
unix_perms_mask = 0o7777
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
::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
|
|
423
556
|
end
|
|
557
|
+
# rubocop:enable Style/GuardClause
|
|
424
558
|
|
|
425
559
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
|
426
560
|
return unless file? || directory?
|
|
@@ -429,40 +563,43 @@ module Zip
|
|
|
429
563
|
when ::Zip::FSTYPE_UNIX
|
|
430
564
|
set_unix_attributes_on_path(dest_path)
|
|
431
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
|
|
432
571
|
end
|
|
433
572
|
|
|
434
|
-
def pack_c_dir_entry
|
|
435
|
-
zip64 = @extra[
|
|
573
|
+
def pack_c_dir_entry # :nodoc:
|
|
574
|
+
zip64 = @extra[:zip64]
|
|
436
575
|
[
|
|
437
576
|
@header_signature,
|
|
438
577
|
@version, # version of encoding software
|
|
439
578
|
@fstype, # filesystem type
|
|
440
579
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
441
580
|
@gp_flags, # @gp_flags
|
|
442
|
-
|
|
581
|
+
compression_method,
|
|
443
582
|
@time.to_binary_dos_time, # @last_mod_time
|
|
444
583
|
@time.to_binary_dos_date, # @last_mod_date
|
|
445
584
|
@crc,
|
|
446
585
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
447
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
586
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
448
587
|
name_size,
|
|
449
588
|
@extra ? @extra.c_dir_size : 0,
|
|
450
589
|
comment_size,
|
|
451
590
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
|
452
591
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
453
592
|
@external_file_attributes, # native filesystem attributes
|
|
454
|
-
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
455
|
-
@name,
|
|
456
|
-
@extra,
|
|
457
|
-
@comment
|
|
593
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
458
594
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
459
595
|
end
|
|
460
596
|
|
|
461
|
-
def write_c_dir_entry(io)
|
|
462
|
-
|
|
597
|
+
def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
|
|
598
|
+
prep_cdir_zip64_extra
|
|
599
|
+
|
|
463
600
|
case @fstype
|
|
464
601
|
when ::Zip::FSTYPE_UNIX
|
|
465
|
-
ft = case
|
|
602
|
+
ft = case ftype
|
|
466
603
|
when :file
|
|
467
604
|
@unix_perms ||= 0o644
|
|
468
605
|
::Zip::FILE_TYPE_FILE
|
|
@@ -475,10 +612,11 @@ module Zip
|
|
|
475
612
|
end
|
|
476
613
|
|
|
477
614
|
unless ft.nil?
|
|
478
|
-
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
|
615
|
+
@external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16
|
|
479
616
|
end
|
|
480
617
|
end
|
|
481
618
|
|
|
619
|
+
@extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
|
|
482
620
|
io << pack_c_dir_entry
|
|
483
621
|
|
|
484
622
|
io << @name
|
|
@@ -486,42 +624,43 @@ module Zip
|
|
|
486
624
|
io << @comment
|
|
487
625
|
end
|
|
488
626
|
|
|
489
|
-
def ==(other)
|
|
627
|
+
def ==(other) # :nodoc:
|
|
490
628
|
return false unless other.class == self.class
|
|
629
|
+
|
|
491
630
|
# Compares contents of local entry and exposed fields
|
|
492
|
-
|
|
631
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
|
493
632
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
|
494
633
|
end
|
|
495
|
-
keys_equal && time.dos_equals(other.time)
|
|
496
634
|
end
|
|
497
635
|
|
|
498
|
-
def <=>(other)
|
|
636
|
+
def <=>(other) # :nodoc:
|
|
499
637
|
to_s <=> other.to_s
|
|
500
638
|
end
|
|
501
639
|
|
|
502
640
|
# Returns an IO like object for the given ZipEntry.
|
|
641
|
+
# Optional decrypter can be provided for an encrypted entry
|
|
503
642
|
# Warning: may behave weird with symlinks.
|
|
504
|
-
def get_input_stream(&block)
|
|
505
|
-
if
|
|
506
|
-
yield ::Zip::NullInputStream if
|
|
643
|
+
def get_input_stream(decrypter: nil, &block)
|
|
644
|
+
if ftype == :directory
|
|
645
|
+
yield ::Zip::NullInputStream if block
|
|
507
646
|
::Zip::NullInputStream
|
|
508
647
|
elsif @filepath
|
|
509
|
-
case
|
|
648
|
+
case ftype
|
|
510
649
|
when :file
|
|
511
650
|
::File.open(@filepath, 'rb', &block)
|
|
512
651
|
when :symlink
|
|
513
652
|
linkpath = ::File.readlink(@filepath)
|
|
514
653
|
stringio = ::StringIO.new(linkpath)
|
|
515
|
-
yield(stringio) if
|
|
654
|
+
yield(stringio) if block
|
|
516
655
|
stringio
|
|
517
656
|
else
|
|
518
|
-
raise "unknown @file_type #{
|
|
657
|
+
raise "unknown @file_type #{ftype}"
|
|
519
658
|
end
|
|
520
659
|
else
|
|
521
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
|
660
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset, decrypter: decrypter)
|
|
522
661
|
zis.instance_variable_set(:@complete_entry, self)
|
|
523
662
|
zis.get_next_entry
|
|
524
|
-
if
|
|
663
|
+
if block
|
|
525
664
|
begin
|
|
526
665
|
yield(zis)
|
|
527
666
|
ensure
|
|
@@ -539,8 +678,8 @@ module Zip
|
|
|
539
678
|
when 'file'
|
|
540
679
|
if name_is_directory?
|
|
541
680
|
raise ArgumentError,
|
|
542
|
-
"entry name '#{
|
|
543
|
-
|
|
681
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
682
|
+
"'#{src_path}' is not a directory"
|
|
544
683
|
end
|
|
545
684
|
:file
|
|
546
685
|
when 'directory'
|
|
@@ -549,8 +688,8 @@ module Zip
|
|
|
549
688
|
when 'link'
|
|
550
689
|
if name_is_directory?
|
|
551
690
|
raise ArgumentError,
|
|
552
|
-
"entry name '#{
|
|
553
|
-
|
|
691
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
692
|
+
"'#{src_path}' is not a directory"
|
|
554
693
|
end
|
|
555
694
|
:symlink
|
|
556
695
|
else
|
|
@@ -558,27 +697,30 @@ module Zip
|
|
|
558
697
|
end
|
|
559
698
|
|
|
560
699
|
@filepath = src_path
|
|
700
|
+
@size = stat.size
|
|
561
701
|
get_extra_attributes_from_path(@filepath)
|
|
562
702
|
end
|
|
563
703
|
|
|
564
|
-
def write_to_zip_output_stream(zip_output_stream)
|
|
565
|
-
if
|
|
566
|
-
zip_output_stream.put_next_entry(self
|
|
704
|
+
def write_to_zip_output_stream(zip_output_stream) # :nodoc:
|
|
705
|
+
if ftype == :directory
|
|
706
|
+
zip_output_stream.put_next_entry(self)
|
|
567
707
|
elsif @filepath
|
|
568
|
-
zip_output_stream.put_next_entry(self
|
|
569
|
-
get_input_stream
|
|
708
|
+
zip_output_stream.put_next_entry(self)
|
|
709
|
+
get_input_stream do |is|
|
|
710
|
+
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
|
711
|
+
end
|
|
570
712
|
else
|
|
571
713
|
zip_output_stream.copy_raw_entry(self)
|
|
572
714
|
end
|
|
573
715
|
end
|
|
574
716
|
|
|
575
|
-
def parent_as_string
|
|
717
|
+
def parent_as_string # :nodoc:
|
|
576
718
|
entry_name = name.chomp('/')
|
|
577
719
|
slash_index = entry_name.rindex('/')
|
|
578
720
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
|
579
721
|
end
|
|
580
722
|
|
|
581
|
-
def get_raw_input_stream(&block)
|
|
723
|
+
def get_raw_input_stream(&block) # :nodoc:
|
|
582
724
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
|
583
725
|
yield @zipfile
|
|
584
726
|
else
|
|
@@ -586,12 +728,22 @@ module Zip
|
|
|
586
728
|
end
|
|
587
729
|
end
|
|
588
730
|
|
|
589
|
-
def clean_up
|
|
590
|
-
#
|
|
731
|
+
def clean_up # :nodoc:
|
|
732
|
+
@dirty = false # Any changes are written at this point.
|
|
591
733
|
end
|
|
592
734
|
|
|
593
735
|
private
|
|
594
736
|
|
|
737
|
+
def read_local_header_fields(io) # :nodoc:
|
|
738
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
|
739
|
+
|
|
740
|
+
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
|
741
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
unpack_local_entry(static_sized_fields_buf)
|
|
745
|
+
end
|
|
746
|
+
|
|
595
747
|
def set_time(binary_dos_date, binary_dos_time)
|
|
596
748
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
|
597
749
|
rescue ArgumentError
|
|
@@ -600,26 +752,23 @@ module Zip
|
|
|
600
752
|
|
|
601
753
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
602
754
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
603
|
-
raise
|
|
604
|
-
"Destination '#{dest_path}' already exists"
|
|
755
|
+
raise DestinationExistsError, dest_path
|
|
605
756
|
end
|
|
757
|
+
|
|
606
758
|
::File.open(dest_path, 'wb') do |os|
|
|
607
759
|
get_input_stream do |is|
|
|
608
760
|
bytes_written = 0
|
|
609
761
|
warned = false
|
|
610
|
-
buf =
|
|
611
|
-
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
|
762
|
+
while (buf = is.sysread(Decompressor::CHUNK_SIZE))
|
|
612
763
|
os << buf
|
|
613
764
|
bytes_written += buf.bytesize
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
end
|
|
622
|
-
end
|
|
765
|
+
next unless bytes_written > size && !warned
|
|
766
|
+
|
|
767
|
+
error = EntrySizeError.new(self)
|
|
768
|
+
raise error if Zip.validate_entry_sizes
|
|
769
|
+
|
|
770
|
+
warn "WARNING: #{error.message}"
|
|
771
|
+
warned = true
|
|
623
772
|
end
|
|
624
773
|
end
|
|
625
774
|
end
|
|
@@ -629,15 +778,13 @@ module Zip
|
|
|
629
778
|
|
|
630
779
|
def create_directory(dest_path)
|
|
631
780
|
return if ::File.directory?(dest_path)
|
|
781
|
+
|
|
632
782
|
if ::File.exist?(dest_path)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
raise ::Zip::DestinationFileExistsError,
|
|
637
|
-
"Cannot create directory '#{dest_path}'. " \
|
|
638
|
-
'A file already exists with that name'
|
|
639
|
-
end
|
|
783
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
|
784
|
+
|
|
785
|
+
::FileUtils.rm_f dest_path
|
|
640
786
|
end
|
|
787
|
+
|
|
641
788
|
::FileUtils.mkdir_p(dest_path)
|
|
642
789
|
set_extra_attributes_on_path(dest_path)
|
|
643
790
|
end
|
|
@@ -651,51 +798,85 @@ module Zip
|
|
|
651
798
|
|
|
652
799
|
# apply missing data from the zip64 extra information field, if present
|
|
653
800
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
654
|
-
def parse_zip64_extra(for_local_header)
|
|
655
|
-
return
|
|
801
|
+
def parse_zip64_extra(for_local_header) # :nodoc:
|
|
802
|
+
return unless zip64?
|
|
803
|
+
|
|
656
804
|
if for_local_header
|
|
657
|
-
@size, @compressed_size = @extra[
|
|
805
|
+
@size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
|
|
658
806
|
else
|
|
659
|
-
@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
|
+
)
|
|
660
810
|
end
|
|
661
811
|
end
|
|
662
812
|
|
|
663
|
-
def
|
|
664
|
-
|
|
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
|
|
665
846
|
end
|
|
666
847
|
|
|
667
|
-
#
|
|
668
|
-
def
|
|
848
|
+
# rubocop:disable Style/GuardClause
|
|
849
|
+
def prep_local_zip64_extra
|
|
669
850
|
return unless ::Zip.write_zip64_support
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
851
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
|
852
|
+
|
|
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
|
|
673
856
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
674
|
-
@extra.
|
|
675
|
-
zip64 = @extra.create('Zip64')
|
|
676
|
-
if for_local_header
|
|
677
|
-
# local header always includes size and compressed size
|
|
678
|
-
zip64.original_size = @size
|
|
679
|
-
zip64.compressed_size = @compressed_size
|
|
680
|
-
else
|
|
681
|
-
# central directory entry entries include whichever fields are necessary
|
|
682
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
|
683
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
684
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
685
|
-
end
|
|
686
|
-
else
|
|
687
|
-
@extra.delete('Zip64')
|
|
857
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
688
858
|
|
|
689
|
-
#
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
|
697
877
|
end
|
|
698
878
|
end
|
|
879
|
+
# rubocop:enable Style/GuardClause
|
|
699
880
|
end
|
|
700
881
|
end
|
|
701
882
|
|