rubyzip 1.1.7 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +137 -54
- data/Rakefile +6 -4
- data/lib/zip/central_directory.rb +17 -13
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +57 -5
- data/lib/zip/crypto/decrypted_io.rb +40 -0
- data/lib/zip/crypto/null_encryption.rb +4 -6
- data/lib/zip/crypto/traditional_encryption.rb +14 -14
- data/lib/zip/decompressor.rb +22 -4
- data/lib/zip/deflater.rb +8 -6
- data/lib/zip/dos_time.rb +17 -13
- data/lib/zip/entry.rb +171 -148
- data/lib/zip/entry_set.rb +16 -14
- data/lib/zip/errors.rb +3 -0
- data/lib/zip/extra_field/generic.rb +14 -13
- data/lib/zip/extra_field/ntfs.rb +18 -16
- data/lib/zip/extra_field/old_unix.rb +12 -11
- data/lib/zip/extra_field/universal_time.rb +46 -16
- data/lib/zip/extra_field/unix.rb +10 -9
- data/lib/zip/extra_field/zip64.rb +15 -12
- data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
- data/lib/zip/extra_field.rb +18 -16
- data/lib/zip/file.rb +147 -115
- data/lib/zip/filesystem.rb +289 -272
- data/lib/zip/inflater.rb +24 -36
- data/lib/zip/input_stream.rb +44 -28
- data/lib/zip/ioextras/abstract_input_stream.rb +24 -17
- data/lib/zip/ioextras/abstract_output_stream.rb +4 -6
- data/lib/zip/ioextras.rb +2 -4
- data/lib/zip/null_compressor.rb +2 -2
- data/lib/zip/null_decompressor.rb +3 -11
- data/lib/zip/null_input_stream.rb +0 -0
- data/lib/zip/output_stream.rb +25 -17
- data/lib/zip/pass_thru_compressor.rb +6 -6
- data/lib/zip/pass_thru_decompressor.rb +14 -24
- data/lib/zip/streamable_directory.rb +3 -3
- data/lib/zip/streamable_stream.rb +7 -11
- data/lib/zip/version.rb +1 -1
- data/lib/zip.rb +15 -6
- data/samples/example.rb +29 -39
- data/samples/example_filesystem.rb +16 -18
- data/samples/example_recursive.rb +31 -25
- data/samples/gtk_ruby_zip.rb +84 -0
- data/samples/qtzip.rb +23 -32
- data/samples/write_simple.rb +10 -13
- data/samples/zipfind.rb +33 -40
- metadata +50 -141
- data/samples/gtkRubyzip.rb +0 -86
- data/test/basic_zip_file_test.rb +0 -64
- data/test/central_directory_entry_test.rb +0 -73
- data/test/central_directory_test.rb +0 -104
- data/test/crypto/null_encryption_test.rb +0 -53
- 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/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +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 -67
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -138
- data/test/entry_test.rb +0 -165
- data/test/errors_test.rb +0 -36
- data/test/extra_field_test.rb +0 -78
- data/test/file_extract_directory_test.rb +0 -56
- data/test/file_extract_test.rb +0 -90
- data/test/file_split_test.rb +0 -60
- data/test/file_test.rb +0 -559
- data/test/filesystem/dir_iterator_test.rb +0 -62
- data/test/filesystem/directory_test.rb +0 -131
- data/test/filesystem/file_mutating_test.rb +0 -100
- data/test/filesystem/file_nonmutating_test.rb +0 -514
- data/test/filesystem/file_stat_test.rb +0 -66
- data/test/gentestfiles.rb +0 -134
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -170
- data/test/ioextras/abstract_input_stream_test.rb +0 -103
- 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 -156
- data/test/output_stream_test.rb +0 -129
- data/test/pass_thru_compressor_test.rb +0 -31
- data/test/pass_thru_decompressor_test.rb +0 -15
- data/test/settings_test.rb +0 -92
- data/test/test_helper.rb +0 -228
- data/test/unicode_file_names_and_comments_test.rb +0 -52
- data/test/zip64_full_test.rb +0 -53
- data/test/zip64_support_test.rb +0 -15
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,
|
@@ -32,22 +34,22 @@ module Zip
|
|
32
34
|
end
|
33
35
|
@follow_symlinks = false
|
34
36
|
|
35
|
-
@restore_times =
|
37
|
+
@restore_times = false
|
36
38
|
@restore_permissions = false
|
37
39
|
@restore_ownership = false
|
38
40
|
# BUG: need an extra field to support uid/gid's
|
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
|
-
|
50
|
+
return unless name.start_with?('/')
|
51
|
+
|
52
|
+
raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
|
51
53
|
end
|
52
54
|
|
53
55
|
def initialize(*args)
|
@@ -68,7 +70,15 @@ module Zip
|
|
68
70
|
@time = args[8] || ::Zip::DOSTime.now
|
69
71
|
|
70
72
|
@ftype = name_is_directory? ? :directory : :file
|
71
|
-
@extra = ::Zip::ExtraField.new(@extra.to_s) unless ::Zip::ExtraField
|
73
|
+
@extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypted?
|
77
|
+
gp_flags & 1 == 1
|
78
|
+
end
|
79
|
+
|
80
|
+
def incomplete?
|
81
|
+
gp_flags & 8 == 8
|
72
82
|
end
|
73
83
|
|
74
84
|
def time
|
@@ -83,23 +93,24 @@ module Zip
|
|
83
93
|
end
|
84
94
|
end
|
85
95
|
|
86
|
-
alias
|
96
|
+
alias mtime time
|
87
97
|
|
88
98
|
def time=(value)
|
89
99
|
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
90
100
|
@extra.create('UniversalTime')
|
91
101
|
end
|
92
102
|
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
93
|
-
@time
|
103
|
+
@time = value
|
94
104
|
end
|
95
105
|
|
96
106
|
def file_type_is?(type)
|
97
|
-
raise InternalError, "current filetype is unknown: #{
|
107
|
+
raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
|
108
|
+
|
98
109
|
@ftype == type
|
99
110
|
end
|
100
111
|
|
101
112
|
# Dynamic checkers
|
102
|
-
%w
|
113
|
+
%w[directory file symlink].each do |k|
|
103
114
|
define_method "#{k}?" do
|
104
115
|
file_type_is?(k.to_sym)
|
105
116
|
end
|
@@ -109,6 +120,18 @@ module Zip
|
|
109
120
|
@name.end_with?('/')
|
110
121
|
end
|
111
122
|
|
123
|
+
# Is the name a relative path, free of `..` patterns that could lead to
|
124
|
+
# path traversal attacks? This does NOT handle symlinks; if the path
|
125
|
+
# contains symlinks, this check is NOT enough to guarantee safety.
|
126
|
+
def name_safe?
|
127
|
+
cleanpath = Pathname.new(@name).cleanpath
|
128
|
+
return false unless cleanpath.relative?
|
129
|
+
|
130
|
+
root = ::File::SEPARATOR
|
131
|
+
naive_expanded_path = ::File.join(root, cleanpath.to_s)
|
132
|
+
::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
|
133
|
+
end
|
134
|
+
|
112
135
|
def local_entry_offset #:nodoc:all
|
113
136
|
local_header_offset + @local_header_size
|
114
137
|
end
|
@@ -133,6 +156,7 @@ module Zip
|
|
133
156
|
# that we didn't change the header size (and thus clobber file data or something)
|
134
157
|
def verify_local_header_size!
|
135
158
|
return if @local_header_size.nil?
|
159
|
+
|
136
160
|
new_size = calculate_local_header_size
|
137
161
|
raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
|
138
162
|
end
|
@@ -143,19 +167,24 @@ module Zip
|
|
143
167
|
end
|
144
168
|
|
145
169
|
def next_header_offset #:nodoc:all
|
146
|
-
local_entry_offset +
|
170
|
+
local_entry_offset + compressed_size + data_descriptor_size
|
147
171
|
end
|
148
172
|
|
149
173
|
# Extracts entry to file dest_path (defaults to @name).
|
150
|
-
|
174
|
+
# NB: The caller is responsible for making sure dest_path is safe, if it
|
175
|
+
# is passed.
|
176
|
+
def extract(dest_path = nil, &block)
|
177
|
+
if dest_path.nil? && !name_safe?
|
178
|
+
warn "WARNING: skipped '#{@name}' as unsafe."
|
179
|
+
return self
|
180
|
+
end
|
181
|
+
|
182
|
+
dest_path ||= @name
|
151
183
|
block ||= proc { ::Zip.on_exists_proc }
|
152
184
|
|
153
|
-
|
154
|
-
self.__send__("create_#{@ftype}", dest_path, &block)
|
155
|
-
else
|
156
|
-
raise RuntimeError, "unknown file type #{self.inspect}"
|
157
|
-
end
|
185
|
+
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
158
186
|
|
187
|
+
__send__("create_#{@ftype}", dest_path, &block)
|
159
188
|
self
|
160
189
|
end
|
161
190
|
|
@@ -163,27 +192,25 @@ module Zip
|
|
163
192
|
@name
|
164
193
|
end
|
165
194
|
|
166
|
-
protected
|
167
|
-
|
168
195
|
class << self
|
169
196
|
def read_zip_short(io) # :nodoc:
|
170
|
-
io.read(2).
|
197
|
+
io.read(2).unpack1('v')
|
171
198
|
end
|
172
199
|
|
173
200
|
def read_zip_long(io) # :nodoc:
|
174
|
-
io.read(4).
|
201
|
+
io.read(4).unpack1('V')
|
175
202
|
end
|
176
203
|
|
177
204
|
def read_zip_64_long(io) # :nodoc:
|
178
|
-
io.read(8).
|
205
|
+
io.read(8).unpack1('Q<')
|
179
206
|
end
|
180
207
|
|
181
208
|
def read_c_dir_entry(io) #:nodoc:all
|
182
|
-
path = if io.
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
209
|
+
path = if io.respond_to?(:path)
|
210
|
+
io.path
|
211
|
+
else
|
212
|
+
io
|
213
|
+
end
|
187
214
|
entry = new(path)
|
188
215
|
entry.read_c_dir_entry(io)
|
189
216
|
entry
|
@@ -192,17 +219,14 @@ module Zip
|
|
192
219
|
end
|
193
220
|
|
194
221
|
def read_local_entry(io)
|
195
|
-
entry =
|
222
|
+
entry = new(io)
|
196
223
|
entry.read_local_entry(io)
|
197
224
|
entry
|
198
225
|
rescue Error
|
199
226
|
nil
|
200
227
|
end
|
201
|
-
|
202
228
|
end
|
203
229
|
|
204
|
-
public
|
205
|
-
|
206
230
|
def unpack_local_entry(buf)
|
207
231
|
@header_signature,
|
208
232
|
@version,
|
@@ -221,10 +245,10 @@ module Zip
|
|
221
245
|
def read_local_entry(io) #:nodoc:all
|
222
246
|
@local_header_offset = io.tell
|
223
247
|
|
224
|
-
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH)
|
248
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
225
249
|
|
226
250
|
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
227
|
-
raise Error,
|
251
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
228
252
|
end
|
229
253
|
|
230
254
|
unpack_local_entry(static_sized_fields_buf)
|
@@ -232,22 +256,27 @@ module Zip
|
|
232
256
|
unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
|
233
257
|
raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
234
258
|
end
|
259
|
+
|
235
260
|
set_time(@last_mod_date, @last_mod_time)
|
236
261
|
|
237
262
|
@name = io.read(@name_length)
|
238
263
|
extra = io.read(@extra_length)
|
239
264
|
|
240
|
-
@name.
|
265
|
+
@name.tr!('\\', '/')
|
266
|
+
if ::Zip.force_entry_names_encoding
|
267
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
268
|
+
end
|
241
269
|
|
242
270
|
if extra && extra.bytesize != @extra_length
|
243
|
-
raise ::Zip::Error,
|
271
|
+
raise ::Zip::Error, 'Truncated local zip entry header'
|
272
|
+
end
|
273
|
+
|
274
|
+
if @extra.kind_of?(::Zip::ExtraField)
|
275
|
+
@extra.merge(extra) if extra
|
244
276
|
else
|
245
|
-
|
246
|
-
@extra.merge(extra)
|
247
|
-
else
|
248
|
-
@extra = ::Zip::ExtraField.new(extra)
|
249
|
-
end
|
277
|
+
@extra = ::Zip::ExtraField.new(extra)
|
250
278
|
end
|
279
|
+
|
251
280
|
parse_zip64_extra(true)
|
252
281
|
@local_header_size = calculate_local_header_size
|
253
282
|
end
|
@@ -256,13 +285,13 @@ module Zip
|
|
256
285
|
zip64 = @extra['Zip64']
|
257
286
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
258
287
|
@version_needed_to_extract, # version needed to extract
|
259
|
-
@gp_flags, # @gp_flags
|
288
|
+
@gp_flags, # @gp_flags
|
260
289
|
@compression_method,
|
261
|
-
@time.to_binary_dos_time, # @last_mod_time
|
262
|
-
@time.to_binary_dos_date, # @last_mod_date
|
290
|
+
@time.to_binary_dos_time, # @last_mod_time
|
291
|
+
@time.to_binary_dos_date, # @last_mod_date
|
263
292
|
@crc,
|
264
|
-
|
265
|
-
|
293
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
294
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
266
295
|
name_size,
|
267
296
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
268
297
|
end
|
@@ -306,7 +335,7 @@ module Zip
|
|
306
335
|
def set_ftype_from_c_dir_entry
|
307
336
|
@ftype = case @fstype
|
308
337
|
when ::Zip::FSTYPE_UNIX
|
309
|
-
@unix_perms = (@external_file_attributes >> 16) &
|
338
|
+
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
310
339
|
case (@external_file_attributes >> 28)
|
311
340
|
when ::Zip::FILE_TYPE_DIR
|
312
341
|
:directory
|
@@ -315,8 +344,8 @@ module Zip
|
|
315
344
|
when ::Zip::FILE_TYPE_SYMLINK
|
316
345
|
:symlink
|
317
346
|
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
|
347
|
+
# best case guess for whether it is a file or not
|
348
|
+
# Otherwise this would be set to unknown and that entry would never be able to extracted
|
320
349
|
if name_is_directory?
|
321
350
|
:directory
|
322
351
|
else
|
@@ -333,25 +362,25 @@ module Zip
|
|
333
362
|
end
|
334
363
|
|
335
364
|
def check_c_dir_entry_static_header_length(buf)
|
336
|
-
|
337
|
-
|
338
|
-
end
|
365
|
+
return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
366
|
+
|
367
|
+
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
339
368
|
end
|
340
369
|
|
341
370
|
def check_c_dir_entry_signature
|
342
|
-
|
343
|
-
|
344
|
-
|
371
|
+
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
372
|
+
|
373
|
+
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
345
374
|
end
|
346
375
|
|
347
376
|
def check_c_dir_entry_comment_size
|
348
|
-
|
349
|
-
|
350
|
-
|
377
|
+
return if @comment && @comment.bytesize == @comment_length
|
378
|
+
|
379
|
+
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
351
380
|
end
|
352
381
|
|
353
382
|
def read_c_dir_extra_field(io)
|
354
|
-
if @extra.
|
383
|
+
if @extra.kind_of?(::Zip::ExtraField)
|
355
384
|
@extra.merge(io.read(@extra_length))
|
356
385
|
else
|
357
386
|
@extra = ::Zip::ExtraField.new(io.read(@extra_length))
|
@@ -364,7 +393,10 @@ module Zip
|
|
364
393
|
unpack_c_dir_entry(static_sized_fields_buf)
|
365
394
|
check_c_dir_entry_signature
|
366
395
|
set_time(@last_mod_date, @last_mod_time)
|
367
|
-
@name = io.read(@name_length)
|
396
|
+
@name = io.read(@name_length)
|
397
|
+
if ::Zip.force_entry_names_encoding
|
398
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
399
|
+
end
|
368
400
|
read_c_dir_extra_field(io)
|
369
401
|
@comment = io.read(@comment_length)
|
370
402
|
check_c_dir_entry_comment_size
|
@@ -374,37 +406,41 @@ module Zip
|
|
374
406
|
|
375
407
|
def file_stat(path) # :nodoc:
|
376
408
|
if @follow_symlinks
|
377
|
-
::File
|
409
|
+
::File.stat(path)
|
378
410
|
else
|
379
|
-
::File
|
411
|
+
::File.lstat(path)
|
380
412
|
end
|
381
413
|
end
|
382
414
|
|
383
415
|
def get_extra_attributes_from_path(path) # :nodoc:
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
416
|
+
return if Zip::RUNNING_ON_WINDOWS
|
417
|
+
|
418
|
+
stat = file_stat(path)
|
419
|
+
@unix_uid = stat.uid
|
420
|
+
@unix_gid = stat.gid
|
421
|
+
@unix_perms = stat.mode & 0o7777
|
422
|
+
@time = ::Zip::DOSTime.from_time(stat.mtime)
|
390
423
|
end
|
391
424
|
|
392
|
-
def
|
393
|
-
# BUG: does not update timestamps into account
|
425
|
+
def set_unix_attributes_on_path(dest_path)
|
394
426
|
# ignore setuid/setgid bits by default. honor if @restore_ownership
|
395
|
-
unix_perms_mask =
|
396
|
-
unix_perms_mask =
|
427
|
+
unix_perms_mask = 0o1777
|
428
|
+
unix_perms_mask = 0o7777 if @restore_ownership
|
397
429
|
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
|
398
430
|
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
399
|
-
|
431
|
+
|
432
|
+
# Restore the timestamp on a file. This will either have come from the
|
433
|
+
# original source file that was copied into the archive, or from the
|
434
|
+
# creation date of the archive if there was no original source file.
|
435
|
+
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
400
436
|
end
|
401
437
|
|
402
438
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
403
|
-
return unless
|
439
|
+
return unless file? || directory?
|
404
440
|
|
405
441
|
case @fstype
|
406
442
|
when ::Zip::FSTYPE_UNIX
|
407
|
-
|
443
|
+
set_unix_attributes_on_path(dest_path)
|
408
444
|
end
|
409
445
|
end
|
410
446
|
|
@@ -414,21 +450,21 @@ module Zip
|
|
414
450
|
@header_signature,
|
415
451
|
@version, # version of encoding software
|
416
452
|
@fstype, # filesystem type
|
417
|
-
@version_needed_to_extract, # @versionNeededToExtract
|
418
|
-
@gp_flags, # @gp_flags
|
453
|
+
@version_needed_to_extract, # @versionNeededToExtract
|
454
|
+
@gp_flags, # @gp_flags
|
419
455
|
@compression_method,
|
420
|
-
@time.to_binary_dos_time, # @last_mod_time
|
421
|
-
@time.to_binary_dos_date, # @last_mod_date
|
456
|
+
@time.to_binary_dos_time, # @last_mod_time
|
457
|
+
@time.to_binary_dos_date, # @last_mod_date
|
422
458
|
@crc,
|
423
|
-
|
424
|
-
|
459
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
460
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
425
461
|
name_size,
|
426
462
|
@extra ? @extra.c_dir_size : 0,
|
427
463
|
comment_size,
|
428
|
-
|
464
|
+
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
429
465
|
@internal_file_attributes, # file type (binary=0, text=1)
|
430
466
|
@external_file_attributes, # native filesystem attributes
|
431
|
-
|
467
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
|
432
468
|
@name,
|
433
469
|
@extra,
|
434
470
|
@comment
|
@@ -441,18 +477,18 @@ module Zip
|
|
441
477
|
when ::Zip::FSTYPE_UNIX
|
442
478
|
ft = case @ftype
|
443
479
|
when :file
|
444
|
-
@unix_perms ||=
|
480
|
+
@unix_perms ||= 0o644
|
445
481
|
::Zip::FILE_TYPE_FILE
|
446
482
|
when :directory
|
447
|
-
@unix_perms ||=
|
483
|
+
@unix_perms ||= 0o755
|
448
484
|
::Zip::FILE_TYPE_DIR
|
449
485
|
when :symlink
|
450
|
-
@unix_perms ||=
|
486
|
+
@unix_perms ||= 0o755
|
451
487
|
::Zip::FILE_TYPE_SYMLINK
|
452
488
|
end
|
453
489
|
|
454
490
|
unless ft.nil?
|
455
|
-
@external_file_attributes = (ft << 12 | (@unix_perms &
|
491
|
+
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
456
492
|
end
|
457
493
|
end
|
458
494
|
|
@@ -465,15 +501,16 @@ module Zip
|
|
465
501
|
|
466
502
|
def ==(other)
|
467
503
|
return false unless other.class == self.class
|
504
|
+
|
468
505
|
# Compares contents of local entry and exposed fields
|
469
|
-
keys_equal = %w
|
470
|
-
other.__send__(k.to_sym) ==
|
506
|
+
keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
|
507
|
+
other.__send__(k.to_sym) == __send__(k.to_sym)
|
471
508
|
end
|
472
|
-
keys_equal &&
|
509
|
+
keys_equal && time.dos_equals(other.time)
|
473
510
|
end
|
474
511
|
|
475
|
-
def <=>
|
476
|
-
|
512
|
+
def <=>(other)
|
513
|
+
to_s <=> other.to_s
|
477
514
|
end
|
478
515
|
|
479
516
|
# Returns an IO like object for the given ZipEntry.
|
@@ -496,6 +533,7 @@ module Zip
|
|
496
533
|
end
|
497
534
|
else
|
498
535
|
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
536
|
+
zis.instance_variable_set(:@complete_entry, self)
|
499
537
|
zis.get_next_entry
|
500
538
|
if block_given?
|
501
539
|
begin
|
@@ -515,8 +553,8 @@ module Zip
|
|
515
553
|
when 'file'
|
516
554
|
if name_is_directory?
|
517
555
|
raise ArgumentError,
|
518
|
-
"entry name '#{newEntry}' indicates directory entry, but "
|
519
|
-
|
556
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
557
|
+
"'#{src_path}' is not a directory"
|
520
558
|
end
|
521
559
|
:file
|
522
560
|
when 'directory'
|
@@ -525,12 +563,12 @@ module Zip
|
|
525
563
|
when 'link'
|
526
564
|
if name_is_directory?
|
527
565
|
raise ArgumentError,
|
528
|
-
"entry name '#{newEntry}' indicates directory entry, but "
|
529
|
-
|
566
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
567
|
+
"'#{src_path}' is not a directory"
|
530
568
|
end
|
531
569
|
:symlink
|
532
570
|
else
|
533
|
-
raise
|
571
|
+
raise "unknown file type: #{src_path.inspect} #{stat.inspect}"
|
534
572
|
end
|
535
573
|
|
536
574
|
@filepath = src_path
|
@@ -541,7 +579,7 @@ module Zip
|
|
541
579
|
if @ftype == :directory
|
542
580
|
zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED)
|
543
581
|
elsif @filepath
|
544
|
-
zip_output_stream.put_next_entry(self, nil, nil,
|
582
|
+
zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED)
|
545
583
|
get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) }
|
546
584
|
else
|
547
585
|
zip_output_stream.copy_raw_entry(self)
|
@@ -551,14 +589,14 @@ module Zip
|
|
551
589
|
def parent_as_string
|
552
590
|
entry_name = name.chomp('/')
|
553
591
|
slash_index = entry_name.rindex('/')
|
554
|
-
slash_index ? entry_name.slice(0, slash_index+1) : nil
|
592
|
+
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
555
593
|
end
|
556
594
|
|
557
595
|
def get_raw_input_stream(&block)
|
558
|
-
if @zipfile.
|
596
|
+
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
559
597
|
yield @zipfile
|
560
598
|
else
|
561
|
-
::File.open(@zipfile,
|
599
|
+
::File.open(@zipfile, 'rb', &block)
|
562
600
|
end
|
563
601
|
end
|
564
602
|
|
@@ -571,35 +609,46 @@ module Zip
|
|
571
609
|
def set_time(binary_dos_date, binary_dos_time)
|
572
610
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
573
611
|
rescue ArgumentError
|
574
|
-
|
612
|
+
warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
|
575
613
|
end
|
576
614
|
|
577
|
-
def create_file(dest_path,
|
615
|
+
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
578
616
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
579
617
|
raise ::Zip::DestinationFileExistsError,
|
580
618
|
"Destination '#{dest_path}' already exists"
|
581
619
|
end
|
582
|
-
::File.open(dest_path,
|
620
|
+
::File.open(dest_path, 'wb') do |os|
|
583
621
|
get_input_stream do |is|
|
584
|
-
|
585
|
-
|
586
|
-
buf = ''
|
587
|
-
while buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)
|
622
|
+
bytes_written = 0
|
623
|
+
warned = false
|
624
|
+
buf = +''
|
625
|
+
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
588
626
|
os << buf
|
627
|
+
bytes_written += buf.bytesize
|
628
|
+
next unless bytes_written > size && !warned
|
629
|
+
|
630
|
+
message = "entry '#{name}' should be #{size}B, but is larger when inflated."
|
631
|
+
raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes
|
632
|
+
|
633
|
+
warn "WARNING: #{message}"
|
634
|
+
warned = true
|
589
635
|
end
|
590
636
|
end
|
591
637
|
end
|
638
|
+
|
639
|
+
set_extra_attributes_on_path(dest_path)
|
592
640
|
end
|
593
641
|
|
594
642
|
def create_directory(dest_path)
|
595
643
|
return if ::File.directory?(dest_path)
|
644
|
+
|
596
645
|
if ::File.exist?(dest_path)
|
597
646
|
if block_given? && yield(self, dest_path)
|
598
|
-
::FileUtils
|
647
|
+
::FileUtils.rm_f dest_path
|
599
648
|
else
|
600
649
|
raise ::Zip::DestinationFileExistsError,
|
601
|
-
"Cannot create directory '#{dest_path}'. "
|
602
|
-
|
650
|
+
"Cannot create directory '#{dest_path}'. " \
|
651
|
+
'A file already exists with that name'
|
603
652
|
end
|
604
653
|
end
|
605
654
|
::FileUtils.mkdir_p(dest_path)
|
@@ -608,43 +657,20 @@ module Zip
|
|
608
657
|
|
609
658
|
# BUG: create_symlink() does not use &block
|
610
659
|
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)
|
660
|
+
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
661
|
+
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
662
|
+
warn "WARNING: skipped symlink '#{dest_path}'."
|
637
663
|
end
|
638
664
|
|
639
665
|
# apply missing data from the zip64 extra information field, if present
|
640
666
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
641
667
|
def parse_zip64_extra(for_local_header) #:nodoc:all
|
642
|
-
if
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
668
|
+
return if @extra['Zip64'].nil?
|
669
|
+
|
670
|
+
if for_local_header
|
671
|
+
@size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
|
672
|
+
else
|
673
|
+
@size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset)
|
648
674
|
end
|
649
675
|
end
|
650
676
|
|
@@ -655,11 +681,9 @@ module Zip
|
|
655
681
|
# create a zip64 extra information field if we need one
|
656
682
|
def prep_zip64_extra(for_local_header) #:nodoc:all
|
657
683
|
return unless ::Zip.write_zip64_support
|
658
|
-
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
659
|
-
unless for_local_header
|
660
|
-
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF
|
661
|
-
end
|
662
684
|
|
685
|
+
need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
686
|
+
need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
|
663
687
|
if need_zip64
|
664
688
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
665
689
|
@extra.delete('Zip64Placeholder')
|
@@ -687,7 +711,6 @@ module Zip
|
|
687
711
|
end
|
688
712
|
end
|
689
713
|
end
|
690
|
-
|
691
714
|
end
|
692
715
|
end
|
693
716
|
|