rubyzip 1.1.7 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubyzip might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/README.md +87 -45
- data/Rakefile +3 -4
- data/lib/zip.rb +11 -5
- 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 +120 -128
- data/lib/zip/entry_set.rb +14 -14
- data/lib/zip/errors.rb +1 -0
- data/lib/zip/extra_field.rb +8 -8
- 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/file.rb +81 -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.rb +1 -3
- data/lib/zip/ioextras/abstract_input_stream.rb +6 -10
- data/lib/zip/ioextras/abstract_output_stream.rb +3 -5
- 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/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 +33 -40
- data/test/file_permissions_test.rb +65 -0
- data/test/file_split_test.rb +24 -27
- data/test/file_test.rb +266 -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 +94 -65
@@ -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,20 +591,20 @@ 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
|
-
buf = ''
|
587
|
-
while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
|
606
|
+
buf = ''.dup
|
607
|
+
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
588
608
|
os << buf
|
589
609
|
end
|
590
610
|
end
|
@@ -595,11 +615,11 @@ module Zip
|
|
595
615
|
return if ::File.directory?(dest_path)
|
596
616
|
if ::File.exist?(dest_path)
|
597
617
|
if block_given? && yield(self, dest_path)
|
598
|
-
::FileUtils
|
618
|
+
::FileUtils.rm_f dest_path
|
599
619
|
else
|
600
620
|
raise ::Zip::DestinationFileExistsError,
|
601
|
-
"Cannot create directory '#{dest_path}'. "
|
602
|
-
|
621
|
+
"Cannot create directory '#{dest_path}'. " \
|
622
|
+
'A file already exists with that name'
|
603
623
|
end
|
604
624
|
end
|
605
625
|
::FileUtils.mkdir_p(dest_path)
|
@@ -608,43 +628,19 @@ module Zip
|
|
608
628
|
|
609
629
|
# BUG: create_symlink() does not use &block
|
610
630
|
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)
|
631
|
+
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
632
|
+
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
633
|
+
puts "WARNING: skipped symlink #{dest_path}"
|
637
634
|
end
|
638
635
|
|
639
636
|
# apply missing data from the zip64 extra information field, if present
|
640
637
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
641
638
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
642
|
-
if
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
end
|
639
|
+
return if @extra['Zip64'].nil?
|
640
|
+
if for_local_header
|
641
|
+
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
642
|
+
else
|
643
|
+
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
|
648
644
|
end
|
649
645
|
end
|
650
646
|
|
@@ -656,10 +652,7 @@ module Zip
|
|
656
652
|
def prep_zip64_extra(for_local_header) #:nodoc:all
|
657
653
|
return unless ::Zip.write_zip64_support
|
658
654
|
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
659
|
-
unless for_local_header
|
660
|
-
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
|
661
|
-
end
|
662
|
-
|
655
|
+
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
|
663
656
|
if need_zip64
|
664
657
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
665
658
|
@extra.delete('Zip64Placeholder')
|
@@ -687,7 +680,6 @@ module Zip
|
|
687
680
|
end
|
688
681
|
end
|
689
682
|
end
|
690
|
-
|
691
683
|
end
|
692
684
|
end
|
693
685
|
|