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.
- data/code/superant.com.rubyslippers/listgemrecord.rb +2 -2
- data/configuration/rubyslippersversion.cnf +1 -1
- data/extras/zip/ioextras.rb +114 -0
- data/extras/zip/stdrubyext.rb +111 -0
- data/extras/zip/tempfile_bugfixed.rb +195 -0
- data/extras/zip/zip.rb +1376 -0
- data/extras/zip/zipfilesystem.rb +558 -0
- data/extras/zip/ziprequire.rb +61 -0
- data/rwd_files/HowTo_RubySlippers.txt +3 -0
- metadata +8 -2
@@ -1,7 +1,7 @@
|
|
1
1
|
# this code is to list the gemspecs
|
2
2
|
def listgemrecordfiles
|
3
|
-
|
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.
|
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.
|