rubyzip 2.3.1 → 3.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +368 -0
- data/README.md +122 -39
- data/Rakefile +11 -7
- data/lib/zip/central_directory.rb +164 -118
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +25 -21
- data/lib/zip/crypto/decrypted_io.rb +3 -1
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -3
- data/lib/zip/crypto/traditional_encryption.rb +5 -3
- data/lib/zip/decompressor.rb +4 -3
- data/lib/zip/deflater.rb +10 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +41 -5
- data/lib/zip/entry.rb +273 -170
- data/lib/zip/entry_set.rb +9 -7
- data/lib/zip/errors.rb +115 -16
- data/lib/zip/extra_field/generic.rb +3 -10
- data/lib/zip/extra_field/ntfs.rb +4 -2
- 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 +15 -21
- data/lib/zip/file.rb +152 -221
- data/lib/zip/file_split.rb +97 -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 +26 -595
- data/lib/zip/inflater.rb +6 -4
- data/lib/zip/input_stream.rb +43 -25
- data/lib/zip/ioextras/abstract_input_stream.rb +13 -8
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
- data/lib/zip/ioextras.rb +6 -6
- 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 +45 -39
- 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 +3 -1
- data/lib/zip.rb +15 -22
- data/rubyzip.gemspec +38 -0
- data/samples/example.rb +8 -3
- data/samples/example_filesystem.rb +2 -1
- 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 +1 -0
- data/samples/zipfind.rb +1 -0
- metadata +83 -35
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/entry.rb
CHANGED
@@ -1,19 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathname'
|
4
|
+
|
5
|
+
require_relative 'dirtyable'
|
6
|
+
|
2
7
|
module Zip
|
8
|
+
# Zip::Entry represents an entry in a Zip archive.
|
3
9
|
class Entry
|
4
|
-
|
5
|
-
|
10
|
+
include Dirtyable
|
11
|
+
|
12
|
+
STORED = ::Zip::COMPRESSION_METHOD_STORE
|
13
|
+
DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE
|
14
|
+
|
6
15
|
# Language encoding flag (EFS) bit
|
7
16
|
EFS = 0b100000000000
|
8
17
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
:
|
16
|
-
|
18
|
+
# Compression level flags (used as part of the gp flags).
|
19
|
+
COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110
|
20
|
+
COMPRESSION_LEVEL_FAST_GPFLAG = 0b100
|
21
|
+
COMPRESSION_LEVEL_MAX_GPFLAG = 0b010
|
22
|
+
|
23
|
+
attr_accessor :comment, :compressed_size, :follow_symlinks, :name,
|
24
|
+
:restore_ownership, :restore_permissions, :restore_times,
|
25
|
+
:unix_gid, :unix_perms, :unix_uid
|
26
|
+
|
27
|
+
attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags,
|
28
|
+
:internal_file_attributes, :local_header_offset # :nodoc:
|
29
|
+
|
30
|
+
attr_reader :extra, :compression_level, :filepath # :nodoc:
|
31
|
+
|
32
|
+
attr_writer :size # :nodoc:
|
33
|
+
|
34
|
+
mark_dirty :comment=, :compressed_size=, :external_file_attributes=,
|
35
|
+
:fstype=, :gp_flags=, :name=, :size=,
|
36
|
+
:unix_gid=, :unix_perms=, :unix_uid=
|
17
37
|
|
18
38
|
def set_default_vars_values
|
19
39
|
@local_header_offset = 0
|
@@ -34,43 +54,53 @@ module Zip
|
|
34
54
|
end
|
35
55
|
@follow_symlinks = false
|
36
56
|
|
37
|
-
@restore_times =
|
38
|
-
@restore_permissions =
|
39
|
-
@restore_ownership =
|
57
|
+
@restore_times = DEFAULT_RESTORE_OPTIONS[:restore_times]
|
58
|
+
@restore_permissions = DEFAULT_RESTORE_OPTIONS[:restore_permissions]
|
59
|
+
@restore_ownership = DEFAULT_RESTORE_OPTIONS[:restore_ownership]
|
40
60
|
# BUG: need an extra field to support uid/gid's
|
41
61
|
@unix_uid = nil
|
42
62
|
@unix_gid = nil
|
43
63
|
@unix_perms = nil
|
44
|
-
# @posix_acl = nil
|
45
|
-
# @ntfs_acl = nil
|
46
|
-
@dirty = false
|
47
64
|
end
|
48
65
|
|
49
66
|
def check_name(name)
|
50
|
-
|
51
|
-
|
52
|
-
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
67
|
+
raise EntryNameError, name if name.start_with?('/')
|
68
|
+
raise EntryNameError if name.length > 65_535
|
53
69
|
end
|
54
70
|
|
55
|
-
def initialize(
|
56
|
-
|
57
|
-
|
71
|
+
def initialize(
|
72
|
+
zipfile = '', name = '',
|
73
|
+
comment: '', size: nil, compressed_size: 0, crc: 0,
|
74
|
+
compression_method: DEFLATED,
|
75
|
+
compression_level: ::Zip.default_compression,
|
76
|
+
time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new
|
77
|
+
)
|
78
|
+
super()
|
79
|
+
@name = name
|
80
|
+
check_name(@name)
|
58
81
|
|
59
82
|
set_default_vars_values
|
60
83
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
61
84
|
|
62
|
-
@zipfile =
|
63
|
-
@
|
64
|
-
@
|
65
|
-
@
|
66
|
-
@compressed_size =
|
67
|
-
@crc =
|
68
|
-
@
|
69
|
-
@
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
85
|
+
@zipfile = zipfile
|
86
|
+
@comment = comment || ''
|
87
|
+
@compression_method = compression_method || DEFLATED
|
88
|
+
@compression_level = compression_level || ::Zip.default_compression
|
89
|
+
@compressed_size = compressed_size || 0
|
90
|
+
@crc = crc || 0
|
91
|
+
@size = size
|
92
|
+
@time = case time
|
93
|
+
when ::Zip::DOSTime
|
94
|
+
time
|
95
|
+
when Time
|
96
|
+
::Zip::DOSTime.from_time(time)
|
97
|
+
else
|
98
|
+
::Zip::DOSTime.now
|
99
|
+
end
|
100
|
+
@extra =
|
101
|
+
extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s)
|
102
|
+
|
103
|
+
set_compression_level_flags
|
74
104
|
end
|
75
105
|
|
76
106
|
def encrypted?
|
@@ -81,32 +111,76 @@ module Zip
|
|
81
111
|
gp_flags & 8 == 8
|
82
112
|
end
|
83
113
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@
|
93
|
-
|
114
|
+
def size
|
115
|
+
@size || 0
|
116
|
+
end
|
117
|
+
|
118
|
+
def time(component: :mtime)
|
119
|
+
time =
|
120
|
+
if @extra['UniversalTime']
|
121
|
+
@extra['UniversalTime'].send(component)
|
122
|
+
elsif @extra['NTFS']
|
123
|
+
@extra['NTFS'].send(component)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Standard time field in central directory has local time
|
127
|
+
# under archive creator. Then, we can't get timezone.
|
128
|
+
time || (@time if component == :mtime)
|
94
129
|
end
|
95
130
|
|
96
131
|
alias mtime time
|
97
132
|
|
98
|
-
def
|
133
|
+
def atime
|
134
|
+
time(component: :atime)
|
135
|
+
end
|
136
|
+
|
137
|
+
def ctime
|
138
|
+
time(component: :ctime)
|
139
|
+
end
|
140
|
+
|
141
|
+
def time=(value, component: :mtime)
|
142
|
+
@dirty = true
|
99
143
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
100
144
|
@extra.create('UniversalTime')
|
101
145
|
end
|
102
|
-
|
103
|
-
|
146
|
+
|
147
|
+
value = DOSTime.from_time(value)
|
148
|
+
comp = "#{component}=" unless component.to_s.end_with?('=')
|
149
|
+
(@extra['UniversalTime'] || @extra['NTFS']).send(comp, value)
|
150
|
+
@time = value if component == :mtime
|
151
|
+
end
|
152
|
+
|
153
|
+
alias mtime= time=
|
154
|
+
|
155
|
+
def atime=(value)
|
156
|
+
send(:time=, value, component: :atime)
|
157
|
+
end
|
158
|
+
|
159
|
+
def ctime=(value)
|
160
|
+
send(:time=, value, component: :ctime)
|
161
|
+
end
|
162
|
+
|
163
|
+
def compression_method
|
164
|
+
return STORED if ftype == :directory || @compression_level == 0
|
165
|
+
|
166
|
+
@compression_method
|
167
|
+
end
|
168
|
+
|
169
|
+
def compression_method=(method)
|
170
|
+
@dirty = true
|
171
|
+
@compression_method = (ftype == :directory ? STORED : method)
|
172
|
+
end
|
173
|
+
|
174
|
+
def zip64?
|
175
|
+
!@extra['Zip64'].nil?
|
104
176
|
end
|
105
177
|
|
106
178
|
def file_type_is?(type)
|
107
|
-
|
179
|
+
ftype == type
|
180
|
+
end
|
108
181
|
|
109
|
-
|
182
|
+
def ftype # :nodoc:
|
183
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
110
184
|
end
|
111
185
|
|
112
186
|
# Dynamic checkers
|
@@ -128,8 +202,9 @@ module Zip
|
|
128
202
|
return false unless cleanpath.relative?
|
129
203
|
|
130
204
|
root = ::File::SEPARATOR
|
131
|
-
|
132
|
-
|
205
|
+
naive = ::File.join(root, cleanpath.to_s)
|
206
|
+
# Allow for Windows drive mappings at the root.
|
207
|
+
::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i)
|
133
208
|
end
|
134
209
|
|
135
210
|
def local_entry_offset #:nodoc:all
|
@@ -158,7 +233,10 @@ module Zip
|
|
158
233
|
return if @local_header_size.nil?
|
159
234
|
|
160
235
|
new_size = calculate_local_header_size
|
161
|
-
|
236
|
+
return unless @local_header_size != new_size
|
237
|
+
|
238
|
+
raise Error,
|
239
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
162
240
|
end
|
163
241
|
|
164
242
|
def cdir_header_size #:nodoc:all
|
@@ -167,24 +245,28 @@ module Zip
|
|
167
245
|
end
|
168
246
|
|
169
247
|
def next_header_offset #:nodoc:all
|
170
|
-
local_entry_offset + compressed_size
|
248
|
+
local_entry_offset + compressed_size
|
171
249
|
end
|
172
250
|
|
173
|
-
# Extracts entry to file
|
174
|
-
#
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
251
|
+
# Extracts this entry to a file at `entry_path`, with
|
252
|
+
# `destination_directory` as the base location in the filesystem.
|
253
|
+
#
|
254
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
255
|
+
# safe, if it is passed.
|
256
|
+
def extract(entry_path = @name, destination_directory: '.', &block)
|
257
|
+
dest_dir = ::File.absolute_path(destination_directory || '.')
|
258
|
+
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
259
|
+
|
260
|
+
unless extract_path.start_with?(dest_dir)
|
261
|
+
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
179
262
|
return self
|
180
263
|
end
|
181
264
|
|
182
|
-
dest_path ||= @name
|
183
265
|
block ||= proc { ::Zip.on_exists_proc }
|
184
266
|
|
185
267
|
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
186
268
|
|
187
|
-
__send__("create_#{
|
269
|
+
__send__("create_#{ftype}", extract_path, &block)
|
188
270
|
self
|
189
271
|
end
|
190
272
|
|
@@ -193,18 +275,6 @@ module Zip
|
|
193
275
|
end
|
194
276
|
|
195
277
|
class << self
|
196
|
-
def read_zip_short(io) # :nodoc:
|
197
|
-
io.read(2).unpack1('v')
|
198
|
-
end
|
199
|
-
|
200
|
-
def read_zip_long(io) # :nodoc:
|
201
|
-
io.read(4).unpack1('V')
|
202
|
-
end
|
203
|
-
|
204
|
-
def read_zip_64_long(io) # :nodoc:
|
205
|
-
io.read(8).unpack1('Q<')
|
206
|
-
end
|
207
|
-
|
208
278
|
def read_c_dir_entry(io) #:nodoc:all
|
209
279
|
path = if io.respond_to?(:path)
|
210
280
|
io.path
|
@@ -222,6 +292,8 @@ module Zip
|
|
222
292
|
entry = new(io)
|
223
293
|
entry.read_local_entry(io)
|
224
294
|
entry
|
295
|
+
rescue SplitArchiveError
|
296
|
+
raise
|
225
297
|
rescue Error
|
226
298
|
nil
|
227
299
|
end
|
@@ -243,6 +315,7 @@ module Zip
|
|
243
315
|
end
|
244
316
|
|
245
317
|
def read_local_entry(io) #:nodoc:all
|
318
|
+
@dirty = false # No changes at this point.
|
246
319
|
@local_header_offset = io.tell
|
247
320
|
|
248
321
|
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
@@ -253,30 +326,32 @@ module Zip
|
|
253
326
|
|
254
327
|
unpack_local_entry(static_sized_fields_buf)
|
255
328
|
|
256
|
-
unless @header_signature ==
|
257
|
-
|
329
|
+
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
330
|
+
if @header_signature == SPLIT_FILE_SIGNATURE
|
331
|
+
raise SplitArchiveError
|
332
|
+
end
|
333
|
+
|
334
|
+
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
258
335
|
end
|
259
336
|
|
260
337
|
set_time(@last_mod_date, @last_mod_time)
|
261
338
|
|
262
339
|
@name = io.read(@name_length)
|
263
|
-
extra = io.read(@extra_length)
|
264
|
-
|
265
|
-
@name.tr!('\\', '/')
|
266
340
|
if ::Zip.force_entry_names_encoding
|
267
341
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
268
342
|
end
|
343
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
269
344
|
|
345
|
+
# We need to do this here because `initialize` has so many side-effects.
|
346
|
+
# :-(
|
347
|
+
@ftype = name_is_directory? ? :directory : :file
|
348
|
+
|
349
|
+
extra = io.read(@extra_length)
|
270
350
|
if extra && extra.bytesize != @extra_length
|
271
351
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
272
352
|
end
|
273
353
|
|
274
|
-
|
275
|
-
@extra.merge(extra) if extra
|
276
|
-
else
|
277
|
-
@extra = ::Zip::ExtraField.new(extra)
|
278
|
-
end
|
279
|
-
|
354
|
+
read_extra_field(extra, local: true)
|
280
355
|
parse_zip64_extra(true)
|
281
356
|
@local_header_size = calculate_local_header_size
|
282
357
|
end
|
@@ -286,18 +361,18 @@ module Zip
|
|
286
361
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
287
362
|
@version_needed_to_extract, # version needed to extract
|
288
363
|
@gp_flags, # @gp_flags
|
289
|
-
|
364
|
+
compression_method,
|
290
365
|
@time.to_binary_dos_time, # @last_mod_time
|
291
366
|
@time.to_binary_dos_date, # @last_mod_date
|
292
367
|
@crc,
|
293
368
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
294
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
369
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
295
370
|
name_size,
|
296
371
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
297
372
|
end
|
298
373
|
|
299
|
-
def write_local_entry(io, rewrite
|
300
|
-
|
374
|
+
def write_local_entry(io, rewrite: false) #:nodoc:all
|
375
|
+
prep_local_zip64_extra
|
301
376
|
verify_local_header_size! if rewrite
|
302
377
|
@local_header_offset = io.tell
|
303
378
|
|
@@ -344,8 +419,9 @@ module Zip
|
|
344
419
|
when ::Zip::FILE_TYPE_SYMLINK
|
345
420
|
:symlink
|
346
421
|
else
|
347
|
-
#
|
348
|
-
# Otherwise this would be set to unknown and that
|
422
|
+
# Best case guess for whether it is a file or not.
|
423
|
+
# Otherwise this would be set to unknown and that
|
424
|
+
# entry would never be able to be extracted.
|
349
425
|
if name_is_directory?
|
350
426
|
:directory
|
351
427
|
else
|
@@ -362,13 +438,13 @@ module Zip
|
|
362
438
|
end
|
363
439
|
|
364
440
|
def check_c_dir_entry_static_header_length(buf)
|
365
|
-
return
|
441
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
366
442
|
|
367
443
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
368
444
|
end
|
369
445
|
|
370
446
|
def check_c_dir_entry_signature
|
371
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
447
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
372
448
|
|
373
449
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
374
450
|
end
|
@@ -379,25 +455,29 @@ module Zip
|
|
379
455
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
380
456
|
end
|
381
457
|
|
382
|
-
def
|
458
|
+
def read_extra_field(buf, local: false)
|
383
459
|
if @extra.kind_of?(::Zip::ExtraField)
|
384
|
-
@extra.merge(
|
460
|
+
@extra.merge(buf, local: local) if buf
|
385
461
|
else
|
386
|
-
@extra = ::Zip::ExtraField.new(
|
462
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
387
463
|
end
|
388
464
|
end
|
389
465
|
|
390
466
|
def read_c_dir_entry(io) #:nodoc:all
|
467
|
+
@dirty = false # No changes at this point.
|
391
468
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
392
469
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
393
470
|
unpack_c_dir_entry(static_sized_fields_buf)
|
394
471
|
check_c_dir_entry_signature
|
395
472
|
set_time(@last_mod_date, @last_mod_time)
|
473
|
+
|
396
474
|
@name = io.read(@name_length)
|
397
475
|
if ::Zip.force_entry_names_encoding
|
398
476
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
399
477
|
end
|
400
|
-
|
478
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
479
|
+
|
480
|
+
read_extra_field(io.read(@extra_length))
|
401
481
|
@comment = io.read(@comment_length)
|
402
482
|
check_c_dir_entry_comment_size
|
403
483
|
set_ftype_from_c_dir_entry
|
@@ -413,27 +493,27 @@ module Zip
|
|
413
493
|
end
|
414
494
|
|
415
495
|
def get_extra_attributes_from_path(path) # :nodoc:
|
416
|
-
|
496
|
+
stat = file_stat(path)
|
497
|
+
@time = DOSTime.from_time(stat.mtime)
|
498
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
417
499
|
|
418
|
-
stat = file_stat(path)
|
419
500
|
@unix_uid = stat.uid
|
420
501
|
@unix_gid = stat.gid
|
421
502
|
@unix_perms = stat.mode & 0o7777
|
422
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
423
503
|
end
|
424
504
|
|
505
|
+
# rubocop:disable Style/GuardClause
|
425
506
|
def set_unix_attributes_on_path(dest_path)
|
426
|
-
#
|
427
|
-
unix_perms_mask = 0o1777
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
# creation date of the archive if there was no original source file.
|
435
|
-
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
507
|
+
# Ignore setuid/setgid bits by default. Honour if @restore_ownership.
|
508
|
+
unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777)
|
509
|
+
if @restore_permissions && @unix_perms
|
510
|
+
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path)
|
511
|
+
end
|
512
|
+
if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
513
|
+
::FileUtils.chown(@unix_uid, @unix_gid, dest_path)
|
514
|
+
end
|
436
515
|
end
|
516
|
+
# rubocop:enable Style/GuardClause
|
437
517
|
|
438
518
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
439
519
|
return unless file? || directory?
|
@@ -442,6 +522,11 @@ module Zip
|
|
442
522
|
when ::Zip::FSTYPE_UNIX
|
443
523
|
set_unix_attributes_on_path(dest_path)
|
444
524
|
end
|
525
|
+
|
526
|
+
# Restore the timestamp on a file. This will either have come from the
|
527
|
+
# original source file that was copied into the archive, or from the
|
528
|
+
# creation date of the archive if there was no original source file.
|
529
|
+
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
445
530
|
end
|
446
531
|
|
447
532
|
def pack_c_dir_entry
|
@@ -452,12 +537,12 @@ module Zip
|
|
452
537
|
@fstype, # filesystem type
|
453
538
|
@version_needed_to_extract, # @versionNeededToExtract
|
454
539
|
@gp_flags, # @gp_flags
|
455
|
-
|
540
|
+
compression_method,
|
456
541
|
@time.to_binary_dos_time, # @last_mod_time
|
457
542
|
@time.to_binary_dos_date, # @last_mod_date
|
458
543
|
@crc,
|
459
544
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
460
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
545
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
461
546
|
name_size,
|
462
547
|
@extra ? @extra.c_dir_size : 0,
|
463
548
|
comment_size,
|
@@ -472,10 +557,11 @@ module Zip
|
|
472
557
|
end
|
473
558
|
|
474
559
|
def write_c_dir_entry(io) #:nodoc:all
|
475
|
-
|
560
|
+
prep_cdir_zip64_extra
|
561
|
+
|
476
562
|
case @fstype
|
477
563
|
when ::Zip::FSTYPE_UNIX
|
478
|
-
ft = case
|
564
|
+
ft = case ftype
|
479
565
|
when :file
|
480
566
|
@unix_perms ||= 0o644
|
481
567
|
::Zip::FILE_TYPE_FILE
|
@@ -503,10 +589,9 @@ module Zip
|
|
503
589
|
return false unless other.class == self.class
|
504
590
|
|
505
591
|
# Compares contents of local entry and exposed fields
|
506
|
-
|
592
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
507
593
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
508
594
|
end
|
509
|
-
keys_equal && time.dos_equals(other.time)
|
510
595
|
end
|
511
596
|
|
512
597
|
def <=>(other)
|
@@ -516,26 +601,26 @@ module Zip
|
|
516
601
|
# Returns an IO like object for the given ZipEntry.
|
517
602
|
# Warning: may behave weird with symlinks.
|
518
603
|
def get_input_stream(&block)
|
519
|
-
if
|
520
|
-
yield ::Zip::NullInputStream if
|
604
|
+
if ftype == :directory
|
605
|
+
yield ::Zip::NullInputStream if block
|
521
606
|
::Zip::NullInputStream
|
522
607
|
elsif @filepath
|
523
|
-
case
|
608
|
+
case ftype
|
524
609
|
when :file
|
525
610
|
::File.open(@filepath, 'rb', &block)
|
526
611
|
when :symlink
|
527
612
|
linkpath = ::File.readlink(@filepath)
|
528
613
|
stringio = ::StringIO.new(linkpath)
|
529
|
-
yield(stringio) if
|
614
|
+
yield(stringio) if block
|
530
615
|
stringio
|
531
616
|
else
|
532
|
-
raise "unknown @file_type #{
|
617
|
+
raise "unknown @file_type #{ftype}"
|
533
618
|
end
|
534
619
|
else
|
535
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
620
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
536
621
|
zis.instance_variable_set(:@complete_entry, self)
|
537
622
|
zis.get_next_entry
|
538
|
-
if
|
623
|
+
if block
|
539
624
|
begin
|
540
625
|
yield(zis)
|
541
626
|
ensure
|
@@ -572,15 +657,18 @@ module Zip
|
|
572
657
|
end
|
573
658
|
|
574
659
|
@filepath = src_path
|
660
|
+
@size = stat.size
|
575
661
|
get_extra_attributes_from_path(@filepath)
|
576
662
|
end
|
577
663
|
|
578
664
|
def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
|
579
|
-
if
|
580
|
-
zip_output_stream.put_next_entry(self
|
665
|
+
if ftype == :directory
|
666
|
+
zip_output_stream.put_next_entry(self)
|
581
667
|
elsif @filepath
|
582
|
-
zip_output_stream.put_next_entry(self
|
583
|
-
get_input_stream
|
668
|
+
zip_output_stream.put_next_entry(self)
|
669
|
+
get_input_stream do |is|
|
670
|
+
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
671
|
+
end
|
584
672
|
else
|
585
673
|
zip_output_stream.copy_raw_entry(self)
|
586
674
|
end
|
@@ -601,7 +689,7 @@ module Zip
|
|
601
689
|
end
|
602
690
|
|
603
691
|
def clean_up
|
604
|
-
#
|
692
|
+
@dirty = false # Any changes are written at this point.
|
605
693
|
end
|
606
694
|
|
607
695
|
private
|
@@ -614,9 +702,9 @@ module Zip
|
|
614
702
|
|
615
703
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
616
704
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
617
|
-
raise ::Zip::
|
618
|
-
"Destination '#{dest_path}' already exists"
|
705
|
+
raise ::Zip::DestinationExistsError, dest_path
|
619
706
|
end
|
707
|
+
|
620
708
|
::File.open(dest_path, 'wb') do |os|
|
621
709
|
get_input_stream do |is|
|
622
710
|
bytes_written = 0
|
@@ -627,10 +715,10 @@ module Zip
|
|
627
715
|
bytes_written += buf.bytesize
|
628
716
|
next unless bytes_written > size && !warned
|
629
717
|
|
630
|
-
|
631
|
-
raise
|
718
|
+
error = ::Zip::EntrySizeError.new(self)
|
719
|
+
raise error if ::Zip.validate_entry_sizes
|
632
720
|
|
633
|
-
warn "WARNING: #{message}"
|
721
|
+
warn "WARNING: #{error.message}"
|
634
722
|
warned = true
|
635
723
|
end
|
636
724
|
end
|
@@ -643,14 +731,11 @@ module Zip
|
|
643
731
|
return if ::File.directory?(dest_path)
|
644
732
|
|
645
733
|
if ::File.exist?(dest_path)
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
raise ::Zip::DestinationFileExistsError,
|
650
|
-
"Cannot create directory '#{dest_path}'. " \
|
651
|
-
'A file already exists with that name'
|
652
|
-
end
|
734
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
735
|
+
|
736
|
+
::FileUtils.rm_f dest_path
|
653
737
|
end
|
738
|
+
|
654
739
|
::FileUtils.mkdir_p(dest_path)
|
655
740
|
set_extra_attributes_on_path(dest_path)
|
656
741
|
end
|
@@ -665,52 +750,70 @@ module Zip
|
|
665
750
|
# apply missing data from the zip64 extra information field, if present
|
666
751
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
667
752
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
668
|
-
return
|
753
|
+
return unless zip64?
|
669
754
|
|
670
755
|
if for_local_header
|
671
756
|
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
672
757
|
else
|
673
|
-
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(
|
758
|
+
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(
|
759
|
+
@size, @compressed_size, @local_header_offset
|
760
|
+
)
|
674
761
|
end
|
675
762
|
end
|
676
763
|
|
677
|
-
|
678
|
-
|
764
|
+
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
765
|
+
# indicate compression level. This seems to be mainly cosmetic but they are
|
766
|
+
# generally set by other tools - including in docx files. It is these flags
|
767
|
+
# that are used by commandline tools (and elsewhere) to give an indication
|
768
|
+
# of how compressed a file is. See the PKWARE APPNOTE for more information:
|
769
|
+
# https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
770
|
+
#
|
771
|
+
# It's safe to simply OR these flags here as compression_level is read only.
|
772
|
+
def set_compression_level_flags
|
773
|
+
return unless compression_method == DEFLATED
|
774
|
+
|
775
|
+
case @compression_level
|
776
|
+
when 1
|
777
|
+
@gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG
|
778
|
+
when 2
|
779
|
+
@gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG
|
780
|
+
when 8, 9
|
781
|
+
@gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG
|
782
|
+
end
|
679
783
|
end
|
680
784
|
|
681
|
-
#
|
682
|
-
def
|
785
|
+
# rubocop:disable Style/GuardClause
|
786
|
+
def prep_local_zip64_extra
|
683
787
|
return unless ::Zip.write_zip64_support
|
788
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
684
789
|
|
685
|
-
|
686
|
-
|
687
|
-
if
|
790
|
+
# Might not know size here, so need ZIP64 just in case.
|
791
|
+
# If we already have a ZIP64 extra (placeholder) then we must fill it in.
|
792
|
+
if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
688
793
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
689
|
-
@extra.
|
690
|
-
zip64 = @extra.create('Zip64')
|
691
|
-
if for_local_header
|
692
|
-
# local header always includes size and compressed size
|
693
|
-
zip64.original_size = @size
|
694
|
-
zip64.compressed_size = @compressed_size
|
695
|
-
else
|
696
|
-
# central directory entry entries include whichever fields are necessary
|
697
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
698
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
699
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
700
|
-
end
|
701
|
-
else
|
702
|
-
@extra.delete('Zip64')
|
794
|
+
zip64 = @extra['Zip64'] || @extra.create('Zip64')
|
703
795
|
|
704
|
-
#
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
796
|
+
# Local header always includes size and compressed size.
|
797
|
+
zip64.original_size = @size || 0
|
798
|
+
zip64.compressed_size = @compressed_size
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
def prep_cdir_zip64_extra
|
803
|
+
return unless ::Zip.write_zip64_support
|
804
|
+
|
805
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
806
|
+
@local_header_offset >= 0xFFFFFFFF
|
807
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
808
|
+
zip64 = @extra['Zip64'] || @extra.create('Zip64')
|
809
|
+
|
810
|
+
# Central directory entry entries include whichever fields are necessary.
|
811
|
+
zip64.original_size = @size if @size && @size >= 0xFFFFFFFF
|
812
|
+
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
813
|
+
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
712
814
|
end
|
713
815
|
end
|
816
|
+
# rubocop:enable Style/GuardClause
|
714
817
|
end
|
715
818
|
end
|
716
819
|
|