archive-zip 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MANIFEST +7 -6
- data/NEWS +17 -7
- data/README +22 -1
- data/lib/archive/zip.rb +105 -36
- data/lib/archive/zip/codec.rb +35 -14
- data/lib/archive/zip/codec/deflate.rb +34 -32
- data/lib/archive/zip/codec/null_encryption.rb +176 -0
- data/lib/archive/zip/codec/store.rb +46 -40
- data/lib/archive/zip/codec/traditional_encryption.rb +308 -0
- data/lib/archive/zip/{datadescriptor.rb → data_descriptor.rb} +8 -6
- data/lib/archive/zip/entry.rb +290 -135
- data/lib/archive/zip/{extrafield.rb → extra_field.rb} +3 -3
- data/lib/archive/zip/{extrafield/extendedtimestamp.rb → extra_field/extended_timestamp.rb} +0 -0
- data/lib/archive/zip/{extrafield → extra_field}/raw.rb +0 -0
- data/lib/archive/zip/{extrafield → extra_field}/unix.rb +0 -0
- metadata +9 -8
- data/test/test_archive.rb +0 -8
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'archive/support/io-like'
|
2
|
+
require 'archive/zip/codec'
|
3
|
+
|
4
|
+
module Archive; class Zip; module Codec
|
5
|
+
# Archive::Zip::Codec::NullEncryption is a handle for an encryption codec
|
6
|
+
# which passes data through itself unchanged.
|
7
|
+
class NullEncryption
|
8
|
+
# Archive::Zip::Codec::NullEncryption::Encrypt is a writable, IO-like object
|
9
|
+
# which writes all data written to it directly to a delegate IO object. A
|
10
|
+
# _close_ method is also provided which can optionally closed the delegate
|
11
|
+
# object.
|
12
|
+
class Encrypt
|
13
|
+
include IO::Like
|
14
|
+
|
15
|
+
# Creates a new instance of this class with the given argument using #new
|
16
|
+
# and then passes the instance to the given block. The #close method is
|
17
|
+
# guaranteed to be called after the block completes.
|
18
|
+
#
|
19
|
+
# Equivalent to #new if no block is given.
|
20
|
+
def self.open(io)
|
21
|
+
encrypt_io = new(io)
|
22
|
+
return encrypt_io unless block_given?
|
23
|
+
|
24
|
+
begin
|
25
|
+
yield(encrypt_io)
|
26
|
+
ensure
|
27
|
+
encrypt_io.close unless encrypt_io.closed?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creates a new instance of this class using _io_ as a data sink. _io_
|
32
|
+
# must be writable and must provide a write method as IO does or errors
|
33
|
+
# will be raised when performing write operations.
|
34
|
+
#
|
35
|
+
# The _flush_size_ attribute is set to <tt>0</tt> by default under the
|
36
|
+
# assumption that _io_ is already buffered.
|
37
|
+
def initialize(io)
|
38
|
+
@io = io
|
39
|
+
# Assume that the delegate IO object is already buffered.
|
40
|
+
self.flush_size = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
# Closes this object so that further write operations will fail. If
|
44
|
+
# _close_delegate_ is +true+, the delegate object used as a data sink will
|
45
|
+
# also be closed using its close method.
|
46
|
+
def close(close_delegate = true)
|
47
|
+
super()
|
48
|
+
@io.close if close_delegate
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Writes _string_ to the delegate IO object and returns the result.
|
54
|
+
def unbuffered_write(string)
|
55
|
+
@io.write(string)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Archive::Zip::Codec::NullEncryption::Decrypt is a readable, IO-like object
|
60
|
+
# which reads data directly from a delegate IO object, making no changes. A
|
61
|
+
# _close_ method is also provided which can optionally closed the delegate
|
62
|
+
# object.
|
63
|
+
class Decrypt
|
64
|
+
include IO::Like
|
65
|
+
|
66
|
+
# Creates a new instance of this class with the given argument using #new
|
67
|
+
# and then passes the instance to the given block. The #close method is
|
68
|
+
# guaranteed to be called after the block completes.
|
69
|
+
#
|
70
|
+
# Equivalent to #new if no block is given.
|
71
|
+
def self.open(io)
|
72
|
+
decrypt_io = new(io)
|
73
|
+
return decrypt_io unless block_given?
|
74
|
+
|
75
|
+
begin
|
76
|
+
yield(decrypt_io)
|
77
|
+
ensure
|
78
|
+
decrypt_io.close unless decrypt_io.closed?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates a new instance of this class using _io_ as a data source. _io_
|
83
|
+
# must be readable and provide a read method as IO does or errors will be
|
84
|
+
# raised when performing read operations. If _io_ provides a rewind
|
85
|
+
# method, this class' rewind method will be enabled.
|
86
|
+
#
|
87
|
+
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
88
|
+
# assumption that _io_ is already buffered.
|
89
|
+
def initialize(io)
|
90
|
+
@io = io
|
91
|
+
# Assume that the delegate IO object is already buffered.
|
92
|
+
self.fill_size = 0
|
93
|
+
end
|
94
|
+
|
95
|
+
# Closes this object so that further write operations will fail. If
|
96
|
+
# _close_delegate_ is +true+, the delegate object used as a data sink will
|
97
|
+
# also be closed using its close method.
|
98
|
+
def close(close_delegate = true)
|
99
|
+
super()
|
100
|
+
@io.close if close_delegate
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Reads and returns at most _length_ bytes from the delegate IO object.
|
106
|
+
#
|
107
|
+
# Raises EOFError if there is no data to read.
|
108
|
+
def unbuffered_read(length)
|
109
|
+
buffer = @io.read(length)
|
110
|
+
raise EOFError, 'end of file reached' if buffer.nil?
|
111
|
+
|
112
|
+
buffer
|
113
|
+
end
|
114
|
+
|
115
|
+
# Allows resetting this object and the delegate object back to the
|
116
|
+
# beginning of the stream. _offset_ must be <tt>0</tt> and _whence_ must
|
117
|
+
# be IO::SEEK_SET or an error will be raised. The delegate object must
|
118
|
+
# respond to the _rewind_ method or an error will be raised.
|
119
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
120
|
+
unless offset == 0 && whence == IO::SEEK_SET then
|
121
|
+
raise Errno::EINVAL, 'Invalid argument'
|
122
|
+
end
|
123
|
+
unless @io.respond_to?(:rewind) then
|
124
|
+
raise Errno::ESPIPE, 'Illegal seek'
|
125
|
+
end
|
126
|
+
@io.rewind
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# This method signature is part of the interface contract expected by
|
131
|
+
# Archive::Zip::Entry for encryption codec objects.
|
132
|
+
#
|
133
|
+
# A convenience method for creating an
|
134
|
+
# Archive::Zip::Codec::NullEncryption::Encrypt object using that class' open
|
135
|
+
# method.
|
136
|
+
def encryptor(io, password, &b)
|
137
|
+
Encrypt.open(io, &b)
|
138
|
+
end
|
139
|
+
|
140
|
+
# This method signature is part of the interface contract expected by
|
141
|
+
# Archive::Zip::Entry for encryption codec objects.
|
142
|
+
#
|
143
|
+
# A convenience method for creating an
|
144
|
+
# Archive::Zip::Codec::NullEncryption::Decrypt object using that class' open
|
145
|
+
# method.
|
146
|
+
def decryptor(io, password, &b)
|
147
|
+
Decrypt.open(io, &b)
|
148
|
+
end
|
149
|
+
|
150
|
+
# This method signature is part of the interface contract expected by
|
151
|
+
# Archive::Zip::Entry for encryption codec objects.
|
152
|
+
#
|
153
|
+
# Returns an integer which indicates the version of the official ZIP
|
154
|
+
# specification which introduced support for this encryption codec.
|
155
|
+
def version_needed_to_extract
|
156
|
+
0x0014
|
157
|
+
end
|
158
|
+
|
159
|
+
# This method signature is part of the interface contract expected by
|
160
|
+
# Archive::Zip::Entry for encryption codec objects.
|
161
|
+
#
|
162
|
+
# Returns an integer representing the general purpose flags of a ZIP archive
|
163
|
+
# entry using this encryption codec.
|
164
|
+
def general_purpose_flags
|
165
|
+
0b0000000000000000
|
166
|
+
end
|
167
|
+
|
168
|
+
# This method signature is part of the interface contract expected by
|
169
|
+
# Archive::Zip::Entry for encryption codec objects.
|
170
|
+
#
|
171
|
+
# Returns the size of the encryption header in bytes.
|
172
|
+
def header_size
|
173
|
+
0
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end; end; end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
require 'archive/support/io-like'
|
2
2
|
require 'archive/zip/codec'
|
3
|
-
require 'archive/zip/
|
3
|
+
require 'archive/zip/data_descriptor'
|
4
4
|
|
5
5
|
module Archive; class Zip; module Codec
|
6
6
|
# Archive::Zip::Codec::Store is a handle for the store-unstore (no
|
7
7
|
# compression) codec.
|
8
8
|
class Store
|
9
|
-
# Archive::Zip::Codec::Store::
|
9
|
+
# Archive::Zip::Codec::Store::Compress is simply a writable, IO-like wrapper
|
10
10
|
# around a writable, IO-like object which provides a CRC32 checksum of the
|
11
11
|
# data written through it as well as the count of the total amount of data.
|
12
|
-
# A
|
12
|
+
# A _close_ method is also provided which can optionally close the delegate
|
13
13
|
# object. In addition a convenience method is provided for generating
|
14
14
|
# DataDescriptor objects based on the data which is passed through this
|
15
15
|
# object.
|
16
|
-
|
16
|
+
#
|
17
|
+
# Instances of this class should only be accessed via the
|
18
|
+
# Archive::Zip::Codec::Store#compressor method.
|
19
|
+
class Compress
|
17
20
|
include IO::Like
|
18
21
|
|
19
22
|
# Creates a new instance of this class with the given argument using #new
|
@@ -36,8 +39,8 @@ module Archive; class Zip; module Codec
|
|
36
39
|
# must be writable and must provide a write method as IO does or errors
|
37
40
|
# will be raised when performing write operations.
|
38
41
|
#
|
39
|
-
# The _flush_size_ attribute is set to
|
40
|
-
# that _io_ is already buffered.
|
42
|
+
# The _flush_size_ attribute is set to <tt>0</tt> by default under the
|
43
|
+
# assumption that _io_ is already buffered.
|
41
44
|
def initialize(io)
|
42
45
|
@io = io
|
43
46
|
@crc32 = 0
|
@@ -82,14 +85,17 @@ module Archive; class Zip; module Codec
|
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
# Archive::Zip::Codec::Store::
|
86
|
-
# a readable, IO-like object which provides a CRC32 checksum of the
|
87
|
-
# read through it as well as the count of the total amount of data. A
|
88
|
-
#
|
88
|
+
# Archive::Zip::Codec::Store::Decompress is a readable, IO-like wrapper
|
89
|
+
# around a readable, IO-like object which provides a CRC32 checksum of the
|
90
|
+
# data read through it as well as the count of the total amount of data. A
|
91
|
+
# _close_ method is also provided which can optionally close the delegate
|
89
92
|
# object. In addition a convenience method is provided for generating
|
90
93
|
# DataDescriptor objects based on the data which is passed through this
|
91
94
|
# object.
|
92
|
-
|
95
|
+
#
|
96
|
+
# Instances of this class should only be accessed via the
|
97
|
+
# Archive::Zip::Codec::Store#decompressor method.
|
98
|
+
class Decompress
|
93
99
|
include IO::Like
|
94
100
|
|
95
101
|
# Creates a new instance of this class with the given arguments using #new
|
@@ -113,8 +119,8 @@ module Archive; class Zip; module Codec
|
|
113
119
|
# raised when performing read operations. If _io_ provides a rewind
|
114
120
|
# method, this class' rewind method will be enabled.
|
115
121
|
#
|
116
|
-
# The _fill_size_ attribute is set to
|
117
|
-
# that _io_ is already buffered.
|
122
|
+
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
123
|
+
# assumption that _io_ is already buffered.
|
118
124
|
def initialize(io)
|
119
125
|
@io = io
|
120
126
|
@crc32 = 0
|
@@ -151,18 +157,16 @@ module Archive; class Zip; module Codec
|
|
151
157
|
# uncompressed_size and crc32 attributes as a side effect.
|
152
158
|
def unbuffered_read(length)
|
153
159
|
buffer = @io.read(length)
|
154
|
-
if buffer.nil?
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@crc32 = Zlib.crc32(buffer, @crc32)
|
159
|
-
end
|
160
|
+
raise EOFError, 'end of file reached' if buffer.nil?
|
161
|
+
|
162
|
+
@uncompressed_size += buffer.length
|
163
|
+
@crc32 = Zlib.crc32(buffer, @crc32)
|
160
164
|
buffer
|
161
165
|
end
|
162
166
|
|
163
167
|
# Allows resetting this object and the delegate object back to the
|
164
|
-
# beginning of the stream. _offset_ must be
|
165
|
-
# IO::SEEK_SET or an error will be raised. The delegate object must
|
168
|
+
# beginning of the stream. _offset_ must be <tt>0</tt> and _whence_ must
|
169
|
+
# be IO::SEEK_SET or an error will be raised. The delegate object must
|
166
170
|
# respond to the _rewind_ method or an error will be raised. The
|
167
171
|
# uncompressed_size and crc32 attributes are reinitialized as a side
|
168
172
|
# effect.
|
@@ -179,14 +183,15 @@ module Archive; class Zip; module Codec
|
|
179
183
|
end
|
180
184
|
end
|
181
185
|
|
182
|
-
# The numeric identifier assigned to this codec by the ZIP
|
186
|
+
# The numeric identifier assigned to this compresion codec by the ZIP
|
187
|
+
# specification.
|
183
188
|
ID = 0
|
184
189
|
|
185
|
-
# Register this codec.
|
186
|
-
|
190
|
+
# Register this compression codec.
|
191
|
+
COMPRESSION_CODECS[ID] = self
|
187
192
|
|
188
193
|
# This method signature is part of the interface contract expected by
|
189
|
-
# Archive::Zip::Entry for codec objects.
|
194
|
+
# Archive::Zip::Entry for compression codec objects.
|
190
195
|
#
|
191
196
|
# Creates a new instance of this class. _general_purpose_flags_ is not
|
192
197
|
# used.
|
@@ -194,46 +199,47 @@ module Archive; class Zip; module Codec
|
|
194
199
|
end
|
195
200
|
|
196
201
|
# This method signature is part of the interface contract expected by
|
197
|
-
# Archive::Zip::Entry for codec objects.
|
202
|
+
# Archive::Zip::Entry for compression codec objects.
|
198
203
|
#
|
199
|
-
# A convenience method for creating an Archive::Zip::Codec::Store::
|
204
|
+
# A convenience method for creating an Archive::Zip::Codec::Store::Compress
|
200
205
|
# object using that class' open method.
|
201
206
|
def compressor(io, &b)
|
202
|
-
|
207
|
+
Compress.open(io, &b)
|
203
208
|
end
|
204
209
|
|
205
210
|
# This method signature is part of the interface contract expected by
|
206
|
-
# Archive::Zip::Entry for codec objects.
|
211
|
+
# Archive::Zip::Entry for compression codec objects.
|
207
212
|
#
|
208
|
-
# A convenience method for creating an
|
209
|
-
# object using that class' open
|
213
|
+
# A convenience method for creating an
|
214
|
+
# Archive::Zip::Codec::Store::Decompress object using that class' open
|
215
|
+
# method.
|
210
216
|
def decompressor(io, &b)
|
211
|
-
|
217
|
+
Decompress.open(io, &b)
|
212
218
|
end
|
213
219
|
|
214
220
|
# This method signature is part of the interface contract expected by
|
215
|
-
# Archive::Zip::Entry for codec objects.
|
221
|
+
# Archive::Zip::Entry for compression codec objects.
|
216
222
|
#
|
217
223
|
# Returns an integer which indicates the version of the official ZIP
|
218
|
-
# specification which introduced support for this codec.
|
224
|
+
# specification which introduced support for this compression codec.
|
219
225
|
def version_needed_to_extract
|
220
226
|
0x000a
|
221
227
|
end
|
222
228
|
|
223
229
|
# This method signature is part of the interface contract expected by
|
224
|
-
# Archive::Zip::Entry for codec objects.
|
230
|
+
# Archive::Zip::Entry for compression codec objects.
|
225
231
|
#
|
226
|
-
# Returns an integer used to flag that this codec is used for a
|
227
|
-
# ZIP archive entry.
|
232
|
+
# Returns an integer used to flag that this compression codec is used for a
|
233
|
+
# particular ZIP archive entry.
|
228
234
|
def compression_method
|
229
235
|
ID
|
230
236
|
end
|
231
237
|
|
232
238
|
# This method signature is part of the interface contract expected by
|
233
|
-
# Archive::Zip::Entry for codec objects.
|
239
|
+
# Archive::Zip::Entry for compression codec objects.
|
234
240
|
#
|
235
|
-
# Returns
|
236
|
-
# ZIP archive entries.
|
241
|
+
# Returns <tt>0</tt> since this compression codec does not make use of
|
242
|
+
# general purpose flags of ZIP archive entries.
|
237
243
|
def general_purpose_flags
|
238
244
|
0
|
239
245
|
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'archive/support/io-like'
|
2
|
+
require 'archive/support/time'
|
3
|
+
require 'archive/support/zlib'
|
4
|
+
require 'archive/zip/codec'
|
5
|
+
|
6
|
+
module Archive; class Zip; module Codec
|
7
|
+
# Archive::Zip::Codec::TraditionalEncryption is a handle for the traditional
|
8
|
+
# encryption codec.
|
9
|
+
class TraditionalEncryption
|
10
|
+
# Archive::Zip::Codec::TraditionalEncryption::Base provides some basic
|
11
|
+
# methods which are shared between
|
12
|
+
# Archive::Zip::Codec::TraditionalEncryption::Encrypt and
|
13
|
+
# Archive::Zip::Codec::TraditionalEncryption::Decrypt.
|
14
|
+
#
|
15
|
+
# Do not use this class directly.
|
16
|
+
class Base
|
17
|
+
# Creates a new instance of this class. _io_ must be an IO-like object to
|
18
|
+
# be used as a delegate for IO operations. _password_ should be the
|
19
|
+
# encryption key. _mtime_ must be the last modified time of the entry to
|
20
|
+
# be encrypted/decrypted.
|
21
|
+
def initialize(io, password, mtime)
|
22
|
+
@io = io
|
23
|
+
@password = password.nil? ? '' : password
|
24
|
+
@mtime = mtime
|
25
|
+
initialize_keys
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# The delegate IO-like object.
|
31
|
+
attr_reader :io
|
32
|
+
# The encryption key.
|
33
|
+
attr_reader :password
|
34
|
+
# The last modified time of the entry being encrypted. This is used in
|
35
|
+
# the entryption header as a way to check the password.
|
36
|
+
attr_reader :mtime
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Initializes the keys used for encrypting/decrypting data by setting the
|
41
|
+
# keys to well known values and then processing them with the password.
|
42
|
+
def initialize_keys
|
43
|
+
@key0 = 0x12345678
|
44
|
+
@key1 = 0x23456789
|
45
|
+
@key2 = 0x34567890
|
46
|
+
@password.each_byte { |byte| update_keys(byte.chr) }
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Updates the keys following the ZIP specification using _char_, which
|
51
|
+
# must be a single byte String.
|
52
|
+
def update_keys(char)
|
53
|
+
# For some reason not explained in the ZIP specification but discovered
|
54
|
+
# in the source for InfoZIP, the old CRC value must first have its bits
|
55
|
+
# flipped before processing. The new CRC value must have its bits
|
56
|
+
# flipped as well for storage and later use. This applies to the
|
57
|
+
# handling of @key0 and @key2.
|
58
|
+
#
|
59
|
+
# NOTE: XOR'ing with 0xffffffff is used instead of simple bit negation
|
60
|
+
# in case this is run on a platform with a native integer size of
|
61
|
+
# something other than 32 bits.
|
62
|
+
@key0 = Zlib.crc32(char, @key0 ^ 0xffffffff) ^ 0xffffffff
|
63
|
+
@key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff
|
64
|
+
@key2 = Zlib.crc32((@key1 >> 24).chr, @key2 ^ 0xffffffff) ^ 0xffffffff
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the next decryption byte based on the current keys.
|
69
|
+
def decrypt_byte
|
70
|
+
temp = (@key2 | 2) & 0x0000ffff
|
71
|
+
((temp * (temp ^ 1)) >> 8) & 0x000000ff
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Archive::Zip::Codec::TraditionalEncryption::Encrypt is a writable, IO-like
|
76
|
+
# object which encrypts data written to it using the traditional encryption
|
77
|
+
# algorithm as documented in the ZIP specification and writes the result to
|
78
|
+
# a delegate IO object. A _close_ method is also provided which can
|
79
|
+
# optionally close the delegate object.
|
80
|
+
#
|
81
|
+
# Instances of this class should only be accessed via the
|
82
|
+
# Archive::Zip::Codec::TraditionalEncryption#compressor method.
|
83
|
+
class Encrypt < Base
|
84
|
+
include IO::Like
|
85
|
+
|
86
|
+
# Creates a new instance of this class with the given argument using #new
|
87
|
+
# and then passes the instance to the given block. The #close method is
|
88
|
+
# guaranteed to be called after the block completes.
|
89
|
+
#
|
90
|
+
# Equivalent to #new if no block is given.
|
91
|
+
def self.open(io, password, mtime)
|
92
|
+
encrypt_io = new(io, password, mtime)
|
93
|
+
return encrypt_io unless block_given?
|
94
|
+
|
95
|
+
begin
|
96
|
+
yield(encrypt_io)
|
97
|
+
ensure
|
98
|
+
encrypt_io.close unless encrypt_io.closed?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Creates a new instance of this class using _io_ as a data sink. _io_
|
103
|
+
# must be writable and must provide a write method as IO does or errors
|
104
|
+
# will be raised when performing write operations. _password_ should be
|
105
|
+
# the encryption key. _mtime_ must be the last modified time of the entry
|
106
|
+
# to be encrypted/decrypted.
|
107
|
+
#
|
108
|
+
# The _flush_size_ attribute is set to <tt>0</tt> by default under the
|
109
|
+
# assumption that _io_ is already buffered.
|
110
|
+
def initialize(io, password, mtime)
|
111
|
+
super(io, password, mtime)
|
112
|
+
|
113
|
+
# Assume that the delegate IO object is already buffered.
|
114
|
+
self.flush_size = 0
|
115
|
+
end
|
116
|
+
|
117
|
+
# Closes this object so that further write operations will fail. If
|
118
|
+
# _close_delegate_ is +true+, the delegate object used as a data sink will
|
119
|
+
# also be closed using its close method.
|
120
|
+
def close(close_delegate = true)
|
121
|
+
super()
|
122
|
+
io.close if close_delegate
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Extend the inherited initialize_keys method to further initialize the
|
128
|
+
# keys by encrypting and writing a 12 byte header to the delegate IO
|
129
|
+
# object.
|
130
|
+
def initialize_keys
|
131
|
+
super
|
132
|
+
|
133
|
+
# Create and encrypt a 12 byte header to protect the encrypted file data
|
134
|
+
# from attack. The first 10 bytes are random, and the lat 2 bytes are
|
135
|
+
# the low order word of the last modified time of the entry in DOS
|
136
|
+
# format.
|
137
|
+
10.times do
|
138
|
+
unbuffered_write(rand(256).chr)
|
139
|
+
end
|
140
|
+
time = mtime.to_dos_time.to_i
|
141
|
+
unbuffered_write((time & 0xff).chr)
|
142
|
+
unbuffered_write(((time >> 8) & 0xff).chr)
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# Encrypts and writes _string_ to the delegate IO object. Returns the
|
147
|
+
# number of bytes of _string_ written. If _string_ is not a String, it is
|
148
|
+
# converted into one using its _to_s_ method.
|
149
|
+
def unbuffered_write(string)
|
150
|
+
string = string.to_s
|
151
|
+
bytes_written = 0
|
152
|
+
string.each_byte do |byte|
|
153
|
+
temp = decrypt_byte
|
154
|
+
break unless io.write((byte ^ temp).chr) > 0
|
155
|
+
bytes_written += 1
|
156
|
+
update_keys(byte.chr)
|
157
|
+
end
|
158
|
+
bytes_written
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Archive::Zip::Codec::TraditionalEncryption::Decrypt is a readable, IO-like
|
163
|
+
# object which decrypts data data it reads from a delegate IO object using
|
164
|
+
# the traditional encryption algorithm as documented in the ZIP
|
165
|
+
# specification. A _close_ method is also provided which can optionally
|
166
|
+
# close the delegate object.
|
167
|
+
#
|
168
|
+
# Instances of this class should only be accessed via the
|
169
|
+
# Archive::Zip::Codec::TraditionalEncryption#decompressor method.
|
170
|
+
class Decrypt < Base
|
171
|
+
include IO::Like
|
172
|
+
|
173
|
+
# Creates a new instance of this class with the given argument using #new
|
174
|
+
# and then passes the instance to the given block. The #close method is
|
175
|
+
# guaranteed to be called after the block completes.
|
176
|
+
#
|
177
|
+
# Equivalent to #new if no block is given.
|
178
|
+
def self.open(io, password, mtime)
|
179
|
+
decrypt_io = new(io, password, mtime)
|
180
|
+
return decrypt_io unless block_given?
|
181
|
+
|
182
|
+
begin
|
183
|
+
yield(decrypt_io)
|
184
|
+
ensure
|
185
|
+
decrypt_io.close unless decrypt_io.closed?
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Creates a new instance of this class using _io_ as a data source. _io_
|
190
|
+
# must be readable and provide a read method as IO does or errors will be
|
191
|
+
# raised when performing read operations. If _io_ provides a rewind
|
192
|
+
# method, this class' rewind method will be enabled. _password_ should be
|
193
|
+
# the encryption key. _mtime_ must be the last modified time of the entry
|
194
|
+
# to be encrypted/decrypted.
|
195
|
+
#
|
196
|
+
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
197
|
+
# assumption that _io_ is already buffered.
|
198
|
+
def initialize(io, password, mtime)
|
199
|
+
super(io, password, mtime)
|
200
|
+
|
201
|
+
# Assume that the delegate IO object is already buffered.
|
202
|
+
self.fill_size = 0
|
203
|
+
end
|
204
|
+
|
205
|
+
# Closes this object so that further write operations will fail. If
|
206
|
+
# _close_delegate_ is +true+, the delegate object used as a data sink will
|
207
|
+
# also be closed using its close method.
|
208
|
+
def close(close_delegate = true)
|
209
|
+
super()
|
210
|
+
io.close if close_delegate
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# Extend the inherited initialize_keys method to further initialize the
|
216
|
+
# keys by encrypting and writing a 12 byte header to the delegate IO
|
217
|
+
# object.
|
218
|
+
def initialize_keys
|
219
|
+
super
|
220
|
+
|
221
|
+
# Decrypt the 12 byte header.
|
222
|
+
unbuffered_read(12)
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
|
226
|
+
# Reads, decrypts, and returns at most _length_ bytes from the delegate IO
|
227
|
+
# object.
|
228
|
+
#
|
229
|
+
# Raises EOFError if there is no data to read.
|
230
|
+
def unbuffered_read(length)
|
231
|
+
buffer = io.read(length)
|
232
|
+
raise EOFError, 'end of file reached' if buffer.nil?
|
233
|
+
|
234
|
+
(0 ... buffer.size).each do |i|
|
235
|
+
buffer[i] = (buffer[i] ^ decrypt_byte)
|
236
|
+
update_keys(buffer[i].chr)
|
237
|
+
end
|
238
|
+
buffer
|
239
|
+
end
|
240
|
+
|
241
|
+
# Allows resetting this object and the delegate object back to the
|
242
|
+
# beginning of the stream. _offset_ must be <tt>0</tt> and _whence_ must
|
243
|
+
# be IO::SEEK_SET or an error will be raised. The delegate object must
|
244
|
+
# respond to the _rewind_ method or an error will be raised.
|
245
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
246
|
+
unless offset == 0 && whence == IO::SEEK_SET then
|
247
|
+
raise Errno::EINVAL, 'Invalid argument'
|
248
|
+
end
|
249
|
+
unless io.respond_to?(:rewind) then
|
250
|
+
raise Errno::ESPIPE, 'Illegal seek'
|
251
|
+
end
|
252
|
+
io.rewind
|
253
|
+
initialize_keys
|
254
|
+
0
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# The last modified time of the entry to be processed. Set this before
|
259
|
+
# calling #encryptor or #decryptor.
|
260
|
+
attr_accessor :mtime
|
261
|
+
|
262
|
+
# This method signature is part of the interface contract expected by
|
263
|
+
# Archive::Zip::Entry for encryption codec objects.
|
264
|
+
#
|
265
|
+
# A convenience method for creating an
|
266
|
+
# Archive::Zip::Codec::TraditionalEncryption::Encrypt object using that
|
267
|
+
# class' open method.
|
268
|
+
def encryptor(io, password, &b)
|
269
|
+
Encrypt.open(io, password, mtime, &b)
|
270
|
+
end
|
271
|
+
|
272
|
+
# This method signature is part of the interface contract expected by
|
273
|
+
# Archive::Zip::Entry for encryption codec objects.
|
274
|
+
#
|
275
|
+
# A convenience method for creating an
|
276
|
+
# Archive::Zip::Codec::TraditionalEncryption::Decrypt object using that
|
277
|
+
# class' open method.
|
278
|
+
def decryptor(io, password, &b)
|
279
|
+
Decrypt.open(io, password, mtime, &b)
|
280
|
+
end
|
281
|
+
|
282
|
+
# This method signature is part of the interface contract expected by
|
283
|
+
# Archive::Zip::Entry for encryption codec objects.
|
284
|
+
#
|
285
|
+
# Returns an integer which indicates the version of the official ZIP
|
286
|
+
# specification which introduced support for this encryption codec.
|
287
|
+
def version_needed_to_extract
|
288
|
+
0x0014
|
289
|
+
end
|
290
|
+
|
291
|
+
# This method signature is part of the interface contract expected by
|
292
|
+
# Archive::Zip::Entry for encryption codec objects.
|
293
|
+
#
|
294
|
+
# Returns an integer representing the general purpose flags of a ZIP archive
|
295
|
+
# entry using this encryption codec.
|
296
|
+
def general_purpose_flags
|
297
|
+
0b0000000000000001
|
298
|
+
end
|
299
|
+
|
300
|
+
# This method signature is part of the interface contract expected by
|
301
|
+
# Archive::Zip::Entry for encryption codec objects.
|
302
|
+
#
|
303
|
+
# Returns the size of the encryption header in bytes.
|
304
|
+
def header_size
|
305
|
+
12
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end; end; end
|