Class: Archive::Zip::Codec::TraditionalEncryption::Encrypt

Inherits:
Base
  • Object
show all
Includes:
IO::Like
Defined in:
lib/archive/zip/codec/traditional_encryption.rb

Overview

Archive::Zip::Codec::TraditionalEncryption::Encrypt is a writable, IO-like object which encrypts data written to it using the traditional encryption algorithm as documented in the ZIP specification and writes the result to a delegate IO object. A close method is also provided which can optionally close the delegate object.

Instances of this class should only be accessed via the Archive::Zip::Codec::TraditionalEncryption#compressor method.

Instance Attribute Summary

Attributes inherited from Base

#io, #mtime, #password

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#decrypt_byte, #update_keys

Constructor Details

#initialize(io, password, mtime) ⇒ Encrypt

Creates a new instance of this class using io as a data sink. io must be writable and must provide a write method as IO does or errors will be raised when performing write operations. password should be the encryption key. mtime must be the last modified time of the entry to be encrypted/decrypted.

The flush_size attribute is set to 0 by default under the assumption that io is already buffered.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 109

def initialize(io, password, mtime)
  # Keep track of the total number of bytes written.
  # Set this here so that the call to #initialize_keys caused by the call
  # to super below does not cause errors in #unbuffered_write due to this
  # attribute being uninitialized.
  @total_bytes_in = 0

  # This buffer is used to hold the encrypted version of the string most
  # recently sent to #unbuffered_write.
  @encrypt_buffer = ''

  super(io, password, mtime)

  # Assume that the delegate IO object is already buffered.
  self.flush_size = 0
end

Class Method Details

.open(io, password, mtime) ⇒ Object

Creates a new instance of this class with the given argument using #new and then passes the instance to the given block. The #close method is guaranteed to be called after the block completes.

Equivalent to #new if no block is given.



90
91
92
93
94
95
96
97
98
99
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 90

def self.open(io, password, mtime)
  encrypt_io = new(io, password, mtime)
  return encrypt_io unless block_given?

  begin
    yield(encrypt_io)
  ensure
    encrypt_io.close unless encrypt_io.closed?
  end
end

Instance Method Details

#close(close_delegate = true) ⇒ Object

Closes the stream after flushing the encryption buffer to the delegate. If close_delegate is true, the delegate object used as a data sink will also be closed using its close method.

Raises IOError if called more than once.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 131

def close(close_delegate = true)
  flush()
  begin
    until @encrypt_buffer.empty? do
      @encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
    end
  rescue Errno::EAGAIN, Errno::EINTR
    retry if write_ready?
  end

  super()
  io.close if close_delegate
  nil
end

#initialize_keysObject (private)

Extend the inherited initialize_keys method to further initialize the keys by encrypting and writing a 12 byte header to the delegate IO object.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 151

def initialize_keys
  super

  # Create and encrypt a 12 byte header to protect the encrypted file data
  # from attack.  The first 10 bytes are random, and the last 2 bytes are
  # the low order word in little endian byte order of the last modified
  # time of the entry in DOS format.
  header = ''
  10.times do
    header << rand(256).chr
  end
  header << mtime.to_dos_time.pack[0, 2]

  # Take care to ensure that all bytes in the header are written.
  while header.size > 0 do
    begin
      header.slice!(0, unbuffered_write(header))
    rescue Errno::EAGAIN, Errno::EINTR
      sleep(1)
    end
  end

  # Reset the total bytes written in order to disregard the header.
  @total_bytes_in = 0

  nil
end

#unbuffered_seek(offset, whence = IO::SEEK_SET) ⇒ Object (private)

Allows resetting this object and the delegate object back to the beginning of the stream or reporting the current position in the stream.

Raises Errno::EINVAL unless offset is 0 and whence is either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if whence is IO::SEEK_SEK and the delegate object does not respond to the rewind method.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 186

def unbuffered_seek(offset, whence = IO::SEEK_SET)
  unless offset == 0 &&
         ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
          whence == IO::SEEK_CUR) then
    raise Errno::EINVAL
  end

  case whence
  when IO::SEEK_SET
    io.rewind
    @encrypt_buffer = ''
    initialize_keys
    @total_bytes_in = 0
  when IO::SEEK_CUR
    @total_bytes_in
  end
end

#unbuffered_write(string) ⇒ Object (private)

Encrypts and writes string to the delegate IO object. Returns the number of bytes of string written.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/archive/zip/codec/traditional_encryption.rb', line 206

def unbuffered_write(string)
  # First try to write out the contents of the encrypt buffer because if
  # that raises a failure we can let that pass up the call stack without
  # having polluted the encryption state.
  until @encrypt_buffer.empty? do
    @encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
  end
  # At this point we can encrypt the given string into a new buffer and
  # behave as if it was written.
  string.each_byte do |byte|
    temp = decrypt_byte
    @encrypt_buffer << (byte ^ temp).chr
    update_keys(byte.chr)
  end
  @total_bytes_in += string.length
  string.length
end