rubyslippers 0.98 → 0.99

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.
@@ -1,7 +1,7 @@
1
1
  # this code is to list the gemspecs
2
2
  def listgemrecordfiles
3
- # fileList = Dir.new($gemspecificationsdirectory).entries.sort.reverse.delete_if { |x| ! (x =~ /gemspec$/) }
4
- fileList = Dir.new($gemspecificationsdirectory).entries.sort
3
+ fileList = Dir.new($gemspecificationsdirectory).entries.sort.delete_if { |x| ! (x =~ /gemspec$/) }
4
+ # fileList = Dir.new($gemspecificationsdirectory).entries.sort
5
5
  @listgemrecordfilesresult = fileList.rwd_method("fillgemrecordname")
6
6
  end
7
7
 
@@ -1,2 +1,2 @@
1
1
 
2
- RubySlippersVersion = "0.98"
2
+ RubySlippersVersion = "0.99"
@@ -0,0 +1,114 @@
1
+ module IOExtras
2
+ module FakeIO
3
+ def kind_of?(object)
4
+ object == IO || super
5
+ end
6
+ end
7
+
8
+ # Implements many of the convenience methods of IO
9
+ # such as gets, getc, readline and readlines
10
+ # depends on: input_finished?, produce_input and read
11
+ module AbstractInputStream
12
+ include Enumerable
13
+ include FakeIO
14
+
15
+ def initialize
16
+ super
17
+ @lineno = 0
18
+ @outputBuffer = ""
19
+ end
20
+
21
+ attr_accessor :lineno
22
+
23
+ def readlines(aSepString = $/)
24
+ retVal = []
25
+ each_line(aSepString) { |line| retVal << line }
26
+ return retVal
27
+ end
28
+
29
+ def gets(aSepString=$/)
30
+ @lineno = @lineno.next
31
+ return read if aSepString == nil
32
+ aSepString="#{$/}#{$/}" if aSepString == ""
33
+
34
+ bufferIndex=0
35
+ while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
36
+ bufferIndex=@outputBuffer.length
37
+ if input_finished?
38
+ return @outputBuffer.empty? ? nil : flush
39
+ end
40
+ @outputBuffer << produce_input
41
+ end
42
+ sepIndex=matchIndex + aSepString.length
43
+ return @outputBuffer.slice!(0...sepIndex)
44
+ end
45
+
46
+ def flush
47
+ retVal=@outputBuffer
48
+ @outputBuffer=""
49
+ return retVal
50
+ end
51
+
52
+ def readline(aSepString = $/)
53
+ retVal = gets(aSepString)
54
+ raise EOFError if retVal == nil
55
+ return retVal
56
+ end
57
+
58
+ def each_line(aSepString = $/)
59
+ while true
60
+ yield readline(aSepString)
61
+ end
62
+ rescue EOFError
63
+ end
64
+
65
+ alias_method :each, :each_line
66
+ end
67
+
68
+
69
+ #relies on <<
70
+ module AbstractOutputStream
71
+ include FakeIO
72
+
73
+ def write(data)
74
+ self << data
75
+ data.to_s.length
76
+ end
77
+
78
+
79
+ def print(*params)
80
+ self << params.to_s << $\.to_s
81
+ end
82
+
83
+ def printf(aFormatString, *params)
84
+ self << sprintf(aFormatString, *params)
85
+ end
86
+
87
+ def putc(anObject)
88
+ self << case anObject
89
+ when Fixnum then anObject.chr
90
+ when String then anObject
91
+ else raise TypeError, "putc: Only Fixnum and String supported"
92
+ end
93
+ anObject
94
+ end
95
+
96
+ def puts(*params)
97
+ params << "\n" if params.empty?
98
+ params.flatten.each {
99
+ |element|
100
+ val = element.to_s
101
+ self << val
102
+ self << "\n" unless val[-1,1] == "\n"
103
+ }
104
+ end
105
+
106
+ end
107
+
108
+ end # IOExtras namespace module
109
+
110
+
111
+
112
+ # Copyright (C) 2002-2004 Thomas Sondergaard
113
+ # rubyzip is free software; you can redistribute it and/or
114
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,111 @@
1
+ unless Enumerable.method_defined?(:inject)
2
+ module Enumerable #:nodoc:all
3
+ def inject(n = 0)
4
+ each { |value| n = yield(n, value) }
5
+ n
6
+ end
7
+ end
8
+ end
9
+
10
+ module Enumerable #:nodoc:all
11
+ # returns a new array of all the return values not equal to nil
12
+ # This implementation could be faster
13
+ def select_map(&aProc)
14
+ map(&aProc).reject { |e| e.nil? }
15
+ end
16
+ end
17
+
18
+ unless Object.method_defined?(:object_id)
19
+ class Object
20
+ # Using object_id which is the new thing, so we need
21
+ # to make that work in versions prior to 1.8.0
22
+ alias object_id id
23
+ end
24
+ end
25
+
26
+ unless File.respond_to?(:read)
27
+ class File
28
+ # singleton method read does not exist in 1.6.x
29
+ def self.read(fileName)
30
+ open(fileName) { |f| f.read }
31
+ end
32
+ end
33
+ end
34
+
35
+ class String
36
+ def starts_with(aString)
37
+ rindex(aString, 0) == 0
38
+ end
39
+
40
+ def ends_with(aString)
41
+ index(aString, -aString.size)
42
+ end
43
+
44
+ def ensure_end(aString)
45
+ ends_with(aString) ? self : self + aString
46
+ end
47
+
48
+ def lchop
49
+ slice(1, length)
50
+ end
51
+ end
52
+
53
+ class Time
54
+
55
+ #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
56
+ #
57
+ # Register CX, the Time:
58
+ # Bits 0-4 2 second increments (0-29)
59
+ # Bits 5-10 minutes (0-59)
60
+ # bits 11-15 hours (0-24)
61
+ #
62
+ # Register DX, the Date:
63
+ # Bits 0-4 day (1-31)
64
+ # bits 5-8 month (1-12)
65
+ # bits 9-15 year (four digit year minus 1980)
66
+
67
+
68
+ def to_binary_dos_time
69
+ (sec/2) +
70
+ (min << 5) +
71
+ (hour << 11)
72
+ end
73
+
74
+ def to_binary_dos_date
75
+ (day) +
76
+ (month << 5) +
77
+ ((year - 1980) << 9)
78
+ end
79
+
80
+ # Dos time is only stored with two seconds accuracy
81
+ def dos_equals(other)
82
+ to_i/2 == other.to_i/2
83
+ end
84
+
85
+ def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
86
+ second = 2 * ( 0b11111 & binaryDosTime)
87
+ minute = ( 0b11111100000 & binaryDosTime) >> 5
88
+ hour = (0b1111100000000000 & binaryDosTime) >> 11
89
+ day = ( 0b11111 & binaryDosDate)
90
+ month = ( 0b111100000 & binaryDosDate) >> 5
91
+ year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
92
+ begin
93
+ return Time.local(year, month, day, hour, minute, second)
94
+ end
95
+ end
96
+ end
97
+
98
+ class Module
99
+ def forward_message(forwarder, *messagesToForward)
100
+ methodDefs = messagesToForward.map {
101
+ |msg|
102
+ "def #{msg}; #{forwarder}(:#{msg}); end"
103
+ }
104
+ module_eval(methodDefs.join("\n"))
105
+ end
106
+ end
107
+
108
+
109
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
110
+ # rubyzip is free software; you can redistribute it and/or
111
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,195 @@
1
+ #
2
+ # tempfile - manipulates temporary files
3
+ #
4
+ # $Id: tempfile_bugfixed.rb,v 1.2 2004/03/28 12:46:36 thomas Exp $
5
+ #
6
+
7
+ require 'delegate'
8
+ require 'tmpdir'
9
+
10
+ module BugFix
11
+
12
+ # A class for managing temporary files. This library is written to be
13
+ # thread safe.
14
+ class Tempfile < DelegateClass(File)
15
+ MAX_TRY = 10
16
+ @@cleanlist = []
17
+
18
+ # Creates a temporary file of mode 0600 in the temporary directory
19
+ # whose name is basename.pid.n and opens with mode "w+". A Tempfile
20
+ # object works just like a File object.
21
+ #
22
+ # If tmpdir is omitted, the temporary directory is determined by
23
+ # Dir::tmpdir provided by 'tmpdir.rb'.
24
+ # When $SAFE > 0 and the given tmpdir is tainted, it uses
25
+ # /tmp. (Note that ENV values are tainted by default)
26
+ def initialize(basename, tmpdir=Dir::tmpdir)
27
+ if $SAFE > 0 and tmpdir.tainted?
28
+ tmpdir = '/tmp'
29
+ end
30
+
31
+ lock = nil
32
+ n = failure = 0
33
+
34
+ begin
35
+ Thread.critical = true
36
+
37
+ begin
38
+ tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n)
39
+ lock = tmpname + '.lock'
40
+ n += 1
41
+ end while @@cleanlist.include?(tmpname) or
42
+ File.exist?(lock) or File.exist?(tmpname)
43
+
44
+ Dir.mkdir(lock)
45
+ rescue
46
+ failure += 1
47
+ retry if failure < MAX_TRY
48
+ raise "cannot generate tempfile `%s'" % tmpname
49
+ ensure
50
+ Thread.critical = false
51
+ end
52
+
53
+ @data = [tmpname]
54
+ @clean_proc = Tempfile.callback(@data)
55
+ ObjectSpace.define_finalizer(self, @clean_proc)
56
+
57
+ @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
58
+ @tmpname = tmpname
59
+ @@cleanlist << @tmpname
60
+ @data[1] = @tmpfile
61
+ @data[2] = @@cleanlist
62
+
63
+ super(@tmpfile)
64
+
65
+ # Now we have all the File/IO methods defined, you must not
66
+ # carelessly put bare puts(), etc. after this.
67
+
68
+ Dir.rmdir(lock)
69
+ end
70
+
71
+ # Opens or reopens the file with mode "r+".
72
+ def open
73
+ @tmpfile.close if @tmpfile
74
+ @tmpfile = File.open(@tmpname, 'r+')
75
+ @data[1] = @tmpfile
76
+ __setobj__(@tmpfile)
77
+ end
78
+
79
+ def _close # :nodoc:
80
+ @tmpfile.close if @tmpfile
81
+ @data[1] = @tmpfile = nil
82
+ end
83
+ protected :_close
84
+
85
+ # Closes the file. If the optional flag is true, unlinks the file
86
+ # after closing.
87
+ #
88
+ # If you don't explicitly unlink the temporary file, the removal
89
+ # will be delayed until the object is finalized.
90
+ def close(unlink_now=false)
91
+ if unlink_now
92
+ close!
93
+ else
94
+ _close
95
+ end
96
+ end
97
+
98
+ # Closes and unlinks the file.
99
+ def close!
100
+ _close
101
+ @clean_proc.call
102
+ ObjectSpace.undefine_finalizer(self)
103
+ end
104
+
105
+ # Unlinks the file. On UNIX-like systems, it is often a good idea
106
+ # to unlink a temporary file immediately after creating and opening
107
+ # it, because it leaves other programs zero chance to access the
108
+ # file.
109
+ def unlink
110
+ # keep this order for thread safeness
111
+ File.unlink(@tmpname) if File.exist?(@tmpname)
112
+ @@cleanlist.delete(@tmpname) if @@cleanlist
113
+ end
114
+ alias delete unlink
115
+
116
+ if RUBY_VERSION > '1.8.0'
117
+ def __setobj__(obj)
118
+ @_dc_obj = obj
119
+ end
120
+ else
121
+ def __setobj__(obj)
122
+ @obj = obj
123
+ end
124
+ end
125
+
126
+ # Returns the full path name of the temporary file.
127
+ def path
128
+ @tmpname
129
+ end
130
+
131
+ # Returns the size of the temporary file. As a side effect, the IO
132
+ # buffer is flushed before determining the size.
133
+ def size
134
+ if @tmpfile
135
+ @tmpfile.flush
136
+ @tmpfile.stat.size
137
+ else
138
+ 0
139
+ end
140
+ end
141
+ alias length size
142
+
143
+ class << self
144
+ def callback(data) # :nodoc:
145
+ pid = $$
146
+ lambda{
147
+ if pid == $$
148
+ path, tmpfile, cleanlist = *data
149
+
150
+ print "removing ", path, "..." if $DEBUG
151
+
152
+ tmpfile.close if tmpfile
153
+
154
+ # keep this order for thread safeness
155
+ File.unlink(path) if File.exist?(path)
156
+ cleanlist.delete(path) if cleanlist
157
+
158
+ print "done\n" if $DEBUG
159
+ end
160
+ }
161
+ end
162
+
163
+ # If no block is given, this is a synonym for new().
164
+ #
165
+ # If a block is given, it will be passed tempfile as an argument,
166
+ # and the tempfile will automatically be closed when the block
167
+ # terminates. In this case, open() returns nil.
168
+ def open(*args)
169
+ tempfile = new(*args)
170
+
171
+ if block_given?
172
+ begin
173
+ yield(tempfile)
174
+ ensure
175
+ tempfile.close
176
+ end
177
+
178
+ nil
179
+ else
180
+ tempfile
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ end # module BugFix
187
+ if __FILE__ == $0
188
+ # $DEBUG = true
189
+ f = Tempfile.new("foo")
190
+ f.print("foo\n")
191
+ f.close
192
+ f.open
193
+ p f.gets # => "foo\n"
194
+ f.close!
195
+ end
data/extras/zip/zip.rb ADDED
@@ -0,0 +1,1376 @@
1
+ require 'delegate'
2
+ require 'singleton'
3
+ require 'tempfile'
4
+ require 'ftools'
5
+ require 'zlib'
6
+ require 'extras/zip/stdrubyext'
7
+ require 'extras/zip/ioextras'
8
+
9
+ if Tempfile.superclass == SimpleDelegator
10
+ require 'zip/tempfile_bugfixed'
11
+ Tempfile = BugFix::Tempfile
12
+ end
13
+
14
+ module Zlib
15
+ if ! const_defined? :MAX_WBITS
16
+ MAX_WBITS = Zlib::Deflate.MAX_WBITS
17
+ end
18
+ end
19
+
20
+ module Zip
21
+
22
+ RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i
23
+
24
+ # Ruby 1.7.x compatibility
25
+ # In ruby 1.6.x and 1.8.0 reading from an empty stream returns
26
+ # an empty string the first time and then nil.
27
+ # not so in 1.7.x
28
+ EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7
29
+
30
+ class ZipInputStream
31
+ include IOExtras::AbstractInputStream
32
+
33
+ def initialize(filename, offset = 0)
34
+ super()
35
+ @archiveIO = File.open(filename, "rb")
36
+ @archiveIO.seek(offset, IO::SEEK_SET)
37
+ @decompressor = NullDecompressor.instance
38
+ @currentEntry = nil
39
+ end
40
+
41
+ def close
42
+ @archiveIO.close
43
+ end
44
+
45
+ def ZipInputStream.open(filename)
46
+ return new(filename) unless block_given?
47
+
48
+ zio = new(filename)
49
+ yield zio
50
+ ensure
51
+ zio.close if zio
52
+ end
53
+
54
+ def get_next_entry
55
+ @archiveIO.seek(@currentEntry.next_header_offset,
56
+ IO::SEEK_SET) if @currentEntry
57
+ open_entry
58
+ end
59
+
60
+ def rewind
61
+ return if @currentEntry.nil?
62
+ @lineno = 0
63
+ @archiveIO.seek(@currentEntry.localHeaderOffset,
64
+ IO::SEEK_SET)
65
+ open_entry
66
+ end
67
+
68
+ def open_entry
69
+ @currentEntry = ZipEntry.read_local_entry(@archiveIO)
70
+ if (@currentEntry == nil)
71
+ @decompressor = NullDecompressor.instance
72
+ elsif @currentEntry.compression_method == ZipEntry::STORED
73
+ @decompressor = PassThruDecompressor.new(@archiveIO,
74
+ @currentEntry.size)
75
+ elsif @currentEntry.compression_method == ZipEntry::DEFLATED
76
+ @decompressor = Inflater.new(@archiveIO)
77
+ else
78
+ raise ZipCompressionMethodError,
79
+ "Unsupported compression method #{@currentEntry.compression_method}"
80
+ end
81
+ flush
82
+ return @currentEntry
83
+ end
84
+
85
+ def read(numberOfBytes = nil)
86
+ @decompressor.read(numberOfBytes)
87
+ end
88
+ protected
89
+ def produce_input
90
+ @decompressor.produce_input
91
+ end
92
+
93
+ def input_finished?
94
+ @decompressor.input_finished?
95
+ end
96
+ end
97
+
98
+
99
+
100
+ class Decompressor #:nodoc:all
101
+ CHUNK_SIZE=32768
102
+ def initialize(inputStream)
103
+ super()
104
+ @inputStream=inputStream
105
+ end
106
+ end
107
+
108
+ class Inflater < Decompressor #:nodoc:all
109
+ def initialize(inputStream)
110
+ super
111
+ @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
112
+ @outputBuffer=""
113
+ @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
114
+ end
115
+
116
+ def read(numberOfBytes = nil)
117
+ readEverything = (numberOfBytes == nil)
118
+ while (readEverything || @outputBuffer.length < numberOfBytes)
119
+ break if internal_input_finished?
120
+ @outputBuffer << internal_produce_input
121
+ end
122
+ return value_when_finished if @outputBuffer.length==0 && input_finished?
123
+ endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
124
+ return @outputBuffer.slice!(0...endIndex)
125
+ end
126
+
127
+ def produce_input
128
+ if (@outputBuffer.empty?)
129
+ return internal_produce_input
130
+ else
131
+ return @outputBuffer.slice!(0...(@outputBuffer.length))
132
+ end
133
+ end
134
+
135
+ # to be used with produce_input, not read (as read may still have more data cached)
136
+ def input_finished?
137
+ @outputBuffer.empty? && internal_input_finished?
138
+ end
139
+
140
+ private
141
+
142
+ def internal_produce_input
143
+ @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE))
144
+ end
145
+
146
+ def internal_input_finished?
147
+ @zlibInflater.finished?
148
+ end
149
+
150
+ # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
151
+ def value_when_finished # mimic behaviour of ruby File object.
152
+ return nil if @hasReturnedEmptyString
153
+ @hasReturnedEmptyString=true
154
+ return ""
155
+ end
156
+ end
157
+
158
+ class PassThruDecompressor < Decompressor #:nodoc:all
159
+ def initialize(inputStream, charsToRead)
160
+ super inputStream
161
+ @charsToRead = charsToRead
162
+ @readSoFar = 0
163
+ @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
164
+ end
165
+
166
+ # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
167
+ def read(numberOfBytes = nil)
168
+ if input_finished?
169
+ hasReturnedEmptyStringVal=@hasReturnedEmptyString
170
+ @hasReturnedEmptyString=true
171
+ return "" unless hasReturnedEmptyStringVal
172
+ return nil
173
+ end
174
+
175
+ if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead)
176
+ numberOfBytes = @charsToRead-@readSoFar
177
+ end
178
+ @readSoFar += numberOfBytes
179
+ @inputStream.read(numberOfBytes)
180
+ end
181
+
182
+ def produce_input
183
+ read(Decompressor::CHUNK_SIZE)
184
+ end
185
+
186
+ def input_finished?
187
+ (@readSoFar >= @charsToRead)
188
+ end
189
+ end
190
+
191
+ class NullDecompressor #:nodoc:all
192
+ include Singleton
193
+ def read(numberOfBytes = nil)
194
+ nil
195
+ end
196
+
197
+ def produce_input
198
+ nil
199
+ end
200
+
201
+ def input_finished?
202
+ true
203
+ end
204
+ end
205
+
206
+ class NullInputStream < NullDecompressor #:nodoc:all
207
+ include IOExtras::AbstractInputStream
208
+ end
209
+
210
+ class ZipEntry
211
+ STORED = 0
212
+ DEFLATED = 8
213
+
214
+ attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
215
+ :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes
216
+
217
+ def initialize(zipfile = "", name = "", comment = "", extra = "",
218
+ compressed_size = 0, crc = 0,
219
+ compression_method = ZipEntry::DEFLATED, size = 0,
220
+ time = Time.now)
221
+ super()
222
+ if name.starts_with("/")
223
+ raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
224
+ end
225
+ @localHeaderOffset = 0
226
+ @internalFileAttributes = 1
227
+ @externalFileAttributes = 0
228
+ @version = 52 # this library's version
229
+ @fstype = 0 # default is fat
230
+ @zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
231
+ @name, @size = zipfile, comment, compressed_size, crc,
232
+ extra, compression_method, name, size
233
+ @time = time
234
+ unless ZipExtraField === @extra
235
+ @extra = ZipExtraField.new(@extra.to_s)
236
+ end
237
+ end
238
+
239
+ def time
240
+ if @extra["UniversalTime"]
241
+ @extra["UniversalTime"].mtime
242
+ else
243
+ # Atandard time field in central directory has local time
244
+ # under archive creator. Then, we can't get timezone.
245
+ @time
246
+ end
247
+ end
248
+ alias :mtime :time
249
+
250
+ def time=(aTime)
251
+ unless @extra.member?("UniversalTime")
252
+ @extra.create("UniversalTime")
253
+ end
254
+ @extra["UniversalTime"].mtime = aTime
255
+ @time = aTime
256
+ end
257
+
258
+ def directory?
259
+ return (%r{\/$} =~ @name) != nil
260
+ end
261
+ alias :is_directory :directory?
262
+
263
+ def file?
264
+ ! directory?
265
+ end
266
+
267
+ def local_entry_offset #:nodoc:all
268
+ localHeaderOffset + local_header_size
269
+ end
270
+
271
+ def local_header_size #:nodoc:all
272
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0)
273
+ end
274
+
275
+ def cdir_header_size #:nodoc:all
276
+ CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) +
277
+ (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0)
278
+ end
279
+
280
+ def next_header_offset #:nodoc:all
281
+ local_entry_offset + self.compressed_size
282
+ end
283
+
284
+ def to_s
285
+ @name
286
+ end
287
+
288
+ protected
289
+
290
+ def ZipEntry.read_zip_short(io)
291
+ io.read(2).unpack('v')[0]
292
+ end
293
+
294
+ def ZipEntry.read_zip_long(io)
295
+ io.read(4).unpack('V')[0]
296
+ end
297
+ public
298
+
299
+ LOCAL_ENTRY_SIGNATURE = 0x04034b50
300
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
301
+
302
+ def read_local_entry(io) #:nodoc:all
303
+ @localHeaderOffset = io.tell
304
+ staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH)
305
+ unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH)
306
+ raise ZipError, "Premature end of file. Not enough data for zip entry local header"
307
+ end
308
+
309
+ localHeader ,
310
+ @version ,
311
+ @fstype ,
312
+ @gpFlags ,
313
+ @compression_method,
314
+ lastModTime ,
315
+ lastModDate ,
316
+ @crc ,
317
+ @compressed_size ,
318
+ @size ,
319
+ nameLength ,
320
+ extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
321
+
322
+ unless (localHeader == LOCAL_ENTRY_SIGNATURE)
323
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
324
+ end
325
+ set_time(lastModDate, lastModTime)
326
+
327
+ @name = io.read(nameLength)
328
+ extra = io.read(extraLength)
329
+
330
+ if (extra && extra.length != extraLength)
331
+ raise ZipError, "Truncated local zip entry header"
332
+ else
333
+ if ZipExtraField === @extra
334
+ @extra.merge(extra)
335
+ else
336
+ @extra = ZipExtraField.new(extra)
337
+ end
338
+ end
339
+ end
340
+
341
+ def ZipEntry.read_local_entry(io)
342
+ entry = new(io.path)
343
+ entry.read_local_entry(io)
344
+ return entry
345
+ rescue ZipError
346
+ return nil
347
+ end
348
+
349
+ def write_local_entry(io) #:nodoc:all
350
+ @localHeaderOffset = io.tell
351
+
352
+ io <<
353
+ [LOCAL_ENTRY_SIGNATURE ,
354
+ 0 ,
355
+ 0 , # @gpFlags ,
356
+ @compression_method ,
357
+ @time.to_binary_dos_time , # @lastModTime ,
358
+ @time.to_binary_dos_date , # @lastModDate ,
359
+ @crc ,
360
+ @compressed_size ,
361
+ @size ,
362
+ @name ? @name.length : 0,
363
+ @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv')
364
+ io << @name
365
+ io << (@extra ? @extra.to_local_bin : "")
366
+ end
367
+
368
+ CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
369
+ CDIR_ENTRY_STATIC_HEADER_LENGTH = 46
370
+
371
+ def read_c_dir_entry(io) #:nodoc:all
372
+ staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH)
373
+ unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
374
+ raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
375
+ end
376
+
377
+ cdirSignature ,
378
+ @version , # version of encoding software
379
+ @fstype , # filesystem type
380
+ @versionNeededToExtract,
381
+ @gpFlags ,
382
+ @compression_method ,
383
+ lastModTime ,
384
+ lastModDate ,
385
+ @crc ,
386
+ @compressed_size ,
387
+ @size ,
388
+ nameLength ,
389
+ extraLength ,
390
+ commentLength ,
391
+ diskNumberStart ,
392
+ @internalFileAttributes,
393
+ @externalFileAttributes,
394
+ @localHeaderOffset ,
395
+ @name ,
396
+ @extra ,
397
+ @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
398
+
399
+ unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
400
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
401
+ end
402
+ set_time(lastModDate, lastModTime)
403
+
404
+ @name = io.read(nameLength)
405
+ if ZipExtraField === @extra
406
+ @extra.merge(io.read(extraLength))
407
+ else
408
+ @extra = ZipExtraField.new(io.read(extraLength))
409
+ end
410
+ @comment = io.read(commentLength)
411
+ unless (@comment && @comment.length == commentLength)
412
+ raise ZipError, "Truncated cdir zip entry header"
413
+ end
414
+ end
415
+
416
+ def ZipEntry.read_c_dir_entry(io) #:nodoc:all
417
+ entry = new(io.path)
418
+ entry.read_c_dir_entry(io)
419
+ return entry
420
+ rescue ZipError
421
+ return nil
422
+ end
423
+
424
+
425
+ def write_c_dir_entry(io) #:nodoc:all
426
+ io <<
427
+ [CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
428
+ @version , # version of encoding software
429
+ @fstype , # filesystem type
430
+ 0 , # @versionNeededToExtract ,
431
+ 0 , # @gpFlags ,
432
+ @compression_method ,
433
+ @time.to_binary_dos_time , # @lastModTime ,
434
+ @time.to_binary_dos_date , # @lastModDate ,
435
+ @crc ,
436
+ @compressed_size ,
437
+ @size ,
438
+ @name ? @name.length : 0 ,
439
+ @extra ? @extra.c_dir_length : 0 ,
440
+ @comment ? comment.length : 0 ,
441
+ 0 , # disk number start
442
+ @internalFileAttributes , # file type (binary=0, text=1)
443
+ @externalFileAttributes , # native filesystem attributes
444
+ @localHeaderOffset ,
445
+ @name ,
446
+ @extra ,
447
+ @comment ].pack('VCCvvvvvVVVvvvvvVV')
448
+
449
+ io << @name
450
+ io << (@extra ? @extra.to_c_dir_bin : "")
451
+ io << @comment
452
+ end
453
+
454
+ def == (other)
455
+ return false unless other.class == ZipEntry
456
+ # Compares contents of local entry and exposed fields
457
+ (@compression_method == other.compression_method &&
458
+ @crc == other.crc &&
459
+ @compressed_size == other.compressed_size &&
460
+ @size == other.size &&
461
+ @name == other.name &&
462
+ @extra == other.extra &&
463
+ self.time.dos_equals(other.time))
464
+ end
465
+
466
+ def <=> (other)
467
+ return to_s <=> other.to_s
468
+ end
469
+
470
+ def get_input_stream
471
+ zis = ZipInputStream.new(@zipfile, localHeaderOffset)
472
+ zis.get_next_entry
473
+ if block_given?
474
+ begin
475
+ return yield(zis)
476
+ ensure
477
+ zis.close
478
+ end
479
+ else
480
+ return zis
481
+ end
482
+ end
483
+
484
+
485
+ def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all
486
+ aZipOutputStream.copy_raw_entry(self)
487
+ end
488
+
489
+ def parent_as_string
490
+ entry_name = name.chomp("/")
491
+ slash_index = entry_name.rindex("/")
492
+ slash_index ? entry_name.slice(0, slash_index+1) : nil
493
+ end
494
+
495
+ def get_raw_input_stream(&aProc)
496
+ File.open(@zipfile, "rb", &aProc)
497
+ end
498
+
499
+ private
500
+ def set_time(binaryDosDate, binaryDosTime)
501
+ @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime)
502
+ rescue ArgumentError
503
+ puts "Invalid date/time in zip entry"
504
+ end
505
+ end
506
+
507
+
508
+ class ZipOutputStream
509
+ include IOExtras::AbstractOutputStream
510
+
511
+ attr_accessor :comment
512
+
513
+ def initialize(fileName)
514
+ super()
515
+ @fileName = fileName
516
+ @outputStream = File.new(@fileName, "wb")
517
+ @entrySet = ZipEntrySet.new
518
+ @compressor = NullCompressor.instance
519
+ @closed = false
520
+ @currentEntry = nil
521
+ @comment = nil
522
+ end
523
+
524
+ def ZipOutputStream.open(fileName)
525
+ return new(fileName) unless block_given?
526
+ zos = new(fileName)
527
+ yield zos
528
+ ensure
529
+ zos.close if zos
530
+ end
531
+
532
+ def close
533
+ return if @closed
534
+ finalize_current_entry
535
+ update_local_headers
536
+ write_central_directory
537
+ @outputStream.close
538
+ @closed = true
539
+ end
540
+
541
+ def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
542
+ raise ZipError, "zip stream is closed" if @closed
543
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s)
544
+ init_next_entry(newEntry)
545
+ @currentEntry=newEntry
546
+ end
547
+
548
+ def copy_raw_entry(entry)
549
+ entry = entry.dup
550
+ raise ZipError, "zip stream is closed" if @closed
551
+ raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry)
552
+ finalize_current_entry
553
+ @entrySet << entry
554
+ src_pos = entry.local_entry_offset
555
+ entry.write_local_entry(@outputStream)
556
+ @compressor = NullCompressor.instance
557
+ @outputStream << entry.get_raw_input_stream {
558
+ |is|
559
+ is.seek(src_pos, IO::SEEK_SET)
560
+ is.read(entry.compressed_size)
561
+ }
562
+ @compressor = NullCompressor.instance
563
+ @currentEntry = nil
564
+ end
565
+
566
+ private
567
+ def finalize_current_entry
568
+ return unless @currentEntry
569
+ finish
570
+ @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset -
571
+ @currentEntry.local_header_size
572
+ @currentEntry.size = @compressor.size
573
+ @currentEntry.crc = @compressor.crc
574
+ @currentEntry = nil
575
+ @compressor = NullCompressor.instance
576
+ end
577
+
578
+ def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
579
+ finalize_current_entry
580
+ @entrySet << entry
581
+ entry.write_local_entry(@outputStream)
582
+ @compressor = get_compressor(entry, level)
583
+ end
584
+
585
+ def get_compressor(entry, level)
586
+ case entry.compression_method
587
+ when ZipEntry::DEFLATED then Deflater.new(@outputStream, level)
588
+ when ZipEntry::STORED then PassThruCompressor.new(@outputStream)
589
+ else raise ZipCompressionMethodError,
590
+ "Invalid compression method: '#{entry.compression_method}'"
591
+ end
592
+ end
593
+
594
+ def update_local_headers
595
+ pos = @outputStream.tell
596
+ @entrySet.each {
597
+ |entry|
598
+ @outputStream.pos = entry.localHeaderOffset
599
+ entry.write_local_entry(@outputStream)
600
+ }
601
+ @outputStream.pos = pos
602
+ end
603
+
604
+ def write_central_directory
605
+ cdir = ZipCentralDirectory.new(@entrySet, @comment)
606
+ cdir.write_to_stream(@outputStream)
607
+ end
608
+
609
+ protected
610
+
611
+ def finish
612
+ @compressor.finish
613
+ end
614
+
615
+ public
616
+ def << (data)
617
+ @compressor << data
618
+ end
619
+ end
620
+
621
+
622
+ class Compressor #:nodoc:all
623
+ def finish
624
+ end
625
+ end
626
+
627
+ class PassThruCompressor < Compressor #:nodoc:all
628
+ def initialize(outputStream)
629
+ super()
630
+ @outputStream = outputStream
631
+ @crc = Zlib::crc32
632
+ @size = 0
633
+ end
634
+
635
+ def << (data)
636
+ val = data.to_s
637
+ @crc = Zlib::crc32(val, @crc)
638
+ @size += val.size
639
+ @outputStream << val
640
+ end
641
+
642
+ attr_reader :size, :crc
643
+ end
644
+
645
+ class NullCompressor < Compressor #:nodoc:all
646
+ include Singleton
647
+
648
+ def << (data)
649
+ raise IOError, "closed stream"
650
+ end
651
+
652
+ attr_reader :size, :compressed_size
653
+ end
654
+
655
+ class Deflater < Compressor #:nodoc:all
656
+ def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION)
657
+ super()
658
+ @outputStream = outputStream
659
+ @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS)
660
+ @size = 0
661
+ @crc = Zlib::crc32
662
+ end
663
+
664
+ def << (data)
665
+ val = data.to_s
666
+ @crc = Zlib::crc32(val, @crc)
667
+ @size += val.size
668
+ @outputStream << @zlibDeflater.deflate(data)
669
+ end
670
+
671
+ def finish
672
+ until @zlibDeflater.finished?
673
+ @outputStream << @zlibDeflater.finish
674
+ end
675
+ end
676
+
677
+ attr_reader :size, :crc
678
+ end
679
+
680
+
681
+ class ZipEntrySet
682
+ include Enumerable
683
+
684
+ def initialize(anEnumerable = [])
685
+ super()
686
+ @entrySet = {}
687
+ anEnumerable.each { |o| push(o) }
688
+ end
689
+
690
+ def include?(entry)
691
+ @entrySet.include?(entry.to_s)
692
+ end
693
+
694
+ def <<(entry)
695
+ @entrySet[entry.to_s] = entry
696
+ end
697
+ alias :push :<<
698
+
699
+ def size
700
+ @entrySet.size
701
+ end
702
+ alias :length :size
703
+
704
+ def delete(entry)
705
+ @entrySet.delete(entry.to_s) ? entry : nil
706
+ end
707
+
708
+ def each(&aProc)
709
+ @entrySet.values.each(&aProc)
710
+ end
711
+
712
+ def entries
713
+ @entrySet.values
714
+ end
715
+
716
+ # deep clone
717
+ def dup
718
+ newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup })
719
+ end
720
+
721
+ def == (other)
722
+ return false unless other.kind_of?(ZipEntrySet)
723
+ return @entrySet == other.entrySet
724
+ end
725
+
726
+ def parent(entry)
727
+ @entrySet[entry.parent_as_string]
728
+ end
729
+
730
+ #TODO attr_accessor :auto_create_directories
731
+ protected
732
+ attr_accessor :entrySet
733
+ end
734
+
735
+
736
+ class ZipCentralDirectory #:nodoc:all
737
+ include Enumerable
738
+
739
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
740
+ MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18
741
+ STATIC_EOCD_SIZE = 22
742
+
743
+ attr_reader :comment
744
+
745
+ def entries
746
+ @entrySet.entries
747
+ end
748
+
749
+ def initialize(entries = ZipEntrySet.new, comment = "")
750
+ super()
751
+ @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
752
+ @comment = comment
753
+ end
754
+
755
+ def write_to_stream(io)
756
+ offset = io.tell
757
+ @entrySet.each { |entry| entry.write_c_dir_entry(io) }
758
+ write_e_o_c_d(io, offset)
759
+ end
760
+
761
+ def write_e_o_c_d(io, offset)
762
+ io <<
763
+ [END_OF_CENTRAL_DIRECTORY_SIGNATURE,
764
+ 0 , # @numberOfThisDisk
765
+ 0 , # @numberOfDiskWithStartOfCDir
766
+ @entrySet? @entrySet.size : 0 ,
767
+ @entrySet? @entrySet.size : 0 ,
768
+ cdir_size ,
769
+ offset ,
770
+ @comment ? @comment.length : 0 ].pack('VvvvvVVv')
771
+ io << @comment
772
+ end
773
+ private :write_e_o_c_d
774
+
775
+ def cdir_size
776
+ # does not include eocd
777
+ @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
778
+ end
779
+ private :cdir_size
780
+
781
+ def read_e_o_c_d(io)
782
+ buf = get_e_o_c_d(io)
783
+ @numberOfThisDisk = ZipEntry::read_zip_short(buf)
784
+ @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
785
+ @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf)
786
+ @size = ZipEntry::read_zip_short(buf)
787
+ @sizeInBytes = ZipEntry::read_zip_long(buf)
788
+ @cdirOffset = ZipEntry::read_zip_long(buf)
789
+ commentLength = ZipEntry::read_zip_short(buf)
790
+ @comment = buf.read(commentLength)
791
+ raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
792
+ end
793
+
794
+ def read_central_directory_entries(io)
795
+ begin
796
+ io.seek(@cdirOffset, IO::SEEK_SET)
797
+ rescue Errno::EINVAL
798
+ raise ZipError, "Zip consistency problem while reading central directory entry"
799
+ end
800
+ @entrySet = ZipEntrySet.new
801
+ @size.times {
802
+ @entrySet << ZipEntry.read_c_dir_entry(io)
803
+ }
804
+ end
805
+
806
+ def read_from_stream(io)
807
+ read_e_o_c_d(io)
808
+ read_central_directory_entries(io)
809
+ end
810
+
811
+ def get_e_o_c_d(io)
812
+ begin
813
+ io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
814
+ rescue Errno::EINVAL
815
+ io.seek(0, IO::SEEK_SET)
816
+ rescue Errno::EFBIG # FreeBSD 4.9 returns Errno::EFBIG instead of Errno::EINVAL
817
+ io.seek(0, IO::SEEK_SET)
818
+ end
819
+ buf = io.read
820
+ sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
821
+ raise ZipError, "Zip end of central directory signature not found" unless sigIndex
822
+ buf=buf.slice!((sigIndex+4)...(buf.size))
823
+ def buf.read(count)
824
+ slice!(0, count)
825
+ end
826
+ return buf
827
+ end
828
+
829
+ def each(&proc)
830
+ @entrySet.each(&proc)
831
+ end
832
+
833
+ def size
834
+ @entrySet.size
835
+ end
836
+
837
+ def ZipCentralDirectory.read_from_stream(io)
838
+ cdir = new
839
+ cdir.read_from_stream(io)
840
+ return cdir
841
+ rescue ZipError
842
+ return nil
843
+ end
844
+
845
+ def == (other)
846
+ return false unless other.kind_of?(ZipCentralDirectory)
847
+ @entrySet.entries.sort == other.entries.sort && comment == other.comment
848
+ end
849
+ end
850
+
851
+
852
+ class ZipError < StandardError ; end
853
+
854
+ class ZipEntryExistsError < ZipError; end
855
+ class ZipDestinationFileExistsError < ZipError; end
856
+ class ZipCompressionMethodError < ZipError; end
857
+ class ZipEntryNameError < ZipError; end
858
+
859
+ class ZipFile < ZipCentralDirectory
860
+
861
+ CREATE = 1
862
+
863
+ attr_reader :name
864
+
865
+ def initialize(fileName, create = nil)
866
+ super()
867
+ @name = fileName
868
+ @comment = ""
869
+ if (File.exists?(fileName))
870
+ File.open(name, "rb") { |f| read_from_stream(f) }
871
+ elsif (create == ZipFile::CREATE)
872
+ @entrySet = ZipEntrySet.new
873
+ else
874
+ raise ZipError, "File #{fileName} not found"
875
+ end
876
+ @create = create
877
+ @storedEntries = @entrySet.dup
878
+ end
879
+
880
+ def ZipFile.open(fileName, create = nil)
881
+ zf = ZipFile.new(fileName, create)
882
+ if block_given?
883
+ begin
884
+ yield zf
885
+ ensure
886
+ zf.close
887
+ end
888
+ else
889
+ zf
890
+ end
891
+ end
892
+
893
+ attr_accessor :comment
894
+
895
+ def ZipFile.foreach(aZipFileName, &block)
896
+ ZipFile.open(aZipFileName) {
897
+ |zipFile|
898
+ zipFile.each(&block)
899
+ }
900
+ end
901
+
902
+ def get_input_stream(entry, &aProc)
903
+ get_entry(entry).get_input_stream(&aProc)
904
+ end
905
+
906
+ def get_output_stream(entry, &aProc)
907
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
908
+ if newEntry.directory?
909
+ raise ArgumentError,
910
+ "cannot open stream to directory entry - '#{newEntry}'"
911
+ end
912
+ zipStreamableEntry = ZipStreamableStream.new(newEntry)
913
+ @entrySet << zipStreamableEntry
914
+ zipStreamableEntry.get_output_stream(&aProc)
915
+ end
916
+
917
+ def to_s
918
+ @name
919
+ end
920
+
921
+ def read(entry)
922
+ get_input_stream(entry) { |is| is.read }
923
+ end
924
+
925
+ def add(entry, srcPath, &continueOnExistsProc)
926
+ continueOnExistsProc ||= proc { false }
927
+ check_entry_exists(entry, continueOnExistsProc, "add")
928
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
929
+ if is_directory(newEntry, srcPath)
930
+ @entrySet << ZipStreamableDirectory.new(newEntry)
931
+ else
932
+ @entrySet << ZipStreamableFile.new(newEntry, srcPath)
933
+ end
934
+ end
935
+
936
+ def remove(entry)
937
+ @entrySet.delete(get_entry(entry))
938
+ end
939
+
940
+ def rename(entry, newName, &continueOnExistsProc)
941
+ foundEntry = get_entry(entry)
942
+ check_entry_exists(newName, continueOnExistsProc, "rename")
943
+ foundEntry.name=newName
944
+ end
945
+
946
+ def replace(entry, srcPath)
947
+ check_file(srcPath)
948
+ add(remove(entry), srcPath)
949
+ end
950
+
951
+ def extract(entry, destPath, &onExistsProc)
952
+ onExistsProc ||= proc { false }
953
+ foundEntry = get_entry(entry)
954
+ if foundEntry.is_directory
955
+ create_directory(foundEntry, destPath, &onExistsProc)
956
+ else
957
+ write_file(foundEntry, destPath, &onExistsProc)
958
+ end
959
+ end
960
+
961
+ def commit
962
+ return if ! commit_required?
963
+ on_success_replace(name) {
964
+ |tmpFile|
965
+ ZipOutputStream.open(tmpFile) {
966
+ |zos|
967
+
968
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
969
+ zos.comment = comment
970
+ }
971
+ true
972
+ }
973
+ initialize(name)
974
+ end
975
+
976
+ def close
977
+ commit
978
+ end
979
+
980
+ def commit_required?
981
+ return @entrySet != @storedEntries || @create == ZipFile::CREATE
982
+ end
983
+
984
+ def find_entry(entry)
985
+ @entrySet.detect {
986
+ |e|
987
+ e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
988
+ }
989
+ end
990
+
991
+ def get_entry(entry)
992
+ selectedEntry = find_entry(entry)
993
+ unless selectedEntry
994
+ raise Errno::ENOENT, entry
995
+ end
996
+ return selectedEntry
997
+ end
998
+
999
+ def mkdir(entryName, permissionInt = 0) #permissionInt ignored
1000
+ if find_entry(entryName)
1001
+ raise Errno::EEXIST, "File exists - #{entryName}"
1002
+ end
1003
+ @entrySet << ZipStreamableDirectory.new(ZipEntry.new(name, entryName.to_s.ensure_end("/")))
1004
+ end
1005
+
1006
+ private
1007
+
1008
+ def create_directory(entry, destPath)
1009
+ if File.directory? destPath
1010
+ return
1011
+ elsif File.exists? destPath
1012
+ if block_given? && yield(entry, destPath)
1013
+ File.rm_f destPath
1014
+ else
1015
+ raise ZipDestinationFileExistsError,
1016
+ "Cannot create directory '#{destPath}'. "+
1017
+ "A file already exists with that name"
1018
+ end
1019
+ end
1020
+ Dir.mkdir destPath
1021
+ end
1022
+
1023
+ def is_directory(newEntry, srcPath)
1024
+ srcPathIsDirectory = File.directory?(srcPath)
1025
+ if newEntry.is_directory && ! srcPathIsDirectory
1026
+ raise ArgumentError,
1027
+ "entry name '#{newEntry}' indicates directory entry, but "+
1028
+ "'#{srcPath}' is not a directory"
1029
+ elsif ! newEntry.is_directory && srcPathIsDirectory
1030
+ newEntry.name += "/"
1031
+ end
1032
+ return newEntry.is_directory && srcPathIsDirectory
1033
+ end
1034
+
1035
+ def check_entry_exists(entryName, continueOnExistsProc, procedureName)
1036
+ continueOnExistsProc ||= proc { false }
1037
+ if @entrySet.detect { |e| e.name == entryName }
1038
+ if continueOnExistsProc.call
1039
+ remove get_entry(entryName)
1040
+ else
1041
+ raise ZipEntryExistsError,
1042
+ procedureName+" failed. Entry #{entryName} already exists"
1043
+ end
1044
+ end
1045
+ end
1046
+
1047
+ def write_file(entry, destPath, continueOnExistsProc = proc { false })
1048
+ if File.exists?(destPath) && ! yield(entry, destPath)
1049
+ raise ZipDestinationFileExistsError,
1050
+ "Destination '#{destPath}' already exists"
1051
+ end
1052
+ File.open(destPath, "wb") {
1053
+ |os|
1054
+ entry.get_input_stream { |is| os << is.read }
1055
+ }
1056
+ end
1057
+
1058
+ def check_file(path)
1059
+ unless File.readable? path
1060
+ raise Errno::ENOENT, path
1061
+ end
1062
+ end
1063
+
1064
+ def on_success_replace(aFilename)
1065
+ tmpfile = get_tempfile
1066
+ tmpFilename = tmpfile.path
1067
+ tmpfile.close
1068
+ if yield tmpFilename
1069
+ File.move(tmpFilename, name)
1070
+ end
1071
+ end
1072
+
1073
+ def get_tempfile
1074
+ tempFile = Tempfile.new(File.basename(name), File.dirname(name))
1075
+ tempFile.binmode
1076
+ tempFile
1077
+ end
1078
+
1079
+ end
1080
+
1081
+ class ZipStreamableFile < DelegateClass(ZipEntry) #:nodoc:all
1082
+ def initialize(entry, filepath)
1083
+ super(entry)
1084
+ @delegate = entry
1085
+ @filepath = filepath
1086
+ end
1087
+
1088
+ def get_input_stream(&aProc)
1089
+ File.open(@filepath, "rb", &aProc)
1090
+ end
1091
+
1092
+ def write_to_zip_output_stream(aZipOutputStream)
1093
+ aZipOutputStream.put_next_entry(self)
1094
+ aZipOutputStream << get_input_stream { |is| is.read }
1095
+ end
1096
+
1097
+ def == (other)
1098
+ return false unless other.class == ZipStreamableFile
1099
+ @filepath == other.filepath && super(other.delegate)
1100
+ end
1101
+
1102
+ protected
1103
+ attr_reader :filepath, :delegate
1104
+ end
1105
+
1106
+ class ZipStreamableDirectory < DelegateClass(ZipEntry) #:nodoc:all
1107
+ def initialize(entry)
1108
+ super(entry)
1109
+ end
1110
+
1111
+ def get_input_stream(&aProc)
1112
+ return yield(NullInputStream.instance) if block_given?
1113
+ NullInputStream.instance
1114
+ end
1115
+
1116
+ def write_to_zip_output_stream(aZipOutputStream)
1117
+ aZipOutputStream.put_next_entry(self)
1118
+ end
1119
+ end
1120
+
1121
+ class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all
1122
+ def initialize(entry)
1123
+ super(entry)
1124
+ @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile))
1125
+ @tempFile.binmode
1126
+ end
1127
+
1128
+ def get_output_stream
1129
+ if block_given?
1130
+ begin
1131
+ yield(@tempFile)
1132
+ ensure
1133
+ @tempFile.close
1134
+ end
1135
+ else
1136
+ @tempFile
1137
+ end
1138
+ end
1139
+
1140
+ def get_input_stream
1141
+ if ! @tempFile.closed?
1142
+ raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
1143
+ end
1144
+ @tempFile.open # reopens tempfile from top
1145
+ if block_given?
1146
+ begin
1147
+ yield(@tempFile)
1148
+ ensure
1149
+ @tempFile.close
1150
+ end
1151
+ else
1152
+ @tempFile
1153
+ end
1154
+ end
1155
+
1156
+ def write_to_zip_output_stream(aZipOutputStream)
1157
+ aZipOutputStream.put_next_entry(self)
1158
+ aZipOutputStream << get_input_stream { |is| is.read }
1159
+ end
1160
+ end
1161
+
1162
+ class ZipExtraField < Hash
1163
+ ID_MAP = {}
1164
+
1165
+ # Meta class for extra fields
1166
+ class Generic
1167
+ def self.register_map
1168
+ if self.const_defined?(:HEADER_ID)
1169
+ ID_MAP[self.const_get(:HEADER_ID)] = self
1170
+ end
1171
+ end
1172
+
1173
+ def self.name
1174
+ self.to_s.split("::")[-1]
1175
+ end
1176
+
1177
+ # return field [size, content] or false
1178
+ def initial_parse(binstr)
1179
+ if ! binstr
1180
+ # If nil, start with empty.
1181
+ return false
1182
+ elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
1183
+ $stderr.puts "Warning: weired extra feild header ID. skip parsing"
1184
+ return false
1185
+ end
1186
+ [binstr[2,2].unpack("v")[0], binstr[4..-1]]
1187
+ end
1188
+
1189
+ def ==(other)
1190
+ self.class != other.class and return false
1191
+ each { |k, v|
1192
+ v != other[k] and return false
1193
+ }
1194
+ true
1195
+ end
1196
+
1197
+ def to_local_bin
1198
+ s = pack_for_local
1199
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
1200
+ end
1201
+
1202
+ def to_c_dir_bin
1203
+ s = pack_for_c_dir
1204
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
1205
+ end
1206
+ end
1207
+
1208
+ # Info-ZIP Additional timestamp field
1209
+ class UniversalTime < Generic
1210
+ HEADER_ID = "UT"
1211
+ register_map
1212
+
1213
+ def initialize(binstr = nil)
1214
+ @ctime = nil
1215
+ @mtime = nil
1216
+ @atime = nil
1217
+ @flag = nil
1218
+ binstr and merge(binstr)
1219
+ end
1220
+ attr_accessor :atime, :ctime, :mtime, :flag
1221
+
1222
+ def merge(binstr)
1223
+ binstr == "" and return
1224
+ size, content = initial_parse(binstr)
1225
+ size or return
1226
+ @flag, mtime, atime, ctime = content.unpack("CVVV")
1227
+ mtime and @mtime ||= Time.at(mtime)
1228
+ atime and @atime ||= Time.at(atime)
1229
+ ctime and @ctime ||= Time.at(ctime)
1230
+ end
1231
+
1232
+ def ==(other)
1233
+ @mtime == other.mtime &&
1234
+ @atime == other.atime &&
1235
+ @ctime == other.ctime
1236
+ end
1237
+
1238
+ def pack_for_local
1239
+ s = [@flag].pack("C")
1240
+ @flag & 1 != 0 and s << [@mtime.to_i].pack("V")
1241
+ @flag & 2 != 0 and s << [@atime.to_i].pack("V")
1242
+ @flag & 4 != 0 and s << [@ctime.to_i].pack("V")
1243
+ s
1244
+ end
1245
+
1246
+ def pack_for_c_dir
1247
+ s = [@flag].pack("C")
1248
+ @flag & 1 == 1 and s << [@mtime.to_i].pack("V")
1249
+ s
1250
+ end
1251
+ end
1252
+
1253
+ # Info-ZIP Extra for UNIX uid/gid
1254
+ class IUnix < Generic
1255
+ HEADER_ID = "Ux"
1256
+ register_map
1257
+
1258
+ def initialize(binstr = nil)
1259
+ @uid = nil
1260
+ @gid = nil
1261
+ binstr and merge(binstr)
1262
+ end
1263
+ attr_accessor :uid, :gid
1264
+
1265
+ def merge(binstr)
1266
+ binstr == "" and return
1267
+ size, content = initial_parse(binstr)
1268
+ # size: 0 for central direcotry. 4 for local header
1269
+ return if(! size || size == 0)
1270
+ uid, gid = content.unpack("vv")
1271
+ @uid ||= uid
1272
+ @gid ||= gid
1273
+ end
1274
+
1275
+ def ==(other)
1276
+ @uid == other.uid &&
1277
+ @gid == other.gid
1278
+ end
1279
+
1280
+ def pack_for_local
1281
+ [@uid, @gid].pack("vv")
1282
+ end
1283
+
1284
+ def pack_for_c_dir
1285
+ ""
1286
+ end
1287
+ end
1288
+
1289
+ ## start main of ZipExtraField < Hash
1290
+ def initialize(binstr = nil)
1291
+ binstr and merge(binstr)
1292
+ end
1293
+
1294
+ def merge(binstr)
1295
+ binstr == "" and return
1296
+ i = 0
1297
+ while i < binstr.length
1298
+ id = binstr[i,2]
1299
+ len = binstr[i+2,2].to_s.unpack("v")[0]
1300
+ if id && ID_MAP.member?(id)
1301
+ field_name = ID_MAP[id].name
1302
+ if self.member?(field_name)
1303
+ self[field_name].mergea(binstr[i, len+4])
1304
+ else
1305
+ field_obj = ID_MAP[id].new(binstr[i, len+4])
1306
+ self[field_name] = field_obj
1307
+ end
1308
+ elsif id
1309
+ unless self["Unknown"]
1310
+ s = ""
1311
+ class << s
1312
+ alias_method :to_c_dir_bin, :to_s
1313
+ alias_method :to_local_bin, :to_s
1314
+ end
1315
+ self["Unknown"] = s
1316
+ end
1317
+ if ! len || len+4 > binstr[i..-1].length
1318
+ self["Unknown"] << binstr[i..-1]
1319
+ break;
1320
+ end
1321
+ self["Unknown"] << binstr[i, len+4]
1322
+ end
1323
+ i += len+4
1324
+ end
1325
+ end
1326
+
1327
+ def create(name)
1328
+ field_class = nil
1329
+ ID_MAP.each { |id, klass|
1330
+ if klass.name == name
1331
+ field_class = klass
1332
+ break
1333
+ end
1334
+ }
1335
+ if ! field_class
1336
+ raise ZipError, "Unknown extra field '#{name}'"
1337
+ end
1338
+ self[name] = field_class.new()
1339
+ end
1340
+
1341
+ def to_local_bin
1342
+ s = ""
1343
+ each { |k, v|
1344
+ s << v.to_local_bin
1345
+ }
1346
+ s
1347
+ end
1348
+ alias :to_s :to_local_bin
1349
+
1350
+ def to_c_dir_bin
1351
+ s = ""
1352
+ each { |k, v|
1353
+ s << v.to_c_dir_bin
1354
+ }
1355
+ s
1356
+ end
1357
+
1358
+ def c_dir_length
1359
+ to_c_dir_bin.length
1360
+ end
1361
+ def local_length
1362
+ to_local_bin.length
1363
+ end
1364
+ alias :c_dir_size :c_dir_length
1365
+ alias :local_size :local_length
1366
+ alias :length :local_length
1367
+ alias :size :local_length
1368
+ end # end ZipExtraField
1369
+
1370
+ end # Zip namespace module
1371
+
1372
+
1373
+
1374
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
1375
+ # rubyzip is free software; you can redistribute it and/or
1376
+ # modify it under the terms of the ruby license.