mezza-rubyzip 0.9.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ module Zip
2
+ class ZipEntrySet #:nodoc:all
3
+ include Enumerable
4
+
5
+ def initialize(anEnumerable = [])
6
+ super()
7
+ @entrySet = {}
8
+ anEnumerable.each { |o| push(o) }
9
+ end
10
+
11
+ def include?(entry)
12
+ @entrySet.include?(entry.to_s)
13
+ end
14
+
15
+ def <<(entry)
16
+ @entrySet[entry.to_s] = entry
17
+ end
18
+ alias :push :<<
19
+
20
+ def size
21
+ @entrySet.size
22
+ end
23
+ alias :length :size
24
+
25
+ def delete(entry)
26
+ @entrySet.delete(entry.to_s) ? entry : nil
27
+ end
28
+
29
+ def each(&aProc)
30
+ @entrySet.values.each(&aProc)
31
+ end
32
+
33
+ def entries
34
+ @entrySet.values
35
+ end
36
+
37
+ # deep clone
38
+ def dup
39
+ newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup })
40
+ end
41
+
42
+ def == (other)
43
+ return false unless other.kind_of?(ZipEntrySet)
44
+ return @entrySet == other.entrySet
45
+ end
46
+
47
+ def parent(entry)
48
+ @entrySet[entry.parent_as_string]
49
+ end
50
+
51
+ def glob(pattern, flags = ::File::FNM_PATHNAME|::File::FNM_DOTMATCH)
52
+ entries.select {
53
+ |entry|
54
+ ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
55
+ }
56
+ end
57
+
58
+ #TODO attr_accessor :auto_create_directories
59
+ protected
60
+ attr_accessor :entrySet
61
+ end
62
+ end
63
+
64
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
65
+ # rubyzip is free software; you can redistribute it and/or
66
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,213 @@
1
+ module Zip
2
+ class ZipExtraField < Hash
3
+ ID_MAP = {}
4
+
5
+ # Meta class for extra fields
6
+ class Generic
7
+ def self.register_map
8
+ if self.const_defined?(:HEADER_ID)
9
+ ID_MAP[self.const_get(:HEADER_ID)] = self
10
+ end
11
+ end
12
+
13
+ def self.name
14
+ self.to_s.split("::")[-1]
15
+ end
16
+
17
+ # return field [size, content] or false
18
+ def initial_parse(binstr)
19
+ if ! binstr
20
+ # If nil, start with empty.
21
+ return false
22
+ elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
23
+ $stderr.puts "Warning: weired extra feild header ID. skip parsing"
24
+ return false
25
+ end
26
+ [binstr[2,2].unpack("v")[0], binstr[4..-1]]
27
+ end
28
+
29
+ def ==(other)
30
+ self.class != other.class and return false
31
+ each { |k, v|
32
+ v != other[k] and return false
33
+ }
34
+ true
35
+ end
36
+
37
+ def to_local_bin
38
+ s = pack_for_local
39
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
40
+ end
41
+
42
+ def to_c_dir_bin
43
+ s = pack_for_c_dir
44
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
45
+ end
46
+ end
47
+
48
+ # Info-ZIP Additional timestamp field
49
+ class UniversalTime < Generic
50
+ HEADER_ID = "UT"
51
+ register_map
52
+
53
+ def initialize(binstr = nil)
54
+ @ctime = nil
55
+ @mtime = nil
56
+ @atime = nil
57
+ @flag = nil
58
+ binstr and merge(binstr)
59
+ end
60
+ attr_accessor :atime, :ctime, :mtime, :flag
61
+
62
+ def merge(binstr)
63
+ binstr == "" and return
64
+ size, content = initial_parse(binstr)
65
+ size or return
66
+ @flag, mtime, atime, ctime = content.unpack("CVVV")
67
+ mtime and @mtime ||= Time.at(mtime)
68
+ atime and @atime ||= Time.at(atime)
69
+ ctime and @ctime ||= Time.at(ctime)
70
+ end
71
+
72
+ def ==(other)
73
+ @mtime == other.mtime &&
74
+ @atime == other.atime &&
75
+ @ctime == other.ctime
76
+ end
77
+
78
+ def pack_for_local
79
+ s = [@flag].pack("C")
80
+ @flag & 1 != 0 and s << [@mtime.to_i].pack("V")
81
+ @flag & 2 != 0 and s << [@atime.to_i].pack("V")
82
+ @flag & 4 != 0 and s << [@ctime.to_i].pack("V")
83
+ s
84
+ end
85
+
86
+ def pack_for_c_dir
87
+ s = [@flag].pack("C")
88
+ @flag & 1 == 1 and s << [@mtime.to_i].pack("V")
89
+ s
90
+ end
91
+ end
92
+
93
+ # Info-ZIP Extra for UNIX uid/gid
94
+ class IUnix < Generic
95
+ HEADER_ID = "Ux"
96
+ register_map
97
+
98
+ def initialize(binstr = nil)
99
+ @uid = 0
100
+ @gid = 0
101
+ binstr and merge(binstr)
102
+ end
103
+ attr_accessor :uid, :gid
104
+
105
+ def merge(binstr)
106
+ binstr == "" and return
107
+ size, content = initial_parse(binstr)
108
+ # size: 0 for central direcotry. 4 for local header
109
+ return if(! size || size == 0)
110
+ uid, gid = content.unpack("vv")
111
+ @uid ||= uid
112
+ @gid ||= gid
113
+ end
114
+
115
+ def ==(other)
116
+ @uid == other.uid &&
117
+ @gid == other.gid
118
+ end
119
+
120
+ def pack_for_local
121
+ [@uid, @gid].pack("vv")
122
+ end
123
+
124
+ def pack_for_c_dir
125
+ ""
126
+ end
127
+ end
128
+
129
+ ## start main of ZipExtraField < Hash
130
+ def initialize(binstr = nil)
131
+ binstr and merge(binstr)
132
+ end
133
+
134
+ def merge(binstr)
135
+ binstr == "" and return
136
+ i = 0
137
+ while i < binstr.length
138
+ id = binstr[i,2]
139
+ len = binstr[i+2,2].to_s.unpack("v")[0]
140
+ if id && ID_MAP.member?(id)
141
+ field_name = ID_MAP[id].name
142
+ if self.member?(field_name)
143
+ self[field_name].mergea(binstr[i, len+4])
144
+ else
145
+ field_obj = ID_MAP[id].new(binstr[i, len+4])
146
+ self[field_name] = field_obj
147
+ end
148
+ elsif id
149
+ unless self["Unknown"]
150
+ s = ""
151
+ class << s
152
+ alias_method :to_c_dir_bin, :to_s
153
+ alias_method :to_local_bin, :to_s
154
+ end
155
+ self["Unknown"] = s
156
+ end
157
+ if ! len || len+4 > binstr[i..-1].length
158
+ self["Unknown"] << binstr[i..-1]
159
+ break;
160
+ end
161
+ self["Unknown"] << binstr[i, len+4]
162
+ end
163
+ i += len+4
164
+ end
165
+ end
166
+
167
+ def create(name)
168
+ field_class = nil
169
+ ID_MAP.each { |id, klass|
170
+ if klass.name == name
171
+ field_class = klass
172
+ break
173
+ end
174
+ }
175
+ if ! field_class
176
+ raise ZipError, "Unknown extra field '#{name}'"
177
+ end
178
+ self[name] = field_class.new()
179
+ end
180
+
181
+ def to_local_bin
182
+ s = ""
183
+ each { |k, v|
184
+ s << v.to_local_bin
185
+ }
186
+ s
187
+ end
188
+ alias :to_s :to_local_bin
189
+
190
+ def to_c_dir_bin
191
+ s = ""
192
+ each { |k, v|
193
+ s << v.to_c_dir_bin
194
+ }
195
+ s
196
+ end
197
+
198
+ def c_dir_length
199
+ to_c_dir_bin.length
200
+ end
201
+ def local_length
202
+ to_local_bin.length
203
+ end
204
+ alias :c_dir_size :c_dir_length
205
+ alias :local_size :local_length
206
+ alias :length :local_length
207
+ alias :size :local_length
208
+ end
209
+ end
210
+
211
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
212
+ # rubyzip is free software; you can redistribute it and/or
213
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,310 @@
1
+ module Zip
2
+ # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
3
+ # The most important methods are those inherited from
4
+ # ZipCentralDirectory for accessing information about the entries in
5
+ # the archive and methods such as get_input_stream and
6
+ # get_output_stream for reading from and writing entries to the
7
+ # archive. The class includes a few convenience methods such as
8
+ # #extract for extracting entries to the filesystem, and #remove,
9
+ # #replace, #rename and #mkdir for making simple modifications to
10
+ # the archive.
11
+ #
12
+ # Modifications to a zip archive are not committed until #commit or
13
+ # #close is called. The method #open accepts a block following
14
+ # the pattern from File.open offering a simple way to
15
+ # automatically close the archive when the block returns.
16
+ #
17
+ # The following example opens zip archive <code>my.zip</code>
18
+ # (creating it if it doesn't exist) and adds an entry
19
+ # <code>first.txt</code> and a directory entry <code>a_dir</code>
20
+ # to it.
21
+ #
22
+ # require 'zip/zip'
23
+ #
24
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
25
+ # |zipfile|
26
+ # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
27
+ # zipfile.mkdir("a_dir")
28
+ # }
29
+ #
30
+ # The next example reopens <code>my.zip</code> writes the contents of
31
+ # <code>first.txt</code> to standard out and deletes the entry from
32
+ # the archive.
33
+ #
34
+ # require 'zip/zip'
35
+ #
36
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
37
+ # |zipfile|
38
+ # puts zipfile.read("first.txt")
39
+ # zipfile.remove("first.txt")
40
+ # }
41
+ #
42
+ # ZipFileSystem offers an alternative API that emulates ruby's
43
+ # interface for accessing the filesystem, ie. the File and Dir classes.
44
+
45
+ class ZipFile < ZipCentralDirectory
46
+
47
+ CREATE = 1
48
+
49
+ attr_reader :name
50
+
51
+ # default -> false
52
+ attr_accessor :restore_ownership
53
+ # default -> false
54
+ attr_accessor :restore_permissions
55
+ # default -> true
56
+ attr_accessor :restore_times
57
+
58
+ # Opens a zip archive. Pass true as the second parameter to create
59
+ # a new archive if it doesn't exist already.
60
+ def initialize(fileName, create = nil, buffer = false)
61
+ super()
62
+ @name = fileName
63
+ @comment = ""
64
+ if (::File.exists?(fileName)) and !buffer
65
+ ::File.open(name, "rb") { |f| read_from_stream(f) }
66
+ elsif (create)
67
+ @entrySet = ZipEntrySet.new
68
+ else
69
+ raise ZipError, "File #{fileName} not found"
70
+ end
71
+ @create = create
72
+ @storedEntries = @entrySet.dup
73
+
74
+ @restore_ownership = false
75
+ @restore_permissions = false
76
+ @restore_times = true
77
+ end
78
+
79
+ # Same as #new. If a block is passed the ZipFile object is passed
80
+ # to the block and is automatically closed afterwards just as with
81
+ # ruby's builtin File.open method.
82
+ def ZipFile.open(fileName, create = nil)
83
+ zf = ZipFile.new(fileName, create)
84
+ if block_given?
85
+ begin
86
+ yield zf
87
+ ensure
88
+ zf.close
89
+ end
90
+ else
91
+ zf
92
+ end
93
+ end
94
+
95
+ # Same as #open. But outputs data to a buffer instead of a file
96
+ def ZipFile.add_buffer
97
+ zf = ZipFile.new('', true, true)
98
+ begin
99
+ yield zf
100
+ ensure
101
+ buffer = zf.write_buffer
102
+ return buffer
103
+ end
104
+ end
105
+
106
+ # Returns the zip files comment, if it has one
107
+ attr_accessor :comment
108
+
109
+ # Iterates over the contents of the ZipFile. This is more efficient
110
+ # than using a ZipInputStream since this methods simply iterates
111
+ # through the entries in the central directory structure in the archive
112
+ # whereas ZipInputStream jumps through the entire archive accessing the
113
+ # local entry headers (which contain the same information as the
114
+ # central directory).
115
+ def ZipFile.foreach(aZipFileName, &block)
116
+ ZipFile.open(aZipFileName) {
117
+ |zipFile|
118
+ zipFile.each(&block)
119
+ }
120
+ end
121
+
122
+ # Returns an input stream to the specified entry. If a block is passed
123
+ # the stream object is passed to the block and the stream is automatically
124
+ # closed afterwards just as with ruby's builtin File.open method.
125
+ def get_input_stream(entry, &aProc)
126
+ get_entry(entry).get_input_stream(&aProc)
127
+ end
128
+
129
+ # Returns an output stream to the specified entry. If a block is passed
130
+ # the stream object is passed to the block and the stream is automatically
131
+ # closed afterwards just as with ruby's builtin File.open method.
132
+ def get_output_stream(entry, &aProc)
133
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
134
+ if newEntry.directory?
135
+ raise ArgumentError,
136
+ "cannot open stream to directory entry - '#{newEntry}'"
137
+ end
138
+ zipStreamableEntry = ZipStreamableStream.new(newEntry)
139
+ @entrySet << zipStreamableEntry
140
+ zipStreamableEntry.get_output_stream(&aProc)
141
+ end
142
+
143
+ # Returns the name of the zip archive
144
+ def to_s
145
+ @name
146
+ end
147
+
148
+ # Returns a string containing the contents of the specified entry
149
+ def read(entry)
150
+ get_input_stream(entry) { |is| is.read }
151
+ end
152
+
153
+ # Convenience method for adding the contents of a file to the archive
154
+ def add(entry, srcPath, &continueOnExistsProc)
155
+ continueOnExistsProc ||= proc { false }
156
+ check_entry_exists(entry, continueOnExistsProc, "add")
157
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
158
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
159
+ @entrySet << newEntry
160
+ end
161
+
162
+ # Removes the specified entry.
163
+ def remove(entry)
164
+ @entrySet.delete(get_entry(entry))
165
+ end
166
+
167
+ # Renames the specified entry.
168
+ def rename(entry, newName, &continueOnExistsProc)
169
+ foundEntry = get_entry(entry)
170
+ check_entry_exists(newName, continueOnExistsProc, "rename")
171
+ @entrySet.delete(foundEntry)
172
+ foundEntry.name = newName
173
+ @entrySet << foundEntry
174
+ end
175
+
176
+ # Replaces the specified entry with the contents of srcPath (from
177
+ # the file system).
178
+ def replace(entry, srcPath)
179
+ check_file(srcPath)
180
+ add(remove(entry), srcPath)
181
+ end
182
+
183
+ # Extracts entry to file destPath.
184
+ def extract(entry, destPath, &onExistsProc)
185
+ onExistsProc ||= proc { false }
186
+ foundEntry = get_entry(entry)
187
+ foundEntry.extract(destPath, &onExistsProc)
188
+ end
189
+
190
+ # Commits changes that has been made since the previous commit to
191
+ # the zip archive.
192
+ def commit
193
+ return if ! commit_required?
194
+ on_success_replace(name) {
195
+ |tmpFile|
196
+ ZipOutputStream.open(tmpFile) {
197
+ |zos|
198
+
199
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
200
+ zos.comment = comment
201
+ }
202
+ true
203
+ }
204
+ initialize(name)
205
+ end
206
+
207
+ # Write buffer write changes to buffer and return
208
+ def write_buffer
209
+ buffer = ZipOutputStream.write_buffer do |zos|
210
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
211
+ zos.comment = comment
212
+ end
213
+ return buffer
214
+ end
215
+
216
+ # Closes the zip file committing any changes that has been made.
217
+ def close
218
+ commit
219
+ end
220
+
221
+ # Returns true if any changes has been made to this archive since
222
+ # the previous commit
223
+ def commit_required?
224
+ return @entrySet != @storedEntries || @create == ZipFile::CREATE
225
+ end
226
+
227
+ # Searches for entry with the specified name. Returns nil if
228
+ # no entry is found. See also get_entry
229
+ def find_entry(entry)
230
+ @entrySet.detect {
231
+ |e|
232
+ e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
233
+ }
234
+ end
235
+
236
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
237
+ # if no entry is found.
238
+ def get_entry(entry)
239
+ selectedEntry = find_entry(entry)
240
+ unless selectedEntry
241
+ raise Errno::ENOENT, entry
242
+ end
243
+ selectedEntry.restore_ownership = @restore_ownership
244
+ selectedEntry.restore_permissions = @restore_permissions
245
+ selectedEntry.restore_times = @restore_times
246
+
247
+ return selectedEntry
248
+ end
249
+
250
+ # Creates a directory
251
+ def mkdir(entryName, permissionInt = 0755)
252
+ if find_entry(entryName)
253
+ raise Errno::EEXIST, "File exists - #{entryName}"
254
+ end
255
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
256
+ end
257
+
258
+ private
259
+
260
+ def is_directory(newEntry, srcPath)
261
+ srcPathIsDirectory = ::File.directory?(srcPath)
262
+ if newEntry.is_directory && ! srcPathIsDirectory
263
+ raise ArgumentError,
264
+ "entry name '#{newEntry}' indicates directory entry, but "+
265
+ "'#{srcPath}' is not a directory"
266
+ elsif ! newEntry.is_directory && srcPathIsDirectory
267
+ newEntry.name += "/"
268
+ end
269
+ return newEntry.is_directory && srcPathIsDirectory
270
+ end
271
+
272
+ def check_entry_exists(entryName, continueOnExistsProc, procedureName)
273
+ continueOnExistsProc ||= proc { false }
274
+ if @entrySet.detect { |e| e.name == entryName }
275
+ if continueOnExistsProc.call
276
+ remove get_entry(entryName)
277
+ else
278
+ raise ZipEntryExistsError,
279
+ procedureName+" failed. Entry #{entryName} already exists"
280
+ end
281
+ end
282
+ end
283
+
284
+ def check_file(path)
285
+ unless ::File.readable? path
286
+ raise Errno::ENOENT, path
287
+ end
288
+ end
289
+
290
+ def on_success_replace(aFilename)
291
+ tmpfile = get_tempfile
292
+ tmpFilename = tmpfile.path
293
+ tmpfile.close
294
+ if yield tmpFilename
295
+ ::File.rename(tmpFilename, name)
296
+ end
297
+ end
298
+
299
+ def get_tempfile
300
+ tempFile = Tempfile.new(::File.basename(name), ::File.dirname(name))
301
+ tempFile.binmode
302
+ tempFile
303
+ end
304
+
305
+ end
306
+ end
307
+
308
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
309
+ # rubyzip is free software; you can redistribute it and/or
310
+ # modify it under the terms of the ruby license.