rubyzip 2.2.0 → 2.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 +4 -4
- data/Rakefile +3 -0
- data/lib/zip.rb +1 -0
- data/lib/zip/central_directory.rb +9 -5
- data/lib/zip/constants.rb +18 -18
- data/lib/zip/crypto/decrypted_io.rb +4 -3
- data/lib/zip/crypto/traditional_encryption.rb +9 -9
- data/lib/zip/dos_time.rb +7 -7
- data/lib/zip/entry.rb +35 -28
- data/lib/zip/entry_set.rb +2 -0
- data/lib/zip/extra_field.rb +11 -9
- data/lib/zip/extra_field/generic.rb +9 -8
- data/lib/zip/extra_field/ntfs.rb +4 -0
- data/lib/zip/extra_field/old_unix.rb +3 -1
- data/lib/zip/extra_field/universal_time.rb +3 -0
- data/lib/zip/extra_field/unix.rb +3 -1
- data/lib/zip/extra_field/zip64.rb +4 -2
- data/lib/zip/file.rb +49 -38
- data/lib/zip/filesystem.rb +193 -177
- data/lib/zip/inflater.rb +5 -3
- data/lib/zip/input_stream.rb +6 -3
- data/lib/zip/ioextras.rb +1 -1
- data/lib/zip/ioextras/abstract_input_stream.rb +14 -9
- data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
- data/lib/zip/output_stream.rb +14 -5
- data/lib/zip/pass_thru_compressor.rb +2 -2
- data/lib/zip/pass_thru_decompressor.rb +2 -2
- data/lib/zip/streamable_directory.rb +3 -3
- data/lib/zip/streamable_stream.rb +5 -4
- data/lib/zip/version.rb +1 -1
- data/samples/example.rb +2 -2
- data/samples/example_filesystem.rb +1 -1
- data/samples/gtk_ruby_zip.rb +19 -19
- data/samples/qtzip.rb +6 -6
- data/samples/write_simple.rb +2 -4
- data/samples/zipfind.rb +23 -22
- metadata +25 -19
@@ -1,9 +1,9 @@
|
|
1
1
|
module Zip
|
2
2
|
class ExtraField::Generic
|
3
3
|
def self.register_map
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
return unless const_defined?(:HEADER_ID)
|
5
|
+
|
6
|
+
::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.name
|
@@ -12,18 +12,19 @@ module Zip
|
|
12
12
|
|
13
13
|
# return field [size, content] or false
|
14
14
|
def initial_parse(binstr)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
15
|
+
return false unless binstr
|
16
|
+
|
17
|
+
if binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
19
18
|
warn 'WARNING: weird extra field header ID. Skip parsing it.'
|
20
19
|
return false
|
21
20
|
end
|
22
|
-
|
21
|
+
|
22
|
+
[binstr[2, 2].unpack1('v'), binstr[4..-1]]
|
23
23
|
end
|
24
24
|
|
25
25
|
def ==(other)
|
26
26
|
return false if self.class != other.class
|
27
|
+
|
27
28
|
each do |k, v|
|
28
29
|
return false if v != other[k]
|
29
30
|
end
|
data/lib/zip/extra_field/ntfs.rb
CHANGED
@@ -19,6 +19,7 @@ module Zip
|
|
19
19
|
|
20
20
|
def merge(binstr)
|
21
21
|
return if binstr.empty?
|
22
|
+
|
22
23
|
size, content = initial_parse(binstr)
|
23
24
|
(size && content) || return
|
24
25
|
|
@@ -27,6 +28,7 @@ module Zip
|
|
27
28
|
|
28
29
|
tag1 = tags[1]
|
29
30
|
return unless tag1
|
31
|
+
|
30
32
|
ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
|
31
33
|
ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
|
32
34
|
ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
|
@@ -65,12 +67,14 @@ module Zip
|
|
65
67
|
|
66
68
|
def parse_tags(content)
|
67
69
|
return {} if content.nil?
|
70
|
+
|
68
71
|
tags = {}
|
69
72
|
i = 0
|
70
73
|
while i < content.bytesize
|
71
74
|
tag, size = content[i, 4].unpack('vv')
|
72
75
|
i += 4
|
73
76
|
break unless tag && size
|
77
|
+
|
74
78
|
value = content[i, size]
|
75
79
|
i += size
|
76
80
|
tags[tag] = value
|
@@ -16,14 +16,16 @@ module Zip
|
|
16
16
|
|
17
17
|
def merge(binstr)
|
18
18
|
return if binstr.empty?
|
19
|
+
|
19
20
|
size, content = initial_parse(binstr)
|
20
21
|
# size: 0 for central directory. 4 for local header
|
21
22
|
return if !size || size == 0
|
23
|
+
|
22
24
|
atime, mtime, uid, gid = content.unpack('VVvv')
|
23
25
|
@uid ||= uid
|
24
26
|
@gid ||= gid
|
25
27
|
@atime ||= atime
|
26
|
-
@mtime ||= mtime
|
28
|
+
@mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
|
27
29
|
end
|
28
30
|
|
29
31
|
def ==(other)
|
@@ -44,10 +44,13 @@ module Zip
|
|
44
44
|
|
45
45
|
# Parse the timestamps, in order, based on which flags are set.
|
46
46
|
return if times[0].nil?
|
47
|
+
|
47
48
|
@mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
|
48
49
|
return if times[0].nil?
|
50
|
+
|
49
51
|
@atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
|
50
52
|
return if times[0].nil?
|
53
|
+
|
51
54
|
@ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
|
52
55
|
end
|
53
56
|
|
data/lib/zip/extra_field/unix.rb
CHANGED
@@ -14,12 +14,14 @@ module Zip
|
|
14
14
|
|
15
15
|
def merge(binstr)
|
16
16
|
return if binstr.empty?
|
17
|
+
|
17
18
|
size, content = initial_parse(binstr)
|
18
19
|
# size: 0 for central directory. 4 for local header
|
19
20
|
return if !size || size == 0
|
21
|
+
|
20
22
|
uid, gid = content.unpack('vv')
|
21
23
|
@uid ||= uid
|
22
|
-
@gid ||= gid
|
24
|
+
@gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName
|
23
25
|
end
|
24
26
|
|
25
27
|
def ==(other)
|
@@ -9,7 +9,7 @@ module Zip
|
|
9
9
|
# unparsed binary; we don't actually know what this contains
|
10
10
|
# without looking for FFs in the associated file header
|
11
11
|
# call parse after initializing with a binary string
|
12
|
-
@content
|
12
|
+
@content = nil
|
13
13
|
@original_size = nil
|
14
14
|
@compressed_size = nil
|
15
15
|
@relative_header_offset = nil
|
@@ -26,6 +26,7 @@ module Zip
|
|
26
26
|
|
27
27
|
def merge(binstr)
|
28
28
|
return if binstr.empty?
|
29
|
+
|
29
30
|
_, @content = initial_parse(binstr)
|
30
31
|
end
|
31
32
|
|
@@ -45,13 +46,14 @@ module Zip
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def extract(size, format)
|
48
|
-
@content.slice!(0, size).
|
49
|
+
@content.slice!(0, size).unpack1(format)
|
49
50
|
end
|
50
51
|
private :extract
|
51
52
|
|
52
53
|
def pack_for_local
|
53
54
|
# local header entries must contain original size and compressed size; other fields do not apply
|
54
55
|
return '' unless @original_size && @compressed_size
|
56
|
+
|
55
57
|
[@original_size, @compressed_size].pack('Q<Q<')
|
56
58
|
end
|
57
59
|
|
data/lib/zip/file.rb
CHANGED
@@ -75,7 +75,7 @@ module Zip
|
|
75
75
|
# a new archive if it doesn't exist already.
|
76
76
|
def initialize(path_or_io, create = false, buffer = false, options = {})
|
77
77
|
super()
|
78
|
-
options
|
78
|
+
options = DEFAULT_OPTIONS.merge(options)
|
79
79
|
@name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
|
80
80
|
@comment = ''
|
81
81
|
@create = create ? true : false # allow any truthy value to mean true
|
@@ -120,6 +120,7 @@ module Zip
|
|
120
120
|
def open(file_name, create = false, options = {})
|
121
121
|
zf = ::Zip::File.new(file_name, create, false, options)
|
122
122
|
return zf unless block_given?
|
123
|
+
|
123
124
|
begin
|
124
125
|
yield zf
|
125
126
|
ensure
|
@@ -140,17 +141,18 @@ module Zip
|
|
140
141
|
# (This can be used to extract data from a
|
141
142
|
# downloaded zip archive without first saving it to disk.)
|
142
143
|
def open_buffer(io, options = {})
|
143
|
-
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.
|
144
|
+
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
144
145
|
raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
145
146
|
end
|
146
147
|
|
147
|
-
io = ::StringIO.new(io) if io.
|
148
|
+
io = ::StringIO.new(io) if io.kind_of?(::String)
|
148
149
|
|
149
150
|
# https://github.com/rubyzip/rubyzip/issues/119
|
150
151
|
io.binmode if io.respond_to?(:binmode)
|
151
152
|
|
152
153
|
zf = ::Zip::File.new(io, true, true, options)
|
153
154
|
return zf unless block_given?
|
155
|
+
|
154
156
|
yield zf
|
155
157
|
|
156
158
|
begin
|
@@ -166,9 +168,9 @@ module Zip
|
|
166
168
|
# whereas ZipInputStream jumps through the entire archive accessing the
|
167
169
|
# local entry headers (which contain the same information as the
|
168
170
|
# central directory).
|
169
|
-
def foreach(
|
170
|
-
open(
|
171
|
-
|
171
|
+
def foreach(zip_file_name, &block)
|
172
|
+
::Zip::File.open(zip_file_name) do |zip_file|
|
173
|
+
zip_file.each(&block)
|
172
174
|
end
|
173
175
|
end
|
174
176
|
|
@@ -229,12 +231,14 @@ module Zip
|
|
229
231
|
def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
|
230
232
|
raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
|
231
233
|
raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
|
234
|
+
|
232
235
|
zip_file_size = ::File.size(zip_file_name)
|
233
236
|
segment_size = get_segment_size_for_split(segment_size)
|
234
237
|
return if zip_file_size <= segment_size
|
238
|
+
|
235
239
|
segment_count = get_segment_count_for_split(zip_file_size, segment_size)
|
236
240
|
# Checking for correct zip structure
|
237
|
-
open(zip_file_name) {}
|
241
|
+
::Zip::File.open(zip_file_name) {}
|
238
242
|
partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
|
239
243
|
szip_file_index = 0
|
240
244
|
::File.open(zip_file_name, 'rb') do |zip_file|
|
@@ -251,8 +255,8 @@ module Zip
|
|
251
255
|
# Returns an input stream to the specified entry. If a block is passed
|
252
256
|
# the stream object is passed to the block and the stream is automatically
|
253
257
|
# closed afterwards just as with ruby's builtin File.open method.
|
254
|
-
def get_input_stream(entry, &
|
255
|
-
get_entry(entry).get_input_stream(&
|
258
|
+
def get_input_stream(entry, &a_proc)
|
259
|
+
get_entry(entry).get_input_stream(&a_proc)
|
256
260
|
end
|
257
261
|
|
258
262
|
# Returns an output stream to the specified entry. If entry is not an instance
|
@@ -260,7 +264,11 @@ module Zip
|
|
260
264
|
# specified. If a block is passed the stream object is passed to the block and
|
261
265
|
# the stream is automatically closed afterwards just as with ruby's builtin
|
262
266
|
# File.open method.
|
263
|
-
def get_output_stream(entry, permission_int = nil, comment = nil,
|
267
|
+
def get_output_stream(entry, permission_int = nil, comment = nil,
|
268
|
+
extra = nil, compressed_size = nil, crc = nil,
|
269
|
+
compression_method = nil, size = nil, time = nil,
|
270
|
+
&a_proc)
|
271
|
+
|
264
272
|
new_entry =
|
265
273
|
if entry.kind_of?(Entry)
|
266
274
|
entry
|
@@ -274,7 +282,7 @@ module Zip
|
|
274
282
|
new_entry.unix_perms = permission_int
|
275
283
|
zip_streamable_entry = StreamableStream.new(new_entry)
|
276
284
|
@entry_set << zip_streamable_entry
|
277
|
-
zip_streamable_entry.get_output_stream(&
|
285
|
+
zip_streamable_entry.get_output_stream(&a_proc)
|
278
286
|
end
|
279
287
|
|
280
288
|
# Returns the name of the zip archive
|
@@ -284,7 +292,7 @@ module Zip
|
|
284
292
|
|
285
293
|
# Returns a string containing the contents of the specified entry
|
286
294
|
def read(entry)
|
287
|
-
get_input_stream(entry
|
295
|
+
get_input_stream(entry, &:read)
|
288
296
|
end
|
289
297
|
|
290
298
|
# Convenience method for adding the contents of a file to the archive
|
@@ -311,19 +319,19 @@ module Zip
|
|
311
319
|
|
312
320
|
# Renames the specified entry.
|
313
321
|
def rename(entry, new_name, &continue_on_exists_proc)
|
314
|
-
|
322
|
+
found_entry = get_entry(entry)
|
315
323
|
check_entry_exists(new_name, continue_on_exists_proc, 'rename')
|
316
|
-
@entry_set.delete(
|
317
|
-
|
318
|
-
@entry_set <<
|
324
|
+
@entry_set.delete(found_entry)
|
325
|
+
found_entry.name = new_name
|
326
|
+
@entry_set << found_entry
|
319
327
|
end
|
320
328
|
|
321
|
-
# Replaces the specified entry with the contents of
|
329
|
+
# Replaces the specified entry with the contents of src_path (from
|
322
330
|
# the file system).
|
323
|
-
def replace(entry,
|
324
|
-
check_file(
|
331
|
+
def replace(entry, src_path)
|
332
|
+
check_file(src_path)
|
325
333
|
remove(entry)
|
326
|
-
add(entry,
|
334
|
+
add(entry, src_path)
|
327
335
|
end
|
328
336
|
|
329
337
|
# Extracts entry to file dest_path.
|
@@ -336,7 +344,8 @@ module Zip
|
|
336
344
|
# Commits changes that has been made since the previous commit to
|
337
345
|
# the zip archive.
|
338
346
|
def commit
|
339
|
-
return if name.
|
347
|
+
return if name.kind_of?(StringIO) || !commit_required?
|
348
|
+
|
340
349
|
on_success_replace do |tmp_file|
|
341
350
|
::Zip::OutputStream.open(tmp_file) do |zos|
|
342
351
|
@entry_set.each do |e|
|
@@ -400,35 +409,37 @@ module Zip
|
|
400
409
|
end
|
401
410
|
|
402
411
|
# Creates a directory
|
403
|
-
def mkdir(
|
404
|
-
raise Errno::EEXIST, "File exists - #{
|
405
|
-
|
406
|
-
|
407
|
-
|
412
|
+
def mkdir(entry_name, permission = 0o755)
|
413
|
+
raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
|
414
|
+
|
415
|
+
entry_name = entry_name.dup.to_s
|
416
|
+
entry_name << '/' unless entry_name.end_with?('/')
|
417
|
+
@entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
|
408
418
|
end
|
409
419
|
|
410
420
|
private
|
411
421
|
|
412
|
-
def directory?(
|
413
|
-
|
414
|
-
if
|
422
|
+
def directory?(new_entry, src_path)
|
423
|
+
path_is_directory = ::File.directory?(src_path)
|
424
|
+
if new_entry.directory? && !path_is_directory
|
415
425
|
raise ArgumentError,
|
416
|
-
"entry name '#{
|
417
|
-
"'#{
|
418
|
-
elsif !
|
419
|
-
|
426
|
+
"entry name '#{new_entry}' indicates directory entry, but " \
|
427
|
+
"'#{src_path}' is not a directory"
|
428
|
+
elsif !new_entry.directory? && path_is_directory
|
429
|
+
new_entry.name += '/'
|
420
430
|
end
|
421
|
-
|
431
|
+
new_entry.directory? && path_is_directory
|
422
432
|
end
|
423
433
|
|
424
|
-
def check_entry_exists(
|
434
|
+
def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
|
425
435
|
continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
|
426
|
-
return unless @entry_set.include?(
|
436
|
+
return unless @entry_set.include?(entry_name)
|
437
|
+
|
427
438
|
if continue_on_exists_proc.call
|
428
|
-
remove get_entry(
|
439
|
+
remove get_entry(entry_name)
|
429
440
|
else
|
430
441
|
raise ::Zip::EntryExistsError,
|
431
|
-
|
442
|
+
proc_name + " failed. Entry #{entry_name} already exists"
|
432
443
|
end
|
433
444
|
end
|
434
445
|
|
data/lib/zip/filesystem.rb
CHANGED
@@ -35,25 +35,25 @@ module Zip
|
|
35
35
|
|
36
36
|
module FileSystem
|
37
37
|
def initialize # :nodoc:
|
38
|
-
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@
|
38
|
+
mapped_zip = ZipFileNameMapper.new(self)
|
39
|
+
@zip_fs_dir = ZipFsDir.new(mapped_zip)
|
40
|
+
@zip_fs_file = ZipFsFile.new(mapped_zip)
|
41
|
+
@zip_fs_dir.file = @zip_fs_file
|
42
|
+
@zip_fs_file.dir = @zip_fs_dir
|
43
43
|
end
|
44
44
|
|
45
45
|
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
|
46
46
|
# object, except it works on the Zip::File on which this method is
|
47
47
|
# invoked
|
48
48
|
def dir
|
49
|
-
@
|
49
|
+
@zip_fs_dir
|
50
50
|
end
|
51
51
|
|
52
52
|
# Returns a ZipFsFile which is much like ruby's builtin File (class)
|
53
53
|
# object, except it works on the Zip::File on which this method is
|
54
54
|
# invoked
|
55
55
|
def file
|
56
|
-
@
|
56
|
+
@zip_fs_file
|
57
57
|
end
|
58
58
|
|
59
59
|
# Instances of this class are normally accessed via the accessor
|
@@ -70,22 +70,22 @@ module Zip
|
|
70
70
|
class << self
|
71
71
|
def delegate_to_fs_file(*methods)
|
72
72
|
methods.each do |method|
|
73
|
-
class_eval <<-
|
73
|
+
class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
74
74
|
def #{method} # def file?
|
75
|
-
@
|
75
|
+
@zip_fs_file.#{method}(@entry_name) # @zip_fs_file.file?(@entry_name)
|
76
76
|
end # end
|
77
|
-
|
77
|
+
END_EVAL
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
def initialize(
|
83
|
-
@
|
84
|
-
@
|
82
|
+
def initialize(zip_fs_file, entry_name)
|
83
|
+
@zip_fs_file = zip_fs_file
|
84
|
+
@entry_name = entry_name
|
85
85
|
end
|
86
86
|
|
87
|
-
def kind_of?(
|
88
|
-
super ||
|
87
|
+
def kind_of?(type)
|
88
|
+
super || type == ::File::Stat
|
89
89
|
end
|
90
90
|
|
91
91
|
delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?,
|
@@ -98,7 +98,7 @@ module Zip
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def get_entry
|
101
|
-
@
|
101
|
+
@zip_fs_file.__send__(:get_entry, @entry_name)
|
102
102
|
end
|
103
103
|
private :get_entry
|
104
104
|
|
@@ -168,28 +168,29 @@ module Zip
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
-
def initialize(
|
172
|
-
@
|
171
|
+
def initialize(mapped_zip)
|
172
|
+
@mapped_zip = mapped_zip
|
173
173
|
end
|
174
174
|
|
175
|
-
def get_entry(
|
176
|
-
unless exists?(
|
177
|
-
raise Errno::ENOENT, "No such file or directory - #{
|
175
|
+
def get_entry(filename)
|
176
|
+
unless exists?(filename)
|
177
|
+
raise Errno::ENOENT, "No such file or directory - #{filename}"
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
|
+
@mapped_zip.find_entry(filename)
|
180
181
|
end
|
181
182
|
private :get_entry
|
182
183
|
|
183
|
-
def unix_mode_cmp(
|
184
|
-
e = get_entry(
|
184
|
+
def unix_mode_cmp(filename, mode)
|
185
|
+
e = get_entry(filename)
|
185
186
|
e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0
|
186
187
|
rescue Errno::ENOENT
|
187
188
|
false
|
188
189
|
end
|
189
190
|
private :unix_mode_cmp
|
190
191
|
|
191
|
-
def exists?(
|
192
|
-
expand_path(
|
192
|
+
def exists?(filename)
|
193
|
+
expand_path(filename) == '/' || !@mapped_zip.find_entry(filename).nil?
|
193
194
|
end
|
194
195
|
alias exist? exists?
|
195
196
|
|
@@ -197,133 +198,133 @@ module Zip
|
|
197
198
|
alias owned? exists?
|
198
199
|
alias grpowned? exists?
|
199
200
|
|
200
|
-
def readable?(
|
201
|
-
unix_mode_cmp(
|
201
|
+
def readable?(filename)
|
202
|
+
unix_mode_cmp(filename, 0o444)
|
202
203
|
end
|
203
204
|
alias readable_real? readable?
|
204
205
|
|
205
|
-
def writable?(
|
206
|
-
unix_mode_cmp(
|
206
|
+
def writable?(filename)
|
207
|
+
unix_mode_cmp(filename, 0o222)
|
207
208
|
end
|
208
209
|
alias writable_real? writable?
|
209
210
|
|
210
|
-
def executable?(
|
211
|
-
unix_mode_cmp(
|
211
|
+
def executable?(filename)
|
212
|
+
unix_mode_cmp(filename, 0o111)
|
212
213
|
end
|
213
214
|
alias executable_real? executable?
|
214
215
|
|
215
|
-
def setuid?(
|
216
|
-
unix_mode_cmp(
|
216
|
+
def setuid?(filename)
|
217
|
+
unix_mode_cmp(filename, 0o4000)
|
217
218
|
end
|
218
219
|
|
219
|
-
def setgid?(
|
220
|
-
unix_mode_cmp(
|
220
|
+
def setgid?(filename)
|
221
|
+
unix_mode_cmp(filename, 0o2000)
|
221
222
|
end
|
222
223
|
|
223
|
-
def sticky?(
|
224
|
-
unix_mode_cmp(
|
224
|
+
def sticky?(filename)
|
225
|
+
unix_mode_cmp(filename, 0o1000)
|
225
226
|
end
|
226
227
|
|
227
228
|
def umask(*args)
|
228
229
|
::File.umask(*args)
|
229
230
|
end
|
230
231
|
|
231
|
-
def truncate(
|
232
|
+
def truncate(_filename, _len)
|
232
233
|
raise StandardError, 'truncate not supported'
|
233
234
|
end
|
234
235
|
|
235
|
-
def directory?(
|
236
|
-
entry = @
|
237
|
-
expand_path(
|
236
|
+
def directory?(filename)
|
237
|
+
entry = @mapped_zip.find_entry(filename)
|
238
|
+
expand_path(filename) == '/' || (!entry.nil? && entry.directory?)
|
238
239
|
end
|
239
240
|
|
240
|
-
def open(
|
241
|
-
|
242
|
-
case
|
241
|
+
def open(filename, mode = 'r', permissions = 0o644, &block)
|
242
|
+
mode.delete!('b') # ignore b option
|
243
|
+
case mode
|
243
244
|
when 'r'
|
244
|
-
@
|
245
|
+
@mapped_zip.get_input_stream(filename, &block)
|
245
246
|
when 'w'
|
246
|
-
@
|
247
|
+
@mapped_zip.get_output_stream(filename, permissions, &block)
|
247
248
|
else
|
248
|
-
raise StandardError, "openmode '#{
|
249
|
+
raise StandardError, "openmode '#{mode} not supported" unless mode == 'r'
|
249
250
|
end
|
250
251
|
end
|
251
252
|
|
252
|
-
def new(
|
253
|
-
open(
|
253
|
+
def new(filename, mode = 'r')
|
254
|
+
self.open(filename, mode)
|
254
255
|
end
|
255
256
|
|
256
|
-
def size(
|
257
|
-
@
|
257
|
+
def size(filename)
|
258
|
+
@mapped_zip.get_entry(filename).size
|
258
259
|
end
|
259
260
|
|
260
261
|
# Returns nil for not found and nil for directories
|
261
|
-
def size?(
|
262
|
-
entry = @
|
262
|
+
def size?(filename)
|
263
|
+
entry = @mapped_zip.find_entry(filename)
|
263
264
|
entry.nil? || entry.directory? ? nil : entry.size
|
264
265
|
end
|
265
266
|
|
266
|
-
def chown(
|
267
|
-
filenames.each do |
|
268
|
-
e = get_entry(
|
267
|
+
def chown(owner, group, *filenames)
|
268
|
+
filenames.each do |filename|
|
269
|
+
e = get_entry(filename)
|
269
270
|
e.extra.create('IUnix') unless e.extra.member?('IUnix')
|
270
|
-
e.extra['IUnix'].uid =
|
271
|
-
e.extra['IUnix'].gid =
|
271
|
+
e.extra['IUnix'].uid = owner
|
272
|
+
e.extra['IUnix'].gid = group
|
272
273
|
end
|
273
274
|
filenames.size
|
274
275
|
end
|
275
276
|
|
276
|
-
def chmod(
|
277
|
-
filenames.each do |
|
278
|
-
e = get_entry(
|
277
|
+
def chmod(mode, *filenames)
|
278
|
+
filenames.each do |filename|
|
279
|
+
e = get_entry(filename)
|
279
280
|
e.fstype = 3 # force convertion filesystem type to unix
|
280
|
-
e.unix_perms =
|
281
|
-
e.external_file_attributes =
|
281
|
+
e.unix_perms = mode
|
282
|
+
e.external_file_attributes = mode << 16
|
282
283
|
e.dirty = true
|
283
284
|
end
|
284
285
|
filenames.size
|
285
286
|
end
|
286
287
|
|
287
|
-
def zero?(
|
288
|
-
sz = size(
|
288
|
+
def zero?(filename)
|
289
|
+
sz = size(filename)
|
289
290
|
sz.nil? || sz == 0
|
290
291
|
rescue Errno::ENOENT
|
291
292
|
false
|
292
293
|
end
|
293
294
|
|
294
|
-
def file?(
|
295
|
-
entry = @
|
295
|
+
def file?(filename)
|
296
|
+
entry = @mapped_zip.find_entry(filename)
|
296
297
|
!entry.nil? && entry.file?
|
297
298
|
end
|
298
299
|
|
299
|
-
def dirname(
|
300
|
-
::File.dirname(
|
300
|
+
def dirname(filename)
|
301
|
+
::File.dirname(filename)
|
301
302
|
end
|
302
303
|
|
303
|
-
def basename(
|
304
|
-
::File.basename(
|
304
|
+
def basename(filename)
|
305
|
+
::File.basename(filename)
|
305
306
|
end
|
306
307
|
|
307
|
-
def split(
|
308
|
-
::File.split(
|
308
|
+
def split(filename)
|
309
|
+
::File.split(filename)
|
309
310
|
end
|
310
311
|
|
311
312
|
def join(*fragments)
|
312
313
|
::File.join(*fragments)
|
313
314
|
end
|
314
315
|
|
315
|
-
def utime(
|
316
|
-
|
317
|
-
get_entry(
|
316
|
+
def utime(modified_time, *filenames)
|
317
|
+
filenames.each do |filename|
|
318
|
+
get_entry(filename).time = modified_time
|
318
319
|
end
|
319
320
|
end
|
320
321
|
|
321
|
-
def mtime(
|
322
|
-
@
|
322
|
+
def mtime(filename)
|
323
|
+
@mapped_zip.get_entry(filename).mtime
|
323
324
|
end
|
324
325
|
|
325
|
-
def atime(
|
326
|
-
e = get_entry(
|
326
|
+
def atime(filename)
|
327
|
+
e = get_entry(filename)
|
327
328
|
if e.extra.member? 'UniversalTime'
|
328
329
|
e.extra['UniversalTime'].atime
|
329
330
|
elsif e.extra.member? 'NTFS'
|
@@ -331,8 +332,8 @@ module Zip
|
|
331
332
|
end
|
332
333
|
end
|
333
334
|
|
334
|
-
def ctime(
|
335
|
-
e = get_entry(
|
335
|
+
def ctime(filename)
|
336
|
+
e = get_entry(filename)
|
336
337
|
if e.extra.member? 'UniversalTime'
|
337
338
|
e.extra['UniversalTime'].ctime
|
338
339
|
elsif e.extra.member? 'NTFS'
|
@@ -352,27 +353,27 @@ module Zip
|
|
352
353
|
false
|
353
354
|
end
|
354
355
|
|
355
|
-
def symlink?(
|
356
|
+
def symlink?(_filename)
|
356
357
|
false
|
357
358
|
end
|
358
359
|
|
359
|
-
def socket?(
|
360
|
+
def socket?(_filename)
|
360
361
|
false
|
361
362
|
end
|
362
363
|
|
363
|
-
def ftype(
|
364
|
-
@
|
364
|
+
def ftype(filename)
|
365
|
+
@mapped_zip.get_entry(filename).directory? ? 'directory' : 'file'
|
365
366
|
end
|
366
367
|
|
367
|
-
def readlink(
|
368
|
+
def readlink(_filename)
|
368
369
|
raise NotImplementedError, 'The readlink() function is not implemented'
|
369
370
|
end
|
370
371
|
|
371
|
-
def symlink(
|
372
|
+
def symlink(_filename, _symlink_name)
|
372
373
|
raise NotImplementedError, 'The symlink() function is not implemented'
|
373
374
|
end
|
374
375
|
|
375
|
-
def link(
|
376
|
+
def link(_filename, _symlink_name)
|
376
377
|
raise NotImplementedError, 'The link() function is not implemented'
|
377
378
|
end
|
378
379
|
|
@@ -380,46 +381,48 @@ module Zip
|
|
380
381
|
raise NotImplementedError, 'The pipe() function is not implemented'
|
381
382
|
end
|
382
383
|
|
383
|
-
def stat(
|
384
|
-
raise Errno::ENOENT,
|
385
|
-
|
384
|
+
def stat(filename)
|
385
|
+
raise Errno::ENOENT, filename unless exists?(filename)
|
386
|
+
|
387
|
+
ZipFsStat.new(self, filename)
|
386
388
|
end
|
387
389
|
|
388
390
|
alias lstat stat
|
389
391
|
|
390
|
-
def readlines(
|
391
|
-
open(
|
392
|
+
def readlines(filename)
|
393
|
+
self.open(filename, &:readlines)
|
392
394
|
end
|
393
395
|
|
394
|
-
def read(
|
395
|
-
@
|
396
|
+
def read(filename)
|
397
|
+
@mapped_zip.read(filename)
|
396
398
|
end
|
397
399
|
|
398
|
-
def popen(*args, &
|
399
|
-
::File.popen(*args, &
|
400
|
+
def popen(*args, &a_proc)
|
401
|
+
::File.popen(*args, &a_proc)
|
400
402
|
end
|
401
403
|
|
402
|
-
def foreach(
|
403
|
-
open(
|
404
|
+
def foreach(filename, sep = $INPUT_RECORD_SEPARATOR, &a_proc)
|
405
|
+
self.open(filename) { |is| is.each_line(sep, &a_proc) }
|
404
406
|
end
|
405
407
|
|
406
408
|
def delete(*args)
|
407
|
-
args.each do |
|
408
|
-
if directory?(
|
409
|
-
raise Errno::EISDIR, "Is a directory - \"#{
|
409
|
+
args.each do |filename|
|
410
|
+
if directory?(filename)
|
411
|
+
raise Errno::EISDIR, "Is a directory - \"#{filename}\""
|
410
412
|
end
|
411
|
-
|
413
|
+
|
414
|
+
@mapped_zip.remove(filename)
|
412
415
|
end
|
413
416
|
end
|
414
417
|
|
415
|
-
def rename(
|
416
|
-
@
|
418
|
+
def rename(file_to_rename, new_name)
|
419
|
+
@mapped_zip.rename(file_to_rename, new_name) { true }
|
417
420
|
end
|
418
421
|
|
419
422
|
alias unlink delete
|
420
423
|
|
421
|
-
def expand_path(
|
422
|
-
@
|
424
|
+
def expand_path(path)
|
425
|
+
@mapped_zip.expand_path(path)
|
423
426
|
end
|
424
427
|
end
|
425
428
|
|
@@ -430,76 +433,79 @@ module Zip
|
|
430
433
|
# The individual methods are not documented due to their
|
431
434
|
# similarity with the methods in Dir
|
432
435
|
class ZipFsDir
|
433
|
-
def initialize(
|
434
|
-
@
|
436
|
+
def initialize(mapped_zip)
|
437
|
+
@mapped_zip = mapped_zip
|
435
438
|
end
|
436
439
|
|
437
440
|
attr_writer :file
|
438
441
|
|
439
|
-
def new(
|
440
|
-
ZipFsDirIterator.new(entries(
|
442
|
+
def new(directory_name)
|
443
|
+
ZipFsDirIterator.new(entries(directory_name))
|
441
444
|
end
|
442
445
|
|
443
|
-
def open(
|
444
|
-
|
446
|
+
def open(directory_name)
|
447
|
+
dir_iter = new(directory_name)
|
445
448
|
if block_given?
|
446
449
|
begin
|
447
|
-
yield(
|
450
|
+
yield(dir_iter)
|
448
451
|
return nil
|
449
452
|
ensure
|
450
|
-
|
453
|
+
dir_iter.close
|
451
454
|
end
|
452
455
|
end
|
453
|
-
|
456
|
+
dir_iter
|
454
457
|
end
|
455
458
|
|
456
459
|
def pwd
|
457
|
-
@
|
460
|
+
@mapped_zip.pwd
|
458
461
|
end
|
459
462
|
alias getwd pwd
|
460
463
|
|
461
|
-
def chdir(
|
462
|
-
unless @file.stat(
|
463
|
-
raise Errno::EINVAL, "Invalid argument - #{
|
464
|
+
def chdir(directory_name)
|
465
|
+
unless @file.stat(directory_name).directory?
|
466
|
+
raise Errno::EINVAL, "Invalid argument - #{directory_name}"
|
464
467
|
end
|
465
|
-
|
468
|
+
|
469
|
+
@mapped_zip.pwd = @file.expand_path(directory_name)
|
466
470
|
end
|
467
471
|
|
468
|
-
def entries(
|
472
|
+
def entries(directory_name)
|
469
473
|
entries = []
|
470
|
-
foreach(
|
474
|
+
foreach(directory_name) { |e| entries << e }
|
471
475
|
entries
|
472
476
|
end
|
473
477
|
|
474
478
|
def glob(*args, &block)
|
475
|
-
@
|
479
|
+
@mapped_zip.glob(*args, &block)
|
476
480
|
end
|
477
481
|
|
478
|
-
def foreach(
|
479
|
-
unless @file.stat(
|
480
|
-
raise Errno::ENOTDIR,
|
482
|
+
def foreach(directory_name)
|
483
|
+
unless @file.stat(directory_name).directory?
|
484
|
+
raise Errno::ENOTDIR, directory_name
|
481
485
|
end
|
482
|
-
|
486
|
+
|
487
|
+
path = @file.expand_path(directory_name)
|
483
488
|
path << '/' unless path.end_with?('/')
|
484
489
|
path = Regexp.escape(path)
|
485
|
-
|
486
|
-
@
|
487
|
-
match =
|
490
|
+
subdir_entry_regex = Regexp.new("^#{path}([^/]+)$")
|
491
|
+
@mapped_zip.each do |filename|
|
492
|
+
match = subdir_entry_regex.match(filename)
|
488
493
|
yield(match[1]) unless match.nil?
|
489
494
|
end
|
490
495
|
end
|
491
496
|
|
492
|
-
def delete(
|
493
|
-
unless @file.stat(
|
494
|
-
raise Errno::EINVAL, "Invalid argument - #{
|
497
|
+
def delete(entry_name)
|
498
|
+
unless @file.stat(entry_name).directory?
|
499
|
+
raise Errno::EINVAL, "Invalid argument - #{entry_name}"
|
495
500
|
end
|
496
|
-
|
501
|
+
|
502
|
+
@mapped_zip.remove(entry_name)
|
497
503
|
end
|
498
504
|
alias rmdir delete
|
499
505
|
alias unlink delete
|
500
506
|
|
501
|
-
def mkdir(
|
502
|
-
@
|
507
|
+
def mkdir(entry_name, permissions = 0o755)
|
508
|
+
@mapped_zip.mkdir(entry_name, permissions)
|
503
509
|
end
|
504
510
|
|
505
511
|
def chroot(*_args)
|
@@ -510,37 +516,42 @@ module Zip
|
|
510
516
|
class ZipFsDirIterator # :nodoc:all
|
511
517
|
include Enumerable
|
512
518
|
|
513
|
-
def initialize(
|
514
|
-
@
|
519
|
+
def initialize(filenames)
|
520
|
+
@filenames = filenames
|
515
521
|
@index = 0
|
516
522
|
end
|
517
523
|
|
518
524
|
def close
|
519
|
-
@
|
525
|
+
@filenames = nil
|
520
526
|
end
|
521
527
|
|
522
|
-
def each(&
|
523
|
-
raise IOError, 'closed directory' if @
|
524
|
-
|
528
|
+
def each(&a_proc)
|
529
|
+
raise IOError, 'closed directory' if @filenames.nil?
|
530
|
+
|
531
|
+
@filenames.each(&a_proc)
|
525
532
|
end
|
526
533
|
|
527
534
|
def read
|
528
|
-
raise IOError, 'closed directory' if @
|
529
|
-
|
535
|
+
raise IOError, 'closed directory' if @filenames.nil?
|
536
|
+
|
537
|
+
@filenames[(@index += 1) - 1]
|
530
538
|
end
|
531
539
|
|
532
540
|
def rewind
|
533
|
-
raise IOError, 'closed directory' if @
|
541
|
+
raise IOError, 'closed directory' if @filenames.nil?
|
542
|
+
|
534
543
|
@index = 0
|
535
544
|
end
|
536
545
|
|
537
|
-
def seek(
|
538
|
-
raise IOError, 'closed directory' if @
|
539
|
-
|
546
|
+
def seek(position)
|
547
|
+
raise IOError, 'closed directory' if @filenames.nil?
|
548
|
+
|
549
|
+
@index = position
|
540
550
|
end
|
541
551
|
|
542
552
|
def tell
|
543
|
-
raise IOError, 'closed directory' if @
|
553
|
+
raise IOError, 'closed directory' if @filenames.nil?
|
554
|
+
|
544
555
|
@index
|
545
556
|
end
|
546
557
|
end
|
@@ -550,60 +561,65 @@ module Zip
|
|
550
561
|
class ZipFileNameMapper # :nodoc:all
|
551
562
|
include Enumerable
|
552
563
|
|
553
|
-
def initialize(
|
554
|
-
@
|
564
|
+
def initialize(zip_file)
|
565
|
+
@zip_file = zip_file
|
555
566
|
@pwd = '/'
|
556
567
|
end
|
557
568
|
|
558
569
|
attr_accessor :pwd
|
559
570
|
|
560
|
-
def find_entry(
|
561
|
-
@
|
571
|
+
def find_entry(filename)
|
572
|
+
@zip_file.find_entry(expand_to_entry(filename))
|
562
573
|
end
|
563
574
|
|
564
|
-
def get_entry(
|
565
|
-
@
|
575
|
+
def get_entry(filename)
|
576
|
+
@zip_file.get_entry(expand_to_entry(filename))
|
566
577
|
end
|
567
578
|
|
568
|
-
def get_input_stream(
|
569
|
-
@
|
579
|
+
def get_input_stream(filename, &a_proc)
|
580
|
+
@zip_file.get_input_stream(expand_to_entry(filename), &a_proc)
|
570
581
|
end
|
571
582
|
|
572
|
-
def get_output_stream(
|
573
|
-
@
|
583
|
+
def get_output_stream(filename, permissions = nil, &a_proc)
|
584
|
+
@zip_file.get_output_stream(
|
585
|
+
expand_to_entry(filename), permissions, &a_proc
|
586
|
+
)
|
574
587
|
end
|
575
588
|
|
576
589
|
def glob(pattern, *flags, &block)
|
577
|
-
@
|
590
|
+
@zip_file.glob(expand_to_entry(pattern), *flags, &block)
|
578
591
|
end
|
579
592
|
|
580
|
-
def read(
|
581
|
-
@
|
593
|
+
def read(filename)
|
594
|
+
@zip_file.read(expand_to_entry(filename))
|
582
595
|
end
|
583
596
|
|
584
|
-
def remove(
|
585
|
-
@
|
597
|
+
def remove(filename)
|
598
|
+
@zip_file.remove(expand_to_entry(filename))
|
586
599
|
end
|
587
600
|
|
588
|
-
def rename(
|
589
|
-
@
|
590
|
-
|
601
|
+
def rename(filename, new_name, &continue_on_exists_proc)
|
602
|
+
@zip_file.rename(
|
603
|
+
expand_to_entry(filename),
|
604
|
+
expand_to_entry(new_name),
|
605
|
+
&continue_on_exists_proc
|
606
|
+
)
|
591
607
|
end
|
592
608
|
|
593
|
-
def mkdir(
|
594
|
-
@
|
609
|
+
def mkdir(filename, permissions = 0o755)
|
610
|
+
@zip_file.mkdir(expand_to_entry(filename), permissions)
|
595
611
|
end
|
596
612
|
|
597
613
|
# Turns entries into strings and adds leading /
|
598
614
|
# and removes trailing slash on directories
|
599
615
|
def each
|
600
|
-
@
|
616
|
+
@zip_file.each do |e|
|
601
617
|
yield('/' + e.to_s.chomp('/'))
|
602
618
|
end
|
603
619
|
end
|
604
620
|
|
605
|
-
def expand_path(
|
606
|
-
expanded =
|
621
|
+
def expand_path(path)
|
622
|
+
expanded = path.start_with?('/') ? path : ::File.join(@pwd, path)
|
607
623
|
expanded.gsub!(/\/\.(\/|$)/, '')
|
608
624
|
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '')
|
609
625
|
expanded.empty? ? '/' : expanded
|
@@ -611,8 +627,8 @@ module Zip
|
|
611
627
|
|
612
628
|
private
|
613
629
|
|
614
|
-
def expand_to_entry(
|
615
|
-
expand_path(
|
630
|
+
def expand_to_entry(path)
|
631
|
+
expand_path(path)[1..-1]
|
616
632
|
end
|
617
633
|
end
|
618
634
|
end
|