rubyzip 1.1.7 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +143 -54
- data/Rakefile +3 -4
- data/lib/zip/central_directory.rb +8 -8
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +5 -5
- data/lib/zip/crypto/null_encryption.rb +4 -6
- data/lib/zip/crypto/traditional_encryption.rb +5 -5
- data/lib/zip/decompressor.rb +3 -3
- data/lib/zip/deflater.rb +8 -6
- data/lib/zip/dos_time.rb +5 -6
- data/lib/zip/entry.rb +132 -128
- data/lib/zip/entry_set.rb +14 -14
- data/lib/zip/errors.rb +2 -0
- data/lib/zip/extra_field/generic.rb +8 -8
- data/lib/zip/extra_field/ntfs.rb +14 -16
- data/lib/zip/extra_field/old_unix.rb +9 -10
- data/lib/zip/extra_field/universal_time.rb +14 -14
- data/lib/zip/extra_field/unix.rb +8 -9
- data/lib/zip/extra_field/zip64.rb +12 -11
- data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
- data/lib/zip/extra_field.rb +8 -8
- data/lib/zip/file.rb +88 -81
- data/lib/zip/filesystem.rb +144 -143
- data/lib/zip/inflater.rb +5 -5
- data/lib/zip/input_stream.rb +22 -13
- data/lib/zip/ioextras/abstract_input_stream.rb +6 -10
- data/lib/zip/ioextras/abstract_output_stream.rb +3 -5
- data/lib/zip/ioextras.rb +1 -3
- data/lib/zip/null_compressor.rb +2 -2
- data/lib/zip/null_decompressor.rb +3 -3
- data/lib/zip/null_input_stream.rb +0 -0
- data/lib/zip/output_stream.rb +13 -14
- data/lib/zip/pass_thru_compressor.rb +4 -4
- data/lib/zip/pass_thru_decompressor.rb +3 -4
- data/lib/zip/streamable_directory.rb +2 -2
- data/lib/zip/streamable_stream.rb +3 -3
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +13 -5
- data/samples/example.rb +29 -39
- data/samples/example_filesystem.rb +16 -18
- data/samples/example_recursive.rb +31 -25
- data/samples/{gtkRubyzip.rb → gtk_ruby_zip.rb} +23 -25
- data/samples/qtzip.rb +18 -27
- data/samples/write_simple.rb +12 -13
- data/samples/zipfind.rb +26 -34
- data/test/basic_zip_file_test.rb +11 -15
- data/test/case_sensitivity_test.rb +69 -0
- data/test/central_directory_entry_test.rb +32 -36
- data/test/central_directory_test.rb +46 -50
- data/test/crypto/null_encryption_test.rb +8 -4
- data/test/crypto/traditional_encryption_test.rb +5 -5
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/notzippedruby.rb +1 -1
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +10 -0
- data/test/data/path_traversal/jwilk/README.md +5 -0
- 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 +3 -0
- 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/test.xls +0 -0
- data/test/deflater_test.rb +10 -12
- data/test/encryption_test.rb +2 -2
- data/test/entry_set_test.rb +50 -25
- data/test/entry_test.rb +76 -87
- data/test/errors_test.rb +1 -2
- data/test/extra_field_test.rb +19 -21
- data/test/file_extract_directory_test.rb +12 -14
- data/test/file_extract_test.rb +94 -39
- data/test/file_permissions_test.rb +65 -0
- data/test/file_split_test.rb +24 -27
- data/test/file_test.rb +286 -179
- data/test/filesystem/dir_iterator_test.rb +13 -17
- data/test/filesystem/directory_test.rb +101 -93
- data/test/filesystem/file_mutating_test.rb +52 -65
- data/test/filesystem/file_nonmutating_test.rb +223 -229
- data/test/filesystem/file_stat_test.rb +17 -19
- data/test/gentestfiles.rb +54 -62
- data/test/inflater_test.rb +1 -1
- data/test/input_stream_test.rb +52 -40
- data/test/ioextras/abstract_input_stream_test.rb +22 -23
- data/test/ioextras/abstract_output_stream_test.rb +33 -33
- data/test/ioextras/fake_io_test.rb +1 -1
- data/test/local_entry_test.rb +36 -38
- data/test/output_stream_test.rb +20 -21
- data/test/pass_thru_compressor_test.rb +5 -6
- data/test/pass_thru_decompressor_test.rb +0 -1
- data/test/path_traversal_test.rb +141 -0
- data/test/samples/example_recursive_test.rb +37 -0
- data/test/settings_test.rb +18 -15
- data/test/test_helper.rb +52 -46
- data/test/unicode_file_names_and_comments_test.rb +17 -7
- data/test/zip64_full_test.rb +10 -12
- data/test/zip64_support_test.rb +0 -1
- metadata +100 -66
@@ -12,7 +12,7 @@ module Zip
|
|
12
12
|
class NullEncrypter < Encrypter
|
13
13
|
include NullEncryption
|
14
14
|
|
15
|
-
def header(
|
15
|
+
def header(_mtime)
|
16
16
|
''
|
17
17
|
end
|
18
18
|
|
@@ -20,12 +20,11 @@ module Zip
|
|
20
20
|
data
|
21
21
|
end
|
22
22
|
|
23
|
-
def data_descriptor(
|
23
|
+
def data_descriptor(_crc32, _compressed_size, _uncomprssed_size)
|
24
24
|
''
|
25
25
|
end
|
26
26
|
|
27
|
-
def reset
|
28
|
-
end
|
27
|
+
def reset!; end
|
29
28
|
end
|
30
29
|
|
31
30
|
class NullDecrypter < Decrypter
|
@@ -35,8 +34,7 @@ module Zip
|
|
35
34
|
data
|
36
35
|
end
|
37
36
|
|
38
|
-
def reset!(
|
39
|
-
end
|
37
|
+
def reset!(_header); end
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
@@ -26,7 +26,7 @@ module Zip
|
|
26
26
|
|
27
27
|
def update_keys(n)
|
28
28
|
@key0 = ~Zlib.crc32(n, ~@key0)
|
29
|
-
@key1 = ((@key1 + (@key0 & 0xff)) *
|
29
|
+
@key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff
|
30
30
|
@key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
|
31
31
|
end
|
32
32
|
|
@@ -46,15 +46,15 @@ module Zip
|
|
46
46
|
end
|
47
47
|
header << (mtime.to_binary_dos_time & 0xff)
|
48
48
|
header << (mtime.to_binary_dos_time >> 8)
|
49
|
-
end.map{|x| encode x}.pack(
|
49
|
+
end.map { |x| encode x }.pack('C*')
|
50
50
|
end
|
51
51
|
|
52
52
|
def encrypt(data)
|
53
|
-
data.unpack(
|
53
|
+
data.unpack('C*').map { |x| encode x }.pack('C*')
|
54
54
|
end
|
55
55
|
|
56
56
|
def data_descriptor(crc32, compressed_size, uncomprssed_size)
|
57
|
-
[0x08074b50, crc32, compressed_size, uncomprssed_size].pack(
|
57
|
+
[0x08074b50, crc32, compressed_size, uncomprssed_size].pack('VVVV')
|
58
58
|
end
|
59
59
|
|
60
60
|
def reset!
|
@@ -74,7 +74,7 @@ module Zip
|
|
74
74
|
include TraditionalEncryption
|
75
75
|
|
76
76
|
def decrypt(data)
|
77
|
-
data.unpack(
|
77
|
+
data.unpack('C*').map { |x| decode x }.pack('C*')
|
78
78
|
end
|
79
79
|
|
80
80
|
def reset!(header)
|
data/lib/zip/decompressor.rb
CHANGED
data/lib/zip/deflater.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Zip
|
2
2
|
class Deflater < Compressor #:nodoc:all
|
3
|
-
|
4
3
|
def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new)
|
5
4
|
super()
|
6
5
|
@output_stream = output_stream
|
@@ -8,18 +7,21 @@ module Zip
|
|
8
7
|
@size = 0
|
9
8
|
@crc = ::Zlib.crc32
|
10
9
|
@encrypter = encrypter
|
11
|
-
@buffer_stream = ::StringIO.new('')
|
12
10
|
end
|
13
11
|
|
14
|
-
def <<
|
12
|
+
def <<(data)
|
15
13
|
val = data.to_s
|
16
|
-
@crc = Zlib
|
14
|
+
@crc = Zlib.crc32(val, @crc)
|
17
15
|
@size += val.bytesize
|
18
|
-
|
16
|
+
buffer = @zlib_deflater.deflate(data)
|
17
|
+
if buffer.empty?
|
18
|
+
@output_stream
|
19
|
+
else
|
20
|
+
@output_stream << @encrypter.encrypt(buffer)
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
def finish
|
22
|
-
@output_stream << @encrypter.encrypt(@buffer_stream.string)
|
23
25
|
@output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished?
|
24
26
|
end
|
25
27
|
|
data/lib/zip/dos_time.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module Zip
|
2
2
|
class DOSTime < Time #:nodoc:all
|
3
|
-
|
4
|
-
#MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
|
3
|
+
# MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
|
5
4
|
|
6
5
|
# Register CX, the Time:
|
7
6
|
# Bits 0-4 2 second increments (0-29)
|
@@ -14,20 +13,20 @@ module Zip
|
|
14
13
|
# bits 9-15 year (four digit year minus 1980)
|
15
14
|
|
16
15
|
def to_binary_dos_time
|
17
|
-
(sec/2) +
|
16
|
+
(sec / 2) +
|
18
17
|
(min << 5) +
|
19
18
|
(hour << 11)
|
20
19
|
end
|
21
20
|
|
22
21
|
def to_binary_dos_date
|
23
|
-
|
22
|
+
day +
|
24
23
|
(month << 5) +
|
25
24
|
((year - 1980) << 9)
|
26
25
|
end
|
27
26
|
|
28
27
|
# Dos time is only stored with two seconds accuracy
|
29
28
|
def dos_equals(other)
|
30
|
-
to_i/2 == other.to_i/2
|
29
|
+
to_i / 2 == other.to_i / 2
|
31
30
|
end
|
32
31
|
|
33
32
|
def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
|
@@ -38,7 +37,7 @@ module Zip
|
|
38
37
|
month = (0b111100000 & binaryDosDate) >> 5
|
39
38
|
year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
|
40
39
|
begin
|
41
|
-
|
40
|
+
local(year, month, day, hour, minute, second)
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
data/lib/zip/entry.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'pathname'
|
1
2
|
module Zip
|
2
3
|
class Entry
|
3
4
|
STORED = 0
|
@@ -7,6 +8,7 @@ module Zip
|
|
7
8
|
|
8
9
|
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
9
10
|
:name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
|
11
|
+
:internal_file_attributes,
|
10
12
|
:gp_flags, :header_signature, :follow_symlinks,
|
11
13
|
:restore_times, :restore_permissions, :restore_ownership,
|
12
14
|
:unix_uid, :unix_gid, :unix_perms,
|
@@ -39,15 +41,14 @@ module Zip
|
|
39
41
|
@unix_uid = nil
|
40
42
|
@unix_gid = nil
|
41
43
|
@unix_perms = nil
|
42
|
-
|
43
|
-
|
44
|
+
# @posix_acl = nil
|
45
|
+
# @ntfs_acl = nil
|
44
46
|
@dirty = false
|
45
47
|
end
|
46
48
|
|
47
49
|
def check_name(name)
|
48
|
-
|
49
|
-
|
50
|
-
end
|
50
|
+
return unless name.start_with?('/')
|
51
|
+
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
51
52
|
end
|
52
53
|
|
53
54
|
def initialize(*args)
|
@@ -68,7 +69,7 @@ module Zip
|
|
68
69
|
@time = args[8] || ::Zip::DOSTime.now
|
69
70
|
|
70
71
|
@ftype = name_is_directory? ? :directory : :file
|
71
|
-
@extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField
|
72
|
+
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
|
72
73
|
end
|
73
74
|
|
74
75
|
def time
|
@@ -83,7 +84,7 @@ module Zip
|
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
86
|
-
alias
|
87
|
+
alias mtime time
|
87
88
|
|
88
89
|
def time=(value)
|
89
90
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
@@ -94,12 +95,12 @@ module Zip
|
|
94
95
|
end
|
95
96
|
|
96
97
|
def file_type_is?(type)
|
97
|
-
raise InternalError, "current filetype is unknown: #{
|
98
|
+
raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
|
98
99
|
@ftype == type
|
99
100
|
end
|
100
101
|
|
101
102
|
# Dynamic checkers
|
102
|
-
%w
|
103
|
+
%w[directory file symlink].each do |k|
|
103
104
|
define_method "#{k}?" do
|
104
105
|
file_type_is?(k.to_sym)
|
105
106
|
end
|
@@ -109,6 +110,17 @@ module Zip
|
|
109
110
|
@name.end_with?('/')
|
110
111
|
end
|
111
112
|
|
113
|
+
# Is the name a relative path, free of `..` patterns that could lead to
|
114
|
+
# path traversal attacks? This does NOT handle symlinks; if the path
|
115
|
+
# contains symlinks, this check is NOT enough to guarantee safety.
|
116
|
+
def name_safe?
|
117
|
+
cleanpath = Pathname.new(@name).cleanpath
|
118
|
+
return false unless cleanpath.relative?
|
119
|
+
root = ::File::SEPARATOR
|
120
|
+
naive_expanded_path = ::File.join(root, cleanpath.to_s)
|
121
|
+
::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
|
122
|
+
end
|
123
|
+
|
112
124
|
def local_entry_offset #:nodoc:all
|
113
125
|
local_header_offset + @local_header_size
|
114
126
|
end
|
@@ -143,17 +155,25 @@ module Zip
|
|
143
155
|
end
|
144
156
|
|
145
157
|
def next_header_offset #:nodoc:all
|
146
|
-
local_entry_offset +
|
158
|
+
local_entry_offset + compressed_size + data_descriptor_size
|
147
159
|
end
|
148
160
|
|
149
161
|
# Extracts entry to file dest_path (defaults to @name).
|
150
|
-
|
162
|
+
# NB: The caller is responsible for making sure dest_path is safe, if it
|
163
|
+
# is passed.
|
164
|
+
def extract(dest_path = nil, &block)
|
165
|
+
if dest_path.nil? && !name_safe?
|
166
|
+
puts "WARNING: skipped #{@name} as unsafe"
|
167
|
+
return self
|
168
|
+
end
|
169
|
+
|
170
|
+
dest_path ||= @name
|
151
171
|
block ||= proc { ::Zip.on_exists_proc }
|
152
172
|
|
153
173
|
if directory? || file? || symlink?
|
154
|
-
|
174
|
+
__send__("create_#{@ftype}", dest_path, &block)
|
155
175
|
else
|
156
|
-
raise
|
176
|
+
raise "unknown file type #{inspect}"
|
157
177
|
end
|
158
178
|
|
159
179
|
self
|
@@ -163,8 +183,6 @@ module Zip
|
|
163
183
|
@name
|
164
184
|
end
|
165
185
|
|
166
|
-
protected
|
167
|
-
|
168
186
|
class << self
|
169
187
|
def read_zip_short(io) # :nodoc:
|
170
188
|
io.read(2).unpack('v')[0]
|
@@ -179,11 +197,11 @@ module Zip
|
|
179
197
|
end
|
180
198
|
|
181
199
|
def read_c_dir_entry(io) #:nodoc:all
|
182
|
-
path = if io.
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
200
|
+
path = if io.respond_to?(:path)
|
201
|
+
io.path
|
202
|
+
else
|
203
|
+
io
|
204
|
+
end
|
187
205
|
entry = new(path)
|
188
206
|
entry.read_c_dir_entry(io)
|
189
207
|
entry
|
@@ -192,13 +210,12 @@ module Zip
|
|
192
210
|
end
|
193
211
|
|
194
212
|
def read_local_entry(io)
|
195
|
-
entry =
|
213
|
+
entry = new(io)
|
196
214
|
entry.read_local_entry(io)
|
197
215
|
entry
|
198
216
|
rescue Error
|
199
217
|
nil
|
200
218
|
end
|
201
|
-
|
202
219
|
end
|
203
220
|
|
204
221
|
public
|
@@ -221,10 +238,10 @@ module Zip
|
|
221
238
|
def read_local_entry(io) #:nodoc:all
|
222
239
|
@local_header_offset = io.tell
|
223
240
|
|
224
|
-
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
|
241
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
225
242
|
|
226
243
|
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
227
|
-
raise Error,
|
244
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
228
245
|
end
|
229
246
|
|
230
247
|
unpack_local_entry(static_sized_fields_buf)
|
@@ -237,13 +254,16 @@ module Zip
|
|
237
254
|
@name = io.read(@name_length)
|
238
255
|
extra = io.read(@extra_length)
|
239
256
|
|
240
|
-
@name.
|
257
|
+
@name.tr!('\\', '/')
|
258
|
+
if ::Zip.force_entry_names_encoding
|
259
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
260
|
+
end
|
241
261
|
|
242
262
|
if extra && extra.bytesize != @extra_length
|
243
|
-
raise ::Zip::Error,
|
263
|
+
raise ::Zip::Error, 'Truncated local zip entry header'
|
244
264
|
else
|
245
|
-
if ::Zip::ExtraField
|
246
|
-
@extra.merge(extra)
|
265
|
+
if @extra.is_a?(::Zip::ExtraField)
|
266
|
+
@extra.merge(extra) if extra
|
247
267
|
else
|
248
268
|
@extra = ::Zip::ExtraField.new(extra)
|
249
269
|
end
|
@@ -256,13 +276,13 @@ module Zip
|
|
256
276
|
zip64 = @extra['Zip64']
|
257
277
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
258
278
|
@version_needed_to_extract, # version needed to extract
|
259
|
-
@gp_flags, # @gp_flags
|
279
|
+
@gp_flags, # @gp_flags
|
260
280
|
@compression_method,
|
261
|
-
@time.to_binary_dos_time, # @last_mod_time
|
262
|
-
@time.to_binary_dos_date, # @last_mod_date
|
281
|
+
@time.to_binary_dos_time, # @last_mod_time
|
282
|
+
@time.to_binary_dos_date, # @last_mod_date
|
263
283
|
@crc,
|
264
|
-
|
265
|
-
|
284
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
285
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
266
286
|
name_size,
|
267
287
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
268
288
|
end
|
@@ -306,7 +326,7 @@ module Zip
|
|
306
326
|
def set_ftype_from_c_dir_entry
|
307
327
|
@ftype = case @fstype
|
308
328
|
when ::Zip::FSTYPE_UNIX
|
309
|
-
@unix_perms = (@external_file_attributes >> 16) &
|
329
|
+
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
310
330
|
case (@external_file_attributes >> 28)
|
311
331
|
when ::Zip::FILE_TYPE_DIR
|
312
332
|
:directory
|
@@ -315,8 +335,8 @@ module Zip
|
|
315
335
|
when ::Zip::FILE_TYPE_SYMLINK
|
316
336
|
:symlink
|
317
337
|
else
|
318
|
-
#best case guess for whether it is a file or not
|
319
|
-
#Otherwise this would be set to unknown and that entry would never be able to extracted
|
338
|
+
# best case guess for whether it is a file or not
|
339
|
+
# Otherwise this would be set to unknown and that entry would never be able to extracted
|
320
340
|
if name_is_directory?
|
321
341
|
:directory
|
322
342
|
else
|
@@ -333,21 +353,18 @@ module Zip
|
|
333
353
|
end
|
334
354
|
|
335
355
|
def check_c_dir_entry_static_header_length(buf)
|
336
|
-
|
337
|
-
|
338
|
-
end
|
356
|
+
return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
357
|
+
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
339
358
|
end
|
340
359
|
|
341
360
|
def check_c_dir_entry_signature
|
342
|
-
|
343
|
-
|
344
|
-
end
|
361
|
+
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
362
|
+
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
345
363
|
end
|
346
364
|
|
347
365
|
def check_c_dir_entry_comment_size
|
348
|
-
|
349
|
-
|
350
|
-
end
|
366
|
+
return if @comment && @comment.bytesize == @comment_length
|
367
|
+
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
351
368
|
end
|
352
369
|
|
353
370
|
def read_c_dir_extra_field(io)
|
@@ -364,7 +381,10 @@ module Zip
|
|
364
381
|
unpack_c_dir_entry(static_sized_fields_buf)
|
365
382
|
check_c_dir_entry_signature
|
366
383
|
set_time(@last_mod_date, @last_mod_time)
|
367
|
-
@name = io.read(@name_length)
|
384
|
+
@name = io.read(@name_length)
|
385
|
+
if ::Zip.force_entry_names_encoding
|
386
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
387
|
+
end
|
368
388
|
read_c_dir_extra_field(io)
|
369
389
|
@comment = io.read(@comment_length)
|
370
390
|
check_c_dir_entry_comment_size
|
@@ -374,33 +394,32 @@ module Zip
|
|
374
394
|
|
375
395
|
def file_stat(path) # :nodoc:
|
376
396
|
if @follow_symlinks
|
377
|
-
::File
|
397
|
+
::File.stat(path)
|
378
398
|
else
|
379
|
-
::File
|
399
|
+
::File.lstat(path)
|
380
400
|
end
|
381
401
|
end
|
382
402
|
|
383
403
|
def get_extra_attributes_from_path(path) # :nodoc:
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
end
|
404
|
+
return if Zip::RUNNING_ON_WINDOWS
|
405
|
+
stat = file_stat(path)
|
406
|
+
@unix_uid = stat.uid
|
407
|
+
@unix_gid = stat.gid
|
408
|
+
@unix_perms = stat.mode & 0o7777
|
390
409
|
end
|
391
410
|
|
392
411
|
def set_unix_permissions_on_path(dest_path)
|
393
412
|
# BUG: does not update timestamps into account
|
394
413
|
# ignore setuid/setgid bits by default. honor if @restore_ownership
|
395
|
-
unix_perms_mask =
|
396
|
-
unix_perms_mask =
|
414
|
+
unix_perms_mask = 0o1777
|
415
|
+
unix_perms_mask = 0o7777 if @restore_ownership
|
397
416
|
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
|
398
417
|
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
399
418
|
# File::utimes()
|
400
419
|
end
|
401
420
|
|
402
421
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
403
|
-
return unless
|
422
|
+
return unless file? || directory?
|
404
423
|
|
405
424
|
case @fstype
|
406
425
|
when ::Zip::FSTYPE_UNIX
|
@@ -414,21 +433,21 @@ module Zip
|
|
414
433
|
@header_signature,
|
415
434
|
@version, # version of encoding software
|
416
435
|
@fstype, # filesystem type
|
417
|
-
@version_needed_to_extract, # @versionNeededToExtract
|
418
|
-
@gp_flags, # @gp_flags
|
436
|
+
@version_needed_to_extract, # @versionNeededToExtract
|
437
|
+
@gp_flags, # @gp_flags
|
419
438
|
@compression_method,
|
420
|
-
@time.to_binary_dos_time, # @last_mod_time
|
421
|
-
@time.to_binary_dos_date, # @last_mod_date
|
439
|
+
@time.to_binary_dos_time, # @last_mod_time
|
440
|
+
@time.to_binary_dos_date, # @last_mod_date
|
422
441
|
@crc,
|
423
|
-
|
424
|
-
|
442
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
443
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
425
444
|
name_size,
|
426
445
|
@extra ? @extra.c_dir_size : 0,
|
427
446
|
comment_size,
|
428
|
-
|
447
|
+
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
429
448
|
@internal_file_attributes, # file type (binary=0, text=1)
|
430
449
|
@external_file_attributes, # native filesystem attributes
|
431
|
-
|
450
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
|
432
451
|
@name,
|
433
452
|
@extra,
|
434
453
|
@comment
|
@@ -441,18 +460,18 @@ module Zip
|
|
441
460
|
when ::Zip::FSTYPE_UNIX
|
442
461
|
ft = case @ftype
|
443
462
|
when :file
|
444
|
-
@unix_perms ||=
|
463
|
+
@unix_perms ||= 0o644
|
445
464
|
::Zip::FILE_TYPE_FILE
|
446
465
|
when :directory
|
447
|
-
@unix_perms ||=
|
466
|
+
@unix_perms ||= 0o755
|
448
467
|
::Zip::FILE_TYPE_DIR
|
449
468
|
when :symlink
|
450
|
-
@unix_perms ||=
|
469
|
+
@unix_perms ||= 0o755
|
451
470
|
::Zip::FILE_TYPE_SYMLINK
|
452
471
|
end
|
453
472
|
|
454
473
|
unless ft.nil?
|
455
|
-
@external_file_attributes = (ft << 12 | (@unix_perms &
|
474
|
+
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
456
475
|
end
|
457
476
|
end
|
458
477
|
|
@@ -466,14 +485,14 @@ module Zip
|
|
466
485
|
def ==(other)
|
467
486
|
return false unless other.class == self.class
|
468
487
|
# Compares contents of local entry and exposed fields
|
469
|
-
keys_equal = %w
|
470
|
-
other.__send__(k.to_sym) ==
|
488
|
+
keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
|
489
|
+
other.__send__(k.to_sym) == __send__(k.to_sym)
|
471
490
|
end
|
472
|
-
keys_equal &&
|
491
|
+
keys_equal && time.dos_equals(other.time)
|
473
492
|
end
|
474
493
|
|
475
|
-
def <=>
|
476
|
-
|
494
|
+
def <=>(other)
|
495
|
+
to_s <=> other.to_s
|
477
496
|
end
|
478
497
|
|
479
498
|
# Returns an IO like object for the given ZipEntry.
|
@@ -496,6 +515,7 @@ module Zip
|
|
496
515
|
end
|
497
516
|
else
|
498
517
|
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
518
|
+
zis.instance_variable_set(:@complete_entry, self)
|
499
519
|
zis.get_next_entry
|
500
520
|
if block_given?
|
501
521
|
begin
|
@@ -515,8 +535,8 @@ module Zip
|
|
515
535
|
when 'file'
|
516
536
|
if name_is_directory?
|
517
537
|
raise ArgumentError,
|
518
|
-
"entry name '#{newEntry}' indicates directory entry, but "
|
519
|
-
|
538
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
539
|
+
"'#{src_path}' is not a directory"
|
520
540
|
end
|
521
541
|
:file
|
522
542
|
when 'directory'
|
@@ -525,12 +545,12 @@ module Zip
|
|
525
545
|
when 'link'
|
526
546
|
if name_is_directory?
|
527
547
|
raise ArgumentError,
|
528
|
-
"entry name '#{newEntry}' indicates directory entry, but "
|
529
|
-
|
548
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
549
|
+
"'#{src_path}' is not a directory"
|
530
550
|
end
|
531
551
|
:symlink
|
532
552
|
else
|
533
|
-
raise
|
553
|
+
raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
|
534
554
|
end
|
535
555
|
|
536
556
|
@filepath = src_path
|
@@ -541,7 +561,7 @@ module Zip
|
|
541
561
|
if @ftype == :directory
|
542
562
|
zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
|
543
563
|
elsif @filepath
|
544
|
-
zip_output_stream.put_next_entry(self, nil, nil,
|
564
|
+
zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
|
545
565
|
get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
|
546
566
|
else
|
547
567
|
zip_output_stream.copy_raw_entry(self)
|
@@ -551,14 +571,14 @@ module Zip
|
|
551
571
|
def parent_as_string
|
552
572
|
entry_name = name.chomp('/')
|
553
573
|
slash_index = entry_name.rindex('/')
|
554
|
-
slash_index ? entry_name.slice(0, slash_index+1) : nil
|
574
|
+
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
555
575
|
end
|
556
576
|
|
557
577
|
def get_raw_input_stream(&block)
|
558
|
-
if @zipfile.
|
578
|
+
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
559
579
|
yield @zipfile
|
560
580
|
else
|
561
|
-
::File.open(@zipfile,
|
581
|
+
::File.open(@zipfile, 'rb', &block)
|
562
582
|
end
|
563
583
|
end
|
564
584
|
|
@@ -571,21 +591,33 @@ module Zip
|
|
571
591
|
def set_time(binary_dos_date, binary_dos_time)
|
572
592
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
573
593
|
rescue ArgumentError
|
574
|
-
|
594
|
+
warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
|
575
595
|
end
|
576
596
|
|
577
|
-
def create_file(dest_path,
|
597
|
+
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
578
598
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
579
599
|
raise ::Zip::DestinationFileExistsError,
|
580
600
|
"Destination '#{dest_path}' already exists"
|
581
601
|
end
|
582
|
-
::File.open(dest_path,
|
602
|
+
::File.open(dest_path, 'wb') do |os|
|
583
603
|
get_input_stream do |is|
|
584
604
|
set_extra_attributes_on_path(dest_path)
|
585
605
|
|
586
|
-
|
587
|
-
|
606
|
+
bytes_written = 0
|
607
|
+
warned = false
|
608
|
+
buf = ''.dup
|
609
|
+
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
588
610
|
os << buf
|
611
|
+
bytes_written += buf.bytesize
|
612
|
+
if bytes_written > size && !warned
|
613
|
+
message = "Entry #{name} should be #{size}B but is larger when inflated"
|
614
|
+
if ::Zip.validate_entry_sizes
|
615
|
+
raise ::Zip::EntrySizeError, message
|
616
|
+
else
|
617
|
+
puts "WARNING: #{message}"
|
618
|
+
warned = true
|
619
|
+
end
|
620
|
+
end
|
589
621
|
end
|
590
622
|
end
|
591
623
|
end
|
@@ -595,11 +627,11 @@ module Zip
|
|
595
627
|
return if ::File.directory?(dest_path)
|
596
628
|
if ::File.exist?(dest_path)
|
597
629
|
if block_given? && yield(self, dest_path)
|
598
|
-
::FileUtils
|
630
|
+
::FileUtils.rm_f dest_path
|
599
631
|
else
|
600
632
|
raise ::Zip::DestinationFileExistsError,
|
601
|
-
"Cannot create directory '#{dest_path}'. "
|
602
|
-
|
633
|
+
"Cannot create directory '#{dest_path}'. " \
|
634
|
+
'A file already exists with that name'
|
603
635
|
end
|
604
636
|
end
|
605
637
|
::FileUtils.mkdir_p(dest_path)
|
@@ -608,43 +640,19 @@ module Zip
|
|
608
640
|
|
609
641
|
# BUG: create_symlink() does not use &block
|
610
642
|
def create_symlink(dest_path)
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
rescue Errno::ENOENT
|
615
|
-
end
|
616
|
-
|
617
|
-
io = get_input_stream
|
618
|
-
linkto = io.read
|
619
|
-
|
620
|
-
if stat
|
621
|
-
if stat.symlink?
|
622
|
-
if ::File.readlink(dest_path) == linkto
|
623
|
-
return
|
624
|
-
else
|
625
|
-
raise ::Zip::DestinationFileExistsError,
|
626
|
-
"Cannot create symlink '#{dest_path}'. "+
|
627
|
-
"A symlink already exists with that name"
|
628
|
-
end
|
629
|
-
else
|
630
|
-
raise ::Zip::DestinationFileExistsError,
|
631
|
-
"Cannot create symlink '#{dest_path}'. "+
|
632
|
-
"A file already exists with that name"
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
|
-
::File.symlink(linkto, dest_path)
|
643
|
+
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
644
|
+
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
645
|
+
puts "WARNING: skipped symlink #{dest_path}"
|
637
646
|
end
|
638
647
|
|
639
648
|
# apply missing data from the zip64 extra information field, if present
|
640
649
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
641
650
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
642
|
-
if
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
end
|
651
|
+
return if @extra['Zip64'].nil?
|
652
|
+
if for_local_header
|
653
|
+
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
654
|
+
else
|
655
|
+
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
|
648
656
|
end
|
649
657
|
end
|
650
658
|
|
@@ -656,10 +664,7 @@ module Zip
|
|
656
664
|
def prep_zip64_extra(for_local_header) #:nodoc:all
|
657
665
|
return unless ::Zip.write_zip64_support
|
658
666
|
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
659
|
-
unless for_local_header
|
660
|
-
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
|
661
|
-
end
|
662
|
-
|
667
|
+
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
|
663
668
|
if need_zip64
|
664
669
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
665
670
|
@extra.delete('Zip64Placeholder')
|
@@ -687,7 +692,6 @@ module Zip
|
|
687
692
|
end
|
688
693
|
end
|
689
694
|
end
|
690
|
-
|
691
695
|
end
|
692
696
|
end
|
693
697
|
|