rubyzip 2.4.1 → 3.7.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 +549 -0
- data/LICENSE.md +24 -0
- data/README.md +250 -44
- data/Rakefile +15 -13
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +194 -125
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +30 -21
- data/lib/zip/crypto/aes_encryption.rb +213 -0
- data/lib/zip/crypto/decrypted_io.rb +27 -15
- data/lib/zip/crypto/encryption.rb +13 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +13 -7
- 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 +64 -6
- data/lib/zip/entry.rb +432 -270
- data/lib/zip/entry_set.rb +11 -9
- data/lib/zip/errors.rb +152 -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 +5 -3
- data/lib/zip/extra_field/universal_time.rb +3 -1
- data/lib/zip/extra_field/unix.rb +7 -5
- 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 +261 -262
- 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 +13 -10
- data/lib/zip/input_stream.rb +102 -61
- data/lib/zip/ioextras/abstract_input_stream.rb +156 -58
- data/lib/zip/ioextras/abstract_output_stream.rb +15 -3
- data/lib/zip/ioextras/fake_io.rb +39 -0
- data/lib/zip/ioextras.rb +12 -16
- 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 +76 -64
- data/lib/zip/pass_thru_compressor.rb +6 -3
- data/lib/zip/pass_thru_decompressor.rb +12 -9
- 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 +28 -23
- 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 +88 -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,242 @@ 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
|
+
# Called by `AESEncrypter#prepare_entry` before the local header is first
|
|
212
|
+
# written, so the extra field is in place before its size is measured.
|
|
213
|
+
# Directories have no content to encrypt, so they are left untouched.
|
|
214
|
+
def prep_aes_extra(vendor_version, strength) # :nodoc:
|
|
215
|
+
return if directory?
|
|
216
|
+
|
|
217
|
+
@version_needed_to_extract =
|
|
218
|
+
[@version_needed_to_extract, Zip::VERSION_NEEDED_TO_EXTRACT_AES].max
|
|
219
|
+
aes = @extra[:aes] || @extra.create(:aes)
|
|
220
|
+
aes.vendor_version = vendor_version
|
|
221
|
+
aes.vendor_id = 'AE'
|
|
222
|
+
aes.encryption_strength = strength
|
|
223
|
+
aes.compression_method = compression_method
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def file_type_is?(type) # :nodoc:
|
|
227
|
+
ftype == type
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def ftype # :nodoc:
|
|
231
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
|
125
232
|
end
|
|
126
233
|
|
|
127
234
|
# Dynamic checkers
|
|
128
235
|
%w[directory file symlink].each do |k|
|
|
129
|
-
define_method "#{k}?" do
|
|
236
|
+
define_method :"#{k}?" do
|
|
130
237
|
file_type_is?(k.to_sym)
|
|
131
238
|
end
|
|
132
239
|
end
|
|
133
240
|
|
|
134
|
-
def name_is_directory?
|
|
241
|
+
def name_is_directory? # :nodoc:
|
|
135
242
|
@name.end_with?('/')
|
|
136
243
|
end
|
|
137
244
|
|
|
138
245
|
# Is the name a relative path, free of `..` patterns that could lead to
|
|
139
246
|
# path traversal attacks? This does NOT handle symlinks; if the path
|
|
140
247
|
# contains symlinks, this check is NOT enough to guarantee safety.
|
|
141
|
-
def name_safe?
|
|
248
|
+
def name_safe? # :nodoc:
|
|
142
249
|
cleanpath = Pathname.new(@name).cleanpath
|
|
143
250
|
return false unless cleanpath.relative?
|
|
144
251
|
|
|
145
252
|
root = ::File::SEPARATOR
|
|
146
|
-
|
|
147
|
-
|
|
253
|
+
naive = Regexp.escape(::File.join(root, cleanpath.to_s))
|
|
254
|
+
# Allow for Windows drive mappings at the root.
|
|
255
|
+
::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i)
|
|
148
256
|
end
|
|
149
257
|
|
|
150
|
-
def local_entry_offset
|
|
258
|
+
def local_entry_offset # :nodoc:
|
|
151
259
|
local_header_offset + @local_header_size
|
|
152
260
|
end
|
|
153
261
|
|
|
154
|
-
def name_size
|
|
262
|
+
def name_size # :nodoc:
|
|
155
263
|
@name ? @name.bytesize : 0
|
|
156
264
|
end
|
|
157
265
|
|
|
158
|
-
def extra_size
|
|
266
|
+
def extra_size # :nodoc:
|
|
159
267
|
@extra ? @extra.local_size : 0
|
|
160
268
|
end
|
|
161
269
|
|
|
162
|
-
def comment_size
|
|
270
|
+
def comment_size # :nodoc:
|
|
163
271
|
@comment ? @comment.bytesize : 0
|
|
164
272
|
end
|
|
165
273
|
|
|
166
|
-
def calculate_local_header_size
|
|
274
|
+
def calculate_local_header_size # :nodoc:
|
|
167
275
|
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
|
|
168
276
|
end
|
|
169
277
|
|
|
170
278
|
# check before rewriting an entry (after file sizes are known)
|
|
171
279
|
# that we didn't change the header size (and thus clobber file data or something)
|
|
172
|
-
def verify_local_header_size!
|
|
280
|
+
def verify_local_header_size! # :nodoc:
|
|
173
281
|
return if @local_header_size.nil?
|
|
174
282
|
|
|
175
283
|
new_size = calculate_local_header_size
|
|
176
|
-
|
|
284
|
+
return unless @local_header_size != new_size
|
|
285
|
+
|
|
286
|
+
raise Error,
|
|
287
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
|
177
288
|
end
|
|
178
289
|
|
|
179
|
-
def cdir_header_size
|
|
290
|
+
def cdir_header_size # :nodoc:
|
|
180
291
|
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
|
181
292
|
(@extra ? @extra.c_dir_size : 0) + comment_size
|
|
182
293
|
end
|
|
183
294
|
|
|
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
|
|
295
|
+
def next_header_offset # :nodoc:
|
|
296
|
+
local_entry_offset + compressed_size
|
|
206
297
|
end
|
|
207
298
|
|
|
208
299
|
# Extracts this entry to a file at `entry_path`, with
|
|
@@ -210,11 +301,20 @@ module Zip
|
|
|
210
301
|
#
|
|
211
302
|
# NB: The caller is responsible for making sure `destination_directory` is
|
|
212
303
|
# safe, if it is passed.
|
|
213
|
-
|
|
304
|
+
#
|
|
305
|
+
# `create_parent_directories`, if true, creates any missing intermediate
|
|
306
|
+
# directories for a file entry before writing it. This defaults to
|
|
307
|
+
# false: a missing intermediate directory usually means an earlier
|
|
308
|
+
# entry (e.g. a symlink) was deliberately skipped as unsafe, and letting
|
|
309
|
+
# a later entry silently succeed by re-creating that path as a plain
|
|
310
|
+
# directory would mask that. Bulk operations such as `extract_all`,
|
|
311
|
+
# which have no such earlier-entry to skip past, opt into this.
|
|
312
|
+
def extract(entry_path = @name, destination_directory: '.',
|
|
313
|
+
create_parent_directories: false, &block)
|
|
214
314
|
dest_dir = ::File.absolute_path(destination_directory || '.')
|
|
215
315
|
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
|
216
316
|
|
|
217
|
-
unless extract_path.start_with?(dest_dir)
|
|
317
|
+
unless extract_path.start_with?(dest_dir + ::File::SEPARATOR) || dest_dir == ::File::SEPARATOR
|
|
218
318
|
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
|
219
319
|
return self
|
|
220
320
|
end
|
|
@@ -223,28 +323,18 @@ module Zip
|
|
|
223
323
|
|
|
224
324
|
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
|
225
325
|
|
|
326
|
+
::FileUtils.mkdir_p(::File.dirname(extract_path)) if create_parent_directories && file?
|
|
327
|
+
|
|
226
328
|
__send__(:"create_#{ftype}", extract_path, &block)
|
|
227
329
|
self
|
|
228
330
|
end
|
|
229
331
|
|
|
230
|
-
def to_s
|
|
332
|
+
def to_s # :nodoc:
|
|
231
333
|
@name
|
|
232
334
|
end
|
|
233
335
|
|
|
234
336
|
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
|
|
337
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
248
338
|
path = if io.respond_to?(:path)
|
|
249
339
|
io.path
|
|
250
340
|
else
|
|
@@ -257,16 +347,18 @@ module Zip
|
|
|
257
347
|
nil
|
|
258
348
|
end
|
|
259
349
|
|
|
260
|
-
def read_local_entry(io)
|
|
350
|
+
def read_local_entry(io) # :nodoc:
|
|
261
351
|
entry = new(io)
|
|
262
352
|
entry.read_local_entry(io)
|
|
263
353
|
entry
|
|
354
|
+
rescue SplitArchiveError
|
|
355
|
+
raise
|
|
264
356
|
rescue Error
|
|
265
357
|
nil
|
|
266
358
|
end
|
|
267
359
|
end
|
|
268
360
|
|
|
269
|
-
def unpack_local_entry(buf)
|
|
361
|
+
def unpack_local_entry(buf) # :nodoc:
|
|
270
362
|
@header_signature,
|
|
271
363
|
@version,
|
|
272
364
|
@fstype,
|
|
@@ -281,63 +373,76 @@ module Zip
|
|
|
281
373
|
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
|
282
374
|
end
|
|
283
375
|
|
|
284
|
-
def read_local_entry(io)
|
|
285
|
-
@
|
|
376
|
+
def read_local_entry(io) # :nodoc:
|
|
377
|
+
@dirty = false # No changes at this point.
|
|
378
|
+
current_offset = io.tell
|
|
286
379
|
|
|
287
|
-
|
|
380
|
+
read_local_header_fields(io)
|
|
288
381
|
|
|
289
|
-
|
|
290
|
-
raise
|
|
291
|
-
end
|
|
382
|
+
if @header_signature == SPLIT_FILE_SIGNATURE
|
|
383
|
+
raise SplitArchiveError if current_offset.zero?
|
|
292
384
|
|
|
293
|
-
|
|
385
|
+
# Rewind, skipping the data descriptor, then try to read the local header again.
|
|
386
|
+
current_offset += 16
|
|
387
|
+
io.seek(current_offset)
|
|
388
|
+
read_local_header_fields(io)
|
|
389
|
+
end
|
|
294
390
|
|
|
295
|
-
unless @header_signature ==
|
|
296
|
-
raise
|
|
391
|
+
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
|
392
|
+
raise Error, "Zip local header magic not found at location '#{current_offset}'"
|
|
297
393
|
end
|
|
298
394
|
|
|
395
|
+
@local_header_offset = current_offset
|
|
396
|
+
|
|
299
397
|
set_time(@last_mod_date, @last_mod_time)
|
|
300
398
|
|
|
301
399
|
@name = io.read(@name_length)
|
|
302
|
-
extra = io.read(@extra_length)
|
|
303
|
-
|
|
304
|
-
@name.tr!('\\', '/')
|
|
305
400
|
if ::Zip.force_entry_names_encoding
|
|
306
401
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
307
402
|
end
|
|
403
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
404
|
+
|
|
405
|
+
# We need to do this here because `initialize` has so many side-effects.
|
|
406
|
+
# :-(
|
|
407
|
+
@ftype = name_is_directory? ? :directory : :file
|
|
308
408
|
|
|
409
|
+
extra = io.read(@extra_length)
|
|
309
410
|
if extra && extra.bytesize != @extra_length
|
|
310
411
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
|
311
412
|
end
|
|
312
413
|
|
|
313
|
-
|
|
314
|
-
@extra.merge(extra) if extra
|
|
315
|
-
else
|
|
316
|
-
@extra = ::Zip::ExtraField.new(extra)
|
|
317
|
-
end
|
|
318
|
-
|
|
414
|
+
read_extra_field(extra, local: true)
|
|
319
415
|
parse_zip64_extra(true)
|
|
416
|
+
parse_aes_extra
|
|
320
417
|
@local_header_size = calculate_local_header_size
|
|
321
418
|
end
|
|
322
419
|
|
|
323
|
-
def pack_local_entry
|
|
324
|
-
zip64 = @extra[
|
|
420
|
+
def pack_local_entry # :nodoc:
|
|
421
|
+
zip64 = @extra[:zip64]
|
|
325
422
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
|
326
423
|
@version_needed_to_extract, # version needed to extract
|
|
327
424
|
@gp_flags, # @gp_flags
|
|
328
|
-
|
|
425
|
+
stored_compression_method,
|
|
329
426
|
@time.to_binary_dos_time, # @last_mod_time
|
|
330
427
|
@time.to_binary_dos_date, # @last_mod_date
|
|
331
428
|
@crc,
|
|
332
429
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
333
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
430
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
334
431
|
name_size,
|
|
335
432
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
|
336
433
|
end
|
|
337
434
|
|
|
338
|
-
def write_local_entry(io, rewrite
|
|
339
|
-
|
|
340
|
-
|
|
435
|
+
def write_local_entry(io, suppress_extra_fields: false, rewrite: false) # :nodoc:
|
|
436
|
+
prep_local_zip64_extra
|
|
437
|
+
|
|
438
|
+
# If we are rewriting the local header, then we verify that we haven't changed
|
|
439
|
+
# its size. At this point we have to keep extra fields if they are present.
|
|
440
|
+
if rewrite
|
|
441
|
+
verify_local_header_size!
|
|
442
|
+
elsif suppress_extra_fields
|
|
443
|
+
@extra.suppress_fields!(suppress_extra_fields)
|
|
444
|
+
end
|
|
445
|
+
|
|
341
446
|
@local_header_offset = io.tell
|
|
342
447
|
|
|
343
448
|
io << pack_local_entry
|
|
@@ -347,7 +452,7 @@ module Zip
|
|
|
347
452
|
@local_header_size = io.tell - @local_header_offset
|
|
348
453
|
end
|
|
349
454
|
|
|
350
|
-
def unpack_c_dir_entry(buf)
|
|
455
|
+
def unpack_c_dir_entry(buf) # :nodoc:
|
|
351
456
|
@header_signature,
|
|
352
457
|
@version, # version of encoding software
|
|
353
458
|
@fstype, # filesystem type
|
|
@@ -365,13 +470,10 @@ module Zip
|
|
|
365
470
|
_, # diskNumberStart
|
|
366
471
|
@internal_file_attributes,
|
|
367
472
|
@external_file_attributes,
|
|
368
|
-
@local_header_offset
|
|
369
|
-
@name,
|
|
370
|
-
@extra,
|
|
371
|
-
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
473
|
+
@local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
372
474
|
end
|
|
373
475
|
|
|
374
|
-
def set_ftype_from_c_dir_entry
|
|
476
|
+
def set_ftype_from_c_dir_entry # :nodoc:
|
|
375
477
|
@ftype = case @fstype
|
|
376
478
|
when ::Zip::FSTYPE_UNIX
|
|
377
479
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
|
@@ -383,8 +485,9 @@ module Zip
|
|
|
383
485
|
when ::Zip::FILE_TYPE_SYMLINK
|
|
384
486
|
:symlink
|
|
385
487
|
else
|
|
386
|
-
#
|
|
387
|
-
# Otherwise this would be set to unknown and that
|
|
488
|
+
# Best case guess for whether it is a file or not.
|
|
489
|
+
# Otherwise this would be set to unknown and that
|
|
490
|
+
# entry would never be able to be extracted.
|
|
388
491
|
if name_is_directory?
|
|
389
492
|
:directory
|
|
390
493
|
else
|
|
@@ -400,47 +503,52 @@ module Zip
|
|
|
400
503
|
end
|
|
401
504
|
end
|
|
402
505
|
|
|
403
|
-
def check_c_dir_entry_static_header_length(buf)
|
|
404
|
-
return
|
|
506
|
+
def check_c_dir_entry_static_header_length(buf) # :nodoc:
|
|
507
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
|
405
508
|
|
|
406
509
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
|
407
510
|
end
|
|
408
511
|
|
|
409
|
-
def check_c_dir_entry_signature
|
|
410
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
512
|
+
def check_c_dir_entry_signature # :nodoc:
|
|
513
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
411
514
|
|
|
412
515
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
|
413
516
|
end
|
|
414
517
|
|
|
415
|
-
def check_c_dir_entry_comment_size
|
|
518
|
+
def check_c_dir_entry_comment_size # :nodoc:
|
|
416
519
|
return if @comment && @comment.bytesize == @comment_length
|
|
417
520
|
|
|
418
521
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
|
419
522
|
end
|
|
420
523
|
|
|
421
|
-
def
|
|
524
|
+
def read_extra_field(buf, local: false) # :nodoc:
|
|
422
525
|
if @extra.kind_of?(::Zip::ExtraField)
|
|
423
|
-
@extra.merge(
|
|
526
|
+
@extra.merge(buf, local: local) if buf
|
|
424
527
|
else
|
|
425
|
-
@extra = ::Zip::ExtraField.new(
|
|
528
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
|
426
529
|
end
|
|
427
530
|
end
|
|
428
531
|
|
|
429
|
-
def read_c_dir_entry(io)
|
|
532
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
533
|
+
@dirty = false # No changes at this point.
|
|
430
534
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
431
535
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
432
536
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
433
537
|
check_c_dir_entry_signature
|
|
434
538
|
set_time(@last_mod_date, @last_mod_time)
|
|
539
|
+
|
|
435
540
|
@name = io.read(@name_length)
|
|
436
541
|
if ::Zip.force_entry_names_encoding
|
|
437
542
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
438
543
|
end
|
|
439
|
-
|
|
544
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
545
|
+
|
|
546
|
+
read_extra_field(io.read(@extra_length))
|
|
440
547
|
@comment = io.read(@comment_length)
|
|
441
548
|
check_c_dir_entry_comment_size
|
|
442
549
|
set_ftype_from_c_dir_entry
|
|
443
550
|
parse_zip64_extra(false)
|
|
551
|
+
parse_aes_extra
|
|
444
552
|
end
|
|
445
553
|
|
|
446
554
|
def file_stat(path) # :nodoc:
|
|
@@ -452,27 +560,27 @@ module Zip
|
|
|
452
560
|
end
|
|
453
561
|
|
|
454
562
|
def get_extra_attributes_from_path(path) # :nodoc:
|
|
455
|
-
|
|
563
|
+
stat = file_stat(path)
|
|
564
|
+
@time = DOSTime.from_time(stat.mtime)
|
|
565
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
|
456
566
|
|
|
457
|
-
stat = file_stat(path)
|
|
458
567
|
@unix_uid = stat.uid
|
|
459
568
|
@unix_gid = stat.gid
|
|
460
569
|
@unix_perms = stat.mode & 0o7777
|
|
461
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
|
462
570
|
end
|
|
463
571
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
unix_perms_mask = 0o7777
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
|
572
|
+
# rubocop:disable Style/GuardClause
|
|
573
|
+
def set_unix_attributes_on_path(dest_path) # :nodoc:
|
|
574
|
+
# Ignore setuid/setgid bits by default. Honour if @restore_ownership.
|
|
575
|
+
unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777)
|
|
576
|
+
if @restore_permissions && @unix_perms
|
|
577
|
+
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path)
|
|
578
|
+
end
|
|
579
|
+
if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
|
580
|
+
::FileUtils.chown(@unix_uid, @unix_gid, dest_path)
|
|
581
|
+
end
|
|
475
582
|
end
|
|
583
|
+
# rubocop:enable Style/GuardClause
|
|
476
584
|
|
|
477
585
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
|
478
586
|
return unless file? || directory?
|
|
@@ -481,40 +589,43 @@ module Zip
|
|
|
481
589
|
when ::Zip::FSTYPE_UNIX
|
|
482
590
|
set_unix_attributes_on_path(dest_path)
|
|
483
591
|
end
|
|
592
|
+
|
|
593
|
+
# Restore the timestamp on a file. This will either have come from the
|
|
594
|
+
# original source file that was copied into the archive, or from the
|
|
595
|
+
# creation date of the archive if there was no original source file.
|
|
596
|
+
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
|
484
597
|
end
|
|
485
598
|
|
|
486
|
-
def pack_c_dir_entry
|
|
487
|
-
zip64 = @extra[
|
|
599
|
+
def pack_c_dir_entry # :nodoc:
|
|
600
|
+
zip64 = @extra[:zip64]
|
|
488
601
|
[
|
|
489
602
|
@header_signature,
|
|
490
603
|
@version, # version of encoding software
|
|
491
604
|
@fstype, # filesystem type
|
|
492
605
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
493
606
|
@gp_flags, # @gp_flags
|
|
494
|
-
|
|
607
|
+
stored_compression_method,
|
|
495
608
|
@time.to_binary_dos_time, # @last_mod_time
|
|
496
609
|
@time.to_binary_dos_date, # @last_mod_date
|
|
497
610
|
@crc,
|
|
498
611
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
499
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
612
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
500
613
|
name_size,
|
|
501
614
|
@extra ? @extra.c_dir_size : 0,
|
|
502
615
|
comment_size,
|
|
503
616
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
|
504
617
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
505
618
|
@external_file_attributes, # native filesystem attributes
|
|
506
|
-
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
507
|
-
@name,
|
|
508
|
-
@extra,
|
|
509
|
-
@comment
|
|
619
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
510
620
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
511
621
|
end
|
|
512
622
|
|
|
513
|
-
def write_c_dir_entry(io)
|
|
514
|
-
|
|
623
|
+
def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
|
|
624
|
+
prep_cdir_zip64_extra
|
|
625
|
+
|
|
515
626
|
case @fstype
|
|
516
627
|
when ::Zip::FSTYPE_UNIX
|
|
517
|
-
ft = case
|
|
628
|
+
ft = case ftype
|
|
518
629
|
when :file
|
|
519
630
|
@unix_perms ||= 0o644
|
|
520
631
|
::Zip::FILE_TYPE_FILE
|
|
@@ -527,10 +638,11 @@ module Zip
|
|
|
527
638
|
end
|
|
528
639
|
|
|
529
640
|
unless ft.nil?
|
|
530
|
-
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
|
641
|
+
@external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16
|
|
531
642
|
end
|
|
532
643
|
end
|
|
533
644
|
|
|
645
|
+
@extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
|
|
534
646
|
io << pack_c_dir_entry
|
|
535
647
|
|
|
536
648
|
io << @name
|
|
@@ -538,43 +650,43 @@ module Zip
|
|
|
538
650
|
io << @comment
|
|
539
651
|
end
|
|
540
652
|
|
|
541
|
-
def ==(other)
|
|
653
|
+
def ==(other) # :nodoc:
|
|
542
654
|
return false unless other.class == self.class
|
|
543
655
|
|
|
544
656
|
# Compares contents of local entry and exposed fields
|
|
545
|
-
|
|
657
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
|
546
658
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
|
547
659
|
end
|
|
548
|
-
keys_equal && time == other.time
|
|
549
660
|
end
|
|
550
661
|
|
|
551
|
-
def <=>(other)
|
|
662
|
+
def <=>(other) # :nodoc:
|
|
552
663
|
to_s <=> other.to_s
|
|
553
664
|
end
|
|
554
665
|
|
|
555
666
|
# Returns an IO like object for the given ZipEntry.
|
|
667
|
+
# Optional decrypter can be provided for an encrypted entry
|
|
556
668
|
# Warning: may behave weird with symlinks.
|
|
557
|
-
def get_input_stream(&block)
|
|
558
|
-
if
|
|
559
|
-
yield ::Zip::NullInputStream if
|
|
669
|
+
def get_input_stream(decrypter: nil, &block)
|
|
670
|
+
if ftype == :directory
|
|
671
|
+
yield ::Zip::NullInputStream if block
|
|
560
672
|
::Zip::NullInputStream
|
|
561
673
|
elsif @filepath
|
|
562
|
-
case
|
|
674
|
+
case ftype
|
|
563
675
|
when :file
|
|
564
676
|
::File.open(@filepath, 'rb', &block)
|
|
565
677
|
when :symlink
|
|
566
678
|
linkpath = ::File.readlink(@filepath)
|
|
567
679
|
stringio = ::StringIO.new(linkpath)
|
|
568
|
-
yield(stringio) if
|
|
680
|
+
yield(stringio) if block
|
|
569
681
|
stringio
|
|
570
682
|
else
|
|
571
|
-
raise "unknown @file_type #{
|
|
683
|
+
raise "unknown @file_type #{ftype}"
|
|
572
684
|
end
|
|
573
685
|
else
|
|
574
|
-
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
|
686
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset, decrypter: decrypter)
|
|
575
687
|
zis.instance_variable_set(:@complete_entry, self)
|
|
576
688
|
zis.get_next_entry
|
|
577
|
-
if
|
|
689
|
+
if block
|
|
578
690
|
begin
|
|
579
691
|
yield(zis)
|
|
580
692
|
ensure
|
|
@@ -592,8 +704,8 @@ module Zip
|
|
|
592
704
|
when 'file'
|
|
593
705
|
if name_is_directory?
|
|
594
706
|
raise ArgumentError,
|
|
595
|
-
"entry name '#{
|
|
596
|
-
|
|
707
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
708
|
+
"'#{src_path}' is not a directory"
|
|
597
709
|
end
|
|
598
710
|
:file
|
|
599
711
|
when 'directory'
|
|
@@ -602,8 +714,8 @@ module Zip
|
|
|
602
714
|
when 'link'
|
|
603
715
|
if name_is_directory?
|
|
604
716
|
raise ArgumentError,
|
|
605
|
-
"entry name '#{
|
|
606
|
-
|
|
717
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
718
|
+
"'#{src_path}' is not a directory"
|
|
607
719
|
end
|
|
608
720
|
:symlink
|
|
609
721
|
else
|
|
@@ -611,27 +723,30 @@ module Zip
|
|
|
611
723
|
end
|
|
612
724
|
|
|
613
725
|
@filepath = src_path
|
|
726
|
+
@size = stat.size
|
|
614
727
|
get_extra_attributes_from_path(@filepath)
|
|
615
728
|
end
|
|
616
729
|
|
|
617
|
-
def write_to_zip_output_stream(zip_output_stream)
|
|
618
|
-
if
|
|
619
|
-
zip_output_stream.put_next_entry(self
|
|
730
|
+
def write_to_zip_output_stream(zip_output_stream) # :nodoc:
|
|
731
|
+
if ftype == :directory
|
|
732
|
+
zip_output_stream.put_next_entry(self)
|
|
620
733
|
elsif @filepath
|
|
621
|
-
zip_output_stream.put_next_entry(self
|
|
622
|
-
get_input_stream
|
|
734
|
+
zip_output_stream.put_next_entry(self)
|
|
735
|
+
get_input_stream do |is|
|
|
736
|
+
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
|
737
|
+
end
|
|
623
738
|
else
|
|
624
739
|
zip_output_stream.copy_raw_entry(self)
|
|
625
740
|
end
|
|
626
741
|
end
|
|
627
742
|
|
|
628
|
-
def parent_as_string
|
|
743
|
+
def parent_as_string # :nodoc:
|
|
629
744
|
entry_name = name.chomp('/')
|
|
630
745
|
slash_index = entry_name.rindex('/')
|
|
631
746
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
|
632
747
|
end
|
|
633
748
|
|
|
634
|
-
def get_raw_input_stream(&block)
|
|
749
|
+
def get_raw_input_stream(&block) # :nodoc:
|
|
635
750
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
|
636
751
|
yield @zipfile
|
|
637
752
|
else
|
|
@@ -639,12 +754,22 @@ module Zip
|
|
|
639
754
|
end
|
|
640
755
|
end
|
|
641
756
|
|
|
642
|
-
def clean_up
|
|
643
|
-
#
|
|
757
|
+
def clean_up # :nodoc:
|
|
758
|
+
@dirty = false # Any changes are written at this point.
|
|
644
759
|
end
|
|
645
760
|
|
|
646
761
|
private
|
|
647
762
|
|
|
763
|
+
def read_local_header_fields(io) # :nodoc:
|
|
764
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
|
765
|
+
|
|
766
|
+
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
|
767
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
unpack_local_entry(static_sized_fields_buf)
|
|
771
|
+
end
|
|
772
|
+
|
|
648
773
|
def set_time(binary_dos_date, binary_dos_time)
|
|
649
774
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
|
650
775
|
rescue ArgumentError
|
|
@@ -653,23 +778,22 @@ module Zip
|
|
|
653
778
|
|
|
654
779
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
655
780
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
656
|
-
raise
|
|
657
|
-
"Destination '#{dest_path}' already exists"
|
|
781
|
+
raise DestinationExistsError, dest_path
|
|
658
782
|
end
|
|
783
|
+
|
|
659
784
|
::File.open(dest_path, 'wb') do |os|
|
|
660
785
|
get_input_stream do |is|
|
|
661
786
|
bytes_written = 0
|
|
662
787
|
warned = false
|
|
663
|
-
buf =
|
|
664
|
-
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
|
788
|
+
while (buf = is.sysread(Decompressor::CHUNK_SIZE))
|
|
665
789
|
os << buf
|
|
666
790
|
bytes_written += buf.bytesize
|
|
667
791
|
next unless bytes_written > size && !warned
|
|
668
792
|
|
|
669
|
-
|
|
670
|
-
raise
|
|
793
|
+
error = EntrySizeError.new(self)
|
|
794
|
+
raise error if Zip.validate_entry_sizes
|
|
671
795
|
|
|
672
|
-
warn "WARNING: #{message}"
|
|
796
|
+
warn "WARNING: #{error.message}"
|
|
673
797
|
warned = true
|
|
674
798
|
end
|
|
675
799
|
end
|
|
@@ -682,14 +806,11 @@ module Zip
|
|
|
682
806
|
return if ::File.directory?(dest_path)
|
|
683
807
|
|
|
684
808
|
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
|
|
809
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
|
810
|
+
|
|
811
|
+
::FileUtils.rm_f dest_path
|
|
692
812
|
end
|
|
813
|
+
|
|
693
814
|
::FileUtils.mkdir_p(dest_path)
|
|
694
815
|
set_extra_attributes_on_path(dest_path)
|
|
695
816
|
end
|
|
@@ -703,53 +824,94 @@ module Zip
|
|
|
703
824
|
|
|
704
825
|
# apply missing data from the zip64 extra information field, if present
|
|
705
826
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
706
|
-
def parse_zip64_extra(for_local_header)
|
|
707
|
-
return
|
|
827
|
+
def parse_zip64_extra(for_local_header) # :nodoc:
|
|
828
|
+
return unless zip64?
|
|
708
829
|
|
|
709
830
|
if for_local_header
|
|
710
|
-
@size, @compressed_size = @extra[
|
|
831
|
+
@size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
|
|
711
832
|
else
|
|
712
|
-
@size, @compressed_size, @local_header_offset = @extra[
|
|
833
|
+
@size, @compressed_size, @local_header_offset = @extra[:zip64].parse(
|
|
834
|
+
@size, @compressed_size, @local_header_offset
|
|
835
|
+
)
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
def parse_aes_extra # :nodoc:
|
|
840
|
+
return unless aes?
|
|
841
|
+
|
|
842
|
+
if @extra[:aes].vendor_id != 'AE'
|
|
843
|
+
raise Error, "Unsupported encryption method #{@extra[:aes].vendor_id}"
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
unless ::Zip::AESEncryption::VERSIONS.include? @extra[:aes].vendor_version
|
|
847
|
+
raise Error, "Unsupported encryption style #{@extra[:aes].vendor_version}"
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
@compression_method = @extra[:aes].compression_method if ftype != :directory
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# AES-encrypted entries always store `COMPRESSION_METHOD_AES` (99) on the
|
|
854
|
+
# wire; the real compression method lives in the AES extra field instead
|
|
855
|
+
# (see `parse_aes_extra`, which reverses this on read).
|
|
856
|
+
def stored_compression_method # :nodoc:
|
|
857
|
+
aes? ? COMPRESSION_METHOD_AES : compression_method
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
|
861
|
+
# indicate compression level. This seems to be mainly cosmetic but they are
|
|
862
|
+
# generally set by other tools - including in docx files. It is these flags
|
|
863
|
+
# that are used by commandline tools (and elsewhere) to give an indication
|
|
864
|
+
# of how compressed a file is. See the PKWARE APPNOTE for more information:
|
|
865
|
+
# https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
866
|
+
#
|
|
867
|
+
# It's safe to simply OR these flags here as compression_level is read only.
|
|
868
|
+
def set_compression_level_flags
|
|
869
|
+
return unless compression_method == DEFLATED
|
|
870
|
+
|
|
871
|
+
case @compression_level
|
|
872
|
+
when 1
|
|
873
|
+
@gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG
|
|
874
|
+
when 2
|
|
875
|
+
@gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG
|
|
876
|
+
when 8, 9
|
|
877
|
+
@gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG
|
|
713
878
|
end
|
|
714
879
|
end
|
|
715
880
|
|
|
716
|
-
|
|
717
|
-
|
|
881
|
+
# rubocop:disable Style/GuardClause
|
|
882
|
+
def prep_local_zip64_extra
|
|
883
|
+
return unless ::Zip.write_zip64_support
|
|
884
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
|
885
|
+
|
|
886
|
+
# Might not know size here, so need ZIP64 just in case.
|
|
887
|
+
# If we already have a ZIP64 extra (placeholder) then we must fill it in.
|
|
888
|
+
if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
|
889
|
+
@version_needed_to_extract =
|
|
890
|
+
[@version_needed_to_extract, VERSION_NEEDED_TO_EXTRACT_ZIP64].max
|
|
891
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
892
|
+
|
|
893
|
+
# Local header always includes size and compressed size.
|
|
894
|
+
zip64.original_size = @size || 0
|
|
895
|
+
zip64.compressed_size = @compressed_size
|
|
896
|
+
end
|
|
718
897
|
end
|
|
719
898
|
|
|
720
|
-
|
|
721
|
-
def prep_zip64_extra(for_local_header) #:nodoc:all
|
|
899
|
+
def prep_cdir_zip64_extra
|
|
722
900
|
return unless ::Zip.write_zip64_support
|
|
723
901
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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')
|
|
902
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
|
903
|
+
@local_header_offset >= 0xFFFFFFFF
|
|
904
|
+
@version_needed_to_extract =
|
|
905
|
+
[@version_needed_to_extract, VERSION_NEEDED_TO_EXTRACT_ZIP64].max
|
|
906
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
742
907
|
|
|
743
|
-
#
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if
|
|
747
|
-
@extra.create('Zip64Placeholder')
|
|
748
|
-
else
|
|
749
|
-
@extra.delete('Zip64Placeholder')
|
|
750
|
-
end
|
|
908
|
+
# Central directory entry entries include whichever fields are necessary.
|
|
909
|
+
zip64.original_size = @size if @size && @size >= 0xFFFFFFFF
|
|
910
|
+
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
911
|
+
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
751
912
|
end
|
|
752
913
|
end
|
|
914
|
+
# rubocop:enable Style/GuardClause
|
|
753
915
|
end
|
|
754
916
|
end
|
|
755
917
|
|