rubyzip 1.3.0 → 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 +123 -46
- data/Rakefile +13 -6
- data/lib/zip/central_directory.rb +166 -116
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +77 -21
- data/lib/zip/crypto/decrypted_io.rb +42 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -3
- data/lib/zip/crypto/traditional_encryption.rb +14 -12
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +10 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +53 -12
- data/lib/zip/entry.rb +306 -184
- data/lib/zip/entry_set.rb +11 -7
- data/lib/zip/errors.rb +115 -15
- data/lib/zip/extra_field/generic.rb +11 -17
- data/lib/zip/extra_field/ntfs.rb +8 -2
- data/lib/zip/extra_field/old_unix.rb +6 -2
- data/lib/zip/extra_field/universal_time.rb +45 -13
- data/lib/zip/extra_field/unix.rb +7 -3
- data/lib/zip/extra_field/unknown.rb +33 -0
- data/lib/zip/extra_field/zip64.rb +16 -7
- data/lib/zip/extra_field.rb +22 -26
- data/lib/zip/file.rb +196 -240
- 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 +31 -584
- data/lib/zip/inflater.rb +27 -37
- data/lib/zip/input_stream.rb +67 -42
- data/lib/zip/ioextras/abstract_input_stream.rb +32 -16
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
- data/lib/zip/ioextras.rb +7 -7
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +4 -10
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +58 -43
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +16 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +9 -10
- data/lib/zip/version.rb +3 -1
- data/lib/zip.rb +19 -4
- data/rubyzip.gemspec +38 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +3 -2
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +22 -20
- data/samples/qtzip.rb +12 -11
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +86 -179
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
- data/test/basic_zip_file_test.rb +0 -60
- data/test/case_sensitivity_test.rb +0 -69
- data/test/central_directory_entry_test.rb +0 -69
- data/test/central_directory_test.rb +0 -100
- data/test/crypto/null_encryption_test.rb +0 -57
- data/test/crypto/traditional_encryption_test.rb +0 -80
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +0 -46
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +0 -1504
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +0 -10
- data/test/data/path_traversal/jwilk/README.md +0 -5
- data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
- data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
- data/test/data/path_traversal/jwilk/relative0.zip +0 -0
- data/test/data/path_traversal/jwilk/relative2.zip +0 -0
- data/test/data/path_traversal/jwilk/symlink.zip +0 -0
- data/test/data/path_traversal/relative1.zip +0 -0
- data/test/data/path_traversal/tilde.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
- data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +0 -65
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -163
- data/test/entry_test.rb +0 -154
- data/test/errors_test.rb +0 -35
- data/test/extra_field_test.rb +0 -76
- data/test/file_extract_directory_test.rb +0 -54
- data/test/file_extract_test.rb +0 -145
- data/test/file_permissions_test.rb +0 -65
- data/test/file_split_test.rb +0 -57
- data/test/file_test.rb +0 -666
- data/test/filesystem/dir_iterator_test.rb +0 -58
- data/test/filesystem/directory_test.rb +0 -139
- data/test/filesystem/file_mutating_test.rb +0 -87
- data/test/filesystem/file_nonmutating_test.rb +0 -508
- data/test/filesystem/file_stat_test.rb +0 -64
- data/test/gentestfiles.rb +0 -126
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -182
- data/test/ioextras/abstract_input_stream_test.rb +0 -102
- data/test/ioextras/abstract_output_stream_test.rb +0 -106
- data/test/ioextras/fake_io_test.rb +0 -18
- data/test/local_entry_test.rb +0 -154
- data/test/output_stream_test.rb +0 -128
- data/test/pass_thru_compressor_test.rb +0 -30
- data/test/pass_thru_decompressor_test.rb +0 -14
- data/test/path_traversal_test.rb +0 -141
- data/test/samples/example_recursive_test.rb +0 -37
- data/test/settings_test.rb +0 -95
- data/test/test_helper.rb +0 -234
- data/test/unicode_file_names_and_comments_test.rb +0 -62
- data/test/zip64_full_test.rb +0 -51
- data/test/zip64_support_test.rb +0 -14
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,69 +54,133 @@ 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
|
-
raise
|
67
|
+
raise EntryNameError, name if name.start_with?('/')
|
68
|
+
raise EntryNameError if name.length > 65_535
|
52
69
|
end
|
53
70
|
|
54
|
-
def initialize(
|
55
|
-
|
56
|
-
|
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)
|
57
81
|
|
58
82
|
set_default_vars_values
|
59
83
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
60
84
|
|
61
|
-
@zipfile =
|
62
|
-
@
|
63
|
-
@
|
64
|
-
@
|
65
|
-
@compressed_size =
|
66
|
-
@crc =
|
67
|
-
@
|
68
|
-
@
|
69
|
-
|
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)
|
70
102
|
|
71
|
-
|
72
|
-
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
|
103
|
+
set_compression_level_flags
|
73
104
|
end
|
74
105
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
106
|
+
def encrypted?
|
107
|
+
gp_flags & 1 == 1
|
108
|
+
end
|
109
|
+
|
110
|
+
def incomplete?
|
111
|
+
gp_flags & 8 == 8
|
112
|
+
end
|
113
|
+
|
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)
|
85
129
|
end
|
86
130
|
|
87
131
|
alias mtime time
|
88
132
|
|
89
|
-
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
|
90
143
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
91
144
|
@extra.create('UniversalTime')
|
92
145
|
end
|
93
|
-
|
94
|
-
|
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?
|
95
176
|
end
|
96
177
|
|
97
178
|
def file_type_is?(type)
|
98
|
-
|
99
|
-
|
179
|
+
ftype == type
|
180
|
+
end
|
181
|
+
|
182
|
+
def ftype # :nodoc:
|
183
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
100
184
|
end
|
101
185
|
|
102
186
|
# Dynamic checkers
|
@@ -116,9 +200,11 @@ module Zip
|
|
116
200
|
def name_safe?
|
117
201
|
cleanpath = Pathname.new(@name).cleanpath
|
118
202
|
return false unless cleanpath.relative?
|
203
|
+
|
119
204
|
root = ::File::SEPARATOR
|
120
|
-
|
121
|
-
|
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)
|
122
208
|
end
|
123
209
|
|
124
210
|
def local_entry_offset #:nodoc:all
|
@@ -145,8 +231,12 @@ module Zip
|
|
145
231
|
# that we didn't change the header size (and thus clobber file data or something)
|
146
232
|
def verify_local_header_size!
|
147
233
|
return if @local_header_size.nil?
|
234
|
+
|
148
235
|
new_size = calculate_local_header_size
|
149
|
-
|
236
|
+
return unless @local_header_size != new_size
|
237
|
+
|
238
|
+
raise Error,
|
239
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
150
240
|
end
|
151
241
|
|
152
242
|
def cdir_header_size #:nodoc:all
|
@@ -155,27 +245,28 @@ module Zip
|
|
155
245
|
end
|
156
246
|
|
157
247
|
def next_header_offset #:nodoc:all
|
158
|
-
local_entry_offset + compressed_size
|
248
|
+
local_entry_offset + compressed_size
|
159
249
|
end
|
160
250
|
|
161
|
-
# Extracts entry to file
|
162
|
-
#
|
163
|
-
#
|
164
|
-
|
165
|
-
|
166
|
-
|
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."
|
167
262
|
return self
|
168
263
|
end
|
169
264
|
|
170
|
-
dest_path ||= @name
|
171
265
|
block ||= proc { ::Zip.on_exists_proc }
|
172
266
|
|
173
|
-
|
174
|
-
__send__("create_#{@ftype}", dest_path, &block)
|
175
|
-
else
|
176
|
-
raise "unknown file type #{inspect}"
|
177
|
-
end
|
267
|
+
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
178
268
|
|
269
|
+
__send__("create_#{ftype}", extract_path, &block)
|
179
270
|
self
|
180
271
|
end
|
181
272
|
|
@@ -184,18 +275,6 @@ module Zip
|
|
184
275
|
end
|
185
276
|
|
186
277
|
class << self
|
187
|
-
def read_zip_short(io) # :nodoc:
|
188
|
-
io.read(2).unpack('v')[0]
|
189
|
-
end
|
190
|
-
|
191
|
-
def read_zip_long(io) # :nodoc:
|
192
|
-
io.read(4).unpack('V')[0]
|
193
|
-
end
|
194
|
-
|
195
|
-
def read_zip_64_long(io) # :nodoc:
|
196
|
-
io.read(8).unpack('Q<')[0]
|
197
|
-
end
|
198
|
-
|
199
278
|
def read_c_dir_entry(io) #:nodoc:all
|
200
279
|
path = if io.respond_to?(:path)
|
201
280
|
io.path
|
@@ -213,13 +292,13 @@ module Zip
|
|
213
292
|
entry = new(io)
|
214
293
|
entry.read_local_entry(io)
|
215
294
|
entry
|
295
|
+
rescue SplitArchiveError
|
296
|
+
raise
|
216
297
|
rescue Error
|
217
298
|
nil
|
218
299
|
end
|
219
300
|
end
|
220
301
|
|
221
|
-
public
|
222
|
-
|
223
302
|
def unpack_local_entry(buf)
|
224
303
|
@header_signature,
|
225
304
|
@version,
|
@@ -236,6 +315,7 @@ module Zip
|
|
236
315
|
end
|
237
316
|
|
238
317
|
def read_local_entry(io) #:nodoc:all
|
318
|
+
@dirty = false # No changes at this point.
|
239
319
|
@local_header_offset = io.tell
|
240
320
|
|
241
321
|
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
@@ -246,28 +326,32 @@ module Zip
|
|
246
326
|
|
247
327
|
unpack_local_entry(static_sized_fields_buf)
|
248
328
|
|
249
|
-
unless @header_signature ==
|
250
|
-
|
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}'"
|
251
335
|
end
|
336
|
+
|
252
337
|
set_time(@last_mod_date, @last_mod_time)
|
253
338
|
|
254
339
|
@name = io.read(@name_length)
|
255
|
-
extra = io.read(@extra_length)
|
256
|
-
|
257
|
-
@name.tr!('\\', '/')
|
258
340
|
if ::Zip.force_entry_names_encoding
|
259
341
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
260
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
|
261
348
|
|
349
|
+
extra = io.read(@extra_length)
|
262
350
|
if extra && extra.bytesize != @extra_length
|
263
351
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
264
|
-
else
|
265
|
-
if @extra.is_a?(::Zip::ExtraField)
|
266
|
-
@extra.merge(extra) if extra
|
267
|
-
else
|
268
|
-
@extra = ::Zip::ExtraField.new(extra)
|
269
|
-
end
|
270
352
|
end
|
353
|
+
|
354
|
+
read_extra_field(extra, local: true)
|
271
355
|
parse_zip64_extra(true)
|
272
356
|
@local_header_size = calculate_local_header_size
|
273
357
|
end
|
@@ -277,18 +361,18 @@ module Zip
|
|
277
361
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
278
362
|
@version_needed_to_extract, # version needed to extract
|
279
363
|
@gp_flags, # @gp_flags
|
280
|
-
|
364
|
+
compression_method,
|
281
365
|
@time.to_binary_dos_time, # @last_mod_time
|
282
366
|
@time.to_binary_dos_date, # @last_mod_date
|
283
367
|
@crc,
|
284
368
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
285
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
369
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
286
370
|
name_size,
|
287
371
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
288
372
|
end
|
289
373
|
|
290
|
-
def write_local_entry(io, rewrite
|
291
|
-
|
374
|
+
def write_local_entry(io, rewrite: false) #:nodoc:all
|
375
|
+
prep_local_zip64_extra
|
292
376
|
verify_local_header_size! if rewrite
|
293
377
|
@local_header_offset = io.tell
|
294
378
|
|
@@ -335,8 +419,9 @@ module Zip
|
|
335
419
|
when ::Zip::FILE_TYPE_SYMLINK
|
336
420
|
:symlink
|
337
421
|
else
|
338
|
-
#
|
339
|
-
# 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.
|
340
425
|
if name_is_directory?
|
341
426
|
:directory
|
342
427
|
else
|
@@ -353,39 +438,46 @@ module Zip
|
|
353
438
|
end
|
354
439
|
|
355
440
|
def check_c_dir_entry_static_header_length(buf)
|
356
|
-
return
|
441
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
442
|
+
|
357
443
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
358
444
|
end
|
359
445
|
|
360
446
|
def check_c_dir_entry_signature
|
361
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
447
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
448
|
+
|
362
449
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
363
450
|
end
|
364
451
|
|
365
452
|
def check_c_dir_entry_comment_size
|
366
453
|
return if @comment && @comment.bytesize == @comment_length
|
454
|
+
|
367
455
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
368
456
|
end
|
369
457
|
|
370
|
-
def
|
371
|
-
if @extra.
|
372
|
-
@extra.merge(
|
458
|
+
def read_extra_field(buf, local: false)
|
459
|
+
if @extra.kind_of?(::Zip::ExtraField)
|
460
|
+
@extra.merge(buf, local: local) if buf
|
373
461
|
else
|
374
|
-
@extra = ::Zip::ExtraField.new(
|
462
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
375
463
|
end
|
376
464
|
end
|
377
465
|
|
378
466
|
def read_c_dir_entry(io) #:nodoc:all
|
467
|
+
@dirty = false # No changes at this point.
|
379
468
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
380
469
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
381
470
|
unpack_c_dir_entry(static_sized_fields_buf)
|
382
471
|
check_c_dir_entry_signature
|
383
472
|
set_time(@last_mod_date, @last_mod_time)
|
473
|
+
|
384
474
|
@name = io.read(@name_length)
|
385
475
|
if ::Zip.force_entry_names_encoding
|
386
476
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
387
477
|
end
|
388
|
-
|
478
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
479
|
+
|
480
|
+
read_extra_field(io.read(@extra_length))
|
389
481
|
@comment = io.read(@comment_length)
|
390
482
|
check_c_dir_entry_comment_size
|
391
483
|
set_ftype_from_c_dir_entry
|
@@ -401,30 +493,40 @@ module Zip
|
|
401
493
|
end
|
402
494
|
|
403
495
|
def get_extra_attributes_from_path(path) # :nodoc:
|
404
|
-
|
405
|
-
|
496
|
+
stat = file_stat(path)
|
497
|
+
@time = DOSTime.from_time(stat.mtime)
|
498
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
499
|
+
|
406
500
|
@unix_uid = stat.uid
|
407
501
|
@unix_gid = stat.gid
|
408
502
|
@unix_perms = stat.mode & 0o7777
|
409
503
|
end
|
410
504
|
|
411
|
-
|
412
|
-
|
413
|
-
#
|
414
|
-
unix_perms_mask = 0o1777
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
505
|
+
# rubocop:disable Style/GuardClause
|
506
|
+
def set_unix_attributes_on_path(dest_path)
|
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
|
419
515
|
end
|
516
|
+
# rubocop:enable Style/GuardClause
|
420
517
|
|
421
518
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
422
519
|
return unless file? || directory?
|
423
520
|
|
424
521
|
case @fstype
|
425
522
|
when ::Zip::FSTYPE_UNIX
|
426
|
-
|
523
|
+
set_unix_attributes_on_path(dest_path)
|
427
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
|
428
530
|
end
|
429
531
|
|
430
532
|
def pack_c_dir_entry
|
@@ -435,12 +537,12 @@ module Zip
|
|
435
537
|
@fstype, # filesystem type
|
436
538
|
@version_needed_to_extract, # @versionNeededToExtract
|
437
539
|
@gp_flags, # @gp_flags
|
438
|
-
|
540
|
+
compression_method,
|
439
541
|
@time.to_binary_dos_time, # @last_mod_time
|
440
542
|
@time.to_binary_dos_date, # @last_mod_date
|
441
543
|
@crc,
|
442
544
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
443
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
545
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
444
546
|
name_size,
|
445
547
|
@extra ? @extra.c_dir_size : 0,
|
446
548
|
comment_size,
|
@@ -455,10 +557,11 @@ module Zip
|
|
455
557
|
end
|
456
558
|
|
457
559
|
def write_c_dir_entry(io) #:nodoc:all
|
458
|
-
|
560
|
+
prep_cdir_zip64_extra
|
561
|
+
|
459
562
|
case @fstype
|
460
563
|
when ::Zip::FSTYPE_UNIX
|
461
|
-
ft = case
|
564
|
+
ft = case ftype
|
462
565
|
when :file
|
463
566
|
@unix_perms ||= 0o644
|
464
567
|
::Zip::FILE_TYPE_FILE
|
@@ -484,11 +587,11 @@ module Zip
|
|
484
587
|
|
485
588
|
def ==(other)
|
486
589
|
return false unless other.class == self.class
|
590
|
+
|
487
591
|
# Compares contents of local entry and exposed fields
|
488
|
-
|
592
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
489
593
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
490
594
|
end
|
491
|
-
keys_equal && time.dos_equals(other.time)
|
492
595
|
end
|
493
596
|
|
494
597
|
def <=>(other)
|
@@ -498,26 +601,26 @@ module Zip
|
|
498
601
|
# Returns an IO like object for the given ZipEntry.
|
499
602
|
# Warning: may behave weird with symlinks.
|
500
603
|
def get_input_stream(&block)
|
501
|
-
if
|
502
|
-
yield ::Zip::NullInputStream if
|
604
|
+
if ftype == :directory
|
605
|
+
yield ::Zip::NullInputStream if block
|
503
606
|
::Zip::NullInputStream
|
504
607
|
elsif @filepath
|
505
|
-
case
|
608
|
+
case ftype
|
506
609
|
when :file
|
507
610
|
::File.open(@filepath, 'rb', &block)
|
508
611
|
when :symlink
|
509
612
|
linkpath = ::File.readlink(@filepath)
|
510
613
|
stringio = ::StringIO.new(linkpath)
|
511
|
-
yield(stringio) if
|
614
|
+
yield(stringio) if block
|
512
615
|
stringio
|
513
616
|
else
|
514
|
-
raise "unknown @file_type #{
|
617
|
+
raise "unknown @file_type #{ftype}"
|
515
618
|
end
|
516
619
|
else
|
517
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
620
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
518
621
|
zis.instance_variable_set(:@complete_entry, self)
|
519
622
|
zis.get_next_entry
|
520
|
-
if
|
623
|
+
if block
|
521
624
|
begin
|
522
625
|
yield(zis)
|
523
626
|
ensure
|
@@ -554,15 +657,18 @@ module Zip
|
|
554
657
|
end
|
555
658
|
|
556
659
|
@filepath = src_path
|
660
|
+
@size = stat.size
|
557
661
|
get_extra_attributes_from_path(@filepath)
|
558
662
|
end
|
559
663
|
|
560
664
|
def write_to_zip_output_stream(zip_output_stream) #:nodoc:all
|
561
|
-
if
|
562
|
-
zip_output_stream.put_next_entry(self
|
665
|
+
if ftype == :directory
|
666
|
+
zip_output_stream.put_next_entry(self)
|
563
667
|
elsif @filepath
|
564
|
-
zip_output_stream.put_next_entry(self
|
565
|
-
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
|
566
672
|
else
|
567
673
|
zip_output_stream.copy_raw_entry(self)
|
568
674
|
end
|
@@ -583,7 +689,7 @@ module Zip
|
|
583
689
|
end
|
584
690
|
|
585
691
|
def clean_up
|
586
|
-
#
|
692
|
+
@dirty = false # Any changes are written at this point.
|
587
693
|
end
|
588
694
|
|
589
695
|
private
|
@@ -591,49 +697,45 @@ module Zip
|
|
591
697
|
def set_time(binary_dos_date, binary_dos_time)
|
592
698
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
593
699
|
rescue ArgumentError
|
594
|
-
warn '
|
700
|
+
warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
|
595
701
|
end
|
596
702
|
|
597
703
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
598
704
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
599
|
-
raise ::Zip::
|
600
|
-
"Destination '#{dest_path}' already exists"
|
705
|
+
raise ::Zip::DestinationExistsError, dest_path
|
601
706
|
end
|
707
|
+
|
602
708
|
::File.open(dest_path, 'wb') do |os|
|
603
709
|
get_input_stream do |is|
|
604
|
-
set_extra_attributes_on_path(dest_path)
|
605
|
-
|
606
710
|
bytes_written = 0
|
607
711
|
warned = false
|
608
|
-
buf = ''
|
712
|
+
buf = +''
|
609
713
|
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
610
714
|
os << buf
|
611
715
|
bytes_written += buf.bytesize
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
end
|
620
|
-
end
|
716
|
+
next unless bytes_written > size && !warned
|
717
|
+
|
718
|
+
error = ::Zip::EntrySizeError.new(self)
|
719
|
+
raise error if ::Zip.validate_entry_sizes
|
720
|
+
|
721
|
+
warn "WARNING: #{error.message}"
|
722
|
+
warned = true
|
621
723
|
end
|
622
724
|
end
|
623
725
|
end
|
726
|
+
|
727
|
+
set_extra_attributes_on_path(dest_path)
|
624
728
|
end
|
625
729
|
|
626
730
|
def create_directory(dest_path)
|
627
731
|
return if ::File.directory?(dest_path)
|
732
|
+
|
628
733
|
if ::File.exist?(dest_path)
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
raise ::Zip::DestinationFileExistsError,
|
633
|
-
"Cannot create directory '#{dest_path}'. " \
|
634
|
-
'A file already exists with that name'
|
635
|
-
end
|
734
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
735
|
+
|
736
|
+
::FileUtils.rm_f dest_path
|
636
737
|
end
|
738
|
+
|
637
739
|
::FileUtils.mkdir_p(dest_path)
|
638
740
|
set_extra_attributes_on_path(dest_path)
|
639
741
|
end
|
@@ -642,56 +744,76 @@ module Zip
|
|
642
744
|
def create_symlink(dest_path)
|
643
745
|
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
644
746
|
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
645
|
-
|
747
|
+
warn "WARNING: skipped symlink '#{dest_path}'."
|
646
748
|
end
|
647
749
|
|
648
750
|
# apply missing data from the zip64 extra information field, if present
|
649
751
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
650
752
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
651
|
-
return
|
753
|
+
return unless zip64?
|
754
|
+
|
652
755
|
if for_local_header
|
653
756
|
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
654
757
|
else
|
655
|
-
@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
|
656
782
|
end
|
657
783
|
end
|
658
784
|
|
659
|
-
|
660
|
-
|
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
|
661
800
|
end
|
662
801
|
|
663
|
-
|
664
|
-
def prep_zip64_extra(for_local_header) #:nodoc:all
|
802
|
+
def prep_cdir_zip64_extra
|
665
803
|
return unless ::Zip.write_zip64_support
|
666
|
-
|
667
|
-
|
668
|
-
|
804
|
+
|
805
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
806
|
+
@local_header_offset >= 0xFFFFFFFF
|
669
807
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
670
|
-
@extra.
|
671
|
-
zip64 = @extra.create('Zip64')
|
672
|
-
if for_local_header
|
673
|
-
# local header always includes size and compressed size
|
674
|
-
zip64.original_size = @size
|
675
|
-
zip64.compressed_size = @compressed_size
|
676
|
-
else
|
677
|
-
# central directory entry entries include whichever fields are necessary
|
678
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
679
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
680
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
681
|
-
end
|
682
|
-
else
|
683
|
-
@extra.delete('Zip64')
|
808
|
+
zip64 = @extra['Zip64'] || @extra.create('Zip64')
|
684
809
|
|
685
|
-
#
|
686
|
-
|
687
|
-
|
688
|
-
if
|
689
|
-
@extra.create('Zip64Placeholder')
|
690
|
-
else
|
691
|
-
@extra.delete('Zip64Placeholder')
|
692
|
-
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
|
693
814
|
end
|
694
815
|
end
|
816
|
+
# rubocop:enable Style/GuardClause
|
695
817
|
end
|
696
818
|
end
|
697
819
|
|