rubyzip 2.4.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 +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 +262 -198
- 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 +144 -265
- 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 -20
- 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 +84 -50
- 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
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
|
|