class PuTTY::Key::PPK::Reader

Handles reading .ppk files.

@private

Public Class Methods

new(file) click to toggle source

Initializes a new {Reader} with an IO-like instance to read from.

@param file [Object] The IO-like instance to read from.

# File lib/putty/key/ppk.rb, line 529
def initialize(file)
  @file = file
  @consumed_byte = nil
end
open(path_or_io) { |reader| ... } click to toggle source

Opens a .ppk file for reading (or uses the provided IO-like instance), creates a new instance of Reader and yields it to the caller.

@param path_or_io [Object] The path of the .ppk file to be read or an IO-like object.

@return [Object] The result of yielding to the caller.

raise [Errno::ENOENT] If the file specified by path does not exist.

# File lib/putty/key/ppk.rb, line 510
def self.open(path_or_io)
  if path_or_io.kind_of?(String) || path_or_io.kind_of?(Pathname)
    File.open(path_or_io.to_s, 'rb') do |file|
      yield Reader.new(file)
    end
  else
    path_or_io.binmode if path_or_io.respond_to?(:binmode)

    unless path_or_io.respond_to?(:getbyte)
      path_or_io = GetbyteIo.new(path_or_io)
    end

    yield Reader.new(path_or_io)
  end
end

Public Instance Methods

blob(name) click to toggle source

Reads a blob from the file consisting of a Lines field whose value gives the number of Base64 encoded lines in the blob.

@return [String] The Base64-decoded value of the blob.

@raise [FormatError] If there is not a blob starting at the current file position. @raise [FormatError] If the value of the Lines field is not a positive integer.

# File lib/putty/key/ppk.rb, line 597
def blob(name)
  lines = unsigned_integer("#{name}-Lines")
  lines.times.map { read_line }.join("\n").unpack('m48').first
end
field(name) click to toggle source

Reads the next field from the file.

@param name [String] The expected field name.

@return [String] The value of the field.

@raise [FormatError] If the current position in the file was not the start of a field with the expected name.

# File lib/putty/key/ppk.rb, line 542
def field(name)
  line = read_line
  raise FormatError, "Expected field #{name}, but found #{line}" unless line.start_with?("#{name}: ")
  line.byteslice(name.bytesize + 2, line.bytesize - name.bytesize - 2)
end
field_matching(name_regexp) click to toggle source

Reads the next field from the file.

@param name_regexp [Regexp] A Regexp that matches the expected field name.

@return [String] The value of the field if the regular expression has no captures. @return [Array] An array containing the regular expression captures as the first elements and the value of the field as the last element.

@raise [FormatError] If the current position in the file was not the start of a field with the expected name.

# File lib/putty/key/ppk.rb, line 560
def field_matching(name_regexp)
  line = read_line
  line_regexp = Regexp.new("\\A#{name_regexp.source}: ", name_regexp.options)
  match = line_regexp.match(line)
  raise FormatError, "Expected field matching #{name_regexp}, but found #{line}" unless match
  prefix = match[0]
  value = line.byteslice(prefix.bytesize, line.bytesize - prefix.bytesize)
  captures = match.captures
  captures.empty? ? value : captures + [value]
end
unsigned_integer(name, maximum: nil) click to toggle source

Reads the next field from the file as an unsigned integer.

@param name [String] The expected field name.

@return [Integer] The value of the field.

@raise [FormatError] If the current position in the file was not the start of a field with the expected name. @raise [FormatError] If the field did not contain a positive integer.

# File lib/putty/key/ppk.rb, line 580
def unsigned_integer(name, maximum: nil)
  value = field(name)
  value = value =~ /\A[0-9]+\z/ && value.to_i
  raise FormatError, "Expected field #{name} to contain an unsigned integer value, but found #{value}" unless value
  raise FormatError, "Expected field #{name} to have a maximum of #{maximum}, but found #{value}" if maximum && value > maximum
  value
end

Private Instance Methods

read_line() click to toggle source

Reads a single new-line (n, rn or r) terminated line from the file, removing the new-line character.

@return [String] The line.

@raise [FormatError] If the end of file was detected before reading a line.

# File lib/putty/key/ppk.rb, line 611
def read_line
  line = ''.b

  if @consumed_byte
    line << @consumed_byte
    @consumed_byte = nil
  end

  while byte = @file.getbyte
    return line if byte == 0x0a

    if byte == 0x0d
      byte = @file.getbyte
      return line if !byte || byte == 0x0a
      @consumed_byte = byte
      return line
    end

    line << byte
  end

  return line if line.bytesize > 0
  raise FormatError, 'Truncated ppk file detected'
end