mezza-rubyzip 0.9.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.