archive-zip 0.1.0

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.
@@ -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