archive-zip 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HACKING +25 -42
- data/NEWS +25 -0
- data/README +2 -2
- data/Rakefile +202 -0
- data/TODO +5 -0
- data/default.mspec +8 -0
- data/lib/archive/support/binary_stringio.rb +23 -0
- data/lib/archive/support/integer.rb +13 -0
- data/lib/archive/support/io-like.rb +3 -1
- data/lib/archive/support/ioextensions.rb +16 -0
- data/lib/archive/support/iowindow.rb +10 -18
- data/lib/archive/support/time.rb +2 -0
- data/lib/archive/support/zlib.rb +298 -71
- data/lib/archive/zip.rb +161 -139
- data/lib/archive/zip/codec.rb +2 -0
- data/lib/archive/zip/codec/deflate.rb +59 -11
- data/lib/archive/zip/codec/null_encryption.rb +75 -14
- data/lib/archive/zip/codec/store.rb +75 -26
- data/lib/archive/zip/codec/traditional_encryption.rb +146 -35
- data/lib/archive/zip/data_descriptor.rb +6 -4
- data/lib/archive/zip/entry.rb +184 -132
- data/lib/archive/zip/error.rb +2 -0
- data/lib/archive/zip/extra_field.rb +20 -6
- data/lib/archive/zip/extra_field/extended_timestamp.rb +141 -60
- data/lib/archive/zip/extra_field/raw.rb +70 -12
- data/lib/archive/zip/extra_field/unix.rb +58 -16
- data/lib/archive/zip/version.rb +6 -0
- data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +42 -0
- data/spec/archive/zip/codec/deflate/compress/close_spec.rb +44 -0
- data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +21 -0
- data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/compress/new_spec.rb +37 -0
- data/spec/archive/zip/codec/deflate/compress/open_spec.rb +46 -0
- data/spec/archive/zip/codec/deflate/compress/write_spec.rb +109 -0
- data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/deflate/fixtures/classes.rb +25 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +1 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +24 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/store/compress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/compress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/compress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/compress/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/store/compress/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/store/compress/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/store/compress/write_spec.rb +29 -0
- data/spec/archive/zip/codec/store/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/decompress/read_spec.rb +24 -0
- data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/store/decompress/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/store/decompress/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/store/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/store/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +126 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +38 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +82 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +25 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +41 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +75 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +42 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +127 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +27 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +10 -0
- data/spec/binary_stringio/new_spec.rb +34 -0
- data/spec/binary_stringio/set_encoding_spec.rb +14 -0
- data/spec/ioextensions/read_exactly_spec.rb +50 -0
- data/spec/zlib/fixtures/classes.rb +65 -0
- data/spec/zlib/fixtures/compressed_file.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_huffman.bin +2 -0
- data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_minwin.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_raw.bin +1 -0
- data/spec/zlib/fixtures/raw_file.txt +10 -0
- data/spec/zlib/zreader/checksum_spec.rb +40 -0
- data/spec/zlib/zreader/close_spec.rb +14 -0
- data/spec/zlib/zreader/compressed_size_spec.rb +18 -0
- data/spec/zlib/zreader/new_spec.rb +41 -0
- data/spec/zlib/zreader/open_spec.rb +49 -0
- data/spec/zlib/zreader/read_spec.rb +47 -0
- data/spec/zlib/zreader/rewind_spec.rb +23 -0
- data/spec/zlib/zreader/seek_spec.rb +55 -0
- data/spec/zlib/zreader/tell_spec.rb +21 -0
- data/spec/zlib/zreader/uncompressed_size_spec.rb +18 -0
- data/spec/zlib/zwriter/checksum_spec.rb +41 -0
- data/spec/zlib/zwriter/close_spec.rb +14 -0
- data/spec/zlib/zwriter/compressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/new_spec.rb +64 -0
- data/spec/zlib/zwriter/open_spec.rb +68 -0
- data/spec/zlib/zwriter/rewind_spec.rb +26 -0
- data/spec/zlib/zwriter/seek_spec.rb +54 -0
- data/spec/zlib/zwriter/tell_spec.rb +29 -0
- data/spec/zlib/zwriter/uncompressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/write_spec.rb +28 -0
- data/spec_helper.rb +49 -0
- metadata +296 -74
- data/MANIFEST +0 -27
- data/lib/archive/support/io.rb +0 -14
- data/lib/archive/support/stringio.rb +0 -22
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'archive/support/io-like'
|
2
4
|
require 'archive/zip/codec'
|
3
5
|
|
@@ -36,6 +38,10 @@ module Archive; class Zip; module Codec
|
|
36
38
|
# assumption that _io_ is already buffered.
|
37
39
|
def initialize(io)
|
38
40
|
@io = io
|
41
|
+
|
42
|
+
# Keep track of the total number of bytes written.
|
43
|
+
@total_bytes_in = 0
|
44
|
+
|
39
45
|
# Assume that the delegate IO object is already buffered.
|
40
46
|
self.flush_size = 0
|
41
47
|
end
|
@@ -50,9 +56,34 @@ module Archive; class Zip; module Codec
|
|
50
56
|
|
51
57
|
private
|
52
58
|
|
59
|
+
# Allows resetting this object and the delegate object back to the
|
60
|
+
# beginning of the stream or reporting the current position in the stream.
|
61
|
+
#
|
62
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
63
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
64
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
65
|
+
# method.
|
66
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
67
|
+
unless offset == 0 &&
|
68
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
69
|
+
whence == IO::SEEK_CUR) then
|
70
|
+
raise Errno::EINVAL
|
71
|
+
end
|
72
|
+
|
73
|
+
case whence
|
74
|
+
when IO::SEEK_SET
|
75
|
+
@io.rewind
|
76
|
+
@total_bytes_in = 0
|
77
|
+
when IO::SEEK_CUR
|
78
|
+
@total_bytes_in
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
53
82
|
# Writes _string_ to the delegate IO object and returns the result.
|
54
83
|
def unbuffered_write(string)
|
55
|
-
@io.write(string)
|
84
|
+
bytes_written = @io.write(string)
|
85
|
+
@total_bytes_in += bytes_written
|
86
|
+
bytes_written
|
56
87
|
end
|
57
88
|
end
|
58
89
|
|
@@ -80,21 +111,41 @@ module Archive; class Zip; module Codec
|
|
80
111
|
end
|
81
112
|
|
82
113
|
# Creates a new instance of this class using _io_ as a data source. _io_
|
83
|
-
# must be readable and provide a
|
84
|
-
# raised when performing read operations.
|
85
|
-
#
|
114
|
+
# must be readable and provide a _read_ method as an IO instance would or
|
115
|
+
# errors will be raised when performing read operations.
|
116
|
+
#
|
117
|
+
# This class has extremely limited seek capabilities. It is possible to
|
118
|
+
# seek with an offset of <tt>0</tt> and a whence of <tt>IO::SEEK_CUR</tt>.
|
119
|
+
# As a result, the _pos_ and _tell_ methods also work as expected.
|
120
|
+
#
|
121
|
+
# Due to certain optimizations within IO::Like#seek and if there is data
|
122
|
+
# in the read buffer, the _seek_ method can be used to seek forward from
|
123
|
+
# the current stream position up to the end of the buffer. Unless it is
|
124
|
+
# known definitively how much data is in the buffer, it is best to avoid
|
125
|
+
# relying on this behavior.
|
126
|
+
#
|
127
|
+
# If _io_ also responds to _rewind_, then the _rewind_ method of this
|
128
|
+
# class can be used to reset the whole stream back to the beginning. Using
|
129
|
+
# _seek_ of this class to seek directly to offset <tt>0</tt> using
|
130
|
+
# <tt>IO::SEEK_SET</tt> for whence will also work in this case.
|
131
|
+
#
|
132
|
+
# Any other seeking attempts, will raise Errno::EINVAL exceptions.
|
86
133
|
#
|
87
134
|
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
88
135
|
# assumption that _io_ is already buffered.
|
89
136
|
def initialize(io)
|
90
137
|
@io = io
|
138
|
+
|
139
|
+
# Keep track of the total number of bytes read.
|
140
|
+
@total_bytes_out = 0
|
141
|
+
|
91
142
|
# Assume that the delegate IO object is already buffered.
|
92
143
|
self.fill_size = 0
|
93
144
|
end
|
94
145
|
|
95
146
|
# Closes this object so that further write operations will fail. If
|
96
|
-
# _close_delegate_ is +true+, the delegate object used as a data
|
97
|
-
# also be closed using its close method.
|
147
|
+
# _close_delegate_ is +true+, the delegate object used as a data source
|
148
|
+
# will also be closed using its close method.
|
98
149
|
def close(close_delegate = true)
|
99
150
|
super()
|
100
151
|
@io.close if close_delegate
|
@@ -108,22 +159,32 @@ module Archive; class Zip; module Codec
|
|
108
159
|
def unbuffered_read(length)
|
109
160
|
buffer = @io.read(length)
|
110
161
|
raise EOFError, 'end of file reached' if buffer.nil?
|
162
|
+
@total_bytes_out += buffer.length
|
111
163
|
|
112
164
|
buffer
|
113
165
|
end
|
114
166
|
|
115
167
|
# Allows resetting this object and the delegate object back to the
|
116
|
-
# beginning of the stream
|
117
|
-
#
|
118
|
-
#
|
168
|
+
# beginning of the stream or reporting the current position in the stream.
|
169
|
+
#
|
170
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
171
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
172
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
173
|
+
# method.
|
119
174
|
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
120
|
-
unless offset == 0 &&
|
121
|
-
|
175
|
+
unless offset == 0 &&
|
176
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
177
|
+
whence == IO::SEEK_CUR) then
|
178
|
+
raise Errno::EINVAL
|
122
179
|
end
|
123
|
-
|
124
|
-
|
180
|
+
|
181
|
+
case whence
|
182
|
+
when IO::SEEK_SET
|
183
|
+
@io.rewind
|
184
|
+
@total_bytes_out = 0
|
185
|
+
when IO::SEEK_CUR
|
186
|
+
@total_bytes_out
|
125
187
|
end
|
126
|
-
@io.rewind
|
127
188
|
end
|
128
189
|
end
|
129
190
|
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'archive/support/io-like'
|
4
|
+
require 'archive/support/zlib'
|
2
5
|
require 'archive/zip/codec'
|
3
6
|
require 'archive/zip/data_descriptor'
|
4
7
|
|
@@ -58,12 +61,12 @@ module Archive; class Zip; module Codec
|
|
58
61
|
nil
|
59
62
|
end
|
60
63
|
|
61
|
-
# Returns an instance of Archive::Zip::
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
64
|
+
# Returns an instance of Archive::Zip::DataDescriptor with information
|
65
|
+
# regarding the data which has passed through this object to the delegate
|
66
|
+
# object. The close or flush methods should be called before using this
|
67
|
+
# method in order to ensure that any possibly buffered data is flushed to
|
68
|
+
# the delegate object; otherwise, the contents of the data descriptor may
|
69
|
+
# be inaccurate.
|
67
70
|
def data_descriptor
|
68
71
|
DataDescriptor.new(
|
69
72
|
@crc32,
|
@@ -74,6 +77,30 @@ module Archive; class Zip; module Codec
|
|
74
77
|
|
75
78
|
private
|
76
79
|
|
80
|
+
# Allows resetting this object and the delegate object back to the
|
81
|
+
# beginning of the stream or reporting the current position in the stream.
|
82
|
+
#
|
83
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
84
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
85
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
86
|
+
# method.
|
87
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
88
|
+
unless offset == 0 &&
|
89
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
90
|
+
whence == IO::SEEK_CUR) then
|
91
|
+
raise Errno::EINVAL
|
92
|
+
end
|
93
|
+
|
94
|
+
case whence
|
95
|
+
when IO::SEEK_SET
|
96
|
+
@io.rewind
|
97
|
+
@crc32 = 0
|
98
|
+
@uncompressed_size = 0
|
99
|
+
when IO::SEEK_CUR
|
100
|
+
@uncompressed_size
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
77
104
|
# Writes _string_ to the delegate object and returns the number of bytes
|
78
105
|
# actually written. Updates the uncompressed_size and crc32 attributes as
|
79
106
|
# a side effect.
|
@@ -115,9 +142,25 @@ module Archive; class Zip; module Codec
|
|
115
142
|
end
|
116
143
|
|
117
144
|
# Creates a new instance of this class using _io_ as a data source. _io_
|
118
|
-
# must be readable and provide a
|
119
|
-
# raised when performing read operations.
|
120
|
-
#
|
145
|
+
# must be readable and provide a _read_ method as an IO instance would or
|
146
|
+
# errors will be raised when performing read operations.
|
147
|
+
#
|
148
|
+
# This class has extremely limited seek capabilities. It is possible to
|
149
|
+
# seek with an offset of <tt>0</tt> and a whence of <tt>IO::SEEK_CUR</tt>.
|
150
|
+
# As a result, the _pos_ and _tell_ methods also work as expected.
|
151
|
+
#
|
152
|
+
# Due to certain optimizations within IO::Like#seek and if there is data
|
153
|
+
# in the read buffer, the _seek_ method can be used to seek forward from
|
154
|
+
# the current stream position up to the end of the buffer. Unless it is
|
155
|
+
# known definitively how much data is in the buffer, it is best to avoid
|
156
|
+
# relying on this behavior.
|
157
|
+
#
|
158
|
+
# If _io_ also responds to _rewind_, then the _rewind_ method of this
|
159
|
+
# class can be used to reset the whole stream back to the beginning. Using
|
160
|
+
# _seek_ of this class to seek directly to offset <tt>0</tt> using
|
161
|
+
# <tt>IO::SEEK_SET</tt> for whence will also work in this case.
|
162
|
+
#
|
163
|
+
# Any other seeking attempts, will raise Errno::EINVAL exceptions.
|
121
164
|
#
|
122
165
|
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
123
166
|
# assumption that _io_ is already buffered.
|
@@ -138,11 +181,11 @@ module Archive; class Zip; module Codec
|
|
138
181
|
nil
|
139
182
|
end
|
140
183
|
|
141
|
-
# Returns an instance of Archive::Zip::
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
184
|
+
# Returns an instance of Archive::Zip::DataDescriptor with information
|
185
|
+
# regarding the data which has passed through this object from the
|
186
|
+
# delegate object. It is recommended to call the close method before
|
187
|
+
# calling this in order to ensure that no further read operations change
|
188
|
+
# the state of this object.
|
146
189
|
def data_descriptor
|
147
190
|
DataDescriptor.new(
|
148
191
|
@crc32,
|
@@ -165,21 +208,27 @@ module Archive; class Zip; module Codec
|
|
165
208
|
end
|
166
209
|
|
167
210
|
# Allows resetting this object and the delegate object back to the
|
168
|
-
# beginning of the stream
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
211
|
+
# beginning of the stream or reporting the current position in the stream.
|
212
|
+
#
|
213
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
214
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
215
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
216
|
+
# method.
|
173
217
|
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
174
|
-
unless offset == 0 &&
|
175
|
-
|
218
|
+
unless offset == 0 &&
|
219
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
220
|
+
whence == IO::SEEK_CUR) then
|
221
|
+
raise Errno::EINVAL
|
176
222
|
end
|
177
|
-
|
178
|
-
|
223
|
+
|
224
|
+
case whence
|
225
|
+
when IO::SEEK_SET
|
226
|
+
@io.rewind
|
227
|
+
@crc32 = 0
|
228
|
+
@uncompressed_size = 0
|
229
|
+
when IO::SEEK_CUR
|
230
|
+
@uncompressed_size
|
179
231
|
end
|
180
|
-
@io.rewind
|
181
|
-
@crc32 = 0
|
182
|
-
@uncompressed_size = 0
|
183
232
|
end
|
184
233
|
end
|
185
234
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'archive/support/integer'
|
1
4
|
require 'archive/support/io-like'
|
2
5
|
require 'archive/support/time'
|
3
6
|
require 'archive/support/zlib'
|
@@ -108,18 +111,40 @@ module Archive; class Zip; module Codec
|
|
108
111
|
# The _flush_size_ attribute is set to <tt>0</tt> by default under the
|
109
112
|
# assumption that _io_ is already buffered.
|
110
113
|
def initialize(io, password, mtime)
|
114
|
+
# Keep track of the total number of bytes written.
|
115
|
+
# Set this here so that the call to #initialize_keys caused by the call
|
116
|
+
# to super below does not cause errors in #unbuffered_write due to this
|
117
|
+
# attribute being uninitialized.
|
118
|
+
@total_bytes_in = 0
|
119
|
+
|
120
|
+
# This buffer is used to hold the encrypted version of the string most
|
121
|
+
# recently sent to #unbuffered_write.
|
122
|
+
@encrypt_buffer = ''
|
123
|
+
|
111
124
|
super(io, password, mtime)
|
112
125
|
|
113
126
|
# Assume that the delegate IO object is already buffered.
|
114
127
|
self.flush_size = 0
|
115
128
|
end
|
116
129
|
|
117
|
-
# Closes
|
118
|
-
# _close_delegate_ is +true+, the delegate object used as a data sink
|
119
|
-
# also be closed using its close method.
|
130
|
+
# Closes the stream after flushing the encryption buffer to the delegate.
|
131
|
+
# If _close_delegate_ is +true+, the delegate object used as a data sink
|
132
|
+
# will also be closed using its close method.
|
133
|
+
#
|
134
|
+
# Raises IOError if called more than once.
|
120
135
|
def close(close_delegate = true)
|
136
|
+
flush()
|
137
|
+
begin
|
138
|
+
until @encrypt_buffer.empty? do
|
139
|
+
@encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
|
140
|
+
end
|
141
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
142
|
+
retry if write_ready?
|
143
|
+
end
|
144
|
+
|
121
145
|
super()
|
122
146
|
io.close if close_delegate
|
147
|
+
nil
|
123
148
|
end
|
124
149
|
|
125
150
|
private
|
@@ -131,31 +156,74 @@ module Archive; class Zip; module Codec
|
|
131
156
|
super
|
132
157
|
|
133
158
|
# Create and encrypt a 12 byte header to protect the encrypted file data
|
134
|
-
# from attack. The first 10 bytes are random, and the
|
159
|
+
# from attack. The first 10 bytes are random, and the last 2 bytes are
|
135
160
|
# the low order word of the last modified time of the entry in DOS
|
136
161
|
# format.
|
162
|
+
header = ''
|
137
163
|
10.times do
|
138
|
-
|
164
|
+
header << rand(256).chr
|
139
165
|
end
|
140
166
|
time = mtime.to_dos_time.to_i
|
141
|
-
|
142
|
-
|
167
|
+
header << (time & 0xff).chr << ((time >> 8) & 0xff).chr
|
168
|
+
|
169
|
+
# Take care to ensure that all bytes in the header are written.
|
170
|
+
while header.size > 0 do
|
171
|
+
begin
|
172
|
+
header.slice!(0, unbuffered_write(header))
|
173
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
174
|
+
sleep(1)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Reset the total bytes written in order to disregard the header.
|
179
|
+
@total_bytes_in = 0
|
180
|
+
|
143
181
|
nil
|
144
182
|
end
|
145
183
|
|
184
|
+
# Allows resetting this object and the delegate object back to the
|
185
|
+
# beginning of the stream or reporting the current position in the stream.
|
186
|
+
#
|
187
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
188
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
189
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
190
|
+
# method.
|
191
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
192
|
+
unless offset == 0 &&
|
193
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
194
|
+
whence == IO::SEEK_CUR) then
|
195
|
+
raise Errno::EINVAL
|
196
|
+
end
|
197
|
+
|
198
|
+
case whence
|
199
|
+
when IO::SEEK_SET
|
200
|
+
io.rewind
|
201
|
+
@encrypt_buffer = ''
|
202
|
+
initialize_keys
|
203
|
+
@total_bytes_in = 0
|
204
|
+
when IO::SEEK_CUR
|
205
|
+
@total_bytes_in
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
146
209
|
# Encrypts and writes _string_ to the delegate IO object. Returns the
|
147
|
-
# number of bytes of _string_ written.
|
148
|
-
# converted into one using its _to_s_ method.
|
210
|
+
# number of bytes of _string_ written.
|
149
211
|
def unbuffered_write(string)
|
150
|
-
|
151
|
-
|
212
|
+
# First try to write out the contents of the encrypt buffer because if
|
213
|
+
# that raises a failure we can let that pass up the call stack without
|
214
|
+
# having polluted the encryption state.
|
215
|
+
until @encrypt_buffer.empty? do
|
216
|
+
@encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
|
217
|
+
end
|
218
|
+
# At this point we can encrypt the given string into a new buffer and
|
219
|
+
# behave as if it was written.
|
152
220
|
string.each_byte do |byte|
|
153
221
|
temp = decrypt_byte
|
154
|
-
|
155
|
-
bytes_written += 1
|
222
|
+
@encrypt_buffer << (byte ^ temp).chr
|
156
223
|
update_keys(byte.chr)
|
157
224
|
end
|
158
|
-
|
225
|
+
@total_bytes_in += string.length
|
226
|
+
string.length
|
159
227
|
end
|
160
228
|
end
|
161
229
|
|
@@ -187,15 +255,37 @@ module Archive; class Zip; module Codec
|
|
187
255
|
end
|
188
256
|
|
189
257
|
# Creates a new instance of this class using _io_ as a data source. _io_
|
190
|
-
# must be readable and provide a
|
191
|
-
# raised when performing read operations.
|
192
|
-
#
|
193
|
-
# the
|
194
|
-
#
|
258
|
+
# must be readable and provide a _read_ method as an IO instance would or
|
259
|
+
# errors will be raised when performing read operations. _password_
|
260
|
+
# should be the encryption key. _mtime_ must be the last modified time of
|
261
|
+
# the entry to be encrypted/decrypted.
|
262
|
+
#
|
263
|
+
# This class has extremely limited seek capabilities. It is possible to
|
264
|
+
# seek with an offset of <tt>0</tt> and a whence of <tt>IO::SEEK_CUR</tt>.
|
265
|
+
# As a result, the _pos_ and _tell_ methods also work as expected.
|
266
|
+
#
|
267
|
+
# Due to certain optimizations within IO::Like#seek and if there is data
|
268
|
+
# in the read buffer, the _seek_ method can be used to seek forward from
|
269
|
+
# the current stream position up to the end of the buffer. Unless it is
|
270
|
+
# known definitively how much data is in the buffer, it is best to avoid
|
271
|
+
# relying on this behavior.
|
272
|
+
#
|
273
|
+
# If _io_ also responds to _rewind_, then the _rewind_ method of this
|
274
|
+
# class can be used to reset the whole stream back to the beginning. Using
|
275
|
+
# _seek_ of this class to seek directly to offset <tt>0</tt> using
|
276
|
+
# <tt>IO::SEEK_SET</tt> for whence will also work in this case.
|
277
|
+
#
|
278
|
+
# Any other seeking attempts, will raise Errno::EINVAL exceptions.
|
195
279
|
#
|
196
280
|
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
197
281
|
# assumption that _io_ is already buffered.
|
198
282
|
def initialize(io, password, mtime)
|
283
|
+
# Keep track of the total number of bytes read.
|
284
|
+
# Set this here so that the call to #initialize_keys caused by the call
|
285
|
+
# to super below does not cause errors in #unbuffered_read due to this
|
286
|
+
# attribute being uninitialized.
|
287
|
+
@total_bytes_out = 0
|
288
|
+
|
199
289
|
super(io, password, mtime)
|
200
290
|
|
201
291
|
# Assume that the delegate IO object is already buffered.
|
@@ -203,8 +293,8 @@ module Archive; class Zip; module Codec
|
|
203
293
|
end
|
204
294
|
|
205
295
|
# Closes this object so that further write operations will fail. If
|
206
|
-
# _close_delegate_ is +true+, the delegate object used as a data
|
207
|
-
# also be closed using its close method.
|
296
|
+
# _close_delegate_ is +true+, the delegate object used as a data source
|
297
|
+
# will also be closed using its close method.
|
208
298
|
def close(close_delegate = true)
|
209
299
|
super()
|
210
300
|
io.close if close_delegate
|
@@ -218,8 +308,20 @@ module Archive; class Zip; module Codec
|
|
218
308
|
def initialize_keys
|
219
309
|
super
|
220
310
|
|
221
|
-
#
|
222
|
-
|
311
|
+
# Load the 12 byte header taking care to ensure that all bytes are read.
|
312
|
+
bytes_needed = 12
|
313
|
+
while bytes_needed > 0 do
|
314
|
+
begin
|
315
|
+
bytes_read = unbuffered_read(bytes_needed)
|
316
|
+
bytes_needed -= bytes_read.size
|
317
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
318
|
+
sleep(1)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Reset the total bytes read in order to disregard the header.
|
323
|
+
@total_bytes_out = 0
|
324
|
+
|
223
325
|
nil
|
224
326
|
end
|
225
327
|
|
@@ -230,28 +332,37 @@ module Archive; class Zip; module Codec
|
|
230
332
|
def unbuffered_read(length)
|
231
333
|
buffer = io.read(length)
|
232
334
|
raise EOFError, 'end of file reached' if buffer.nil?
|
335
|
+
@total_bytes_out += buffer.length
|
233
336
|
|
234
|
-
(
|
235
|
-
buffer[i] = (buffer[i] ^ decrypt_byte)
|
337
|
+
0.upto(buffer.length - 1) do |i|
|
338
|
+
buffer[i] = (buffer[i].ord ^ decrypt_byte).chr
|
236
339
|
update_keys(buffer[i].chr)
|
237
340
|
end
|
238
341
|
buffer
|
239
342
|
end
|
240
343
|
|
241
344
|
# Allows resetting this object and the delegate object back to the
|
242
|
-
# beginning of the stream
|
243
|
-
#
|
244
|
-
#
|
345
|
+
# beginning of the stream or reporting the current position in the stream.
|
346
|
+
#
|
347
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
348
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
349
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
350
|
+
# method.
|
245
351
|
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
246
|
-
unless offset == 0 &&
|
247
|
-
|
352
|
+
unless offset == 0 &&
|
353
|
+
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
354
|
+
whence == IO::SEEK_CUR) then
|
355
|
+
raise Errno::EINVAL
|
248
356
|
end
|
249
|
-
|
250
|
-
|
357
|
+
|
358
|
+
case whence
|
359
|
+
when IO::SEEK_SET
|
360
|
+
io.rewind
|
361
|
+
initialize_keys
|
362
|
+
@total_bytes_out = 0
|
363
|
+
when IO::SEEK_CUR
|
364
|
+
@total_bytes_out
|
251
365
|
end
|
252
|
-
io.rewind
|
253
|
-
initialize_keys
|
254
|
-
0
|
255
366
|
end
|
256
367
|
end
|
257
368
|
|