rubyzip 2.4.1 → 3.2.1
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 +476 -0
- data/LICENSE.md +24 -0
- data/README.md +180 -40
- data/Rakefile +15 -13
- data/lib/zip/central_directory.rb +172 -124
- 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 +45 -5
- data/lib/zip/entry.rb +391 -264
- 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 +174 -267
- 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 +11 -8
- data/lib/zip/input_stream.rb +76 -57
- data/lib/zip/ioextras/abstract_input_stream.rb +19 -13
- 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 +60 -57
- 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 -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 +5 -3
- data/samples/qtzip.rb +7 -6
- data/samples/write_simple.rb +2 -1
- data/samples/zipfind.rb +1 -0
- metadata +86 -52
- 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
|
-
|
|
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
|
|
76
115
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@crc = args[3] || 0
|
|
81
|
-
@compression_method = args[4] || ::Zip::Entry::DEFLATED
|
|
82
|
-
@size = args[5] || 0
|
|
83
|
-
@time = args[6] || ::Zip::DOSTime.now
|
|
84
|
-
end
|
|
116
|
+
def incomplete? # :nodoc:
|
|
117
|
+
(gp_flags & 8 == 8) && (crc == 0 || size == 0 || compressed_size == 0)
|
|
118
|
+
end
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
# The uncompressed size of the entry.
|
|
121
|
+
def size
|
|
122
|
+
@size || 0
|
|
88
123
|
end
|
|
89
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
90
124
|
|
|
91
|
-
|
|
92
|
-
|
|
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)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
alias mtime time
|
|
142
|
+
|
|
143
|
+
# Get the last access time of this entry, if available.
|
|
144
|
+
def atime
|
|
145
|
+
time(component: :atime)
|
|
93
146
|
end
|
|
94
147
|
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
# Get the creation time of this entry, if available.
|
|
149
|
+
def ctime
|
|
150
|
+
time(component: :ctime)
|
|
97
151
|
end
|
|
98
152
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# under archive creator. Then, we can't get timezone.
|
|
107
|
-
@time
|
|
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)
|
|
108
160
|
end
|
|
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
|
|
109
166
|
end
|
|
110
167
|
|
|
111
|
-
alias mtime time
|
|
168
|
+
alias mtime= time=
|
|
112
169
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
end
|
|
117
|
-
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
|
118
|
-
@time = value
|
|
170
|
+
# Set the last access time of this entry.
|
|
171
|
+
def atime=(value)
|
|
172
|
+
send(:time=, value, component: :atime)
|
|
119
173
|
end
|
|
120
174
|
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
# Set the creation time of this entry.
|
|
176
|
+
def ctime=(value)
|
|
177
|
+
send(:time=, value, component: :ctime)
|
|
178
|
+
end
|
|
123
179
|
|
|
124
|
-
|
|
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)
|
|
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
|
|
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,63 +347,76 @@ 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.
|
|
378
|
+
|
|
379
|
+
# We need to do this here because `initialize` has so many side-effects.
|
|
380
|
+
# :-(
|
|
381
|
+
@ftype = name_is_directory? ? :directory : :file
|
|
308
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
|
|
324
|
-
zip64 = @extra[
|
|
394
|
+
def pack_local_entry # :nodoc:
|
|
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
|
-
|
|
340
|
-
|
|
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
|
+
|
|
341
420
|
@local_header_offset = io.tell
|
|
342
421
|
|
|
343
422
|
io << pack_local_entry
|
|
@@ -347,7 +426,7 @@ module Zip
|
|
|
347
426
|
@local_header_size = io.tell - @local_header_offset
|
|
348
427
|
end
|
|
349
428
|
|
|
350
|
-
def unpack_c_dir_entry(buf)
|
|
429
|
+
def unpack_c_dir_entry(buf) # :nodoc:
|
|
351
430
|
@header_signature,
|
|
352
431
|
@version, # version of encoding software
|
|
353
432
|
@fstype, # filesystem type
|
|
@@ -365,13 +444,10 @@ module Zip
|
|
|
365
444
|
_, # diskNumberStart
|
|
366
445
|
@internal_file_attributes,
|
|
367
446
|
@external_file_attributes,
|
|
368
|
-
@local_header_offset
|
|
369
|
-
@name,
|
|
370
|
-
@extra,
|
|
371
|
-
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
447
|
+
@local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
372
448
|
end
|
|
373
449
|
|
|
374
|
-
def set_ftype_from_c_dir_entry
|
|
450
|
+
def set_ftype_from_c_dir_entry # :nodoc:
|
|
375
451
|
@ftype = case @fstype
|
|
376
452
|
when ::Zip::FSTYPE_UNIX
|
|
377
453
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
|
@@ -383,8 +459,9 @@ module Zip
|
|
|
383
459
|
when ::Zip::FILE_TYPE_SYMLINK
|
|
384
460
|
:symlink
|
|
385
461
|
else
|
|
386
|
-
#
|
|
387
|
-
# 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.
|
|
388
465
|
if name_is_directory?
|
|
389
466
|
:directory
|
|
390
467
|
else
|
|
@@ -400,47 +477,52 @@ module Zip
|
|
|
400
477
|
end
|
|
401
478
|
end
|
|
402
479
|
|
|
403
|
-
def check_c_dir_entry_static_header_length(buf)
|
|
404
|
-
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
|
|
405
482
|
|
|
406
483
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
|
407
484
|
end
|
|
408
485
|
|
|
409
|
-
def check_c_dir_entry_signature
|
|
410
|
-
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
|
|
411
488
|
|
|
412
489
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
|
413
490
|
end
|
|
414
491
|
|
|
415
|
-
def check_c_dir_entry_comment_size
|
|
492
|
+
def check_c_dir_entry_comment_size # :nodoc:
|
|
416
493
|
return if @comment && @comment.bytesize == @comment_length
|
|
417
494
|
|
|
418
495
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
|
419
496
|
end
|
|
420
497
|
|
|
421
|
-
def
|
|
498
|
+
def read_extra_field(buf, local: false) # :nodoc:
|
|
422
499
|
if @extra.kind_of?(::Zip::ExtraField)
|
|
423
|
-
@extra.merge(
|
|
500
|
+
@extra.merge(buf, local: local) if buf
|
|
424
501
|
else
|
|
425
|
-
@extra = ::Zip::ExtraField.new(
|
|
502
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
|
426
503
|
end
|
|
427
504
|
end
|
|
428
505
|
|
|
429
|
-
def read_c_dir_entry(io)
|
|
506
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
507
|
+
@dirty = false # No changes at this point.
|
|
430
508
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
431
509
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
432
510
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
433
511
|
check_c_dir_entry_signature
|
|
434
512
|
set_time(@last_mod_date, @last_mod_time)
|
|
513
|
+
|
|
435
514
|
@name = io.read(@name_length)
|
|
436
515
|
if ::Zip.force_entry_names_encoding
|
|
437
516
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
438
517
|
end
|
|
439
|
-
|
|
518
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
519
|
+
|
|
520
|
+
read_extra_field(io.read(@extra_length))
|
|
440
521
|
@comment = io.read(@comment_length)
|
|
441
522
|
check_c_dir_entry_comment_size
|
|
442
523
|
set_ftype_from_c_dir_entry
|
|
443
524
|
parse_zip64_extra(false)
|
|
525
|
+
parse_aes_extra
|
|
444
526
|
end
|
|
445
527
|
|
|
446
528
|
def file_stat(path) # :nodoc:
|
|
@@ -452,27 +534,27 @@ module Zip
|
|
|
452
534
|
end
|
|
453
535
|
|
|
454
536
|
def get_extra_attributes_from_path(path) # :nodoc:
|
|
455
|
-
|
|
537
|
+
stat = file_stat(path)
|
|
538
|
+
@time = DOSTime.from_time(stat.mtime)
|
|
539
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
|
456
540
|
|
|
457
|
-
stat = file_stat(path)
|
|
458
541
|
@unix_uid = stat.uid
|
|
459
542
|
@unix_gid = stat.gid
|
|
460
543
|
@unix_perms = stat.mode & 0o7777
|
|
461
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
|
462
544
|
end
|
|
463
545
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
unix_perms_mask = 0o7777
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
::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
|
|
475
556
|
end
|
|
557
|
+
# rubocop:enable Style/GuardClause
|
|
476
558
|
|
|
477
559
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
|
478
560
|
return unless file? || directory?
|
|
@@ -481,40 +563,43 @@ module Zip
|
|
|
481
563
|
when ::Zip::FSTYPE_UNIX
|
|
482
564
|
set_unix_attributes_on_path(dest_path)
|
|
483
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
|
|
484
571
|
end
|
|
485
572
|
|
|
486
|
-
def pack_c_dir_entry
|
|
487
|
-
zip64 = @extra[
|
|
573
|
+
def pack_c_dir_entry # :nodoc:
|
|
574
|
+
zip64 = @extra[:zip64]
|
|
488
575
|
[
|
|
489
576
|
@header_signature,
|
|
490
577
|
@version, # version of encoding software
|
|
491
578
|
@fstype, # filesystem type
|
|
492
579
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
493
580
|
@gp_flags, # @gp_flags
|
|
494
|
-
|
|
581
|
+
compression_method,
|
|
495
582
|
@time.to_binary_dos_time, # @last_mod_time
|
|
496
583
|
@time.to_binary_dos_date, # @last_mod_date
|
|
497
584
|
@crc,
|
|
498
585
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
499
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
586
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
500
587
|
name_size,
|
|
501
588
|
@extra ? @extra.c_dir_size : 0,
|
|
502
589
|
comment_size,
|
|
503
590
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
|
504
591
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
505
592
|
@external_file_attributes, # native filesystem attributes
|
|
506
|
-
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
507
|
-
@name,
|
|
508
|
-
@extra,
|
|
509
|
-
@comment
|
|
593
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
510
594
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
511
595
|
end
|
|
512
596
|
|
|
513
|
-
def write_c_dir_entry(io)
|
|
514
|
-
|
|
597
|
+
def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
|
|
598
|
+
prep_cdir_zip64_extra
|
|
599
|
+
|
|
515
600
|
case @fstype
|
|
516
601
|
when ::Zip::FSTYPE_UNIX
|
|
517
|
-
ft = case
|
|
602
|
+
ft = case ftype
|
|
518
603
|
when :file
|
|
519
604
|
@unix_perms ||= 0o644
|
|
520
605
|
::Zip::FILE_TYPE_FILE
|
|
@@ -527,10 +612,11 @@ module Zip
|
|
|
527
612
|
end
|
|
528
613
|
|
|
529
614
|
unless ft.nil?
|
|
530
|
-
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
|
615
|
+
@external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16
|
|
531
616
|
end
|
|
532
617
|
end
|
|
533
618
|
|
|
619
|
+
@extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
|
|
534
620
|
io << pack_c_dir_entry
|
|
535
621
|
|
|
536
622
|
io << @name
|
|
@@ -538,43 +624,42 @@ module Zip
|
|
|
538
624
|
io << @comment
|
|
539
625
|
end
|
|
540
626
|
|
|
541
|
-
def ==(other)
|
|
627
|
+
def ==(other) # :nodoc:
|
|
542
628
|
return false unless other.class == self.class
|
|
543
629
|
|
|
544
630
|
# Compares contents of local entry and exposed fields
|
|
545
|
-
|
|
631
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
|
546
632
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
|
547
633
|
end
|
|
548
|
-
keys_equal && time == other.time
|
|
549
634
|
end
|
|
550
635
|
|
|
551
|
-
def <=>(other)
|
|
636
|
+
def <=>(other) # :nodoc:
|
|
552
637
|
to_s <=> other.to_s
|
|
553
638
|
end
|
|
554
639
|
|
|
555
640
|
# Returns an IO like object for the given ZipEntry.
|
|
556
641
|
# Warning: may behave weird with symlinks.
|
|
557
642
|
def get_input_stream(&block)
|
|
558
|
-
if
|
|
559
|
-
yield ::Zip::NullInputStream if
|
|
643
|
+
if ftype == :directory
|
|
644
|
+
yield ::Zip::NullInputStream if block
|
|
560
645
|
::Zip::NullInputStream
|
|
561
646
|
elsif @filepath
|
|
562
|
-
case
|
|
647
|
+
case ftype
|
|
563
648
|
when :file
|
|
564
649
|
::File.open(@filepath, 'rb', &block)
|
|
565
650
|
when :symlink
|
|
566
651
|
linkpath = ::File.readlink(@filepath)
|
|
567
652
|
stringio = ::StringIO.new(linkpath)
|
|
568
|
-
yield(stringio) if
|
|
653
|
+
yield(stringio) if block
|
|
569
654
|
stringio
|
|
570
655
|
else
|
|
571
|
-
raise "unknown @file_type #{
|
|
656
|
+
raise "unknown @file_type #{ftype}"
|
|
572
657
|
end
|
|
573
658
|
else
|
|
574
659
|
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
|
575
660
|
zis.instance_variable_set(:@complete_entry, self)
|
|
576
661
|
zis.get_next_entry
|
|
577
|
-
if
|
|
662
|
+
if block
|
|
578
663
|
begin
|
|
579
664
|
yield(zis)
|
|
580
665
|
ensure
|
|
@@ -592,8 +677,8 @@ module Zip
|
|
|
592
677
|
when 'file'
|
|
593
678
|
if name_is_directory?
|
|
594
679
|
raise ArgumentError,
|
|
595
|
-
"entry name '#{
|
|
596
|
-
|
|
680
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
681
|
+
"'#{src_path}' is not a directory"
|
|
597
682
|
end
|
|
598
683
|
:file
|
|
599
684
|
when 'directory'
|
|
@@ -602,8 +687,8 @@ module Zip
|
|
|
602
687
|
when 'link'
|
|
603
688
|
if name_is_directory?
|
|
604
689
|
raise ArgumentError,
|
|
605
|
-
"entry name '#{
|
|
606
|
-
|
|
690
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
691
|
+
"'#{src_path}' is not a directory"
|
|
607
692
|
end
|
|
608
693
|
:symlink
|
|
609
694
|
else
|
|
@@ -611,27 +696,30 @@ module Zip
|
|
|
611
696
|
end
|
|
612
697
|
|
|
613
698
|
@filepath = src_path
|
|
699
|
+
@size = stat.size
|
|
614
700
|
get_extra_attributes_from_path(@filepath)
|
|
615
701
|
end
|
|
616
702
|
|
|
617
|
-
def write_to_zip_output_stream(zip_output_stream)
|
|
618
|
-
if
|
|
619
|
-
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)
|
|
620
706
|
elsif @filepath
|
|
621
|
-
zip_output_stream.put_next_entry(self
|
|
622
|
-
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
|
|
623
711
|
else
|
|
624
712
|
zip_output_stream.copy_raw_entry(self)
|
|
625
713
|
end
|
|
626
714
|
end
|
|
627
715
|
|
|
628
|
-
def parent_as_string
|
|
716
|
+
def parent_as_string # :nodoc:
|
|
629
717
|
entry_name = name.chomp('/')
|
|
630
718
|
slash_index = entry_name.rindex('/')
|
|
631
719
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
|
632
720
|
end
|
|
633
721
|
|
|
634
|
-
def get_raw_input_stream(&block)
|
|
722
|
+
def get_raw_input_stream(&block) # :nodoc:
|
|
635
723
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
|
636
724
|
yield @zipfile
|
|
637
725
|
else
|
|
@@ -639,12 +727,22 @@ module Zip
|
|
|
639
727
|
end
|
|
640
728
|
end
|
|
641
729
|
|
|
642
|
-
def clean_up
|
|
643
|
-
#
|
|
730
|
+
def clean_up # :nodoc:
|
|
731
|
+
@dirty = false # Any changes are written at this point.
|
|
644
732
|
end
|
|
645
733
|
|
|
646
734
|
private
|
|
647
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
|
+
|
|
648
746
|
def set_time(binary_dos_date, binary_dos_time)
|
|
649
747
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
|
650
748
|
rescue ArgumentError
|
|
@@ -653,9 +751,9 @@ module Zip
|
|
|
653
751
|
|
|
654
752
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
655
753
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
656
|
-
raise ::Zip::
|
|
657
|
-
"Destination '#{dest_path}' already exists"
|
|
754
|
+
raise ::Zip::DestinationExistsError, dest_path
|
|
658
755
|
end
|
|
756
|
+
|
|
659
757
|
::File.open(dest_path, 'wb') do |os|
|
|
660
758
|
get_input_stream do |is|
|
|
661
759
|
bytes_written = 0
|
|
@@ -666,10 +764,10 @@ module Zip
|
|
|
666
764
|
bytes_written += buf.bytesize
|
|
667
765
|
next unless bytes_written > size && !warned
|
|
668
766
|
|
|
669
|
-
|
|
670
|
-
raise
|
|
767
|
+
error = ::Zip::EntrySizeError.new(self)
|
|
768
|
+
raise error if ::Zip.validate_entry_sizes
|
|
671
769
|
|
|
672
|
-
warn "WARNING: #{message}"
|
|
770
|
+
warn "WARNING: #{error.message}"
|
|
673
771
|
warned = true
|
|
674
772
|
end
|
|
675
773
|
end
|
|
@@ -682,14 +780,11 @@ module Zip
|
|
|
682
780
|
return if ::File.directory?(dest_path)
|
|
683
781
|
|
|
684
782
|
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
|
|
783
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
|
784
|
+
|
|
785
|
+
::FileUtils.rm_f dest_path
|
|
692
786
|
end
|
|
787
|
+
|
|
693
788
|
::FileUtils.mkdir_p(dest_path)
|
|
694
789
|
set_extra_attributes_on_path(dest_path)
|
|
695
790
|
end
|
|
@@ -703,53 +798,85 @@ module Zip
|
|
|
703
798
|
|
|
704
799
|
# apply missing data from the zip64 extra information field, if present
|
|
705
800
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
706
|
-
def parse_zip64_extra(for_local_header)
|
|
707
|
-
return
|
|
801
|
+
def parse_zip64_extra(for_local_header) # :nodoc:
|
|
802
|
+
return unless zip64?
|
|
708
803
|
|
|
709
804
|
if for_local_header
|
|
710
|
-
@size, @compressed_size = @extra[
|
|
805
|
+
@size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
|
|
711
806
|
else
|
|
712
|
-
@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
|
+
)
|
|
713
810
|
end
|
|
714
811
|
end
|
|
715
812
|
|
|
716
|
-
def
|
|
717
|
-
|
|
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
|
|
718
846
|
end
|
|
719
847
|
|
|
720
|
-
#
|
|
721
|
-
def
|
|
848
|
+
# rubocop:disable Style/GuardClause
|
|
849
|
+
def prep_local_zip64_extra
|
|
722
850
|
return unless ::Zip.write_zip64_support
|
|
851
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
|
723
852
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
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
|
|
727
856
|
@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')
|
|
857
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
742
858
|
|
|
743
|
-
#
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
|
751
877
|
end
|
|
752
878
|
end
|
|
879
|
+
# rubocop:enable Style/GuardClause
|
|
753
880
|
end
|
|
754
881
|
end
|
|
755
882
|
|