rubyzip 1.1.7 → 2.3.2
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 +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
|
|