lib/symmetric_encryption/reader.rb in symmetric-encryption-3.9.1 vs lib/symmetric_encryption/reader.rb in symmetric-encryption-4.0.0.beta3
- old
+ new
@@ -8,37 +8,20 @@
# * Large file support by only buffering small amounts of data in memory
class Reader
# Open a file for reading, or use the supplied IO Stream
#
# Parameters:
- # filename_or_stream:
- # The filename to open if a string, otherwise the stream to use
+ # file_name_or_stream:
+ # The file_name to open if a string, otherwise the stream to use
# The file or stream will be closed on completion, use .initialize to
# avoid having the stream closed automatically
#
- # options:
- # :mode
- # See File.open for open modes
- # Default: 'rb'
+ # buffer_size:
+ # Amount of data to read at a time.
+ # Minimum Value 128
+ # Default: 16384
#
- # :buffer_size
- # Amount of data to read at a time
- # Minimum Value 128
- # Default: 4096
- #
- # The following options are only used if the stream/file has no header
- # :compress [true|false]
- # Uses Zlib to decompress the data after it is decrypted
- # Note: This option is only used if the file does not have a header
- # indicating whether it is compressed
- # Default: false
- #
- # :version
- # Version of the encryption key to use when decrypting and the
- # file/stream does not include a header at the beginning
- # Default: Current primary key
- #
# Note: Decryption occurs before decompression
#
# # Example: Read and decrypt a line at a time from a file
# SymmetricEncryption::Reader.open('test_file') do |file|
# file.each_line {|line| p line }
@@ -74,47 +57,83 @@
# csv = CSV.new(SymmetricEncryption::Reader.open('csv_encrypted'))
# csv.each {|row| p row}
# ensure
# csv.close if csv
# end
- def self.open(filename_or_stream, options={}, &block)
- raise(ArgumentError, 'options must be a hash') unless options.respond_to?(:each_pair)
- mode = options.fetch(:mode, 'rb')
- compress = options.fetch(:compress, false)
- ios = filename_or_stream.is_a?(String) ? ::File.open(filename_or_stream, mode) : filename_or_stream
+ def self.open(file_name_or_stream, buffer_size: 16384, **args, &block)
+ ios = file_name_or_stream.is_a?(String) ? ::File.open(file_name_or_stream, 'rb') : file_name_or_stream
begin
- file = self.new(ios, options)
- file = Zlib::GzipReader.new(file) if !file.eof? && (file.compressed? || compress)
+ file = self.new(ios, buffer_size: buffer_size, **args)
+ file = Zlib::GzipReader.new(file) if !file.eof? && file.compressed?
block ? block.call(file) : file
ensure
file.close if block && file && (file.respond_to?(:closed?) && !file.closed?)
end
end
+ # 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)
+ open(file_name_or_stream, **args) { |f| f.read }
+ end
+
+ # Decrypt an entire file.
+ #
+ # Returns [Integer] the number of unencrypted bytes written to the target file.
+ #
+ # Params:
+ # source: [String|IO]
+ # 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: 65535, **args)
+ target_ios = target.is_a?(String) ? ::File.open(target, 'wb') : target
+ bytes_written = 0
+ open(source, **args) do |input_ios|
+ while !input_ios.eof?
+ bytes_written += target_ios.write(input_ios.read(block_size))
+ end
+ end
+ bytes_written
+ ensure
+ target_ios.close if target_ios && target_ios.respond_to?(:closed?) && !target_ios.closed?
+ end
+
# Returns [true|false] whether the file or stream contains any data
# excluding the header should it have one
- def self.empty?(filename_or_stream)
- open(filename_or_stream) { |file| file.eof? }
+ def self.empty?(file_name_or_stream)
+ open(file_name_or_stream) { |file| file.eof? }
end
# Returns [true|false] whether the file contains the encryption header
- def self.header_present?(filename)
- ::File.open(filename, 'rb') { |file| new(file).header_present? }
+ def self.header_present?(file_name)
+ ::File.open(file_name, 'rb') { |file| new(file).header_present? }
end
# After opening a file Returns [true|false] whether the file being
# read has an encryption header
def header_present?
@header_present
end
# Decrypt data before reading from the supplied stream
- def initialize(ios, options={})
+ def initialize(ios, buffer_size: 4096, version: nil)
@ios = ios
- @buffer_size = options.fetch(:buffer_size, 4096).to_i
- @version = options[:version]
+ @buffer_size = buffer_size
+ @version = version
@header_present = false
@closed = false
raise(ArgumentError, 'Buffer size cannot be smaller than 128') unless @buffer_size >= 128
@@ -314,36 +333,36 @@
private
# Read the header from the file if present
def read_header
- @pos = 0
+ @pos = 0
# Read first block and check for the header
- buf = @ios.read(@buffer_size)
+ buf = @ios.read(@buffer_size)
# Use cipher specified in header, or global cipher if it has no header
- iv, key = nil
- cipher_name = nil
- decryption_cipher = nil
- if header = SymmetricEncryption::Cipher.parse_header!(buf)
- @header_present = true
- @compressed = header.compressed
- decryption_cipher = header.decryption_cipher
- cipher_name = header.cipher_name || decryption_cipher.cipher_name
- key = header.key
- iv = header.iv
+ iv, key, cipher_name, cipher = nil
+ header = Header.new
+ if header.parse!(buf)
+ @header_present = true
+ @compressed = header.compressed?
+ @version = header.version
+ cipher = header.cipher
+ cipher_name = header.cipher_name || cipher.cipher_name
+ key = header.key
+ iv = header.iv
else
- @header_present = false
- @compressed = nil
- decryption_cipher = SymmetricEncryption.cipher(@version)
- cipher_name = decryption_cipher.cipher_name
+ @header_present = false
+ @compressed = nil
+ cipher = SymmetricEncryption.cipher(@version)
+ cipher_name = cipher.cipher_name
end
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name)
@stream_cipher.decrypt
- @stream_cipher.key = key || decryption_cipher.send(:key)
- @stream_cipher.iv = iv || decryption_cipher.iv
+ @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.length > 0
@read_buffer = @stream_cipher.update(buf)
@read_buffer << @stream_cipher.final if @ios.eof?