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