archive-zip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ module Archive; class Zip
2
+ # Archive::Zip::Codec is a factory class for generating codec object instances
3
+ # based on the compression method and general purpose flag fields of ZIP
4
+ # entries. When adding a new codec, add a mapping in the _CODECS_ constant
5
+ # from the compression method field value reserved for the codec in the ZIP
6
+ # specification to the class implementing the codec. See the implementations
7
+ # of Archive::Zip::Codec::Deflate and Archive::Zip::Codec::Store for details
8
+ # on implementing custom codecs.
9
+ module Codec
10
+ # A Hash mapping compression methods to codec implementations. New codecs
11
+ # must add a mapping here when defined in order to be used.
12
+ CODECS = {}
13
+
14
+ # Returns a new codec instance based on _compression_method_ and
15
+ # _general_purpose_flags_.
16
+ def self.create(compression_method, general_purpose_flags)
17
+ CODECS[compression_method].new(general_purpose_flags)
18
+ end
19
+
20
+ # Returns +true+ if _compression_method_ is mapped to a codec, +false+
21
+ # otherwise.
22
+ def self.supported?(compression_method)
23
+ CODECS.has_key?(compression_method)
24
+ end
25
+ end
26
+ end; end
27
+
28
+ # Load the standard codecs.
29
+ require 'archive/zip/codec/deflate'
30
+ require 'archive/zip/codec/store'
@@ -0,0 +1,206 @@
1
+ require 'archive/support/zlib'
2
+ require 'archive/zip/codec'
3
+ require 'archive/zip/datadescriptor'
4
+
5
+ module Archive; class Zip; module Codec
6
+ # Archive::Zip::Codec::Deflate is a handle for the deflate-inflate codec as
7
+ # defined in Zlib which provides convenient interfaces for writing and reading
8
+ # deflated streams.
9
+ class Deflate
10
+ # Archive::Zip::Codec::Deflate::Deflate extends Zlib::ZWriter in order to
11
+ # specify the standard Zlib options required by ZIP archives and to provide
12
+ # a close method which can optionally close the delegate IO-like object. In
13
+ # addition a convenience method is provided for generating DataDescriptor
14
+ # objects based on the data which is passed through this object.
15
+ #
16
+ # Instances of this class should only be accessed via the
17
+ # Archive::Zip::Codec::Deflate#compressor method.
18
+ class Deflate < Zlib::ZWriter
19
+ # Creates a new instance of this class with the given arguments using #new
20
+ # and then passes the instance to the given block. The #close method is
21
+ # guaranteed to be called after the block completes.
22
+ #
23
+ # Equivalent to #new if no block is given.
24
+ def self.open(io, compression_level)
25
+ deflate_io = new(io, compression_level)
26
+ return deflate_io unless block_given?
27
+
28
+ begin
29
+ yield(deflate_io)
30
+ ensure
31
+ deflate_io.close unless deflate_io.closed?
32
+ end
33
+ end
34
+
35
+ # Creates a new instance of this class using _io_ as a data sink. _io_
36
+ # must be writable and must provide a _write_ method as IO does or errors
37
+ # will be raised when performing write operations. _compression_level_
38
+ # must be one of Zlib::DEFAULT_COMPRESSION, Zlib::BEST_COMPRESSION,
39
+ # Zlib::BEST_SPEED, or Zlib::NO_COMPRESSION and specifies the amount of
40
+ # compression to be applied to the data stream.
41
+ def initialize(io, compression_level)
42
+ super(io, compression_level, -Zlib::MAX_WBITS)
43
+ end
44
+
45
+ # Closes this object so that further write operations will fail. If
46
+ # _close_delegate_ is +true+, the delegate object used as a data sink will
47
+ # also be closed using its close method.
48
+ def close(close_delegate = true)
49
+ super()
50
+ delegate.close if close_delegate
51
+ end
52
+
53
+ # Returns an instance of Archive::Zip::Entry::DataDescriptor with
54
+ # information regarding the data which has passed through this object to
55
+ # the delegate object. The close or flush methods should be called before
56
+ # using this method in order to ensure that any possibly buffered data is
57
+ # flushed to the delegate object; otherwise, the contents of the data
58
+ # descriptor may be inaccurate.
59
+ def data_descriptor
60
+ DataDescriptor.new(crc32, compressed_size, uncompressed_size)
61
+ end
62
+ end
63
+
64
+ # Archive::Zip::Codec::Deflate::Inflate extends Zlib::ZReader in order to
65
+ # specify the standard Zlib options required by ZIP archives and to provide
66
+ # a close method which can optionally close the delegate IO-like object. In
67
+ # addition a convenience method is provided for generating DataDescriptor
68
+ # objects based on the data which is passed through this object.
69
+ #
70
+ # Instances of this class should only be accessed via the
71
+ # Archive::Zip::Codec::Deflate#decompressor method.
72
+ class Inflate < Zlib::ZReader
73
+ # Creates a new instance of this class with the given arguments using #new
74
+ # and then passes the instance to the given block. The #close method is
75
+ # guaranteed to be called after the block completes.
76
+ #
77
+ # Equivalent to #new if no block is given.
78
+ def self.open(io)
79
+ inflate_io = new(io)
80
+ return inflate_io unless block_given?
81
+
82
+ begin
83
+ yield(inflate_io)
84
+ ensure
85
+ inflate_io.close unless inflate_io.closed?
86
+ end
87
+ end
88
+
89
+ # Creates a new instance of this class using _io_ as a data source. _io_
90
+ # must be readable and provide a _read_ method as IO does or errors will
91
+ # be raised when performing read operations. If _io_ provides a _rewind_
92
+ # method, this class' _rewind_ method will be enabled.
93
+ def initialize(io)
94
+ super(io, -Zlib::MAX_WBITS)
95
+ end
96
+
97
+ # Closes this object so that further read operations will fail. If
98
+ # _close_delegate_ is +true+, the delegate object used as a data source
99
+ # will also be closed using its close method.
100
+ def close(close_delegate = true)
101
+ super()
102
+ delegate.close if close_delegate
103
+ end
104
+
105
+ # Returns an instance of Archive::Zip::Entry::DataDescriptor with
106
+ # information regarding the data which has passed through this object
107
+ # from the delegate object. It is recommended to call the close method
108
+ # before calling this in order to ensure that no further read operations
109
+ # change the state of this object.
110
+ def data_descriptor
111
+ DataDescriptor.new(crc32, compressed_size, uncompressed_size)
112
+ end
113
+ end
114
+
115
+ # The numeric identifier assigned to this codec by the ZIP specification.
116
+ ID = 8
117
+
118
+ # Register this codec.
119
+ CODECS[ID] = self
120
+
121
+ # A bit mask used to denote that Zlib's default compression level should be
122
+ # used.
123
+ NORMAL = 0b000
124
+ # A bit mask used to denote that Zlib's highest/slowest compression level
125
+ # should be used.
126
+ MAXIMUM = 0b010
127
+ # A bit mask used to denote that Zlib's lowest/fastest compression level
128
+ # should be used.
129
+ FAST = 0b100
130
+ # A bit mask used to denote that Zlib should not compress data at all.
131
+ SUPER_FAST = 0b110
132
+
133
+ # This method signature is part of the interface contract expected by
134
+ # Archive::Zip::Entry for codec objects.
135
+ #
136
+ # Creates a new instance of this class using bits 1 and 2 of
137
+ # _general_purpose_flags_ to select a compression level to be used by
138
+ # #compressor to set up a compression IO object. The constants NORMAL,
139
+ # MAXIMUM, FAST, and SUPER_FAST can be used for _general_purpose_flags_ to
140
+ # manually set the compression level.
141
+ def initialize(general_purpose_flags = NORMAL)
142
+ @compression_level = general_purpose_flags & 0b110
143
+ @zlib_compression_level = case @compression_level
144
+ when NORMAL
145
+ Zlib::DEFAULT_COMPRESSION
146
+ when MAXIMUM
147
+ Zlib::BEST_COMPRESSION
148
+ when FAST
149
+ Zlib::BEST_SPEED
150
+ when SUPER_FAST
151
+ Zlib::NO_COMPRESSION
152
+ else
153
+ raise Error, 'Invalid compression level'
154
+ end
155
+ end
156
+
157
+ # This method signature is part of the interface contract expected by
158
+ # Archive::Zip::Entry for codec objects.
159
+ #
160
+ # A convenience method for creating an Archive::Zip::Codec::Deflate::Deflate
161
+ # object using that class' open method. The compression level for the open
162
+ # method is pulled from the value of the _general_purpose_flags_ argument of
163
+ # new.
164
+ def compressor(io, &b)
165
+ Deflate.open(io, @zlib_compression_level, &b)
166
+ end
167
+
168
+ # This method signature is part of the interface contract expected by
169
+ # Archive::Zip::Entry for codec objects.
170
+ #
171
+ # A convenience method for creating an Archive::Zip::Codec::Deflate::Inflate
172
+ # object using that class' open method.
173
+ def decompressor(io, &b)
174
+ Inflate.open(io, &b)
175
+ end
176
+
177
+ # This method signature is part of the interface contract expected by
178
+ # Archive::Zip::Entry for codec objects.
179
+ #
180
+ # Returns an integer which indicates the version of the official ZIP
181
+ # specification which introduced support for this codec.
182
+ def version_needed_to_extract
183
+ 0x0014
184
+ end
185
+
186
+ # This method signature is part of the interface contract expected by
187
+ # Archive::Zip::Entry for codec objects.
188
+ #
189
+ # Returns an integer used to flag that this codec is used for a particular
190
+ # ZIP archive entry.
191
+ def compression_method
192
+ ID
193
+ end
194
+
195
+ # This method signature is part of the interface contract expected by
196
+ # Archive::Zip::Entry for codec objects.
197
+ #
198
+ # Returns an integer representing the general purpose flags of a ZIP archive
199
+ # entry where bits 1 and 2 are set according to the compression level
200
+ # selected for this object. All other bits are zero'd out.
201
+ def general_purpose_flags
202
+ @compression_level
203
+ end
204
+ end
205
+ end; end; end
206
+
@@ -0,0 +1,241 @@
1
+ require 'archive/support/io-like'
2
+ require 'archive/zip/codec'
3
+ require 'archive/zip/datadescriptor'
4
+
5
+ module Archive; class Zip; module Codec
6
+ # Archive::Zip::Codec::Store is a handle for the store-unstore (no
7
+ # compression) codec.
8
+ class Store
9
+ # Archive::Zip::Codec::Store::Store is simply a writable, IO-like wrapper
10
+ # around a writable, IO-like object which provides a CRC32 checksum of the
11
+ # data written through it as well as the count of the total amount of data.
12
+ # A #close method is also provided which can optionally close the delegate
13
+ # object. In addition a convenience method is provided for generating
14
+ # DataDescriptor objects based on the data which is passed through this
15
+ # object.
16
+ class Store
17
+ include IO::Like
18
+
19
+ # Creates a new instance of this class with the given argument using #new
20
+ # and then passes the instance to the given block. The #close method is
21
+ # guaranteed to be called after the block completes.
22
+ #
23
+ # Equivalent to #new if no block is given.
24
+ def self.open(io)
25
+ store_io = new(io)
26
+ return store_io unless block_given?
27
+
28
+ begin
29
+ yield(store_io)
30
+ ensure
31
+ store_io.close unless store_io.closed?
32
+ end
33
+ end
34
+
35
+ # Creates a new instance of this class using _io_ as a data sink. _io_
36
+ # must be writable and must provide a write method as IO does or errors
37
+ # will be raised when performing write operations.
38
+ #
39
+ # The _flush_size_ attribute is set to +0+ by default under the assumption
40
+ # that _io_ is already buffered.
41
+ def initialize(io)
42
+ @io = io
43
+ @crc32 = 0
44
+ @uncompressed_size = 0
45
+ # Assume that the delegate IO object is already buffered.
46
+ self.flush_size = 0
47
+ end
48
+
49
+ # Closes this object so that further write operations will fail. If
50
+ # _close_delegate_ is +true+, the delegate object used as a data sink will
51
+ # also be closed using its close method.
52
+ def close(close_delegate = true)
53
+ super()
54
+ @io.close if close_delegate
55
+ nil
56
+ end
57
+
58
+ # Returns an instance of Archive::Zip::Entry::DataDescriptor with
59
+ # information regarding the data which has passed through this object to
60
+ # the delegate object. The close or flush methods should be called before
61
+ # using this method in order to ensure that any possibly buffered data is
62
+ # flushed to the delegate object; otherwise, the contents of the data
63
+ # descriptor may be inaccurate.
64
+ def data_descriptor
65
+ DataDescriptor.new(
66
+ @crc32,
67
+ @uncompressed_size,
68
+ @uncompressed_size
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ # Writes _string_ to the delegate object and returns the number of bytes
75
+ # actually written. Updates the uncompressed_size and crc32 attributes as
76
+ # a side effect.
77
+ def unbuffered_write(string)
78
+ bytes_written = @io.write(string)
79
+ @uncompressed_size += bytes_written
80
+ @crc32 = Zlib.crc32(string.slice(0, bytes_written), @crc32)
81
+ bytes_written
82
+ end
83
+ end
84
+
85
+ # Archive::Zip::Codec::Store::Unstore is a readable, IO-like wrapper around
86
+ # a readable, IO-like object which provides a CRC32 checksum of the data
87
+ # read through it as well as the count of the total amount of data. A
88
+ # #close method is also provided which can optionally close the delegate
89
+ # object. In addition a convenience method is provided for generating
90
+ # DataDescriptor objects based on the data which is passed through this
91
+ # object.
92
+ class Unstore
93
+ include IO::Like
94
+
95
+ # Creates a new instance of this class with the given arguments using #new
96
+ # and then passes the instance to the given block. The #close method is
97
+ # guaranteed to be called after the block completes.
98
+ #
99
+ # Equivalent to #new if no block is given.
100
+ def self.open(io)
101
+ unstore_io = new(io)
102
+ return unstore_io unless block_given?
103
+
104
+ begin
105
+ yield(unstore_io)
106
+ ensure
107
+ unstore_io.close unless unstore_io.closed?
108
+ end
109
+ end
110
+
111
+ # Creates a new instance of this class using _io_ as a data source. _io_
112
+ # must be readable and provide a read method as IO does or errors will be
113
+ # raised when performing read operations. If _io_ provides a rewind
114
+ # method, this class' rewind method will be enabled.
115
+ #
116
+ # The _fill_size_ attribute is set to +0+ by default under the assumption
117
+ # that _io_ is already buffered.
118
+ def initialize(io)
119
+ @io = io
120
+ @crc32 = 0
121
+ @uncompressed_size = 0
122
+ # Assume that the delegate IO object is already buffered.
123
+ self.fill_size = 0
124
+ end
125
+
126
+ # Closes this object so that further read operations will fail. If
127
+ # _close_delegate_ is +true+, the delegate object used as a data source
128
+ # will also be closed using its close method.
129
+ def close(close_delegate = true)
130
+ super()
131
+ @io.close if close_delegate
132
+ nil
133
+ end
134
+
135
+ # Returns an instance of Archive::Zip::Entry::DataDescriptor with
136
+ # information regarding the data which has passed through this object
137
+ # from the delegate object. It is recommended to call the close method
138
+ # before calling this in order to ensure that no further read operations
139
+ # change the state of this object.
140
+ def data_descriptor
141
+ DataDescriptor.new(
142
+ @crc32,
143
+ @uncompressed_size,
144
+ @uncompressed_size
145
+ )
146
+ end
147
+
148
+ private
149
+
150
+ # Returns at most _length_ bytes from the delegate object. Updates the
151
+ # uncompressed_size and crc32 attributes as a side effect.
152
+ def unbuffered_read(length)
153
+ buffer = @io.read(length)
154
+ if buffer.nil? then
155
+ raise EOFError, 'end of file reached'
156
+ else
157
+ @uncompressed_size += buffer.length
158
+ @crc32 = Zlib.crc32(buffer, @crc32)
159
+ end
160
+ buffer
161
+ end
162
+
163
+ # Allows resetting this object and the delegate object back to the
164
+ # beginning of the stream. _offset_ must be +0+ and _whence_ must be
165
+ # IO::SEEK_SET or an error will be raised. The delegate object must
166
+ # respond to the _rewind_ method or an error will be raised. The
167
+ # uncompressed_size and crc32 attributes are reinitialized as a side
168
+ # effect.
169
+ def unbuffered_seek(offset, whence = IO::SEEK_SET)
170
+ unless offset == 0 && whence == IO::SEEK_SET then
171
+ raise Errno::EINVAL, 'Invalid argument'
172
+ end
173
+ unless @io.respond_to?(:rewind) then
174
+ raise Errno::ESPIPE, 'Illegal seek'
175
+ end
176
+ @io.rewind
177
+ @crc32 = 0
178
+ @uncompressed_size = 0
179
+ end
180
+ end
181
+
182
+ # The numeric identifier assigned to this codec by the ZIP specification.
183
+ ID = 0
184
+
185
+ # Register this codec.
186
+ CODECS[ID] = self
187
+
188
+ # This method signature is part of the interface contract expected by
189
+ # Archive::Zip::Entry for codec objects.
190
+ #
191
+ # Creates a new instance of this class. _general_purpose_flags_ is not
192
+ # used.
193
+ def initialize(general_purpose_flags = 0)
194
+ end
195
+
196
+ # This method signature is part of the interface contract expected by
197
+ # Archive::Zip::Entry for codec objects.
198
+ #
199
+ # A convenience method for creating an Archive::Zip::Codec::Store::Store
200
+ # object using that class' open method.
201
+ def compressor(io, &b)
202
+ Store.open(io, &b)
203
+ end
204
+
205
+ # This method signature is part of the interface contract expected by
206
+ # Archive::Zip::Entry for codec objects.
207
+ #
208
+ # A convenience method for creating an Archive::Zip::Codec::Store::Unstore
209
+ # object using that class' open method.
210
+ def decompressor(io, &b)
211
+ Unstore.open(io, &b)
212
+ end
213
+
214
+ # This method signature is part of the interface contract expected by
215
+ # Archive::Zip::Entry for codec objects.
216
+ #
217
+ # Returns an integer which indicates the version of the official ZIP
218
+ # specification which introduced support for this codec.
219
+ def version_needed_to_extract
220
+ 0x000a
221
+ end
222
+
223
+ # This method signature is part of the interface contract expected by
224
+ # Archive::Zip::Entry for codec objects.
225
+ #
226
+ # Returns an integer used to flag that this codec is used for a particular
227
+ # ZIP archive entry.
228
+ def compression_method
229
+ ID
230
+ end
231
+
232
+ # This method signature is part of the interface contract expected by
233
+ # Archive::Zip::Entry for codec objects.
234
+ #
235
+ # Returns +0+ since this codec does not make use of general purpose flags of
236
+ # ZIP archive entries.
237
+ def general_purpose_flags
238
+ 0
239
+ end
240
+ end
241
+ end; end; end