rubyzip 2.3.1 → 3.0.0.alpha
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 +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
|
|