rubyzip 2.4.rc1 → 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 +112 -37
- 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 +32 -3
- data/lib/zip/entry.rb +263 -199
- 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 +143 -264
- 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 +7 -5
- data/lib/zip/input_stream.rb +44 -39
- data/lib/zip/ioextras/abstract_input_stream.rb +14 -9
- 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 +47 -48
- 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 -16
- 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 +81 -46
- 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,59 +54,54 @@ 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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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)
|
59
81
|
|
60
82
|
set_default_vars_values
|
61
83
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
62
84
|
|
63
|
-
@zipfile = zipfile
|
64
|
-
@
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@size = args[5] || 0
|
83
|
-
@time = args[6] || ::Zip::DOSTime.now
|
84
|
-
end
|
85
|
-
|
86
|
-
@ftype = name_is_directory? ? :directory : :file
|
87
|
-
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
|
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
|
88
104
|
end
|
89
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
90
105
|
|
91
106
|
def encrypted?
|
92
107
|
gp_flags & 1 == 1
|
@@ -96,32 +111,76 @@ module Zip
|
|
96
111
|
gp_flags & 8 == 8
|
97
112
|
end
|
98
113
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
@
|
108
|
-
|
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)
|
109
129
|
end
|
110
130
|
|
111
131
|
alias mtime time
|
112
132
|
|
113
|
-
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
|
114
143
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
115
144
|
@extra.create('UniversalTime')
|
116
145
|
end
|
117
|
-
|
118
|
-
|
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?
|
119
176
|
end
|
120
177
|
|
121
178
|
def file_type_is?(type)
|
122
|
-
|
179
|
+
ftype == type
|
180
|
+
end
|
123
181
|
|
124
|
-
|
182
|
+
def ftype # :nodoc:
|
183
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
125
184
|
end
|
126
185
|
|
127
186
|
# Dynamic checkers
|
@@ -143,8 +202,9 @@ module Zip
|
|
143
202
|
return false unless cleanpath.relative?
|
144
203
|
|
145
204
|
root = ::File::SEPARATOR
|
146
|
-
|
147
|
-
|
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)
|
148
208
|
end
|
149
209
|
|
150
210
|
def local_entry_offset #:nodoc:all
|
@@ -173,7 +233,10 @@ module Zip
|
|
173
233
|
return if @local_header_size.nil?
|
174
234
|
|
175
235
|
new_size = calculate_local_header_size
|
176
|
-
|
236
|
+
return unless @local_header_size != new_size
|
237
|
+
|
238
|
+
raise Error,
|
239
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
177
240
|
end
|
178
241
|
|
179
242
|
def cdir_header_size #:nodoc:all
|
@@ -182,27 +245,7 @@ module Zip
|
|
182
245
|
end
|
183
246
|
|
184
247
|
def next_header_offset #:nodoc:all
|
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
|
248
|
+
local_entry_offset + compressed_size
|
206
249
|
end
|
207
250
|
|
208
251
|
# Extracts this entry to a file at `entry_path`, with
|
@@ -210,7 +253,7 @@ module Zip
|
|
210
253
|
#
|
211
254
|
# NB: The caller is responsible for making sure `destination_directory` is
|
212
255
|
# safe, if it is passed.
|
213
|
-
def
|
256
|
+
def extract(entry_path = @name, destination_directory: '.', &block)
|
214
257
|
dest_dir = ::File.absolute_path(destination_directory || '.')
|
215
258
|
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
216
259
|
|
@@ -223,7 +266,7 @@ module Zip
|
|
223
266
|
|
224
267
|
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
225
268
|
|
226
|
-
__send__(
|
269
|
+
__send__("create_#{ftype}", extract_path, &block)
|
227
270
|
self
|
228
271
|
end
|
229
272
|
|
@@ -232,18 +275,6 @@ module Zip
|
|
232
275
|
end
|
233
276
|
|
234
277
|
class << self
|
235
|
-
def read_zip_short(io) # :nodoc:
|
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
278
|
def read_c_dir_entry(io) #:nodoc:all
|
248
279
|
path = if io.respond_to?(:path)
|
249
280
|
io.path
|
@@ -261,6 +292,8 @@ module Zip
|
|
261
292
|
entry = new(io)
|
262
293
|
entry.read_local_entry(io)
|
263
294
|
entry
|
295
|
+
rescue SplitArchiveError
|
296
|
+
raise
|
264
297
|
rescue Error
|
265
298
|
nil
|
266
299
|
end
|
@@ -282,6 +315,7 @@ module Zip
|
|
282
315
|
end
|
283
316
|
|
284
317
|
def read_local_entry(io) #:nodoc:all
|
318
|
+
@dirty = false # No changes at this point.
|
285
319
|
@local_header_offset = io.tell
|
286
320
|
|
287
321
|
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
@@ -292,30 +326,32 @@ module Zip
|
|
292
326
|
|
293
327
|
unpack_local_entry(static_sized_fields_buf)
|
294
328
|
|
295
|
-
unless @header_signature ==
|
296
|
-
|
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}'"
|
297
335
|
end
|
298
336
|
|
299
337
|
set_time(@last_mod_date, @last_mod_time)
|
300
338
|
|
301
339
|
@name = io.read(@name_length)
|
302
|
-
extra = io.read(@extra_length)
|
303
|
-
|
304
|
-
@name.tr!('\\', '/')
|
305
340
|
if ::Zip.force_entry_names_encoding
|
306
341
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
307
342
|
end
|
343
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
344
|
+
|
345
|
+
# We need to do this here because `initialize` has so many side-effects.
|
346
|
+
# :-(
|
347
|
+
@ftype = name_is_directory? ? :directory : :file
|
308
348
|
|
349
|
+
extra = io.read(@extra_length)
|
309
350
|
if extra && extra.bytesize != @extra_length
|
310
351
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
311
352
|
end
|
312
353
|
|
313
|
-
|
314
|
-
@extra.merge(extra) if extra
|
315
|
-
else
|
316
|
-
@extra = ::Zip::ExtraField.new(extra)
|
317
|
-
end
|
318
|
-
|
354
|
+
read_extra_field(extra, local: true)
|
319
355
|
parse_zip64_extra(true)
|
320
356
|
@local_header_size = calculate_local_header_size
|
321
357
|
end
|
@@ -325,18 +361,18 @@ module Zip
|
|
325
361
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
326
362
|
@version_needed_to_extract, # version needed to extract
|
327
363
|
@gp_flags, # @gp_flags
|
328
|
-
|
364
|
+
compression_method,
|
329
365
|
@time.to_binary_dos_time, # @last_mod_time
|
330
366
|
@time.to_binary_dos_date, # @last_mod_date
|
331
367
|
@crc,
|
332
368
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
333
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
369
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
334
370
|
name_size,
|
335
371
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
336
372
|
end
|
337
373
|
|
338
|
-
def write_local_entry(io, rewrite
|
339
|
-
|
374
|
+
def write_local_entry(io, rewrite: false) #:nodoc:all
|
375
|
+
prep_local_zip64_extra
|
340
376
|
verify_local_header_size! if rewrite
|
341
377
|
@local_header_offset = io.tell
|
342
378
|
|
@@ -383,8 +419,9 @@ module Zip
|
|
383
419
|
when ::Zip::FILE_TYPE_SYMLINK
|
384
420
|
:symlink
|
385
421
|
else
|
386
|
-
#
|
387
|
-
# 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.
|
388
425
|
if name_is_directory?
|
389
426
|
:directory
|
390
427
|
else
|
@@ -401,13 +438,13 @@ module Zip
|
|
401
438
|
end
|
402
439
|
|
403
440
|
def check_c_dir_entry_static_header_length(buf)
|
404
|
-
return
|
441
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
405
442
|
|
406
443
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
407
444
|
end
|
408
445
|
|
409
446
|
def check_c_dir_entry_signature
|
410
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
447
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
411
448
|
|
412
449
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
413
450
|
end
|
@@ -418,25 +455,29 @@ module Zip
|
|
418
455
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
419
456
|
end
|
420
457
|
|
421
|
-
def
|
458
|
+
def read_extra_field(buf, local: false)
|
422
459
|
if @extra.kind_of?(::Zip::ExtraField)
|
423
|
-
@extra.merge(
|
460
|
+
@extra.merge(buf, local: local) if buf
|
424
461
|
else
|
425
|
-
@extra = ::Zip::ExtraField.new(
|
462
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
426
463
|
end
|
427
464
|
end
|
428
465
|
|
429
466
|
def read_c_dir_entry(io) #:nodoc:all
|
467
|
+
@dirty = false # No changes at this point.
|
430
468
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
431
469
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
432
470
|
unpack_c_dir_entry(static_sized_fields_buf)
|
433
471
|
check_c_dir_entry_signature
|
434
472
|
set_time(@last_mod_date, @last_mod_time)
|
473
|
+
|
435
474
|
@name = io.read(@name_length)
|
436
475
|
if ::Zip.force_entry_names_encoding
|
437
476
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
438
477
|
end
|
439
|
-
|
478
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
479
|
+
|
480
|
+
read_extra_field(io.read(@extra_length))
|
440
481
|
@comment = io.read(@comment_length)
|
441
482
|
check_c_dir_entry_comment_size
|
442
483
|
set_ftype_from_c_dir_entry
|
@@ -452,27 +493,27 @@ module Zip
|
|
452
493
|
end
|
453
494
|
|
454
495
|
def get_extra_attributes_from_path(path) # :nodoc:
|
455
|
-
|
496
|
+
stat = file_stat(path)
|
497
|
+
@time = DOSTime.from_time(stat.mtime)
|
498
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
456
499
|
|
457
|
-
stat = file_stat(path)
|
458
500
|
@unix_uid = stat.uid
|
459
501
|
@unix_gid = stat.gid
|
460
502
|
@unix_perms = stat.mode & 0o7777
|
461
|
-
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
462
503
|
end
|
463
504
|
|
505
|
+
# rubocop:disable Style/GuardClause
|
464
506
|
def set_unix_attributes_on_path(dest_path)
|
465
|
-
#
|
466
|
-
unix_perms_mask = 0o1777
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
# creation date of the archive if there was no original source file.
|
474
|
-
::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
|
475
515
|
end
|
516
|
+
# rubocop:enable Style/GuardClause
|
476
517
|
|
477
518
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
478
519
|
return unless file? || directory?
|
@@ -481,6 +522,11 @@ module Zip
|
|
481
522
|
when ::Zip::FSTYPE_UNIX
|
482
523
|
set_unix_attributes_on_path(dest_path)
|
483
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
|
484
530
|
end
|
485
531
|
|
486
532
|
def pack_c_dir_entry
|
@@ -491,12 +537,12 @@ module Zip
|
|
491
537
|
@fstype, # filesystem type
|
492
538
|
@version_needed_to_extract, # @versionNeededToExtract
|
493
539
|
@gp_flags, # @gp_flags
|
494
|
-
|
540
|
+
compression_method,
|
495
541
|
@time.to_binary_dos_time, # @last_mod_time
|
496
542
|
@time.to_binary_dos_date, # @last_mod_date
|
497
543
|
@crc,
|
498
544
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
499
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
545
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
500
546
|
name_size,
|
501
547
|
@extra ? @extra.c_dir_size : 0,
|
502
548
|
comment_size,
|
@@ -511,10 +557,11 @@ module Zip
|
|
511
557
|
end
|
512
558
|
|
513
559
|
def write_c_dir_entry(io) #:nodoc:all
|
514
|
-
|
560
|
+
prep_cdir_zip64_extra
|
561
|
+
|
515
562
|
case @fstype
|
516
563
|
when ::Zip::FSTYPE_UNIX
|
517
|
-
ft = case
|
564
|
+
ft = case ftype
|
518
565
|
when :file
|
519
566
|
@unix_perms ||= 0o644
|
520
567
|
::Zip::FILE_TYPE_FILE
|
@@ -542,10 +589,9 @@ module Zip
|
|
542
589
|
return false unless other.class == self.class
|
543
590
|
|
544
591
|
# Compares contents of local entry and exposed fields
|
545
|
-
|
592
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
546
593
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
547
594
|
end
|
548
|
-
keys_equal && time == other.time
|
549
595
|
end
|
550
596
|
|
551
597
|
def <=>(other)
|
@@ -555,26 +601,26 @@ module Zip
|
|
555
601
|
# Returns an IO like object for the given ZipEntry.
|
556
602
|
# Warning: may behave weird with symlinks.
|
557
603
|
def get_input_stream(&block)
|
558
|
-
if
|
559
|
-
yield ::Zip::NullInputStream if
|
604
|
+
if ftype == :directory
|
605
|
+
yield ::Zip::NullInputStream if block
|
560
606
|
::Zip::NullInputStream
|
561
607
|
elsif @filepath
|
562
|
-
case
|
608
|
+
case ftype
|
563
609
|
when :file
|
564
610
|
::File.open(@filepath, 'rb', &block)
|
565
611
|
when :symlink
|
566
612
|
linkpath = ::File.readlink(@filepath)
|
567
613
|
stringio = ::StringIO.new(linkpath)
|
568
|
-
yield(stringio) if
|
614
|
+
yield(stringio) if block
|
569
615
|
stringio
|
570
616
|
else
|
571
|
-
raise "unknown @file_type #{
|
617
|
+
raise "unknown @file_type #{ftype}"
|
572
618
|
end
|
573
619
|
else
|
574
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
620
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
575
621
|
zis.instance_variable_set(:@complete_entry, self)
|
576
622
|
zis.get_next_entry
|
577
|
-
if
|
623
|
+
if block
|
578
624
|
begin
|
579
625
|
yield(zis)
|
580
626
|
ensure
|
@@ -611,15 +657,18 @@ module Zip
|
|
611
657
|
end
|
612
658
|
|
613
659
|
@filepath = src_path
|
660
|
+
@size = stat.size
|
614
661
|
get_extra_attributes_from_path(@filepath)
|
615
662
|
end
|
616
663
|
|
617
664
|
def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
|
618
|
-
if
|
619
|
-
zip_output_stream.put_next_entry(self
|
665
|
+
if ftype == :directory
|
666
|
+
zip_output_stream.put_next_entry(self)
|
620
667
|
elsif @filepath
|
621
|
-
zip_output_stream.put_next_entry(self
|
622
|
-
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
|
623
672
|
else
|
624
673
|
zip_output_stream.copy_raw_entry(self)
|
625
674
|
end
|
@@ -640,7 +689,7 @@ module Zip
|
|
640
689
|
end
|
641
690
|
|
642
691
|
def clean_up
|
643
|
-
#
|
692
|
+
@dirty = false # Any changes are written at this point.
|
644
693
|
end
|
645
694
|
|
646
695
|
private
|
@@ -653,9 +702,9 @@ module Zip
|
|
653
702
|
|
654
703
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
655
704
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
656
|
-
raise ::Zip::
|
657
|
-
"Destination '#{dest_path}' already exists"
|
705
|
+
raise ::Zip::DestinationExistsError, dest_path
|
658
706
|
end
|
707
|
+
|
659
708
|
::File.open(dest_path, 'wb') do |os|
|
660
709
|
get_input_stream do |is|
|
661
710
|
bytes_written = 0
|
@@ -666,10 +715,10 @@ module Zip
|
|
666
715
|
bytes_written += buf.bytesize
|
667
716
|
next unless bytes_written > size && !warned
|
668
717
|
|
669
|
-
|
670
|
-
raise
|
718
|
+
error = ::Zip::EntrySizeError.new(self)
|
719
|
+
raise error if ::Zip.validate_entry_sizes
|
671
720
|
|
672
|
-
warn "WARNING: #{message}"
|
721
|
+
warn "WARNING: #{error.message}"
|
673
722
|
warned = true
|
674
723
|
end
|
675
724
|
end
|
@@ -682,14 +731,11 @@ module Zip
|
|
682
731
|
return if ::File.directory?(dest_path)
|
683
732
|
|
684
733
|
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
|
734
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
735
|
+
|
736
|
+
::FileUtils.rm_f dest_path
|
692
737
|
end
|
738
|
+
|
693
739
|
::FileUtils.mkdir_p(dest_path)
|
694
740
|
set_extra_attributes_on_path(dest_path)
|
695
741
|
end
|
@@ -704,52 +750,70 @@ module Zip
|
|
704
750
|
# apply missing data from the zip64 extra information field, if present
|
705
751
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
706
752
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
707
|
-
return
|
753
|
+
return unless zip64?
|
708
754
|
|
709
755
|
if for_local_header
|
710
756
|
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
711
757
|
else
|
712
|
-
@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
|
+
)
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
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
|
713
782
|
end
|
714
783
|
end
|
715
784
|
|
716
|
-
|
717
|
-
|
785
|
+
# rubocop:disable Style/GuardClause
|
786
|
+
def prep_local_zip64_extra
|
787
|
+
return unless ::Zip.write_zip64_support
|
788
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
789
|
+
|
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
|
793
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
794
|
+
zip64 = @extra['Zip64'] || @extra.create('Zip64')
|
795
|
+
|
796
|
+
# Local header always includes size and compressed size.
|
797
|
+
zip64.original_size = @size || 0
|
798
|
+
zip64.compressed_size = @compressed_size
|
799
|
+
end
|
718
800
|
end
|
719
801
|
|
720
|
-
|
721
|
-
def prep_zip64_extra(for_local_header) #:nodoc:all
|
802
|
+
def prep_cdir_zip64_extra
|
722
803
|
return unless ::Zip.write_zip64_support
|
723
804
|
|
724
|
-
|
725
|
-
|
726
|
-
if need_zip64
|
805
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
806
|
+
@local_header_offset >= 0xFFFFFFFF
|
727
807
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
728
|
-
@extra.
|
729
|
-
zip64 = @extra.create('Zip64')
|
730
|
-
if for_local_header
|
731
|
-
# local header always includes size and compressed size
|
732
|
-
zip64.original_size = @size
|
733
|
-
zip64.compressed_size = @compressed_size
|
734
|
-
else
|
735
|
-
# central directory entry entries include whichever fields are necessary
|
736
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
737
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
738
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
739
|
-
end
|
740
|
-
else
|
741
|
-
@extra.delete('Zip64')
|
808
|
+
zip64 = @extra['Zip64'] || @extra.create('Zip64')
|
742
809
|
|
743
|
-
#
|
744
|
-
|
745
|
-
|
746
|
-
if
|
747
|
-
@extra.create('Zip64Placeholder')
|
748
|
-
else
|
749
|
-
@extra.delete('Zip64Placeholder')
|
750
|
-
end
|
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
|
751
814
|
end
|
752
815
|
end
|
816
|
+
# rubocop:enable Style/GuardClause
|
753
817
|
end
|
754
818
|
end
|
755
819
|
|