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
data/lib/zip/file.rb
CHANGED
@@ -43,13 +43,13 @@ module Zip
|
|
43
43
|
# interface for accessing the filesystem, ie. the File and Dir classes.
|
44
44
|
|
45
45
|
class File < CentralDirectory
|
46
|
-
|
47
|
-
CREATE = 1
|
46
|
+
CREATE = true
|
48
47
|
SPLIT_SIGNATURE = 0x08074b50
|
49
48
|
ZIP64_EOCD_SIGNATURE = 0x06064b50
|
50
|
-
MAX_SEGMENT_SIZE =
|
51
|
-
MIN_SEGMENT_SIZE =
|
49
|
+
MAX_SEGMENT_SIZE = 3_221_225_472
|
50
|
+
MIN_SEGMENT_SIZE = 65_536
|
52
51
|
DATA_BUFFER_SIZE = 8192
|
52
|
+
IO_METHODS = [:tell, :seek, :read, :close]
|
53
53
|
|
54
54
|
attr_reader :name
|
55
55
|
|
@@ -64,23 +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
|
-
|
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.
|
80
90
|
@entry_set = EntrySet.new
|
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?"
|
81
94
|
else
|
82
|
-
|
95
|
+
# Everything is wrong.
|
96
|
+
raise Error, "File #{@name} not found"
|
83
97
|
end
|
98
|
+
|
84
99
|
@stored_entries = @entry_set.dup
|
85
100
|
@stored_comment = @comment
|
86
101
|
@restore_ownership = options[:restore_ownership] || false
|
@@ -92,7 +107,7 @@ module Zip
|
|
92
107
|
# Same as #new. If a block is passed the ZipFile object is passed
|
93
108
|
# to the block and is automatically closed afterwards just as with
|
94
109
|
# ruby's builtin File.open method.
|
95
|
-
def open(file_name, create =
|
110
|
+
def open(file_name, create = false)
|
96
111
|
zf = ::Zip::File.new(file_name, create)
|
97
112
|
return zf unless block_given?
|
98
113
|
begin
|
@@ -115,23 +130,23 @@ module Zip
|
|
115
130
|
# (This can be used to extract data from a
|
116
131
|
# downloaded zip archive without first saving it to disk.)
|
117
132
|
def open_buffer(io, options = {})
|
118
|
-
unless
|
119
|
-
raise "Zip::File.open_buffer expects
|
120
|
-
end
|
121
|
-
if io.is_a?(::String)
|
122
|
-
require 'stringio'
|
123
|
-
io = ::StringIO.new(io)
|
124
|
-
elsif io.is_a?(IO)
|
125
|
-
# https://github.com/rubyzip/rubyzip/issues/119
|
126
|
-
io.binmode
|
133
|
+
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
|
134
|
+
raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
127
135
|
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
|
+
|
128
142
|
zf = ::Zip::File.new(io, true, true, options)
|
129
|
-
zf
|
143
|
+
return zf unless block_given?
|
130
144
|
yield zf
|
145
|
+
|
131
146
|
begin
|
132
147
|
zf.write_buffer(io)
|
133
148
|
rescue IOError => e
|
134
|
-
raise unless e.message ==
|
149
|
+
raise unless e.message == 'not opened for writing'
|
135
150
|
end
|
136
151
|
end
|
137
152
|
|
@@ -148,10 +163,9 @@ module Zip
|
|
148
163
|
end
|
149
164
|
|
150
165
|
def get_segment_size_for_split(segment_size)
|
151
|
-
|
152
|
-
when MIN_SEGMENT_SIZE > segment_size
|
166
|
+
if MIN_SEGMENT_SIZE > segment_size
|
153
167
|
MIN_SEGMENT_SIZE
|
154
|
-
|
168
|
+
elsif MAX_SEGMENT_SIZE < segment_size
|
155
169
|
MAX_SEGMENT_SIZE
|
156
170
|
else
|
157
171
|
segment_size
|
@@ -159,8 +173,10 @@ module Zip
|
|
159
173
|
end
|
160
174
|
|
161
175
|
def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
162
|
-
partial_zip_file_name
|
163
|
-
|
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
|
164
180
|
partial_zip_file_name ||= zip_file_name
|
165
181
|
partial_zip_file_name
|
166
182
|
end
|
@@ -181,7 +197,7 @@ module Zip
|
|
181
197
|
def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
|
182
198
|
ssegment_size = zip_file_size - zip_file.pos
|
183
199
|
ssegment_size = segment_size if ssegment_size > segment_size
|
184
|
-
szip_file_name = "#{partial_zip_file_name}.#{'%03d'
|
200
|
+
szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
|
185
201
|
::File.open(szip_file_name, 'wb') do |szip_file|
|
186
202
|
if szip_file_index == 1
|
187
203
|
ssegment_size = put_split_signature(szip_file, segment_size)
|
@@ -191,7 +207,7 @@ module Zip
|
|
191
207
|
segment_bytes_left = ssegment_size - chunk_bytes
|
192
208
|
buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
|
193
209
|
chunk = zip_file.read(buffer_size)
|
194
|
-
chunk_bytes
|
210
|
+
chunk_bytes += buffer_size
|
195
211
|
szip_file << chunk
|
196
212
|
# Info for track splitting
|
197
213
|
yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
|
@@ -208,7 +224,7 @@ module Zip
|
|
208
224
|
return if zip_file_size <= segment_size
|
209
225
|
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
210
226
|
# Checking for correct zip structure
|
211
|
-
|
227
|
+
open(zip_file_name) {}
|
212
228
|
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
213
229
|
szip_file_index = 0
|
214
230
|
::File.open(zip_file_name, 'rb') do |zip_file|
|
@@ -222,7 +238,6 @@ module Zip
|
|
222
238
|
end
|
223
239
|
end
|
224
240
|
|
225
|
-
|
226
241
|
# Returns an input stream to the specified entry. If a block is passed
|
227
242
|
# the stream object is passed to the block and the stream is automatically
|
228
243
|
# closed afterwards just as with ruby's builtin File.open method.
|
@@ -235,7 +250,7 @@ module Zip
|
|
235
250
|
# specified. If a block is passed the stream object is passed to the block and
|
236
251
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
237
252
|
# File.open method.
|
238
|
-
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)
|
239
254
|
new_entry =
|
240
255
|
if entry.kind_of?(Entry)
|
241
256
|
entry
|
@@ -265,7 +280,7 @@ module Zip
|
|
265
280
|
# Convenience method for adding the contents of a file to the archive
|
266
281
|
def add(entry, src_path, &continue_on_exists_proc)
|
267
282
|
continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
|
268
|
-
check_entry_exists(entry, continue_on_exists_proc,
|
283
|
+
check_entry_exists(entry, continue_on_exists_proc, 'add')
|
269
284
|
new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
|
270
285
|
new_entry.gather_fileinfo_from_srcpath(src_path)
|
271
286
|
new_entry.dirty = true
|
@@ -304,7 +319,7 @@ module Zip
|
|
304
319
|
# Commits changes that has been made since the previous commit to
|
305
320
|
# the zip archive.
|
306
321
|
def commit
|
307
|
-
return
|
322
|
+
return if name.is_a?(StringIO) || !commit_required?
|
308
323
|
on_success_replace do |tmp_file|
|
309
324
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
310
325
|
@entry_set.each do |e|
|
@@ -338,7 +353,7 @@ module Zip
|
|
338
353
|
@entry_set.each do |e|
|
339
354
|
return true if e.dirty
|
340
355
|
end
|
341
|
-
@comment != @stored_comment || @entry_set != @stored_entries || @create
|
356
|
+
@comment != @stored_comment || @entry_set != @stored_entries || @create
|
342
357
|
end
|
343
358
|
|
344
359
|
# Searches for entry with the specified name. Returns nil if
|
@@ -356,9 +371,7 @@ module Zip
|
|
356
371
|
# if no entry is found.
|
357
372
|
def get_entry(entry)
|
358
373
|
selected_entry = find_entry(entry)
|
359
|
-
unless selected_entry
|
360
|
-
raise Errno::ENOENT, entry
|
361
|
-
end
|
374
|
+
raise Errno::ENOENT, entry unless selected_entry
|
362
375
|
selected_entry.restore_ownership = @restore_ownership
|
363
376
|
selected_entry.restore_permissions = @restore_permissions
|
364
377
|
selected_entry.restore_times = @restore_times
|
@@ -366,10 +379,8 @@ module Zip
|
|
366
379
|
end
|
367
380
|
|
368
381
|
# Creates a directory
|
369
|
-
def mkdir(entryName, permissionInt =
|
370
|
-
if find_entry(entryName)
|
371
|
-
raise Errno::EEXIST, "File exists - #{entryName}"
|
372
|
-
end
|
382
|
+
def mkdir(entryName, permissionInt = 0o755)
|
383
|
+
raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
|
373
384
|
entryName = entryName.dup.to_s
|
374
385
|
entryName << '/' unless entryName.end_with?('/')
|
375
386
|
@entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt)
|
@@ -377,57 +388,46 @@ module Zip
|
|
377
388
|
|
378
389
|
private
|
379
390
|
|
380
|
-
def
|
391
|
+
def directory?(newEntry, srcPath)
|
381
392
|
srcPathIsDirectory = ::File.directory?(srcPath)
|
382
|
-
if newEntry.
|
393
|
+
if newEntry.directory? && !srcPathIsDirectory
|
383
394
|
raise ArgumentError,
|
384
|
-
"entry name '#{newEntry}' indicates directory entry, but "
|
385
|
-
|
386
|
-
elsif !newEntry.
|
387
|
-
newEntry.name +=
|
395
|
+
"entry name '#{newEntry}' indicates directory entry, but " \
|
396
|
+
"'#{srcPath}' is not a directory"
|
397
|
+
elsif !newEntry.directory? && srcPathIsDirectory
|
398
|
+
newEntry.name += '/'
|
388
399
|
end
|
389
|
-
newEntry.
|
400
|
+
newEntry.directory? && srcPathIsDirectory
|
390
401
|
end
|
391
402
|
|
392
403
|
def check_entry_exists(entryName, continue_on_exists_proc, procedureName)
|
393
404
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
end
|
405
|
+
return unless @entry_set.include?(entryName)
|
406
|
+
if continue_on_exists_proc.call
|
407
|
+
remove get_entry(entryName)
|
408
|
+
else
|
409
|
+
raise ::Zip::EntryExistsError,
|
410
|
+
procedureName + " failed. Entry #{entryName} already exists"
|
401
411
|
end
|
402
412
|
end
|
403
413
|
|
404
414
|
def check_file(path)
|
405
|
-
unless ::File.readable?(path)
|
406
|
-
raise Errno::ENOENT, path
|
407
|
-
end
|
415
|
+
raise Errno::ENOENT, path unless ::File.readable?(path)
|
408
416
|
end
|
409
417
|
|
410
418
|
def on_success_replace
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
+
dirname, basename = ::File.split(name)
|
420
|
+
::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
|
421
|
+
begin
|
422
|
+
if yield tmp_filename
|
423
|
+
::File.rename(tmp_filename, name)
|
424
|
+
::File.chmod(@file_permissions, name) unless @create
|
425
|
+
end
|
426
|
+
ensure
|
427
|
+
::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
|
419
428
|
end
|
420
429
|
end
|
421
|
-
ensure
|
422
|
-
tmpfile.unlink if tmpfile
|
423
430
|
end
|
424
|
-
|
425
|
-
def get_tempfile
|
426
|
-
temp_file = Tempfile.new(::File.basename(name), ::File.dirname(name))
|
427
|
-
temp_file.binmode
|
428
|
-
temp_file
|
429
|
-
end
|
430
|
-
|
431
431
|
end
|
432
432
|
end
|
433
433
|
|
data/lib/zip/filesystem.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
1
|
require 'zip'
|
2
2
|
|
3
3
|
module Zip
|
4
|
-
|
5
|
-
#
|
6
|
-
# a zip archive that is similar to ruby's builtin File and Dir
|
4
|
+
# The ZipFileSystem API provides an API for accessing entries in
|
5
|
+
# a zip archive that is similar to ruby's builtin File and Dir
|
7
6
|
# classes.
|
8
7
|
#
|
9
8
|
# Requiring 'zip/filesystem' includes this module in Zip::File
|
10
9
|
# making the methods in this module available on Zip::File objects.
|
11
10
|
#
|
12
|
-
# Using this API the following example creates a new zip file
|
11
|
+
# Using this API the following example creates a new zip file
|
13
12
|
# <code>my.zip</code> containing a normal entry with the name
|
14
13
|
# <code>first.txt</code>, a directory entry named <code>mydir</code>
|
15
14
|
# and finally another normal entry named <code>second.txt</code>
|
16
15
|
#
|
17
16
|
# require 'zip/filesystem'
|
18
|
-
#
|
17
|
+
#
|
19
18
|
# Zip::File.open("my.zip", Zip::File::CREATE) {
|
20
19
|
# |zipfile|
|
21
20
|
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
|
@@ -23,19 +22,18 @@ module Zip
|
|
23
22
|
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
|
24
23
|
# }
|
25
24
|
#
|
26
|
-
# Reading is as easy as writing, as the following example shows. The
|
25
|
+
# Reading is as easy as writing, as the following example shows. The
|
27
26
|
# example writes the contents of <code>first.txt</code> from zip archive
|
28
27
|
# <code>my.zip</code> to standard out.
|
29
28
|
#
|
30
29
|
# require 'zip/filesystem'
|
31
|
-
#
|
30
|
+
#
|
32
31
|
# Zip::File.open("my.zip") {
|
33
32
|
# |zipfile|
|
34
33
|
# puts zipfile.file.read("first.txt")
|
35
34
|
# }
|
36
35
|
|
37
36
|
module FileSystem
|
38
|
-
|
39
37
|
def initialize # :nodoc:
|
40
38
|
mappedZip = ZipFileNameMapper.new(self)
|
41
39
|
@zipFsDir = ZipFsDir.new(mappedZip)
|
@@ -65,24 +63,20 @@ module Zip
|
|
65
63
|
# The individual methods are not documented due to their
|
66
64
|
# similarity with the methods in File
|
67
65
|
class ZipFsFile
|
68
|
-
|
69
66
|
attr_writer :dir
|
70
|
-
#
|
67
|
+
# protected :dir
|
71
68
|
|
72
69
|
class ZipFsStat
|
73
|
-
|
74
70
|
class << self
|
75
|
-
|
76
71
|
def delegate_to_fs_file(*methods)
|
77
72
|
methods.each do |method|
|
78
|
-
|
73
|
+
class_eval <<-end_eval, __FILE__, __LINE__ + 1
|
79
74
|
def #{method} # def file?
|
80
75
|
@zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName)
|
81
76
|
end # end
|
82
77
|
end_eval
|
83
78
|
end
|
84
79
|
end
|
85
|
-
|
86
80
|
end
|
87
81
|
|
88
82
|
def initialize(zipFsFile, entryName)
|
@@ -91,15 +85,17 @@ module Zip
|
|
91
85
|
end
|
92
86
|
|
93
87
|
def kind_of?(t)
|
94
|
-
super || t == ::File::Stat
|
88
|
+
super || t == ::File::Stat
|
95
89
|
end
|
96
90
|
|
97
91
|
delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?,
|
98
|
-
|
99
|
-
|
100
|
-
|
92
|
+
:socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime,
|
93
|
+
:writable_real?, :executable?, :executable_real?, :sticky?, :owned?,
|
94
|
+
:grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime
|
101
95
|
|
102
|
-
def blocks
|
96
|
+
def blocks
|
97
|
+
nil
|
98
|
+
end
|
103
99
|
|
104
100
|
def get_entry
|
105
101
|
@zipFsFile.__send__(:get_entry, @entryName)
|
@@ -108,8 +104,8 @@ module Zip
|
|
108
104
|
|
109
105
|
def gid
|
110
106
|
e = get_entry
|
111
|
-
if e.extra.member?
|
112
|
-
e.extra[
|
107
|
+
if e.extra.member? 'IUnix'
|
108
|
+
e.extra['IUnix'].gid || 0
|
113
109
|
else
|
114
110
|
0
|
115
111
|
end
|
@@ -117,43 +113,57 @@ module Zip
|
|
117
113
|
|
118
114
|
def uid
|
119
115
|
e = get_entry
|
120
|
-
if e.extra.member?
|
121
|
-
e.extra[
|
116
|
+
if e.extra.member? 'IUnix'
|
117
|
+
e.extra['IUnix'].uid || 0
|
122
118
|
else
|
123
119
|
0
|
124
120
|
end
|
125
121
|
end
|
126
122
|
|
127
|
-
def ino
|
123
|
+
def ino
|
124
|
+
0
|
125
|
+
end
|
128
126
|
|
129
|
-
def dev
|
127
|
+
def dev
|
128
|
+
0
|
129
|
+
end
|
130
130
|
|
131
|
-
def rdev
|
131
|
+
def rdev
|
132
|
+
0
|
133
|
+
end
|
132
134
|
|
133
|
-
def rdev_major
|
135
|
+
def rdev_major
|
136
|
+
0
|
137
|
+
end
|
134
138
|
|
135
|
-
def rdev_minor
|
139
|
+
def rdev_minor
|
140
|
+
0
|
141
|
+
end
|
136
142
|
|
137
143
|
def ftype
|
138
144
|
if file?
|
139
|
-
|
145
|
+
'file'
|
140
146
|
elsif directory?
|
141
|
-
|
147
|
+
'directory'
|
142
148
|
else
|
143
|
-
raise StandardError,
|
149
|
+
raise StandardError, 'Unknown file type'
|
144
150
|
end
|
145
151
|
end
|
146
152
|
|
147
|
-
def nlink
|
153
|
+
def nlink
|
154
|
+
1
|
155
|
+
end
|
148
156
|
|
149
|
-
def blksize
|
157
|
+
def blksize
|
158
|
+
nil
|
159
|
+
end
|
150
160
|
|
151
161
|
def mode
|
152
162
|
e = get_entry
|
153
163
|
if e.fstype == 3
|
154
164
|
e.external_file_attributes >> 16
|
155
165
|
else
|
156
|
-
|
166
|
+
33_206 # 33206 is equivalent to -rw-rw-rw-
|
157
167
|
end
|
158
168
|
end
|
159
169
|
end
|
@@ -163,7 +173,7 @@ module Zip
|
|
163
173
|
end
|
164
174
|
|
165
175
|
def get_entry(fileName)
|
166
|
-
|
176
|
+
unless exists?(fileName)
|
167
177
|
raise Errno::ENOENT, "No such file or directory - #{fileName}"
|
168
178
|
end
|
169
179
|
@mappedZip.find_entry(fileName)
|
@@ -171,77 +181,75 @@ module Zip
|
|
171
181
|
private :get_entry
|
172
182
|
|
173
183
|
def unix_mode_cmp(fileName, mode)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
false
|
179
|
-
end
|
184
|
+
e = get_entry(fileName)
|
185
|
+
e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0
|
186
|
+
rescue Errno::ENOENT
|
187
|
+
false
|
180
188
|
end
|
181
189
|
private :unix_mode_cmp
|
182
190
|
|
183
191
|
def exists?(fileName)
|
184
|
-
expand_path(fileName) ==
|
192
|
+
expand_path(fileName) == '/' || !@mappedZip.find_entry(fileName).nil?
|
185
193
|
end
|
186
|
-
alias
|
194
|
+
alias exist? exists?
|
187
195
|
|
188
196
|
# Permissions not implemented, so if the file exists it is accessible
|
189
|
-
alias owned?
|
190
|
-
alias grpowned?
|
197
|
+
alias owned? exists?
|
198
|
+
alias grpowned? exists?
|
191
199
|
|
192
200
|
def readable?(fileName)
|
193
|
-
unix_mode_cmp(fileName,
|
201
|
+
unix_mode_cmp(fileName, 0o444)
|
194
202
|
end
|
195
|
-
alias readable_real?
|
203
|
+
alias readable_real? readable?
|
196
204
|
|
197
205
|
def writable?(fileName)
|
198
|
-
unix_mode_cmp(fileName,
|
206
|
+
unix_mode_cmp(fileName, 0o222)
|
199
207
|
end
|
200
|
-
alias writable_real?
|
208
|
+
alias writable_real? writable?
|
201
209
|
|
202
210
|
def executable?(fileName)
|
203
|
-
unix_mode_cmp(fileName,
|
211
|
+
unix_mode_cmp(fileName, 0o111)
|
204
212
|
end
|
205
213
|
alias executable_real? executable?
|
206
214
|
|
207
215
|
def setuid?(fileName)
|
208
|
-
unix_mode_cmp(fileName,
|
216
|
+
unix_mode_cmp(fileName, 0o4000)
|
209
217
|
end
|
210
218
|
|
211
219
|
def setgid?(fileName)
|
212
|
-
unix_mode_cmp(fileName,
|
220
|
+
unix_mode_cmp(fileName, 0o2000)
|
213
221
|
end
|
214
222
|
|
215
223
|
def sticky?(fileName)
|
216
|
-
unix_mode_cmp(fileName,
|
224
|
+
unix_mode_cmp(fileName, 0o1000)
|
217
225
|
end
|
218
226
|
|
219
227
|
def umask(*args)
|
220
228
|
::File.umask(*args)
|
221
229
|
end
|
222
230
|
|
223
|
-
def truncate(
|
224
|
-
raise StandardError,
|
231
|
+
def truncate(_fileName, _len)
|
232
|
+
raise StandardError, 'truncate not supported'
|
225
233
|
end
|
226
234
|
|
227
235
|
def directory?(fileName)
|
228
236
|
entry = @mappedZip.find_entry(fileName)
|
229
|
-
expand_path(fileName) ==
|
237
|
+
expand_path(fileName) == '/' || (!entry.nil? && entry.directory?)
|
230
238
|
end
|
231
239
|
|
232
|
-
def open(fileName, openMode =
|
233
|
-
openMode.
|
240
|
+
def open(fileName, openMode = 'r', permissionInt = 0o644, &block)
|
241
|
+
openMode.delete!('b') # ignore b option
|
234
242
|
case openMode
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
243
|
+
when 'r'
|
244
|
+
@mappedZip.get_input_stream(fileName, &block)
|
245
|
+
when 'w'
|
246
|
+
@mappedZip.get_output_stream(fileName, permissionInt, &block)
|
247
|
+
else
|
248
|
+
raise StandardError, "openmode '#{openMode} not supported" unless openMode == 'r'
|
241
249
|
end
|
242
250
|
end
|
243
251
|
|
244
|
-
def new(fileName, openMode =
|
252
|
+
def new(fileName, openMode = 'r')
|
245
253
|
open(fileName, openMode)
|
246
254
|
end
|
247
255
|
|
@@ -252,43 +260,41 @@ module Zip
|
|
252
260
|
# Returns nil for not found and nil for directories
|
253
261
|
def size?(fileName)
|
254
262
|
entry = @mappedZip.find_entry(fileName)
|
255
|
-
|
263
|
+
entry.nil? || entry.directory? ? nil : entry.size
|
256
264
|
end
|
257
265
|
|
258
266
|
def chown(ownerInt, groupInt, *filenames)
|
259
|
-
filenames.each
|
267
|
+
filenames.each do |fileName|
|
260
268
|
e = get_entry(fileName)
|
261
|
-
unless e.extra.member?(
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
e.extra["IUnix"].gid = groupInt
|
266
|
-
}
|
269
|
+
e.extra.create('IUnix') unless e.extra.member?('IUnix')
|
270
|
+
e.extra['IUnix'].uid = ownerInt
|
271
|
+
e.extra['IUnix'].gid = groupInt
|
272
|
+
end
|
267
273
|
filenames.size
|
268
274
|
end
|
269
275
|
|
270
|
-
def chmod
|
271
|
-
filenames.each
|
276
|
+
def chmod(modeInt, *filenames)
|
277
|
+
filenames.each do |fileName|
|
272
278
|
e = get_entry(fileName)
|
273
279
|
e.fstype = 3 # force convertion filesystem type to unix
|
274
280
|
e.unix_perms = modeInt
|
275
281
|
e.external_file_attributes = modeInt << 16
|
276
282
|
e.dirty = true
|
277
|
-
|
283
|
+
end
|
278
284
|
filenames.size
|
279
285
|
end
|
280
286
|
|
281
287
|
def zero?(fileName)
|
282
288
|
sz = size(fileName)
|
283
|
-
sz
|
289
|
+
sz.nil? || sz == 0
|
284
290
|
rescue Errno::ENOENT
|
285
291
|
false
|
286
292
|
end
|
287
293
|
|
288
294
|
def file?(fileName)
|
289
295
|
entry = @mappedZip.find_entry(fileName)
|
290
|
-
entry
|
291
|
-
end
|
296
|
+
!entry.nil? && entry.file?
|
297
|
+
end
|
292
298
|
|
293
299
|
def dirname(fileName)
|
294
300
|
::File.dirname(fileName)
|
@@ -307,9 +313,9 @@ module Zip
|
|
307
313
|
end
|
308
314
|
|
309
315
|
def utime(modifiedTime, *fileNames)
|
310
|
-
fileNames.each
|
316
|
+
fileNames.each do |fileName|
|
311
317
|
get_entry(fileName).time = modifiedTime
|
312
|
-
|
318
|
+
end
|
313
319
|
end
|
314
320
|
|
315
321
|
def mtime(fileName)
|
@@ -318,70 +324,64 @@ module Zip
|
|
318
324
|
|
319
325
|
def atime(fileName)
|
320
326
|
e = get_entry(fileName)
|
321
|
-
if e.extra.member?
|
322
|
-
e.extra[
|
323
|
-
elsif e.extra.member?
|
324
|
-
e.extra[
|
325
|
-
else
|
326
|
-
nil
|
327
|
+
if e.extra.member? 'UniversalTime'
|
328
|
+
e.extra['UniversalTime'].atime
|
329
|
+
elsif e.extra.member? 'NTFS'
|
330
|
+
e.extra['NTFS'].atime
|
327
331
|
end
|
328
332
|
end
|
329
333
|
|
330
334
|
def ctime(fileName)
|
331
335
|
e = get_entry(fileName)
|
332
|
-
if e.extra.member?
|
333
|
-
e.extra[
|
334
|
-
elsif e.extra.member?
|
335
|
-
e.extra[
|
336
|
-
else
|
337
|
-
nil
|
336
|
+
if e.extra.member? 'UniversalTime'
|
337
|
+
e.extra['UniversalTime'].ctime
|
338
|
+
elsif e.extra.member? 'NTFS'
|
339
|
+
e.extra['NTFS'].ctime
|
338
340
|
end
|
339
341
|
end
|
340
342
|
|
341
|
-
def pipe?(
|
343
|
+
def pipe?(_filename)
|
342
344
|
false
|
343
345
|
end
|
344
346
|
|
345
|
-
def blockdev?(
|
347
|
+
def blockdev?(_filename)
|
346
348
|
false
|
347
349
|
end
|
348
350
|
|
349
|
-
def chardev?(
|
351
|
+
def chardev?(_filename)
|
350
352
|
false
|
351
353
|
end
|
352
354
|
|
353
|
-
def symlink?(
|
355
|
+
def symlink?(_fileName)
|
354
356
|
false
|
355
357
|
end
|
356
358
|
|
357
|
-
def socket?(
|
359
|
+
def socket?(_fileName)
|
358
360
|
false
|
359
361
|
end
|
360
362
|
|
361
363
|
def ftype(fileName)
|
362
|
-
@mappedZip.get_entry(fileName).directory? ?
|
364
|
+
@mappedZip.get_entry(fileName).directory? ? 'directory' : 'file'
|
363
365
|
end
|
364
366
|
|
365
|
-
def readlink(
|
366
|
-
raise NotImplementedError,
|
367
|
+
def readlink(_fileName)
|
368
|
+
raise NotImplementedError, 'The readlink() function is not implemented'
|
367
369
|
end
|
368
370
|
|
369
|
-
def symlink(
|
370
|
-
raise NotImplementedError,
|
371
|
+
def symlink(_fileName, _symlinkName)
|
372
|
+
raise NotImplementedError, 'The symlink() function is not implemented'
|
371
373
|
end
|
372
374
|
|
373
|
-
def link(
|
374
|
-
raise NotImplementedError,
|
375
|
+
def link(_fileName, _symlinkName)
|
376
|
+
raise NotImplementedError, 'The link() function is not implemented'
|
375
377
|
end
|
376
378
|
|
377
379
|
def pipe
|
378
|
-
raise NotImplementedError,
|
380
|
+
raise NotImplementedError, 'The pipe() function is not implemented'
|
379
381
|
end
|
380
382
|
|
381
383
|
def stat(fileName)
|
382
|
-
|
383
|
-
raise Errno::ENOENT, fileName
|
384
|
-
end
|
384
|
+
raise Errno::ENOENT, fileName unless exists?(fileName)
|
385
385
|
ZipFsStat.new(self, fileName)
|
386
386
|
end
|
387
387
|
|
@@ -404,20 +404,19 @@ module Zip
|
|
404
404
|
end
|
405
405
|
|
406
406
|
def delete(*args)
|
407
|
-
args.each
|
408
|
-
|fileName|
|
407
|
+
args.each do |fileName|
|
409
408
|
if directory?(fileName)
|
410
409
|
raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
|
411
410
|
end
|
412
411
|
@mappedZip.remove(fileName)
|
413
|
-
|
412
|
+
end
|
414
413
|
end
|
415
414
|
|
416
415
|
def rename(fileToRename, newName)
|
417
416
|
@mappedZip.rename(fileToRename, newName) { true }
|
418
417
|
end
|
419
418
|
|
420
|
-
alias
|
419
|
+
alias unlink delete
|
421
420
|
|
422
421
|
def expand_path(aPath)
|
423
422
|
@mappedZip.expand_path(aPath)
|
@@ -431,7 +430,6 @@ module Zip
|
|
431
430
|
# The individual methods are not documented due to their
|
432
431
|
# similarity with the methods in Dir
|
433
432
|
class ZipFsDir
|
434
|
-
|
435
433
|
def initialize(mappedZip)
|
436
434
|
@mappedZip = mappedZip
|
437
435
|
end
|
@@ -455,7 +453,9 @@ module Zip
|
|
455
453
|
dirIt
|
456
454
|
end
|
457
455
|
|
458
|
-
def pwd
|
456
|
+
def pwd
|
457
|
+
@mappedZip.pwd
|
458
|
+
end
|
459
459
|
alias getwd pwd
|
460
460
|
|
461
461
|
def chdir(aDirectoryName)
|
@@ -471,8 +471,8 @@ module Zip
|
|
471
471
|
entries
|
472
472
|
end
|
473
473
|
|
474
|
-
def glob(*args
|
475
|
-
@mappedZip.glob(*args
|
474
|
+
def glob(*args, &block)
|
475
|
+
@mappedZip.glob(*args, &block)
|
476
476
|
end
|
477
477
|
|
478
478
|
def foreach(aDirectoryName)
|
@@ -483,11 +483,10 @@ module Zip
|
|
483
483
|
path << '/' unless path.end_with?('/')
|
484
484
|
path = Regexp.escape(path)
|
485
485
|
subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
|
486
|
-
@mappedZip.each
|
487
|
-
|fileName|
|
486
|
+
@mappedZip.each do |fileName|
|
488
487
|
match = subDirEntriesRegex.match(fileName)
|
489
|
-
yield(match[1]) unless match
|
490
|
-
|
488
|
+
yield(match[1]) unless match.nil?
|
489
|
+
end
|
491
490
|
end
|
492
491
|
|
493
492
|
def delete(entryName)
|
@@ -496,17 +495,16 @@ module Zip
|
|
496
495
|
end
|
497
496
|
@mappedZip.remove(entryName)
|
498
497
|
end
|
499
|
-
alias rmdir
|
498
|
+
alias rmdir delete
|
500
499
|
alias unlink delete
|
501
500
|
|
502
|
-
def mkdir(entryName, permissionInt =
|
501
|
+
def mkdir(entryName, permissionInt = 0o755)
|
503
502
|
@mappedZip.mkdir(entryName, permissionInt)
|
504
503
|
end
|
505
504
|
|
506
|
-
def chroot(*
|
507
|
-
raise NotImplementedError,
|
505
|
+
def chroot(*_args)
|
506
|
+
raise NotImplementedError, 'The chroot() function is not implemented'
|
508
507
|
end
|
509
|
-
|
510
508
|
end
|
511
509
|
|
512
510
|
class ZipFsDirIterator # :nodoc:all
|
@@ -522,27 +520,27 @@ module Zip
|
|
522
520
|
end
|
523
521
|
|
524
522
|
def each(&aProc)
|
525
|
-
raise IOError,
|
523
|
+
raise IOError, 'closed directory' if @fileNames.nil?
|
526
524
|
@fileNames.each(&aProc)
|
527
525
|
end
|
528
526
|
|
529
527
|
def read
|
530
|
-
raise IOError,
|
531
|
-
@fileNames[(@index+=1)-1]
|
528
|
+
raise IOError, 'closed directory' if @fileNames.nil?
|
529
|
+
@fileNames[(@index += 1) - 1]
|
532
530
|
end
|
533
531
|
|
534
532
|
def rewind
|
535
|
-
raise IOError,
|
533
|
+
raise IOError, 'closed directory' if @fileNames.nil?
|
536
534
|
@index = 0
|
537
535
|
end
|
538
536
|
|
539
537
|
def seek(anIntegerPosition)
|
540
|
-
raise IOError,
|
538
|
+
raise IOError, 'closed directory' if @fileNames.nil?
|
541
539
|
@index = anIntegerPosition
|
542
540
|
end
|
543
541
|
|
544
542
|
def tell
|
545
|
-
raise IOError,
|
543
|
+
raise IOError, 'closed directory' if @fileNames.nil?
|
546
544
|
@index
|
547
545
|
end
|
548
546
|
end
|
@@ -554,7 +552,7 @@ module Zip
|
|
554
552
|
|
555
553
|
def initialize(zipFile)
|
556
554
|
@zipFile = zipFile
|
557
|
-
@pwd =
|
555
|
+
@pwd = '/'
|
558
556
|
end
|
559
557
|
|
560
558
|
attr_accessor :pwd
|
@@ -575,6 +573,10 @@ module Zip
|
|
575
573
|
@zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc)
|
576
574
|
end
|
577
575
|
|
576
|
+
def glob(pattern, *flags, &block)
|
577
|
+
@zipFile.glob(expand_to_entry(pattern), *flags, &block)
|
578
|
+
end
|
579
|
+
|
578
580
|
def read(fileName)
|
579
581
|
@zipFile.read(expand_to_entry(fileName))
|
580
582
|
end
|
@@ -584,28 +586,27 @@ module Zip
|
|
584
586
|
end
|
585
587
|
|
586
588
|
def rename(fileName, newName, &continueOnExistsProc)
|
587
|
-
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
|
589
|
+
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
|
588
590
|
&continueOnExistsProc)
|
589
591
|
end
|
590
592
|
|
591
|
-
def mkdir(fileName, permissionInt =
|
593
|
+
def mkdir(fileName, permissionInt = 0o755)
|
592
594
|
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
|
593
595
|
end
|
594
596
|
|
595
597
|
# Turns entries into strings and adds leading /
|
596
598
|
# and removes trailing slash on directories
|
597
599
|
def each
|
598
|
-
@zipFile.each
|
599
|
-
|
600
|
-
|
601
|
-
}
|
600
|
+
@zipFile.each do |e|
|
601
|
+
yield('/' + e.to_s.chomp('/'))
|
602
|
+
end
|
602
603
|
end
|
603
604
|
|
604
605
|
def expand_path(aPath)
|
605
|
-
expanded = aPath.start_with?(
|
606
|
-
expanded.gsub!(/\/\.(\/|$)/,
|
607
|
-
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/,
|
608
|
-
expanded.empty? ?
|
606
|
+
expanded = aPath.start_with?('/') ? aPath : ::File.join(@pwd, aPath)
|
607
|
+
expanded.gsub!(/\/\.(\/|$)/, '')
|
608
|
+
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '')
|
609
|
+
expanded.empty? ? '/' : expanded
|
609
610
|
end
|
610
611
|
|
611
612
|
private
|