rubyzip 1.2.0 → 1.3.0
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 +95 -43
- data/lib/zip.rb +11 -1
- data/lib/zip/central_directory.rb +3 -3
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +3 -3
- data/lib/zip/crypto/null_encryption.rb +2 -4
- data/lib/zip/decompressor.rb +1 -1
- data/lib/zip/dos_time.rb +1 -1
- data/lib/zip/entry.rb +70 -54
- data/lib/zip/entry_set.rb +4 -4
- data/lib/zip/errors.rb +1 -0
- data/lib/zip/extra_field.rb +2 -2
- data/lib/zip/extra_field/generic.rb +1 -1
- data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
- data/lib/zip/file.rb +62 -51
- data/lib/zip/filesystem.rb +17 -13
- data/lib/zip/inflater.rb +2 -2
- data/lib/zip/input_stream.rb +10 -7
- data/lib/zip/ioextras/abstract_input_stream.rb +1 -1
- data/lib/zip/ioextras/abstract_output_stream.rb +3 -3
- data/lib/zip/output_stream.rb +5 -5
- data/lib/zip/pass_thru_decompressor.rb +1 -1
- data/lib/zip/streamable_stream.rb +1 -1
- data/lib/zip/version.rb +1 -1
- data/samples/example_recursive.rb +15 -18
- data/samples/gtk_ruby_zip.rb +1 -1
- data/samples/qtzip.rb +1 -1
- data/samples/zipfind.rb +2 -2
- data/test/central_directory_entry_test.rb +2 -2
- data/test/crypto/null_encryption_test.rb +6 -2
- data/test/data/gpbit3stored.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/entry_set_test.rb +13 -2
- data/test/entry_test.rb +3 -12
- data/test/errors_test.rb +1 -0
- data/test/file_extract_test.rb +62 -0
- data/test/file_permissions_test.rb +39 -43
- data/test/file_test.rb +115 -12
- data/test/filesystem/dir_iterator_test.rb +1 -1
- data/test/filesystem/directory_test.rb +29 -11
- data/test/filesystem/file_mutating_test.rb +3 -4
- data/test/filesystem/file_nonmutating_test.rb +34 -34
- data/test/filesystem/file_stat_test.rb +5 -5
- data/test/gentestfiles.rb +17 -13
- data/test/input_stream_test.rb +10 -10
- data/test/ioextras/abstract_input_stream_test.rb +1 -1
- data/test/ioextras/abstract_output_stream_test.rb +2 -2
- data/test/ioextras/fake_io_test.rb +1 -1
- data/test/local_entry_test.rb +1 -1
- data/test/path_traversal_test.rb +141 -0
- data/test/test_helper.rb +16 -3
- data/test/unicode_file_names_and_comments_test.rb +12 -0
- data/test/zip64_full_test.rb +2 -2
- metadata +103 -51
data/lib/zip/entry_set.rb
CHANGED
@@ -5,7 +5,7 @@ module Zip
|
|
5
5
|
|
6
6
|
def initialize(an_enumerable = [])
|
7
7
|
super()
|
8
|
-
@entry_set
|
8
|
+
@entry_set = {}
|
9
9
|
an_enumerable.each { |o| push(o) }
|
10
10
|
end
|
11
11
|
|
@@ -33,9 +33,9 @@ module Zip
|
|
33
33
|
entry if @entry_set.delete(to_key(entry))
|
34
34
|
end
|
35
35
|
|
36
|
-
def each
|
36
|
+
def each
|
37
37
|
@entry_set = sorted_entries.dup.each do |_, value|
|
38
|
-
|
38
|
+
yield(value)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -57,7 +57,7 @@ module Zip
|
|
57
57
|
@entry_set[to_key(entry.parent_as_string)]
|
58
58
|
end
|
59
59
|
|
60
|
-
def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH)
|
60
|
+
def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
|
61
61
|
entries.map do |entry|
|
62
62
|
next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
|
63
63
|
yield(entry) if block_given?
|
data/lib/zip/errors.rb
CHANGED
data/lib/zip/extra_field.rb
CHANGED
@@ -8,7 +8,7 @@ module Zip
|
|
8
8
|
|
9
9
|
def extra_field_type_exist(binstr, id, len, i)
|
10
10
|
field_name = ID_MAP[id].name
|
11
|
-
if
|
11
|
+
if member?(field_name)
|
12
12
|
self[field_name].merge(binstr[i, len + 4])
|
13
13
|
else
|
14
14
|
field_obj = ID_MAP[id].new(binstr[i, len + 4])
|
@@ -26,7 +26,7 @@ module Zip
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def create_unknown_item
|
29
|
-
s = ''
|
29
|
+
s = ''.dup
|
30
30
|
class << s
|
31
31
|
alias_method :to_c_dir_bin, :to_s
|
32
32
|
alias_method :to_local_bin, :to_s
|
data/lib/zip/file.rb
CHANGED
@@ -43,7 +43,7 @@ module Zip
|
|
43
43
|
# interface for accessing the filesystem, ie. the File and Dir classes.
|
44
44
|
|
45
45
|
class File < CentralDirectory
|
46
|
-
CREATE =
|
46
|
+
CREATE = true
|
47
47
|
SPLIT_SIGNATURE = 0x08074b50
|
48
48
|
ZIP64_EOCD_SIGNATURE = 0x06064b50
|
49
49
|
MAX_SEGMENT_SIZE = 3_221_225_472
|
@@ -64,26 +64,38 @@ module Zip
|
|
64
64
|
|
65
65
|
# Opens a zip archive. Pass true as the second parameter to create
|
66
66
|
# a new archive if it doesn't exist already.
|
67
|
-
def initialize(
|
67
|
+
def initialize(path_or_io, create = false, buffer = false, options = {})
|
68
68
|
super()
|
69
|
-
@name =
|
69
|
+
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
70
70
|
@comment = ''
|
71
|
-
@create = create
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@
|
76
|
-
::File.
|
77
|
-
|
71
|
+
@create = create ? true : false # allow any truthy value to mean true
|
72
|
+
|
73
|
+
if ::File.size?(@name.to_s)
|
74
|
+
# There is a file, which exists, that is associated with this zip.
|
75
|
+
@create = false
|
76
|
+
@file_permissions = ::File.stat(@name).mode
|
77
|
+
|
78
|
+
if buffer
|
79
|
+
read_from_stream(path_or_io)
|
80
|
+
else
|
81
|
+
::File.open(@name, 'rb') do |f|
|
82
|
+
read_from_stream(f)
|
83
|
+
end
|
78
84
|
end
|
79
|
-
|
80
|
-
|
85
|
+
elsif buffer && path_or_io.size > 0
|
86
|
+
# This zip is probably a non-empty StringIO.
|
87
|
+
read_from_stream(path_or_io)
|
88
|
+
elsif @create
|
89
|
+
# This zip is completely new/empty and is to be created.
|
81
90
|
@entry_set = EntrySet.new
|
82
|
-
|
83
|
-
|
91
|
+
elsif ::File.zero?(@name)
|
92
|
+
# A file exists, but it is empty.
|
93
|
+
raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
|
84
94
|
else
|
85
|
-
|
95
|
+
# Everything is wrong.
|
96
|
+
raise Error, "File #{@name} not found"
|
86
97
|
end
|
98
|
+
|
87
99
|
@stored_entries = @entry_set.dup
|
88
100
|
@stored_comment = @comment
|
89
101
|
@restore_ownership = options[:restore_ownership] || false
|
@@ -95,7 +107,7 @@ module Zip
|
|
95
107
|
# Same as #new. If a block is passed the ZipFile object is passed
|
96
108
|
# to the block and is automatically closed afterwards just as with
|
97
109
|
# ruby's builtin File.open method.
|
98
|
-
def open(file_name, create =
|
110
|
+
def open(file_name, create = false)
|
99
111
|
zf = ::Zip::File.new(file_name, create)
|
100
112
|
return zf unless block_given?
|
101
113
|
begin
|
@@ -121,16 +133,16 @@ module Zip
|
|
121
133
|
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
|
122
134
|
raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
123
135
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
end
|
136
|
+
|
137
|
+
io = ::StringIO.new(io) if io.is_a?(::String)
|
138
|
+
|
139
|
+
# https://github.com/rubyzip/rubyzip/issues/119
|
140
|
+
io.binmode if io.respond_to?(:binmode)
|
141
|
+
|
131
142
|
zf = ::Zip::File.new(io, true, true, options)
|
132
|
-
zf
|
143
|
+
return zf unless block_given?
|
133
144
|
yield zf
|
145
|
+
|
134
146
|
begin
|
135
147
|
zf.write_buffer(io)
|
136
148
|
rescue IOError => e
|
@@ -151,10 +163,9 @@ module Zip
|
|
151
163
|
end
|
152
164
|
|
153
165
|
def get_segment_size_for_split(segment_size)
|
154
|
-
|
155
|
-
when MIN_SEGMENT_SIZE > segment_size
|
166
|
+
if MIN_SEGMENT_SIZE > segment_size
|
156
167
|
MIN_SEGMENT_SIZE
|
157
|
-
|
168
|
+
elsif MAX_SEGMENT_SIZE < segment_size
|
158
169
|
MAX_SEGMENT_SIZE
|
159
170
|
else
|
160
171
|
segment_size
|
@@ -162,8 +173,10 @@ module Zip
|
|
162
173
|
end
|
163
174
|
|
164
175
|
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
165
|
-
partial_zip_file_name
|
166
|
-
|
176
|
+
unless partial_zip_file_name.nil?
|
177
|
+
partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
|
178
|
+
partial_zip_file_name + ::File.extname(zip_file_name))
|
179
|
+
end
|
167
180
|
partial_zip_file_name ||= zip_file_name
|
168
181
|
partial_zip_file_name
|
169
182
|
end
|
@@ -237,7 +250,7 @@ module Zip
|
|
237
250
|
# specified. If a block is passed the stream object is passed to the block and
|
238
251
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
239
252
|
# File.open method.
|
240
|
-
def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil,
|
253
|
+
def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
|
241
254
|
new_entry =
|
242
255
|
if entry.kind_of?(Entry)
|
243
256
|
entry
|
@@ -274,6 +287,13 @@ module Zip
|
|
274
287
|
@entry_set << new_entry
|
275
288
|
end
|
276
289
|
|
290
|
+
# Convenience method for adding the contents of a file to the archive
|
291
|
+
# in Stored format (uncompressed)
|
292
|
+
def add_stored(entry, src_path, &continue_on_exists_proc)
|
293
|
+
entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
|
294
|
+
add(entry, src_path, &continue_on_exists_proc)
|
295
|
+
end
|
296
|
+
|
277
297
|
# Removes the specified entry.
|
278
298
|
def remove(entry)
|
279
299
|
@entry_set.delete(get_entry(entry))
|
@@ -306,7 +326,7 @@ module Zip
|
|
306
326
|
# Commits changes that has been made since the previous commit to
|
307
327
|
# the zip archive.
|
308
328
|
def commit
|
309
|
-
return
|
329
|
+
return if name.is_a?(StringIO) || !commit_required?
|
310
330
|
on_success_replace do |tmp_file|
|
311
331
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
312
332
|
@entry_set.each do |e|
|
@@ -340,7 +360,7 @@ module Zip
|
|
340
360
|
@entry_set.each do |e|
|
341
361
|
return true if e.dirty
|
342
362
|
end
|
343
|
-
@comment != @stored_comment || @entry_set != @stored_entries || @create
|
363
|
+
@comment != @stored_comment || @entry_set != @stored_entries || @create
|
344
364
|
end
|
345
365
|
|
346
366
|
# Searches for entry with the specified name. Returns nil if
|
@@ -366,7 +386,7 @@ module Zip
|
|
366
386
|
end
|
367
387
|
|
368
388
|
# Creates a directory
|
369
|
-
def mkdir(entryName, permissionInt =
|
389
|
+
def mkdir(entryName, permissionInt = 0o755)
|
370
390
|
raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
|
371
391
|
entryName = entryName.dup.to_s
|
372
392
|
entryName << '/' unless entryName.end_with?('/')
|
@@ -403,27 +423,18 @@ module Zip
|
|
403
423
|
end
|
404
424
|
|
405
425
|
def on_success_replace
|
406
|
-
tmp_filename = create_tmpname
|
407
|
-
if yield tmp_filename
|
408
|
-
::File.rename(tmp_filename, name)
|
409
|
-
::File.chmod(@file_permissions, name) if defined?(@file_permissions)
|
410
|
-
end
|
411
|
-
ensure
|
412
|
-
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
413
|
-
end
|
414
|
-
|
415
|
-
def create_tmpname
|
416
426
|
dirname, basename = ::File.split(name)
|
417
|
-
::Dir::Tmpname.create(basename, dirname) do |
|
418
|
-
|
419
|
-
|
420
|
-
|
427
|
+
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
428
|
+
begin
|
429
|
+
if yield tmp_filename
|
430
|
+
::File.rename(tmp_filename, name)
|
431
|
+
::File.chmod(@file_permissions, name) unless @create
|
432
|
+
end
|
433
|
+
ensure
|
434
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
435
|
+
end
|
421
436
|
end
|
422
437
|
end
|
423
|
-
|
424
|
-
def create_file_permissions
|
425
|
-
::Zip::RUNNING_ON_WINDOWS ? 0644 : 0666 - ::File.umask
|
426
|
-
end
|
427
438
|
end
|
428
439
|
end
|
429
440
|
|
data/lib/zip/filesystem.rb
CHANGED
@@ -142,9 +142,9 @@ module Zip
|
|
142
142
|
|
143
143
|
def ftype
|
144
144
|
if file?
|
145
|
-
|
145
|
+
'file'
|
146
146
|
elsif directory?
|
147
|
-
|
147
|
+
'directory'
|
148
148
|
else
|
149
149
|
raise StandardError, 'Unknown file type'
|
150
150
|
end
|
@@ -198,30 +198,30 @@ module Zip
|
|
198
198
|
alias grpowned? exists?
|
199
199
|
|
200
200
|
def readable?(fileName)
|
201
|
-
unix_mode_cmp(fileName,
|
201
|
+
unix_mode_cmp(fileName, 0o444)
|
202
202
|
end
|
203
203
|
alias readable_real? readable?
|
204
204
|
|
205
205
|
def writable?(fileName)
|
206
|
-
unix_mode_cmp(fileName,
|
206
|
+
unix_mode_cmp(fileName, 0o222)
|
207
207
|
end
|
208
208
|
alias writable_real? writable?
|
209
209
|
|
210
210
|
def executable?(fileName)
|
211
|
-
unix_mode_cmp(fileName,
|
211
|
+
unix_mode_cmp(fileName, 0o111)
|
212
212
|
end
|
213
213
|
alias executable_real? executable?
|
214
214
|
|
215
215
|
def setuid?(fileName)
|
216
|
-
unix_mode_cmp(fileName,
|
216
|
+
unix_mode_cmp(fileName, 0o4000)
|
217
217
|
end
|
218
218
|
|
219
219
|
def setgid?(fileName)
|
220
|
-
unix_mode_cmp(fileName,
|
220
|
+
unix_mode_cmp(fileName, 0o2000)
|
221
221
|
end
|
222
222
|
|
223
223
|
def sticky?(fileName)
|
224
|
-
unix_mode_cmp(fileName,
|
224
|
+
unix_mode_cmp(fileName, 0o1000)
|
225
225
|
end
|
226
226
|
|
227
227
|
def umask(*args)
|
@@ -237,8 +237,8 @@ module Zip
|
|
237
237
|
expand_path(fileName) == '/' || (!entry.nil? && entry.directory?)
|
238
238
|
end
|
239
239
|
|
240
|
-
def open(fileName, openMode = 'r', permissionInt =
|
241
|
-
openMode.
|
240
|
+
def open(fileName, openMode = 'r', permissionInt = 0o644, &block)
|
241
|
+
openMode.delete!('b') # ignore b option
|
242
242
|
case openMode
|
243
243
|
when 'r'
|
244
244
|
@mappedZip.get_input_stream(fileName, &block)
|
@@ -260,7 +260,7 @@ module Zip
|
|
260
260
|
# Returns nil for not found and nil for directories
|
261
261
|
def size?(fileName)
|
262
262
|
entry = @mappedZip.find_entry(fileName)
|
263
|
-
|
263
|
+
entry.nil? || entry.directory? ? nil : entry.size
|
264
264
|
end
|
265
265
|
|
266
266
|
def chown(ownerInt, groupInt, *filenames)
|
@@ -498,7 +498,7 @@ module Zip
|
|
498
498
|
alias rmdir delete
|
499
499
|
alias unlink delete
|
500
500
|
|
501
|
-
def mkdir(entryName, permissionInt =
|
501
|
+
def mkdir(entryName, permissionInt = 0o755)
|
502
502
|
@mappedZip.mkdir(entryName, permissionInt)
|
503
503
|
end
|
504
504
|
|
@@ -573,6 +573,10 @@ module Zip
|
|
573
573
|
@zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc)
|
574
574
|
end
|
575
575
|
|
576
|
+
def glob(pattern, *flags, &block)
|
577
|
+
@zipFile.glob(expand_to_entry(pattern), *flags, &block)
|
578
|
+
end
|
579
|
+
|
576
580
|
def read(fileName)
|
577
581
|
@zipFile.read(expand_to_entry(fileName))
|
578
582
|
end
|
@@ -586,7 +590,7 @@ module Zip
|
|
586
590
|
&continueOnExistsProc)
|
587
591
|
end
|
588
592
|
|
589
|
-
def mkdir(fileName, permissionInt =
|
593
|
+
def mkdir(fileName, permissionInt = 0o755)
|
590
594
|
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
|
591
595
|
end
|
592
596
|
|
data/lib/zip/inflater.rb
CHANGED
@@ -3,9 +3,9 @@ module Zip
|
|
3
3
|
def initialize(input_stream, decrypter = NullDecrypter.new)
|
4
4
|
super(input_stream)
|
5
5
|
@zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
6
|
-
@output_buffer = ''
|
6
|
+
@output_buffer = ''.dup
|
7
7
|
@has_returned_empty_string = false
|
8
|
-
@decrypter
|
8
|
+
@decrypter = decrypter
|
9
9
|
end
|
10
10
|
|
11
11
|
def sysread(number_of_bytes = nil, buf = '')
|
data/lib/zip/input_stream.rb
CHANGED
@@ -129,23 +129,26 @@ module Zip
|
|
129
129
|
end
|
130
130
|
if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \
|
131
131
|
&& @current_entry.compressed_size == 0 \
|
132
|
-
&& @current_entry.size == 0 && !@
|
132
|
+
&& @current_entry.size == 0 && !@complete_entry
|
133
133
|
raise GPFBit3Error,
|
134
134
|
'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \
|
135
135
|
'Please use ::Zip::File instead of ::Zip::InputStream'
|
136
136
|
end
|
137
|
-
@decompressor
|
137
|
+
@decompressor = get_decompressor
|
138
138
|
flush
|
139
139
|
@current_entry
|
140
140
|
end
|
141
141
|
|
142
142
|
def get_decompressor
|
143
|
-
|
144
|
-
when @current_entry.nil?
|
143
|
+
if @current_entry.nil?
|
145
144
|
::Zip::NullDecompressor
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
elsif @current_entry.compression_method == ::Zip::Entry::STORED
|
146
|
+
if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
|
147
|
+
::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size)
|
148
|
+
else
|
149
|
+
::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size)
|
150
|
+
end
|
151
|
+
elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED
|
149
152
|
header = @archive_io.read(@decrypter.header_bytesize)
|
150
153
|
@decrypter.reset!(header)
|
151
154
|
::Zip::Inflater.new(@archive_io, @decrypter)
|
@@ -15,17 +15,17 @@ module Zip
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def printf(a_format_string, *params)
|
18
|
-
self <<
|
18
|
+
self << format(a_format_string, *params)
|
19
19
|
end
|
20
20
|
|
21
21
|
def putc(an_object)
|
22
22
|
self << case an_object
|
23
|
-
when
|
23
|
+
when Integer
|
24
24
|
an_object.chr
|
25
25
|
when String
|
26
26
|
an_object
|
27
27
|
else
|
28
|
-
raise TypeError, 'putc: Only
|
28
|
+
raise TypeError, 'putc: Only Integer and String supported'
|
29
29
|
end
|
30
30
|
an_object
|
31
31
|
end
|