rubyzip 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubyzip might be problematic. Click here for more details.

Files changed (45) hide show
  1. data/{README → README.md} +16 -17
  2. data/Rakefile +6 -104
  3. data/lib/zip/compressor.rb +10 -0
  4. data/lib/zip/constants.rb +10 -0
  5. data/lib/zip/decompressor.rb +13 -0
  6. data/lib/zip/deflater.rb +30 -0
  7. data/lib/zip/inflater.rb +65 -0
  8. data/lib/zip/ioextras.rb +35 -36
  9. data/lib/zip/null_compressor.rb +15 -0
  10. data/lib/zip/null_decompressor.rb +25 -0
  11. data/lib/zip/null_input_stream.rb +9 -0
  12. data/lib/zip/pass_thru_compressor.rb +23 -0
  13. data/lib/zip/pass_thru_decompressor.rb +40 -0
  14. data/lib/zip/stdrubyext.rb +10 -44
  15. data/lib/zip/zip.rb +22 -1848
  16. data/lib/zip/zip_central_directory.rb +139 -0
  17. data/lib/zip/zip_entry.rb +639 -0
  18. data/lib/zip/zip_entry_set.rb +66 -0
  19. data/lib/zip/zip_extra_field.rb +213 -0
  20. data/lib/zip/zip_file.rb +318 -0
  21. data/lib/zip/zip_input_stream.rb +134 -0
  22. data/lib/zip/zip_output_stream.rb +172 -0
  23. data/lib/zip/zip_streamable_directory.rb +15 -0
  24. data/lib/zip/zip_streamable_stream.rb +47 -0
  25. data/lib/zip/zipfilesystem.rb +90 -88
  26. data/samples/example_recursive.rb +49 -0
  27. metadata +54 -60
  28. data/ChangeLog +0 -1146
  29. data/install.rb +0 -23
  30. data/lib/zip/ziprequire.rb +0 -90
  31. data/test/alltests.rb +0 -9
  32. data/test/data/file1.txt +0 -46
  33. data/test/data/file1.txt.deflatedData +0 -0
  34. data/test/data/file2.txt +0 -1504
  35. data/test/data/notzippedruby.rb +0 -7
  36. data/test/data/rubycode.zip +0 -0
  37. data/test/data/rubycode2.zip +0 -0
  38. data/test/data/testDirectory.bin +0 -0
  39. data/test/data/zipWithDirs.zip +0 -0
  40. data/test/gentestfiles.rb +0 -157
  41. data/test/ioextrastest.rb +0 -208
  42. data/test/stdrubyexttest.rb +0 -52
  43. data/test/zipfilesystemtest.rb +0 -841
  44. data/test/ziprequiretest.rb +0 -43
  45. data/test/ziptest.rb +0 -1620
@@ -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,318 @@
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, permissionInt = nil, &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
+ newEntry.unix_perms = permissionInt
139
+ zipStreamableEntry = ZipStreamableStream.new(newEntry)
140
+ @entrySet << zipStreamableEntry
141
+ zipStreamableEntry.get_output_stream(&aProc)
142
+ end
143
+
144
+ # Returns the name of the zip archive
145
+ def to_s
146
+ @name
147
+ end
148
+
149
+ # Returns a string containing the contents of the specified entry
150
+ def read(entry)
151
+ get_input_stream(entry) { |is| is.read }
152
+ end
153
+
154
+ # Convenience method for adding the contents of a file to the archive
155
+ def add(entry, srcPath, &continueOnExistsProc)
156
+ continueOnExistsProc ||= proc { false }
157
+ check_entry_exists(entry, continueOnExistsProc, "add")
158
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
159
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
160
+ @entrySet << newEntry
161
+ end
162
+
163
+ # Removes the specified entry.
164
+ def remove(entry)
165
+ @entrySet.delete(get_entry(entry))
166
+ end
167
+
168
+ # Renames the specified entry.
169
+ def rename(entry, newName, &continueOnExistsProc)
170
+ foundEntry = get_entry(entry)
171
+ check_entry_exists(newName, continueOnExistsProc, "rename")
172
+ @entrySet.delete(foundEntry)
173
+ foundEntry.name = newName
174
+ @entrySet << foundEntry
175
+ end
176
+
177
+ # Replaces the specified entry with the contents of srcPath (from
178
+ # the file system).
179
+ def replace(entry, srcPath)
180
+ check_file(srcPath)
181
+ add(remove(entry), srcPath)
182
+ end
183
+
184
+ # Extracts entry to file destPath.
185
+ def extract(entry, destPath, &onExistsProc)
186
+ onExistsProc ||= proc { false }
187
+ foundEntry = get_entry(entry)
188
+ foundEntry.extract(destPath, &onExistsProc)
189
+ end
190
+
191
+ # Commits changes that has been made since the previous commit to
192
+ # the zip archive.
193
+ def commit
194
+ return if ! commit_required?
195
+ on_success_replace(name) {
196
+ |tmpFile|
197
+ ZipOutputStream.open(tmpFile) {
198
+ |zos|
199
+
200
+ @entrySet.each {
201
+ |e|
202
+ e.write_to_zip_output_stream(zos)
203
+ e.dirty = false
204
+ }
205
+ zos.comment = comment
206
+ }
207
+ true
208
+ }
209
+ initialize(name)
210
+ end
211
+
212
+ # Write buffer write changes to buffer and return
213
+ def write_buffer
214
+ buffer = ZipOutputStream.write_buffer do |zos|
215
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
216
+ zos.comment = comment
217
+ end
218
+ return buffer
219
+ end
220
+
221
+ # Closes the zip file committing any changes that has been made.
222
+ def close
223
+ commit
224
+ end
225
+
226
+ # Returns true if any changes has been made to this archive since
227
+ # the previous commit
228
+ def commit_required?
229
+ @entrySet.each do |e|
230
+ return true if e.dirty
231
+ end
232
+ return @entrySet != @storedEntries || @create == ZipFile::CREATE
233
+ end
234
+
235
+ # Searches for entry with the specified name. Returns nil if
236
+ # no entry is found. See also get_entry
237
+ def find_entry(entry)
238
+ @entrySet.detect {
239
+ |e|
240
+ e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
241
+ }
242
+ end
243
+
244
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
245
+ # if no entry is found.
246
+ def get_entry(entry)
247
+ selectedEntry = find_entry(entry)
248
+ unless selectedEntry
249
+ raise Errno::ENOENT, entry
250
+ end
251
+ selectedEntry.restore_ownership = @restore_ownership
252
+ selectedEntry.restore_permissions = @restore_permissions
253
+ selectedEntry.restore_times = @restore_times
254
+
255
+ return selectedEntry
256
+ end
257
+
258
+ # Creates a directory
259
+ def mkdir(entryName, permissionInt = 0755)
260
+ if find_entry(entryName)
261
+ raise Errno::EEXIST, "File exists - #{entryName}"
262
+ end
263
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
264
+ end
265
+
266
+ private
267
+
268
+ def is_directory(newEntry, srcPath)
269
+ srcPathIsDirectory = File.directory?(srcPath)
270
+ if newEntry.is_directory && ! srcPathIsDirectory
271
+ raise ArgumentError,
272
+ "entry name '#{newEntry}' indicates directory entry, but "+
273
+ "'#{srcPath}' is not a directory"
274
+ elsif ! newEntry.is_directory && srcPathIsDirectory
275
+ newEntry.name += "/"
276
+ end
277
+ return newEntry.is_directory && srcPathIsDirectory
278
+ end
279
+
280
+ def check_entry_exists(entryName, continueOnExistsProc, procedureName)
281
+ continueOnExistsProc ||= proc { false }
282
+ if @entrySet.detect { |e| e.name == entryName }
283
+ if continueOnExistsProc.call
284
+ remove get_entry(entryName)
285
+ else
286
+ raise ZipEntryExistsError,
287
+ procedureName+" failed. Entry #{entryName} already exists"
288
+ end
289
+ end
290
+ end
291
+
292
+ def check_file(path)
293
+ unless File.readable? path
294
+ raise Errno::ENOENT, path
295
+ end
296
+ end
297
+
298
+ def on_success_replace(aFilename)
299
+ tmpfile = get_tempfile
300
+ tmpFilename = tmpfile.path
301
+ tmpfile.close
302
+ if yield tmpFilename
303
+ File.rename(tmpFilename, name)
304
+ end
305
+ end
306
+
307
+ def get_tempfile
308
+ tempFile = Tempfile.new(File.basename(name), File.dirname(name))
309
+ tempFile.binmode
310
+ tempFile
311
+ end
312
+
313
+ end
314
+ end
315
+
316
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
317
+ # rubyzip is free software; you can redistribute it and/or
318
+ # modify it under the terms of the ruby license.