lib/symmetric_encryption/reader.rb in symmetric-encryption-4.1.0.beta1 vs lib/symmetric_encryption/reader.rb in symmetric-encryption-4.1.0

- old
+ new

@@ -74,11 +74,11 @@ # Read the entire contents of a file or stream into memory. # # Notes: # * Do not use this method for reading large files. def self.read(file_name_or_stream, **args) - self.open(file_name_or_stream, **args, &:read) + Reader.open(file_name_or_stream, **args, &:read) end # Decrypt an entire file. # # Returns [Integer] the number of unencrypted bytes written to the target file. @@ -88,26 +88,14 @@ # Source file_name or IOStream # # target: [String|IO] # Target file_name or IOStream # - # block_size: [Integer] - # Number of bytes to read into memory for each read. - # For very large files using a larger block size is faster. - # Default: 65535 - # # Notes: # * The file contents are streamed so that the entire file is _not_ loaded into memory. - def self.decrypt(source:, target:, block_size: 65_535, **args) - target_ios = target.is_a?(String) ? ::File.open(target, 'wb') : target - bytes_written = 0 - self.open(source, **args) do |input_ios| - bytes_written += target_ios.write(input_ios.read(block_size)) until input_ios.eof? - end - bytes_written - ensure - target_ios.close if target_ios&.respond_to?(:closed?) && !target_ios.closed? + def self.decrypt(source:, target:, **args) + Reader.open(source, **args) { |input_file| IO.copy_stream(input_file, target) } end # Returns [true|false] whether the file or stream contains any data # excluding the header should it have one def self.empty?(file_name_or_stream) @@ -130,10 +118,11 @@ @ios = ios @buffer_size = buffer_size @version = version @header_present = false @closed = false + @read_buffer = ''.b raise(ArgumentError, 'Buffer size cannot be smaller than 128') unless @buffer_size >= 128 read_header end @@ -168,10 +157,11 @@ # It is recommended to call Symmetric::EncryptedStream.open or Symmetric::EncryptedStream.io # rather than creating an instance of Symmetric::EncryptedStream directly to # ensure that the encrypted stream is closed before the stream itself is closed def close(close_child_stream = true) return if closed? + @ios.close if close_child_stream @closed = true end # Flush the read stream @@ -192,39 +182,29 @@ # Reads at most length bytes from the I/O stream, or to the end of file if # length is omitted or is nil. length must be a non-negative integer or nil. # # At end of file, it returns nil if no more data is available, or the last # remaining bytes - def read(length = nil) - data = nil - if length - return '' if length.zero? - return nil if eof? - # Read length bytes - read_block while (@read_buffer.length < length) && !@ios.eof? - if @read_buffer.empty? - data = nil - elsif @read_buffer.length > length - data = @read_buffer.slice!(0..length - 1) + def read(length = nil, outbuf = nil) + data = outbuf.to_s.clear + remaining_length = length + + until remaining_length == 0 || eof? + read_block(remaining_length) if @read_buffer.empty? + + if remaining_length && remaining_length < @read_buffer.length + data << @read_buffer.slice!(0, remaining_length) else - data = @read_buffer - @read_buffer = '' + data << @read_buffer + @read_buffer.clear end - else - # Capture anything already in the buffer - data = @read_buffer - @read_buffer = '' - unless @ios.eof? - # Read entire file - buf = @ios.read || '' - data << @stream_cipher.update(buf) if buf && !buf.empty? - data << @stream_cipher.final - end + remaining_length = length - data.length if length end + @pos += data.length - data + data unless data.empty? && length && length.positive? end # Reads a single decrypted line from the file up to and including the optional sep_string. # Raises EOFError on eof # The stream must be opened for reading or an IOError will be raised. @@ -240,16 +220,18 @@ return read(length) if sep_string.nil? # Read more data until we get the sep_string while (index = @read_buffer.index(sep_string)).nil? && !@ios.eof? break if length && @read_buffer.length >= length + read_block end index ||= -1 - data = @read_buffer.slice!(0..index) - @pos += data.length + data = @read_buffer.slice!(0..index) + @pos += data.length return nil if data.empty? && eof? + data end # ios.each(sep_string="\n") {|line| block } => ios # ios.each_line(sep_string="\n") {|line| block } => ios @@ -270,11 +252,11 @@ # Return the number of bytes read so far from the input stream attr_reader :pos # Rewind back to the beginning of the file def rewind - @read_buffer = '' + @read_buffer.clear @ios.rewind read_header end # Seeks to a given offset (Integer) in the stream according to the value of whence: @@ -305,14 +287,14 @@ when IO::SEEK_END rewind # Read and decrypt entire file a block at a time to get its total # unencrypted size size = 0 - until eof + until eof? read_block - size += @read_buffer.size - @read_buffer = '' + size += @read_buffer.size + @read_buffer.clear end rewind offset = size + amount else raise(ArgumentError, "unknown whence:#{whence} supplied to seek()") @@ -326,11 +308,11 @@ # Read the header from the file if present def read_header @pos = 0 # Read first block and check for the header - buf = @ios.read(@buffer_size) + buf = @ios.read(@buffer_size, @output_buffer ||= ''.b) # Use cipher specified in header, or global cipher if it has no header iv, key, cipher_name, cipher = nil header = Header.new if header.parse!(buf) @@ -351,23 +333,33 @@ @stream_cipher = ::OpenSSL::Cipher.new(cipher_name) @stream_cipher.decrypt @stream_cipher.key = key || cipher.send(:key) @stream_cipher.iv = iv || cipher.iv - # First call to #update should return an empty string anyway - if buf && !buf.empty? - @read_buffer = @stream_cipher.update(buf) - @read_buffer << @stream_cipher.final if @ios.eof? - else - @read_buffer = '' - end + decrypt(buf) end # Read a block of data and append the decrypted data in the read buffer - def read_block - buf = @ios.read(@buffer_size) - @read_buffer << @stream_cipher.update(buf) if buf && !buf.empty? - @read_buffer << @stream_cipher.final if @ios.eof? + def read_block(length = nil) + buf = @ios.read(length || @buffer_size, @output_buffer ||= ''.b) + decrypt(buf) + end + + # Decrypts the given chunk of data and returns the result + if defined?(JRuby) + def decrypt(buf) + return if buf.nil? || buf.empty? + + @read_buffer << @stream_cipher.update(buf) + @read_buffer << @stream_cipher.final if @ios.eof? + end + else + def decrypt(buf) + return if buf.nil? || buf.empty? + + @read_buffer << @stream_cipher.update(buf, @cipher_buffer ||= ''.b) + @read_buffer << @stream_cipher.final if @ios.eof? + end end def closed? @closed || @ios.respond_to?(:closed?) && @ios.closed? end