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.
Files changed (132) hide show
  1. data/HACKING +25 -42
  2. data/NEWS +25 -0
  3. data/README +2 -2
  4. data/Rakefile +202 -0
  5. data/TODO +5 -0
  6. data/default.mspec +8 -0
  7. data/lib/archive/support/binary_stringio.rb +23 -0
  8. data/lib/archive/support/integer.rb +13 -0
  9. data/lib/archive/support/io-like.rb +3 -1
  10. data/lib/archive/support/ioextensions.rb +16 -0
  11. data/lib/archive/support/iowindow.rb +10 -18
  12. data/lib/archive/support/time.rb +2 -0
  13. data/lib/archive/support/zlib.rb +298 -71
  14. data/lib/archive/zip.rb +161 -139
  15. data/lib/archive/zip/codec.rb +2 -0
  16. data/lib/archive/zip/codec/deflate.rb +59 -11
  17. data/lib/archive/zip/codec/null_encryption.rb +75 -14
  18. data/lib/archive/zip/codec/store.rb +75 -26
  19. data/lib/archive/zip/codec/traditional_encryption.rb +146 -35
  20. data/lib/archive/zip/data_descriptor.rb +6 -4
  21. data/lib/archive/zip/entry.rb +184 -132
  22. data/lib/archive/zip/error.rb +2 -0
  23. data/lib/archive/zip/extra_field.rb +20 -6
  24. data/lib/archive/zip/extra_field/extended_timestamp.rb +141 -60
  25. data/lib/archive/zip/extra_field/raw.rb +70 -12
  26. data/lib/archive/zip/extra_field/unix.rb +58 -16
  27. data/lib/archive/zip/version.rb +6 -0
  28. data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +42 -0
  29. data/spec/archive/zip/codec/deflate/compress/close_spec.rb +44 -0
  30. data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +21 -0
  31. data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +67 -0
  32. data/spec/archive/zip/codec/deflate/compress/new_spec.rb +37 -0
  33. data/spec/archive/zip/codec/deflate/compress/open_spec.rb +46 -0
  34. data/spec/archive/zip/codec/deflate/compress/write_spec.rb +109 -0
  35. data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +18 -0
  36. data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +33 -0
  37. data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +18 -0
  38. data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +67 -0
  39. data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +14 -0
  40. data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +27 -0
  41. data/spec/archive/zip/codec/deflate/fixtures/classes.rb +25 -0
  42. data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +1 -0
  43. data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
  44. data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +10 -0
  45. data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +33 -0
  46. data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +14 -0
  47. data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +27 -0
  48. data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +24 -0
  49. data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +25 -0
  50. data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +57 -0
  51. data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +21 -0
  52. data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +33 -0
  53. data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +14 -0
  54. data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +27 -0
  55. data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +26 -0
  56. data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +50 -0
  57. data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +29 -0
  58. data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +29 -0
  59. data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +12 -0
  60. data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +10 -0
  61. data/spec/archive/zip/codec/store/compress/close_spec.rb +33 -0
  62. data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +68 -0
  63. data/spec/archive/zip/codec/store/compress/new_spec.rb +14 -0
  64. data/spec/archive/zip/codec/store/compress/open_spec.rb +27 -0
  65. data/spec/archive/zip/codec/store/compress/rewind_spec.rb +26 -0
  66. data/spec/archive/zip/codec/store/compress/seek_spec.rb +50 -0
  67. data/spec/archive/zip/codec/store/compress/tell_spec.rb +29 -0
  68. data/spec/archive/zip/codec/store/compress/write_spec.rb +29 -0
  69. data/spec/archive/zip/codec/store/decompress/close_spec.rb +33 -0
  70. data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +68 -0
  71. data/spec/archive/zip/codec/store/decompress/new_spec.rb +14 -0
  72. data/spec/archive/zip/codec/store/decompress/open_spec.rb +27 -0
  73. data/spec/archive/zip/codec/store/decompress/read_spec.rb +24 -0
  74. data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +25 -0
  75. data/spec/archive/zip/codec/store/decompress/seek_spec.rb +57 -0
  76. data/spec/archive/zip/codec/store/decompress/tell_spec.rb +21 -0
  77. data/spec/archive/zip/codec/store/fixtures/classes.rb +12 -0
  78. data/spec/archive/zip/codec/store/fixtures/raw_file.txt +10 -0
  79. data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +64 -0
  80. data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +18 -0
  81. data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +39 -0
  82. data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +126 -0
  83. data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +38 -0
  84. data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +82 -0
  85. data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +25 -0
  86. data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +64 -0
  87. data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +18 -0
  88. data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +39 -0
  89. data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +41 -0
  90. data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +75 -0
  91. data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +42 -0
  92. data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +127 -0
  93. data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +27 -0
  94. data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
  95. data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +10 -0
  96. data/spec/binary_stringio/new_spec.rb +34 -0
  97. data/spec/binary_stringio/set_encoding_spec.rb +14 -0
  98. data/spec/ioextensions/read_exactly_spec.rb +50 -0
  99. data/spec/zlib/fixtures/classes.rb +65 -0
  100. data/spec/zlib/fixtures/compressed_file.bin +1 -0
  101. data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
  102. data/spec/zlib/fixtures/compressed_file_huffman.bin +2 -0
  103. data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
  104. data/spec/zlib/fixtures/compressed_file_minwin.bin +1 -0
  105. data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
  106. data/spec/zlib/fixtures/compressed_file_raw.bin +1 -0
  107. data/spec/zlib/fixtures/raw_file.txt +10 -0
  108. data/spec/zlib/zreader/checksum_spec.rb +40 -0
  109. data/spec/zlib/zreader/close_spec.rb +14 -0
  110. data/spec/zlib/zreader/compressed_size_spec.rb +18 -0
  111. data/spec/zlib/zreader/new_spec.rb +41 -0
  112. data/spec/zlib/zreader/open_spec.rb +49 -0
  113. data/spec/zlib/zreader/read_spec.rb +47 -0
  114. data/spec/zlib/zreader/rewind_spec.rb +23 -0
  115. data/spec/zlib/zreader/seek_spec.rb +55 -0
  116. data/spec/zlib/zreader/tell_spec.rb +21 -0
  117. data/spec/zlib/zreader/uncompressed_size_spec.rb +18 -0
  118. data/spec/zlib/zwriter/checksum_spec.rb +41 -0
  119. data/spec/zlib/zwriter/close_spec.rb +14 -0
  120. data/spec/zlib/zwriter/compressed_size_spec.rb +19 -0
  121. data/spec/zlib/zwriter/new_spec.rb +64 -0
  122. data/spec/zlib/zwriter/open_spec.rb +68 -0
  123. data/spec/zlib/zwriter/rewind_spec.rb +26 -0
  124. data/spec/zlib/zwriter/seek_spec.rb +54 -0
  125. data/spec/zlib/zwriter/tell_spec.rb +29 -0
  126. data/spec/zlib/zwriter/uncompressed_size_spec.rb +19 -0
  127. data/spec/zlib/zwriter/write_spec.rb +28 -0
  128. data/spec_helper.rb +49 -0
  129. metadata +296 -74
  130. data/MANIFEST +0 -27
  131. data/lib/archive/support/io.rb +0 -14
  132. 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 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.
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 sink will
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. _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.
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 && whence == IO::SEEK_SET then
121
- raise Errno::EINVAL, 'Invalid argument'
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
- unless @io.respond_to?(:rewind) then
124
- raise Errno::ESPIPE, 'Illegal seek'
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::Entry::DataDescriptor with
62
- # information regarding the data which has passed through this object to
63
- # the delegate object. The close or flush methods should be called before
64
- # using this method in order to ensure that any possibly buffered data is
65
- # flushed to the delegate object; otherwise, the contents of the data
66
- # descriptor may be inaccurate.
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 read method as IO does or errors will be
119
- # raised when performing read operations. If _io_ provides a rewind
120
- # method, this class' rewind method will be enabled.
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::Entry::DataDescriptor with
142
- # information regarding the data which has passed through this object
143
- # from the delegate object. It is recommended to call the close method
144
- # before calling this in order to ensure that no further read operations
145
- # change the state of this object.
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. _offset_ must be <tt>0</tt> and _whence_ must
169
- # be IO::SEEK_SET or an error will be raised. The delegate object must
170
- # respond to the _rewind_ method or an error will be raised. The
171
- # uncompressed_size and crc32 attributes are reinitialized as a side
172
- # effect.
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 && whence == IO::SEEK_SET then
175
- raise Errno::EINVAL, 'Invalid argument'
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
- unless @io.respond_to?(:rewind) then
178
- raise Errno::ESPIPE, 'Illegal seek'
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 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.
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 lat 2 bytes are
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
- unbuffered_write(rand(256).chr)
164
+ header << rand(256).chr
139
165
  end
140
166
  time = mtime.to_dos_time.to_i
141
- unbuffered_write((time & 0xff).chr)
142
- unbuffered_write(((time >> 8) & 0xff).chr)
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. If _string_ is not a String, it is
148
- # converted into one using its _to_s_ method.
210
+ # number of bytes of _string_ written.
149
211
  def unbuffered_write(string)
150
- string = string.to_s
151
- bytes_written = 0
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
- break unless io.write((byte ^ temp).chr) > 0
155
- bytes_written += 1
222
+ @encrypt_buffer << (byte ^ temp).chr
156
223
  update_keys(byte.chr)
157
224
  end
158
- bytes_written
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 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.
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 sink will
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
- # Decrypt the 12 byte header.
222
- unbuffered_read(12)
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
- (0 ... buffer.size).each do |i|
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. _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.
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 && whence == IO::SEEK_SET then
247
- raise Errno::EINVAL, 'Invalid argument'
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
- unless io.respond_to?(:rewind) then
250
- raise Errno::ESPIPE, 'Illegal seek'
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