mezza-rubyzip 0.9.4.1
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.
- data/NEWS +162 -0
- data/README +68 -0
- data/Rakefile +13 -0
- data/TODO +16 -0
- data/lib/zip/compressor.rb +23 -0
- data/lib/zip/decompressor.rb +13 -0
- data/lib/zip/deflater.rb +30 -0
- data/lib/zip/inflater.rb +65 -0
- data/lib/zip/ioextras.rb +165 -0
- data/lib/zip/null_compressor.rb +15 -0
- data/lib/zip/null_decompressor.rb +25 -0
- data/lib/zip/null_input_stream.rb +9 -0
- data/lib/zip/pass_thru_compressor.rb +23 -0
- data/lib/zip/pass_thru_decompressor.rb +40 -0
- data/lib/zip/stdrubyext.rb +111 -0
- data/lib/zip/tempfile_bugfixed.rb +195 -0
- data/lib/zip/zip.rb +66 -0
- data/lib/zip/zip_central_directory.rb +137 -0
- data/lib/zip/zip_entry.rb +631 -0
- data/lib/zip/zip_entry_set.rb +66 -0
- data/lib/zip/zip_extra_field.rb +213 -0
- data/lib/zip/zip_file.rb +310 -0
- data/lib/zip/zip_input_stream.rb +134 -0
- data/lib/zip/zip_output_stream.rb +171 -0
- data/lib/zip/zip_streamable_directory.rb +15 -0
- data/lib/zip/zip_streamable_stream.rb +47 -0
- data/lib/zip/zipfilesystem.rb +610 -0
- data/lib/zip/ziprequire.rb +90 -0
- data/samples/example.rb +69 -0
- data/samples/example_filesystem.rb +33 -0
- data/samples/gtkRubyzip.rb +86 -0
- data/samples/qtzip.rb +101 -0
- data/samples/write_simple.rb +13 -0
- data/samples/zipfind.rb +74 -0
- metadata +92 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
module Zip
|
2
|
+
# ZipInputStream is the basic class for reading zip entries in a
|
3
|
+
# zip file. It is possible to create a ZipInputStream object directly,
|
4
|
+
# passing the zip file name to the constructor, but more often than not
|
5
|
+
# the ZipInputStream will be obtained from a ZipFile (perhaps using the
|
6
|
+
# ZipFileSystem interface) object for a particular entry in the zip
|
7
|
+
# archive.
|
8
|
+
#
|
9
|
+
# A ZipInputStream inherits IOExtras::AbstractInputStream in order
|
10
|
+
# to provide an IO-like interface for reading from a single zip
|
11
|
+
# entry. Beyond methods for mimicking an IO-object it contains
|
12
|
+
# the method get_next_entry for iterating through the entries of
|
13
|
+
# an archive. get_next_entry returns a ZipEntry object that describes
|
14
|
+
# the zip entry the ZipInputStream is currently reading from.
|
15
|
+
#
|
16
|
+
# Example that creates a zip archive with ZipOutputStream and reads it
|
17
|
+
# back again with a ZipInputStream.
|
18
|
+
#
|
19
|
+
# require 'zip/zip'
|
20
|
+
#
|
21
|
+
# Zip::ZipOutputStream::open("my.zip") {
|
22
|
+
# |io|
|
23
|
+
#
|
24
|
+
# io.put_next_entry("first_entry.txt")
|
25
|
+
# io.write "Hello world!"
|
26
|
+
#
|
27
|
+
# io.put_next_entry("adir/first_entry.txt")
|
28
|
+
# io.write "Hello again!"
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# Zip::ZipInputStream::open("my.zip") {
|
33
|
+
# |io|
|
34
|
+
#
|
35
|
+
# while (entry = io.get_next_entry)
|
36
|
+
# puts "Contents of #{entry.name}: '#{io.read}'"
|
37
|
+
# end
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# java.util.zip.ZipInputStream is the original inspiration for this
|
41
|
+
# class.
|
42
|
+
|
43
|
+
class ZipInputStream
|
44
|
+
include IOExtras::AbstractInputStream
|
45
|
+
|
46
|
+
# Opens the indicated zip file. An exception is thrown
|
47
|
+
# if the specified offset in the specified filename is
|
48
|
+
# not a local zip entry header.
|
49
|
+
def initialize(filename, offset = 0)
|
50
|
+
super()
|
51
|
+
@archiveIO = ::File.open(filename, "rb")
|
52
|
+
@archiveIO.seek(offset, IO::SEEK_SET)
|
53
|
+
@decompressor = NullDecompressor.instance
|
54
|
+
@currentEntry = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
@archiveIO.close
|
59
|
+
end
|
60
|
+
|
61
|
+
# Same as #initialize but if a block is passed the opened
|
62
|
+
# stream is passed to the block and closed when the block
|
63
|
+
# returns.
|
64
|
+
def ZipInputStream.open(filename)
|
65
|
+
return new(filename) unless block_given?
|
66
|
+
|
67
|
+
zio = new(filename)
|
68
|
+
yield zio
|
69
|
+
ensure
|
70
|
+
zio.close if zio
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a ZipEntry object. It is necessary to call this
|
74
|
+
# method on a newly created ZipInputStream before reading from
|
75
|
+
# the first entry in the archive. Returns nil when there are
|
76
|
+
# no more entries.
|
77
|
+
|
78
|
+
def get_next_entry
|
79
|
+
@archiveIO.seek(@currentEntry.next_header_offset,
|
80
|
+
IO::SEEK_SET) if @currentEntry
|
81
|
+
open_entry
|
82
|
+
end
|
83
|
+
|
84
|
+
# Rewinds the stream to the beginning of the current entry
|
85
|
+
def rewind
|
86
|
+
return if @currentEntry.nil?
|
87
|
+
@lineno = 0
|
88
|
+
@archiveIO.seek(@currentEntry.localHeaderOffset,
|
89
|
+
IO::SEEK_SET)
|
90
|
+
open_entry
|
91
|
+
end
|
92
|
+
|
93
|
+
# Modeled after IO.sysread
|
94
|
+
def sysread(numberOfBytes = nil, buf = nil)
|
95
|
+
@decompressor.sysread(numberOfBytes, buf)
|
96
|
+
end
|
97
|
+
|
98
|
+
def eof
|
99
|
+
@outputBuffer.empty? && @decompressor.eof
|
100
|
+
end
|
101
|
+
alias :eof? :eof
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def open_entry
|
106
|
+
@currentEntry = ZipEntry.read_local_entry(@archiveIO)
|
107
|
+
if (@currentEntry == nil)
|
108
|
+
@decompressor = NullDecompressor.instance
|
109
|
+
elsif @currentEntry.compression_method == ZipEntry::STORED
|
110
|
+
@decompressor = PassThruDecompressor.new(@archiveIO,
|
111
|
+
@currentEntry.size)
|
112
|
+
elsif @currentEntry.compression_method == ZipEntry::DEFLATED
|
113
|
+
@decompressor = Inflater.new(@archiveIO)
|
114
|
+
else
|
115
|
+
raise ZipCompressionMethodError,
|
116
|
+
"Unsupported compression method #{@currentEntry.compression_method}"
|
117
|
+
end
|
118
|
+
flush
|
119
|
+
return @currentEntry
|
120
|
+
end
|
121
|
+
|
122
|
+
def produce_input
|
123
|
+
@decompressor.produce_input
|
124
|
+
end
|
125
|
+
|
126
|
+
def input_finished?
|
127
|
+
@decompressor.input_finished?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
133
|
+
# rubyzip is free software; you can redistribute it and/or
|
134
|
+
# modify it under the terms of the ruby license.
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Zip
|
2
|
+
# ZipOutputStream is the basic class for writing zip files. It is
|
3
|
+
# possible to create a ZipOutputStream object directly, passing
|
4
|
+
# the zip file name to the constructor, but more often than not
|
5
|
+
# the ZipOutputStream will be obtained from a ZipFile (perhaps using the
|
6
|
+
# ZipFileSystem interface) object for a particular entry in the zip
|
7
|
+
# archive.
|
8
|
+
#
|
9
|
+
# A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
|
10
|
+
# to provide an IO-like interface for writing to a single zip
|
11
|
+
# entry. Beyond methods for mimicking an IO-object it contains
|
12
|
+
# the method put_next_entry that closes the current entry
|
13
|
+
# and creates a new.
|
14
|
+
#
|
15
|
+
# Please refer to ZipInputStream for example code.
|
16
|
+
#
|
17
|
+
# java.util.zip.ZipOutputStream is the original inspiration for this
|
18
|
+
# class.
|
19
|
+
|
20
|
+
class ZipOutputStream
|
21
|
+
include IOExtras::AbstractOutputStream
|
22
|
+
|
23
|
+
attr_accessor :comment
|
24
|
+
|
25
|
+
# Opens the indicated zip file. If a file with that name already
|
26
|
+
# exists it will be overwritten.
|
27
|
+
def initialize(fileName, stream=false)
|
28
|
+
super()
|
29
|
+
@fileName = fileName
|
30
|
+
if stream
|
31
|
+
@outputStream = StringIO.new
|
32
|
+
else
|
33
|
+
@outputStream = ::File.new(@fileName, "wb")
|
34
|
+
end
|
35
|
+
@entrySet = ZipEntrySet.new
|
36
|
+
@compressor = NullCompressor.instance
|
37
|
+
@closed = false
|
38
|
+
@currentEntry = nil
|
39
|
+
@comment = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Same as #initialize but if a block is passed the opened
|
43
|
+
# stream is passed to the block and closed when the block
|
44
|
+
# returns.
|
45
|
+
def ZipOutputStream.open(fileName)
|
46
|
+
return new(fileName) unless block_given?
|
47
|
+
zos = new(fileName)
|
48
|
+
yield zos
|
49
|
+
ensure
|
50
|
+
zos.close if zos
|
51
|
+
end
|
52
|
+
|
53
|
+
# Same as #open but writes to a filestream instead
|
54
|
+
def ZipOutputStream.write_buffer
|
55
|
+
zos = new('', true)
|
56
|
+
yield zos
|
57
|
+
return zos.close_buffer
|
58
|
+
end
|
59
|
+
|
60
|
+
# Closes the stream and writes the central directory to the zip file
|
61
|
+
def close
|
62
|
+
return if @closed
|
63
|
+
finalize_current_entry
|
64
|
+
update_local_headers
|
65
|
+
write_central_directory
|
66
|
+
@outputStream.close
|
67
|
+
@closed = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Closes the stream and writes the central directory to the zip file
|
71
|
+
def close_buffer
|
72
|
+
return @outputStream if @closed
|
73
|
+
finalize_current_entry
|
74
|
+
update_local_headers
|
75
|
+
write_central_directory
|
76
|
+
@closed = true
|
77
|
+
return @outputStream
|
78
|
+
end
|
79
|
+
|
80
|
+
# Closes the current entry and opens a new for writing.
|
81
|
+
# +entry+ can be a ZipEntry object or a string.
|
82
|
+
def put_next_entry(entryname, comment = nil, extra = nil, compression_method = ZipEntry::DEFLATED, level = Zlib::DEFAULT_COMPRESSION)
|
83
|
+
raise ZipError, "zip stream is closed" if @closed
|
84
|
+
new_entry = ZipEntry.new(@fileName, entryname.to_s)
|
85
|
+
new_entry.comment = comment if !comment.nil?
|
86
|
+
if (!extra.nil?)
|
87
|
+
new_entry.extra = ZipExtraField === extra ? extra : ZipExtraField.new(extra.to_s)
|
88
|
+
end
|
89
|
+
new_entry.compression_method = compression_method
|
90
|
+
init_next_entry(new_entry, level)
|
91
|
+
@currentEntry = new_entry
|
92
|
+
end
|
93
|
+
|
94
|
+
def copy_raw_entry(entry)
|
95
|
+
entry = entry.dup
|
96
|
+
raise ZipError, "zip stream is closed" if @closed
|
97
|
+
raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry)
|
98
|
+
finalize_current_entry
|
99
|
+
@entrySet << entry
|
100
|
+
src_pos = entry.local_entry_offset
|
101
|
+
entry.write_local_entry(@outputStream)
|
102
|
+
@compressor = NullCompressor.instance
|
103
|
+
entry.get_raw_input_stream {
|
104
|
+
|is|
|
105
|
+
is.seek(src_pos, IO::SEEK_SET)
|
106
|
+
IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size)
|
107
|
+
}
|
108
|
+
@compressor = NullCompressor.instance
|
109
|
+
@currentEntry = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def finalize_current_entry
|
114
|
+
return unless @currentEntry
|
115
|
+
finish
|
116
|
+
@currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset -
|
117
|
+
@currentEntry.calculate_local_header_size
|
118
|
+
@currentEntry.size = @compressor.size
|
119
|
+
@currentEntry.crc = @compressor.crc
|
120
|
+
@currentEntry = nil
|
121
|
+
@compressor = NullCompressor.instance
|
122
|
+
end
|
123
|
+
|
124
|
+
def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
|
125
|
+
finalize_current_entry
|
126
|
+
@entrySet << entry
|
127
|
+
entry.write_local_entry(@outputStream)
|
128
|
+
@compressor = get_compressor(entry, level)
|
129
|
+
end
|
130
|
+
|
131
|
+
def get_compressor(entry, level)
|
132
|
+
case entry.compression_method
|
133
|
+
when ZipEntry::DEFLATED then Deflater.new(@outputStream, level)
|
134
|
+
when ZipEntry::STORED then PassThruCompressor.new(@outputStream)
|
135
|
+
else raise ZipCompressionMethodError,
|
136
|
+
"Invalid compression method: '#{entry.compression_method}'"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def update_local_headers
|
141
|
+
pos = @outputStream.tell
|
142
|
+
@entrySet.each {
|
143
|
+
|entry|
|
144
|
+
@outputStream.pos = entry.localHeaderOffset
|
145
|
+
entry.write_local_entry(@outputStream)
|
146
|
+
}
|
147
|
+
@outputStream.pos = pos
|
148
|
+
end
|
149
|
+
|
150
|
+
def write_central_directory
|
151
|
+
cdir = ZipCentralDirectory.new(@entrySet, @comment)
|
152
|
+
cdir.write_to_stream(@outputStream)
|
153
|
+
end
|
154
|
+
|
155
|
+
protected
|
156
|
+
|
157
|
+
def finish
|
158
|
+
@compressor.finish
|
159
|
+
end
|
160
|
+
|
161
|
+
public
|
162
|
+
# Modeled after IO.<<
|
163
|
+
def << (data)
|
164
|
+
@compressor << data
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
170
|
+
# rubyzip is free software; you can redistribute it and/or
|
171
|
+
# modify it under the terms of the ruby license.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Zip
|
2
|
+
class ZipStreamableDirectory < ZipEntry
|
3
|
+
def initialize(zipfile, entry, srcPath = nil, permissionInt = nil)
|
4
|
+
super(zipfile, entry)
|
5
|
+
|
6
|
+
@ftype = :directory
|
7
|
+
entry.get_extra_attributes_from_path(srcPath) if (srcPath)
|
8
|
+
@unix_perms = permissionInt if (permissionInt)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
14
|
+
# rubyzip is free software; you can redistribute it and/or
|
15
|
+
# modify it under the terms of the ruby license.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Zip
|
2
|
+
class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all
|
3
|
+
def initialize(entry)
|
4
|
+
super(entry)
|
5
|
+
@tempFile = Tempfile.new(::File.basename(name), ::File.dirname(zipfile))
|
6
|
+
@tempFile.binmode
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_output_stream
|
10
|
+
if block_given?
|
11
|
+
begin
|
12
|
+
yield(@tempFile)
|
13
|
+
ensure
|
14
|
+
@tempFile.close
|
15
|
+
end
|
16
|
+
else
|
17
|
+
@tempFile
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_input_stream
|
22
|
+
if ! @tempFile.closed?
|
23
|
+
raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
|
24
|
+
end
|
25
|
+
@tempFile.open # reopens tempfile from top
|
26
|
+
@tempFile.binmode
|
27
|
+
if block_given?
|
28
|
+
begin
|
29
|
+
yield(@tempFile)
|
30
|
+
ensure
|
31
|
+
@tempFile.close
|
32
|
+
end
|
33
|
+
else
|
34
|
+
@tempFile
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_to_zip_output_stream(aZipOutputStream)
|
39
|
+
aZipOutputStream.put_next_entry(self)
|
40
|
+
get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
46
|
+
# rubyzip is free software; you can redistribute it and/or
|
47
|
+
# modify it under the terms of the ruby license.
|
@@ -0,0 +1,610 @@
|
|
1
|
+
require 'zip/zip'
|
2
|
+
|
3
|
+
module Zip
|
4
|
+
|
5
|
+
# The ZipFileSystem API provides an API for accessing entries in
|
6
|
+
# a zip archive that is similar to ruby's builtin File and Dir
|
7
|
+
# classes.
|
8
|
+
#
|
9
|
+
# Requiring 'zip/zipfilesystem' includes this module in ZipFile
|
10
|
+
# making the methods in this module available on ZipFile objects.
|
11
|
+
#
|
12
|
+
# Using this API the following example creates a new zip file
|
13
|
+
# <code>my.zip</code> containing a normal entry with the name
|
14
|
+
# <code>first.txt</code>, a directory entry named <code>mydir</code>
|
15
|
+
# and finally another normal entry named <code>second.txt</code>
|
16
|
+
#
|
17
|
+
# require 'zip/zipfilesystem'
|
18
|
+
#
|
19
|
+
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
|
20
|
+
# |zipfile|
|
21
|
+
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
|
22
|
+
# zipfile.dir.mkdir("mydir")
|
23
|
+
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# Reading is as easy as writing, as the following example shows. The
|
27
|
+
# example writes the contents of <code>first.txt</code> from zip archive
|
28
|
+
# <code>my.zip</code> to standard out.
|
29
|
+
#
|
30
|
+
# require 'zip/zipfilesystem'
|
31
|
+
#
|
32
|
+
# Zip::ZipFile.open("my.zip") {
|
33
|
+
# |zipfile|
|
34
|
+
# puts zipfile.file.read("first.txt")
|
35
|
+
# }
|
36
|
+
|
37
|
+
module ZipFileSystem
|
38
|
+
|
39
|
+
def initialize # :nodoc:
|
40
|
+
mappedZip = ZipFileNameMapper.new(self)
|
41
|
+
@zipFsDir = ZipFsDir.new(mappedZip)
|
42
|
+
@zipFsFile = ZipFsFile.new(mappedZip)
|
43
|
+
@zipFsDir.file = @zipFsFile
|
44
|
+
@zipFsFile.dir = @zipFsDir
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
|
48
|
+
# object, except it works on the ZipFile on which this method is
|
49
|
+
# invoked
|
50
|
+
def dir
|
51
|
+
@zipFsDir
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a ZipFsFile which is much like ruby's builtin File (class)
|
55
|
+
# object, except it works on the ZipFile on which this method is
|
56
|
+
# invoked
|
57
|
+
def file
|
58
|
+
@zipFsFile
|
59
|
+
end
|
60
|
+
|
61
|
+
# Instances of this class are normally accessed via the accessor
|
62
|
+
# ZipFile::file. An instance of ZipFsFile behaves like ruby's
|
63
|
+
# builtin File (class) object, except it works on ZipFile entries.
|
64
|
+
#
|
65
|
+
# The individual methods are not documented due to their
|
66
|
+
# similarity with the methods in File
|
67
|
+
class ZipFsFile
|
68
|
+
|
69
|
+
attr_writer :dir
|
70
|
+
# protected :dir
|
71
|
+
|
72
|
+
class ZipFsStat
|
73
|
+
def initialize(zipFsFile, entryName)
|
74
|
+
@zipFsFile = zipFsFile
|
75
|
+
@entryName = entryName
|
76
|
+
end
|
77
|
+
|
78
|
+
def forward_invoke(msg)
|
79
|
+
@zipFsFile.send(msg, @entryName)
|
80
|
+
end
|
81
|
+
|
82
|
+
def kind_of?(t)
|
83
|
+
super || t == ::File::Stat
|
84
|
+
end
|
85
|
+
|
86
|
+
forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
|
87
|
+
forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
|
88
|
+
forward_message :forward_invoke, :readable?, :readable_real?
|
89
|
+
forward_message :forward_invoke, :writable?, :writable_real?
|
90
|
+
forward_message :forward_invoke, :executable?, :executable_real?
|
91
|
+
forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
|
92
|
+
forward_message :forward_invoke, :setuid?, :setgid?
|
93
|
+
forward_message :forward_invoke, :zero?
|
94
|
+
forward_message :forward_invoke, :size, :size?
|
95
|
+
forward_message :forward_invoke, :mtime, :atime, :ctime
|
96
|
+
|
97
|
+
def blocks; nil; end
|
98
|
+
|
99
|
+
def get_entry
|
100
|
+
@zipFsFile.__send__(:get_entry, @entryName)
|
101
|
+
end
|
102
|
+
private :get_entry
|
103
|
+
|
104
|
+
def gid
|
105
|
+
e = get_entry
|
106
|
+
if e.extra.member? "IUnix"
|
107
|
+
e.extra["IUnix"].gid || 0
|
108
|
+
else
|
109
|
+
0
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def uid
|
114
|
+
e = get_entry
|
115
|
+
if e.extra.member? "IUnix"
|
116
|
+
e.extra["IUnix"].uid || 0
|
117
|
+
else
|
118
|
+
0
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def ino; 0; end
|
123
|
+
|
124
|
+
def dev; 0; end
|
125
|
+
|
126
|
+
def rdev; 0; end
|
127
|
+
|
128
|
+
def rdev_major; 0; end
|
129
|
+
|
130
|
+
def rdev_minor; 0; end
|
131
|
+
|
132
|
+
def ftype
|
133
|
+
if file?
|
134
|
+
return "file"
|
135
|
+
elsif directory?
|
136
|
+
return "directory"
|
137
|
+
else
|
138
|
+
raise StandardError, "Unknown file type"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def nlink; 1; end
|
143
|
+
|
144
|
+
def blksize; nil; end
|
145
|
+
|
146
|
+
def mode
|
147
|
+
e = get_entry
|
148
|
+
if e.fstype == 3
|
149
|
+
e.externalFileAttributes >> 16
|
150
|
+
else
|
151
|
+
33206 # 33206 is equivalent to -rw-rw-rw-
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def initialize(mappedZip)
|
157
|
+
@mappedZip = mappedZip
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_entry(fileName)
|
161
|
+
if ! exists?(fileName)
|
162
|
+
raise Errno::ENOENT, "No such file or directory - #{fileName}"
|
163
|
+
end
|
164
|
+
@mappedZip.find_entry(fileName)
|
165
|
+
end
|
166
|
+
private :get_entry
|
167
|
+
|
168
|
+
def unix_mode_cmp(fileName, mode)
|
169
|
+
begin
|
170
|
+
e = get_entry(fileName)
|
171
|
+
e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
|
172
|
+
rescue Errno::ENOENT
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
private :unix_mode_cmp
|
177
|
+
|
178
|
+
def exists?(fileName)
|
179
|
+
expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
|
180
|
+
end
|
181
|
+
alias :exist? :exists?
|
182
|
+
|
183
|
+
# Permissions not implemented, so if the file exists it is accessible
|
184
|
+
alias owned? exists?
|
185
|
+
alias grpowned? exists?
|
186
|
+
|
187
|
+
def readable?(fileName)
|
188
|
+
unix_mode_cmp(fileName, 0444)
|
189
|
+
end
|
190
|
+
alias readable_real? readable?
|
191
|
+
|
192
|
+
def writable?(fileName)
|
193
|
+
unix_mode_cmp(fileName, 0222)
|
194
|
+
end
|
195
|
+
alias writable_real? writable?
|
196
|
+
|
197
|
+
def executable?(fileName)
|
198
|
+
unix_mode_cmp(fileName, 0111)
|
199
|
+
end
|
200
|
+
alias executable_real? executable?
|
201
|
+
|
202
|
+
def setuid?(fileName)
|
203
|
+
unix_mode_cmp(fileName, 04000)
|
204
|
+
end
|
205
|
+
|
206
|
+
def setgid?(fileName)
|
207
|
+
unix_mode_cmp(fileName, 02000)
|
208
|
+
end
|
209
|
+
|
210
|
+
def sticky?(fileName)
|
211
|
+
unix_mode_cmp(fileName, 01000)
|
212
|
+
end
|
213
|
+
|
214
|
+
def umask(*args)
|
215
|
+
::File.umask(*args)
|
216
|
+
end
|
217
|
+
|
218
|
+
def truncate(fileName, len)
|
219
|
+
raise StandardError, "truncate not supported"
|
220
|
+
end
|
221
|
+
|
222
|
+
def directory?(fileName)
|
223
|
+
entry = @mappedZip.find_entry(fileName)
|
224
|
+
expand_path(fileName) == "/" || (entry != nil && entry.directory?)
|
225
|
+
end
|
226
|
+
|
227
|
+
def open(fileName, openMode = "r", &block)
|
228
|
+
openMode.gsub!("b", "") # ignore b option
|
229
|
+
case openMode
|
230
|
+
when "r"
|
231
|
+
@mappedZip.get_input_stream(fileName, &block)
|
232
|
+
when "w"
|
233
|
+
@mappedZip.get_output_stream(fileName, &block)
|
234
|
+
else
|
235
|
+
raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def new(fileName, openMode = "r")
|
240
|
+
open(fileName, openMode)
|
241
|
+
end
|
242
|
+
|
243
|
+
def size(fileName)
|
244
|
+
@mappedZip.get_entry(fileName).size
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns nil for not found and nil for directories
|
248
|
+
def size?(fileName)
|
249
|
+
entry = @mappedZip.find_entry(fileName)
|
250
|
+
return (entry == nil || entry.directory?) ? nil : entry.size
|
251
|
+
end
|
252
|
+
|
253
|
+
def chown(ownerInt, groupInt, *filenames)
|
254
|
+
filenames.each { |fileName|
|
255
|
+
e = get_entry(fileName)
|
256
|
+
unless e.extra.member?("IUnix")
|
257
|
+
e.extra.create("IUnix")
|
258
|
+
end
|
259
|
+
e.extra["IUnix"].uid = ownerInt
|
260
|
+
e.extra["IUnix"].gid = groupInt
|
261
|
+
}
|
262
|
+
filenames.size
|
263
|
+
end
|
264
|
+
|
265
|
+
def chmod (modeInt, *filenames)
|
266
|
+
filenames.each { |fileName|
|
267
|
+
e = get_entry(fileName)
|
268
|
+
e.fstype = 3 # force convertion filesystem type to unix
|
269
|
+
e.externalFileAttributes = modeInt << 16
|
270
|
+
}
|
271
|
+
filenames.size
|
272
|
+
end
|
273
|
+
|
274
|
+
def zero?(fileName)
|
275
|
+
sz = size(fileName)
|
276
|
+
sz == nil || sz == 0
|
277
|
+
rescue Errno::ENOENT
|
278
|
+
false
|
279
|
+
end
|
280
|
+
|
281
|
+
def file?(fileName)
|
282
|
+
entry = @mappedZip.find_entry(fileName)
|
283
|
+
entry != nil && entry.file?
|
284
|
+
end
|
285
|
+
|
286
|
+
def dirname(fileName)
|
287
|
+
::File.dirname(fileName)
|
288
|
+
end
|
289
|
+
|
290
|
+
def basename(fileName)
|
291
|
+
::File.basename(fileName)
|
292
|
+
end
|
293
|
+
|
294
|
+
def split(fileName)
|
295
|
+
::File.split(fileName)
|
296
|
+
end
|
297
|
+
|
298
|
+
def join(*fragments)
|
299
|
+
::File.join(*fragments)
|
300
|
+
end
|
301
|
+
|
302
|
+
def utime(modifiedTime, *fileNames)
|
303
|
+
fileNames.each { |fileName|
|
304
|
+
get_entry(fileName).time = modifiedTime
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
def mtime(fileName)
|
309
|
+
@mappedZip.get_entry(fileName).mtime
|
310
|
+
end
|
311
|
+
|
312
|
+
def atime(fileName)
|
313
|
+
e = get_entry(fileName)
|
314
|
+
if e.extra.member? "UniversalTime"
|
315
|
+
e.extra["UniversalTime"].atime
|
316
|
+
else
|
317
|
+
nil
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def ctime(fileName)
|
322
|
+
e = get_entry(fileName)
|
323
|
+
if e.extra.member? "UniversalTime"
|
324
|
+
e.extra["UniversalTime"].ctime
|
325
|
+
else
|
326
|
+
nil
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def pipe?(filename)
|
331
|
+
false
|
332
|
+
end
|
333
|
+
|
334
|
+
def blockdev?(filename)
|
335
|
+
false
|
336
|
+
end
|
337
|
+
|
338
|
+
def chardev?(filename)
|
339
|
+
false
|
340
|
+
end
|
341
|
+
|
342
|
+
def symlink?(fileName)
|
343
|
+
false
|
344
|
+
end
|
345
|
+
|
346
|
+
def socket?(fileName)
|
347
|
+
false
|
348
|
+
end
|
349
|
+
|
350
|
+
def ftype(fileName)
|
351
|
+
@mappedZip.get_entry(fileName).directory? ? "directory" : "file"
|
352
|
+
end
|
353
|
+
|
354
|
+
def readlink(fileName)
|
355
|
+
raise NotImplementedError, "The readlink() function is not implemented"
|
356
|
+
end
|
357
|
+
|
358
|
+
def symlink(fileName, symlinkName)
|
359
|
+
raise NotImplementedError, "The symlink() function is not implemented"
|
360
|
+
end
|
361
|
+
|
362
|
+
def link(fileName, symlinkName)
|
363
|
+
raise NotImplementedError, "The link() function is not implemented"
|
364
|
+
end
|
365
|
+
|
366
|
+
def pipe
|
367
|
+
raise NotImplementedError, "The pipe() function is not implemented"
|
368
|
+
end
|
369
|
+
|
370
|
+
def stat(fileName)
|
371
|
+
if ! exists?(fileName)
|
372
|
+
raise Errno::ENOENT, fileName
|
373
|
+
end
|
374
|
+
ZipFsStat.new(self, fileName)
|
375
|
+
end
|
376
|
+
|
377
|
+
alias lstat stat
|
378
|
+
|
379
|
+
def readlines(fileName)
|
380
|
+
open(fileName) { |is| is.readlines }
|
381
|
+
end
|
382
|
+
|
383
|
+
def read(fileName)
|
384
|
+
@mappedZip.read(fileName)
|
385
|
+
end
|
386
|
+
|
387
|
+
def popen(*args, &aProc)
|
388
|
+
File.popen(*args, &aProc)
|
389
|
+
end
|
390
|
+
|
391
|
+
def foreach(fileName, aSep = $/, &aProc)
|
392
|
+
open(fileName) { |is| is.each_line(aSep, &aProc) }
|
393
|
+
end
|
394
|
+
|
395
|
+
def delete(*args)
|
396
|
+
args.each {
|
397
|
+
|fileName|
|
398
|
+
if directory?(fileName)
|
399
|
+
raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
|
400
|
+
end
|
401
|
+
@mappedZip.remove(fileName)
|
402
|
+
}
|
403
|
+
end
|
404
|
+
|
405
|
+
def rename(fileToRename, newName)
|
406
|
+
@mappedZip.rename(fileToRename, newName) { true }
|
407
|
+
end
|
408
|
+
|
409
|
+
alias :unlink :delete
|
410
|
+
|
411
|
+
def expand_path(aPath)
|
412
|
+
@mappedZip.expand_path(aPath)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Instances of this class are normally accessed via the accessor
|
417
|
+
# ZipFile::dir. An instance of ZipFsDir behaves like ruby's
|
418
|
+
# builtin Dir (class) object, except it works on ZipFile entries.
|
419
|
+
#
|
420
|
+
# The individual methods are not documented due to their
|
421
|
+
# similarity with the methods in Dir
|
422
|
+
class ZipFsDir
|
423
|
+
|
424
|
+
def initialize(mappedZip)
|
425
|
+
@mappedZip = mappedZip
|
426
|
+
end
|
427
|
+
|
428
|
+
attr_writer :file
|
429
|
+
|
430
|
+
def new(aDirectoryName)
|
431
|
+
ZipFsDirIterator.new(entries(aDirectoryName))
|
432
|
+
end
|
433
|
+
|
434
|
+
def open(aDirectoryName)
|
435
|
+
dirIt = new(aDirectoryName)
|
436
|
+
if block_given?
|
437
|
+
begin
|
438
|
+
yield(dirIt)
|
439
|
+
return nil
|
440
|
+
ensure
|
441
|
+
dirIt.close
|
442
|
+
end
|
443
|
+
end
|
444
|
+
dirIt
|
445
|
+
end
|
446
|
+
|
447
|
+
def pwd; @mappedZip.pwd; end
|
448
|
+
alias getwd pwd
|
449
|
+
|
450
|
+
def chdir(aDirectoryName)
|
451
|
+
unless @file.stat(aDirectoryName).directory?
|
452
|
+
raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
|
453
|
+
end
|
454
|
+
@mappedZip.pwd = @file.expand_path(aDirectoryName)
|
455
|
+
end
|
456
|
+
|
457
|
+
def entries(aDirectoryName)
|
458
|
+
entries = []
|
459
|
+
foreach(aDirectoryName) { |e| entries << e }
|
460
|
+
entries
|
461
|
+
end
|
462
|
+
|
463
|
+
def foreach(aDirectoryName)
|
464
|
+
unless @file.stat(aDirectoryName).directory?
|
465
|
+
raise Errno::ENOTDIR, aDirectoryName
|
466
|
+
end
|
467
|
+
path = @file.expand_path(aDirectoryName).ensure_end("/")
|
468
|
+
|
469
|
+
subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
|
470
|
+
@mappedZip.each {
|
471
|
+
|fileName|
|
472
|
+
match = subDirEntriesRegex.match(fileName)
|
473
|
+
yield(match[1]) unless match == nil
|
474
|
+
}
|
475
|
+
end
|
476
|
+
|
477
|
+
def delete(entryName)
|
478
|
+
unless @file.stat(entryName).directory?
|
479
|
+
raise Errno::EINVAL, "Invalid argument - #{entryName}"
|
480
|
+
end
|
481
|
+
@mappedZip.remove(entryName)
|
482
|
+
end
|
483
|
+
alias rmdir delete
|
484
|
+
alias unlink delete
|
485
|
+
|
486
|
+
def mkdir(entryName, permissionInt = 0755)
|
487
|
+
@mappedZip.mkdir(entryName, permissionInt)
|
488
|
+
end
|
489
|
+
|
490
|
+
def chroot(*args)
|
491
|
+
raise NotImplementedError, "The chroot() function is not implemented"
|
492
|
+
end
|
493
|
+
|
494
|
+
end
|
495
|
+
|
496
|
+
class ZipFsDirIterator # :nodoc:all
|
497
|
+
include Enumerable
|
498
|
+
|
499
|
+
def initialize(arrayOfFileNames)
|
500
|
+
@fileNames = arrayOfFileNames
|
501
|
+
@index = 0
|
502
|
+
end
|
503
|
+
|
504
|
+
def close
|
505
|
+
@fileNames = nil
|
506
|
+
end
|
507
|
+
|
508
|
+
def each(&aProc)
|
509
|
+
raise IOError, "closed directory" if @fileNames == nil
|
510
|
+
@fileNames.each(&aProc)
|
511
|
+
end
|
512
|
+
|
513
|
+
def read
|
514
|
+
raise IOError, "closed directory" if @fileNames == nil
|
515
|
+
@fileNames[(@index+=1)-1]
|
516
|
+
end
|
517
|
+
|
518
|
+
def rewind
|
519
|
+
raise IOError, "closed directory" if @fileNames == nil
|
520
|
+
@index = 0
|
521
|
+
end
|
522
|
+
|
523
|
+
def seek(anIntegerPosition)
|
524
|
+
raise IOError, "closed directory" if @fileNames == nil
|
525
|
+
@index = anIntegerPosition
|
526
|
+
end
|
527
|
+
|
528
|
+
def tell
|
529
|
+
raise IOError, "closed directory" if @fileNames == nil
|
530
|
+
@index
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
# All access to ZipFile from ZipFsFile and ZipFsDir goes through a
|
535
|
+
# ZipFileNameMapper, which has one responsibility: ensure
|
536
|
+
class ZipFileNameMapper # :nodoc:all
|
537
|
+
include Enumerable
|
538
|
+
|
539
|
+
def initialize(zipFile)
|
540
|
+
@zipFile = zipFile
|
541
|
+
@pwd = "/"
|
542
|
+
end
|
543
|
+
|
544
|
+
attr_accessor :pwd
|
545
|
+
|
546
|
+
def find_entry(fileName)
|
547
|
+
@zipFile.find_entry(expand_to_entry(fileName))
|
548
|
+
end
|
549
|
+
|
550
|
+
def get_entry(fileName)
|
551
|
+
@zipFile.get_entry(expand_to_entry(fileName))
|
552
|
+
end
|
553
|
+
|
554
|
+
def get_input_stream(fileName, &aProc)
|
555
|
+
@zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
|
556
|
+
end
|
557
|
+
|
558
|
+
def get_output_stream(fileName, &aProc)
|
559
|
+
@zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
|
560
|
+
end
|
561
|
+
|
562
|
+
def read(fileName)
|
563
|
+
@zipFile.read(expand_to_entry(fileName))
|
564
|
+
end
|
565
|
+
|
566
|
+
def remove(fileName)
|
567
|
+
@zipFile.remove(expand_to_entry(fileName))
|
568
|
+
end
|
569
|
+
|
570
|
+
def rename(fileName, newName, &continueOnExistsProc)
|
571
|
+
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
|
572
|
+
&continueOnExistsProc)
|
573
|
+
end
|
574
|
+
|
575
|
+
def mkdir(fileName, permissionInt = 0755)
|
576
|
+
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
|
577
|
+
end
|
578
|
+
|
579
|
+
# Turns entries into strings and adds leading /
|
580
|
+
# and removes trailing slash on directories
|
581
|
+
def each
|
582
|
+
@zipFile.each {
|
583
|
+
|e|
|
584
|
+
yield("/"+e.to_s.chomp("/"))
|
585
|
+
}
|
586
|
+
end
|
587
|
+
|
588
|
+
def expand_path(aPath)
|
589
|
+
expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
|
590
|
+
expanded.gsub!(/\/\.(\/|$)/, "")
|
591
|
+
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
|
592
|
+
expanded.empty? ? "/" : expanded
|
593
|
+
end
|
594
|
+
|
595
|
+
private
|
596
|
+
|
597
|
+
def expand_to_entry(aPath)
|
598
|
+
expand_path(aPath).lchop
|
599
|
+
end
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
class ZipFile
|
604
|
+
include ZipFileSystem
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
609
|
+
# rubyzip is free software; you can redistribute it and/or
|
610
|
+
# modify it under the terms of the ruby license.
|