archive-zip 0.1.1 → 0.2.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.
- 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
|