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