module OpenSSL::Buffering

OpenSSL IO buffering mix-in module.

This module allows an OpenSSL::SSL::SSLSocket to behave like an IO.

You typically won't use this module directly, you can see it implemented in OpenSSL::SSL::SSLSocket.

Constants

BLOCK_SIZE

Default size to read from or write to the SSLSocket for buffer operations.

Attributes

sync[RW]

The “sync mode” of the SSLSocket.

See IO#sync for full details.

Public Class Methods

new(*) click to toggle source

Creates an instance of OpenSSL's buffering IO module.

Calls superclass method
# File ext/openssl/lib/openssl/buffering.rb, line 40
def initialize(*)
  super
  @eof = false
  @rbuffer = ""
  @sync = @io.sync
end

Public Instance Methods

<<(s) click to toggle source

Writes s to the stream. s will be converted to a String using String#to_s.

# File ext/openssl/lib/openssl/buffering.rb, line 386
def << (s)
  do_write(s)
  self
end
close() click to toggle source

Closes the SSLSocket and flushes any unwritten data.

# File ext/openssl/lib/openssl/buffering.rb, line 449
def close
  flush rescue nil
  sysclose
end
do_write(s) click to toggle source

Writes s to the buffer. When the buffer is full or sync is true the buffer is flushed to the underlying socket.

# File ext/openssl/lib/openssl/buffering.rb, line 311
def do_write(s)
  @wbuffer = "" unless defined? @wbuffer
  @wbuffer << s
  @wbuffer.force_encoding(Encoding::BINARY)
  @sync ||= false
  if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
    remain = idx ? idx + $/.size : @wbuffer.length
    nwritten = 0
    while remain > 0
      str = @wbuffer[nwritten,remain]
      begin
        nwrote = syswrite(str)
      rescue Errno::EAGAIN
        retry
      end
      remain -= nwrote
      nwritten += nwrote
    end
    @wbuffer[0,nwritten] = ""
  end
end
each(eol=$/) { |line| ... } click to toggle source

Executes the block for every line in the stream where lines are separated by eol.

See also gets

# File ext/openssl/lib/openssl/buffering.rb, line 224
def each(eol=$/)
  while line = self.gets(eol)
    yield line
  end
end
each_byte() { |byte| ... } click to toggle source

Calls the given block once for each byte in the stream.

# File ext/openssl/lib/openssl/buffering.rb, line 265
def each_byte # :yields: byte
  while c = getc
    yield(c.ord)
  end
end
eof?() click to toggle source

Returns true if the stream is at file which means there is no more data to be read.

# File ext/openssl/lib/openssl/buffering.rb, line 296
def eof?
  fill_rbuff if !@eof && @rbuffer.empty?
  @eof && @rbuffer.empty?
end
flush() click to toggle source

Flushes buffered data to the SSLSocket.

# File ext/openssl/lib/openssl/buffering.rb, line 437
def flush
  osync = @sync
  @sync = true
  do_write ""
  return self
ensure
  @sync = osync
end
getc() click to toggle source

Reads one character from the stream. Returns nil if called at end of file.

# File ext/openssl/lib/openssl/buffering.rb, line 258
def getc
  read(1)
end
gets(eol=$/, limit=nil) click to toggle source

Reads the next “line+ from the stream. Lines are separated by eol. If limit is provided the result will not be longer than the given number of bytes.

eol may be a String or Regexp.

Unlike IO#gets the line read will not be assigned to +$_+.

Unlike IO#gets the separator must be provided if a limit is provided.

# File ext/openssl/lib/openssl/buffering.rb, line 200
  def gets(eol=$/, limit=nil)
    idx = @rbuffer.index(eol)
    until @eof
      break if idx
      fill_rbuff
      idx = @rbuffer.index(eol)
    end
    if eol.is_a?(Regexp)
      size = idx ? idx+$&.size : nil
    else
      size = idx ? idx+eol.size : nil
    end
    if size && limit && limit >= 0
      size = [size, limit].min
    end
    consume_rbuff(size)
  end

  ##
  # Executes the block for every line in the stream where lines are separated
  # by +eol+.
  #
  # See also #gets

  def each(eol=$/)
    while line = self.gets(eol)
      yield line
    end
  end
  alias each_line each

  ##
  # Reads lines from the stream which are separated by +eol+.
  #
  # See also #gets

  def readlines(eol=$/)
    ary = []
    while line = self.gets(eol)
      ary << line
    end
    ary
  end

  ##
  # Reads a line from the stream which is separated by +eol+.
  #
  # Raises EOFError if at end of file.

  def readline(eol=$/)
    raise EOFError if eof?
    gets(eol)
  end

  ##
  # Reads one character from the stream.  Returns nil if called at end of
  # file.

  def getc
    read(1)
  end

  ##
  # Calls the given block once for each byte in the stream.

  def each_byte # :yields: byte
    while c = getc
      yield(c.ord)
    end
  end

  ##
  # Reads a one-character string from the stream.  Raises an EOFError at end
  # of file.

  def readchar
    raise EOFError if eof?
    getc
  end

  ##
  # Pushes character +c+ back onto the stream such that a subsequent buffered
  # character read will return it.
  #
  # Unlike IO#getc multiple bytes may be pushed back onto the stream.
  #
  # Has no effect on unbuffered reads (such as #sysread).

  def ungetc(c)
    @rbuffer[0,0] = c.chr
  end

  ##
  # Returns true if the stream is at file which means there is no more data to
  # be read.

  def eof?
    fill_rbuff if !@eof && @rbuffer.empty?
    @eof && @rbuffer.empty?
  end
  alias eof eof?

  #
  # for writing.
  #
  private

  ##
  # Writes +s+ to the buffer.  When the buffer is full or #sync is true the
  # buffer is flushed to the underlying socket.

  def do_write(s)
    @wbuffer = "" unless defined? @wbuffer
    @wbuffer << s
    @wbuffer.force_encoding(Encoding::BINARY)
    @sync ||= false
    if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
      remain = idx ? idx + $/.size : @wbuffer.length
      nwritten = 0
      while remain > 0
        str = @wbuffer[nwritten,remain]
        begin
          nwrote = syswrite(str)
        rescue Errno::EAGAIN
          retry
        end
        remain -= nwrote
        nwritten += nwrote
      end
      @wbuffer[0,nwritten] = ""
    end
  end

  public

  ##
  # Writes +s+ to the stream.  If the argument is not a string it will be
  # converted using String#to_s.  Returns the number of bytes written.

  def write(s)
    do_write(s)
    s.bytesize
  end

  ##
  # Writes +str+ in the non-blocking manner.
  #
  # If there is buffered data, it is flushed first.  This may block.
  #
  # write_nonblock returns number of bytes written to the SSL connection.
  #
  # When no data can be written without blocking it raises
  # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
  #
  # IO::WaitReadable means SSL needs to read internally so write_nonblock
  # should be called again after the underlying IO is readable.
  #
  # IO::WaitWritable means SSL needs to write internally so write_nonblock
  # should be called again after underlying IO is writable.
  #
  # So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
  #
  #   # emulates blocking write.
  #   begin
  #     result = ssl.write_nonblock(str)
  #   rescue IO::WaitReadable
  #     IO.select([io])
  #     retry
  #   rescue IO::WaitWritable
  #     IO.select(nil, [io])
  #     retry
  #   end
  #
  # Note that one reason that write_nonblock reads from the underlying IO
  # is when the peer requests a new TLS/SSL handshake.  See the openssl FAQ
  # for more details.  http://www.openssl.org/support/faq.html

  def write_nonblock(s, exception: true)
    flush
    syswrite_nonblock(s, exception: exception)
  end

  ##
  # Writes +s+ to the stream.  +s+ will be converted to a String using
  # String#to_s.

  def << (s)
    do_write(s)
    self
  end

  ##
  # Writes +args+ to the stream along with a record separator.
  #
  # See IO#puts for full details.

  def puts(*args)
    s = ""
    if args.empty?
      s << "\n"
    end
    args.each{|arg|
      s << arg.to_s
      if $/ && /\n\z/ !~ s
        s << "\n"
      end
    }
    do_write(s)
    nil
  end

  ##
  # Writes +args+ to the stream.
  #
  # See IO#print for full details.

  def print(*args)
    s = ""
    args.each{ |arg| s << arg.to_s }
    do_write(s)
    nil
  end

  ##
  # Formats and writes to the stream converting parameters under control of
  # the format string.
  #
  # See Kernel#sprintf for format string details.

  def printf(s, *args)
    do_write(s % args)
    nil
  end

  ##
  # Flushes buffered data to the SSLSocket.

  def flush
    osync = @sync
    @sync = true
    do_write ""
    return self
  ensure
    @sync = osync
  end

  ##
  # Closes the SSLSocket and flushes any unwritten data.

  def close
    flush rescue nil
    sysclose
  end
end
print(*args) click to toggle source

Writes args to the stream.

See IO#print for full details.

printf(s, *args) click to toggle source

Formats and writes to the stream converting parameters under control of the format string.

See Kernel#sprintf for format string details.

# File ext/openssl/lib/openssl/buffering.rb, line 429
def printf(s, *args)
  do_write(s % args)
  nil
end
puts(*args) click to toggle source

Writes args to the stream along with a record separator.

See IO#puts for full details.

# File ext/openssl/lib/openssl/buffering.rb, line 396
def puts(*args)
  s = ""
  if args.empty?
    s << "\n"
  end
  args.each{|arg|
    s << arg.to_s
    if $/ && /\n\z/ !~ s
      s << "\n"
    end
  }
  do_write(s)
  nil
end
read(size=nil, buf=nil) click to toggle source

Reads size bytes from the stream. If buf is provided it must reference a string which will receive the data.

See IO#read for full details.

# File ext/openssl/lib/openssl/buffering.rb, line 87
def read(size=nil, buf=nil)
  if size == 0
    if buf
      buf.clear
      return buf
    else
      return ""
    end
  end
  until @eof
    break if size && size <= @rbuffer.size
    fill_rbuff
  end
  ret = consume_rbuff(size) || ""
  if buf
    buf.replace(ret)
    ret = buf
  end
  (size && ret.empty?) ? nil : ret
end

##
# Reads at most +maxlen+ bytes from the stream.  If +buf+ is provided it
# must reference a string which will receive the data.
#
# See IO#readpartial for full details.

def readpartial(maxlen, buf=nil)
  if maxlen == 0
    if buf
      buf.clear
      return buf
    else
      return ""
    end
  end
  if @rbuffer.empty?
    begin
      return sysread(maxlen, buf)
    rescue Errno::EAGAIN
      retry
    end
  end
  ret = consume_rbuff(maxlen)
  if buf
    buf.replace(ret)
    ret = buf
  end
  raise EOFError if ret.empty?
  ret
end

##
# Reads at most +maxlen+ bytes in the non-blocking manner.
#
# When no data can be read without blocking it raises
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
#
# IO::WaitReadable means SSL needs to read internally so read_nonblock
# should be called again when the underlying IO is readable.
#
# IO::WaitWritable means SSL needs to write internally so read_nonblock
# should be called again after the underlying IO is writable.
#
# OpenSSL::Buffering#read_nonblock needs two rescue clause as follows:
#
#   # emulates blocking read (readpartial).
#   begin
#     result = ssl.read_nonblock(maxlen)
#   rescue IO::WaitReadable
#     IO.select([io])
#     retry
#   rescue IO::WaitWritable
#     IO.select(nil, [io])
#     retry
#   end
#
# Note that one reason that read_nonblock writes to the underlying IO is
# when the peer requests a new TLS/SSL handshake.  See openssl the FAQ for
# more details.  http://www.openssl.org/support/faq.html

def read_nonblock(maxlen, buf=nil, exception: true)
  if maxlen == 0
    if buf
      buf.clear
      return buf
    else
      return ""
    end
  end
  if @rbuffer.empty?
    return sysread_nonblock(maxlen, buf, exception: exception)
  end
  ret = consume_rbuff(maxlen)
  if buf
    buf.replace(ret)
    ret = buf
  end
  raise EOFError if ret.empty?
  ret
end

##
# Reads the next "line+ from the stream.  Lines are separated by +eol+.  If
# +limit+ is provided the result will not be longer than the given number of
# bytes.
#
# +eol+ may be a String or Regexp.
#
# Unlike IO#gets the line read will not be assigned to +$_+.
#
# Unlike IO#gets the separator must be provided if a limit is provided.

def gets(eol=$/, limit=nil)
  idx = @rbuffer.index(eol)
  until @eof
    break if idx
    fill_rbuff
    idx = @rbuffer.index(eol)
  end
  if eol.is_a?(Regexp)
    size = idx ? idx+$&.size : nil
  else
    size = idx ? idx+eol.size : nil
  end
  if size && limit && limit >= 0
    size = [size, limit].min
  end
  consume_rbuff(size)
end

##
# Executes the block for every line in the stream where lines are separated
# by +eol+.
#
# See also #gets

def each(eol=$/)
  while line = self.gets(eol)
    yield line
  end
end
alias each_line each

##
# Reads lines from the stream which are separated by +eol+.
#
# See also #gets

def readlines(eol=$/)
  ary = []
  while line = self.gets(eol)
    ary << line
  end
  ary
end

##
# Reads a line from the stream which is separated by +eol+.
#
# Raises EOFError if at end of file.

def readline(eol=$/)
  raise EOFError if eof?
  gets(eol)
end

##
# Reads one character from the stream.  Returns nil if called at end of
# file.

def getc
  read(1)
end

##
# Calls the given block once for each byte in the stream.

def each_byte # :yields: byte
  while c = getc
    yield(c.ord)
  end
end

##
# Reads a one-character string from the stream.  Raises an EOFError at end
# of file.

def readchar
  raise EOFError if eof?
  getc
end

##
# Pushes character +c+ back onto the stream such that a subsequent buffered
# character read will return it.
#
# Unlike IO#getc multiple bytes may be pushed back onto the stream.
#
# Has no effect on unbuffered reads (such as #sysread).

def ungetc(c)
  @rbuffer[0,0] = c.chr
end

##
# Returns true if the stream is at file which means there is no more data to
# be read.

def eof?
  fill_rbuff if !@eof && @rbuffer.empty?
  @eof && @rbuffer.empty?
end
alias eof eof?

#
# for writing.
#
private

##
# Writes +s+ to the buffer.  When the buffer is full or #sync is true the
# buffer is flushed to the underlying socket.

def do_write(s)
  @wbuffer = "" unless defined? @wbuffer
  @wbuffer << s
  @wbuffer.force_encoding(Encoding::BINARY)
  @sync ||= false
  if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
    remain = idx ? idx + $/.size : @wbuffer.length
    nwritten = 0
    while remain > 0
      str = @wbuffer[nwritten,remain]
      begin
        nwrote = syswrite(str)
      rescue Errno::EAGAIN
        retry
      end
      remain -= nwrote
      nwritten += nwrote
    end
    @wbuffer[0,nwritten] = ""
  end
end

public

##
# Writes +s+ to the stream.  If the argument is not a string it will be
# converted using String#to_s.  Returns the number of bytes written.

def write(s)
  do_write(s)
  s.bytesize
end

##
# Writes +str+ in the non-blocking manner.
#
# If there is buffered data, it is flushed first.  This may block.
#
# write_nonblock returns number of bytes written to the SSL connection.
#
# When no data can be written without blocking it raises
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
#
# IO::WaitReadable means SSL needs to read internally so write_nonblock
# should be called again after the underlying IO is readable.
#
# IO::WaitWritable means SSL needs to write internally so write_nonblock
# should be called again after underlying IO is writable.
#
# So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
#
#   # emulates blocking write.
#   begin
#     result = ssl.write_nonblock(str)
#   rescue IO::WaitReadable
#     IO.select([io])
#     retry
#   rescue IO::WaitWritable
#     IO.select(nil, [io])
#     retry
#   end
#
# Note that one reason that write_nonblock reads from the underlying IO
# is when the peer requests a new TLS/SSL handshake.  See the openssl FAQ
# for more details.  http://www.openssl.org/support/faq.html

def write_nonblock(s, exception: true)
  flush
  syswrite_nonblock(s, exception: exception)
end

##
# Writes +s+ to the stream.  +s+ will be converted to a String using
# String#to_s.

def << (s)
  do_write(s)
  self
end

##
# Writes +args+ to the stream along with a record separator.
#
# See IO#puts for full details.

def puts(*args)
  s = ""
  if args.empty?
    s << "\n"
  end
  args.each{|arg|
    s << arg.to_s
    if $/ && /\n\z/ !~ s
      s << "\n"
    end
  }
  do_write(s)
  nil
end

##
# Writes +args+ to the stream.
#
# See IO#print for full details.

def print(*args)
  s = ""
  args.each{ |arg| s << arg.to_s }
  do_write(s)
  nil
end

##
# Formats and writes to the stream converting parameters under control of
# the format string.
#
# See Kernel#sprintf for format string details.

def printf(s, *args)
  do_write(s % args)
  nil
end

##
# Flushes buffered data to the SSLSocket.

def flush
  osync = @sync
  @sync = true
  do_write ""
  return self
ensure
  @sync = osync
end

##
# Closes the SSLSocket and flushes any unwritten data.

def close
  flush rescue nil
  sysclose
end
read_nonblock(maxlen, buf=nil, exception: true) click to toggle source

Reads at most maxlen bytes in the non-blocking manner.

When no data can be read without blocking it raises OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.

IO::WaitReadable means SSL needs to read internally so #read_nonblock should be called again when the underlying IO is readable.

IO::WaitWritable means SSL needs to write internally so #read_nonblock should be called again after the underlying IO is writable.

#read_nonblock needs two rescue clause as follows:

# emulates blocking read (readpartial).
begin
  result = ssl.read_nonblock(maxlen)
rescue IO::WaitReadable
  IO.select([io])
  retry
rescue IO::WaitWritable
  IO.select(nil, [io])
  retry
end

Note that one reason that #read_nonblock writes to the underlying IO is when the peer requests a new TLS/SSL handshake. See openssl the FAQ for more details. www.openssl.org/support/faq.html

# File ext/openssl/lib/openssl/buffering.rb, line 168
def read_nonblock(maxlen, buf=nil, exception: true)
  if maxlen == 0
    if buf
      buf.clear
      return buf
    else
      return ""
    end
  end
  if @rbuffer.empty?
    return sysread_nonblock(maxlen, buf, exception: exception)
  end
  ret = consume_rbuff(maxlen)
  if buf
    buf.replace(ret)
    ret = buf
  end
  raise EOFError if ret.empty?
  ret
end
readchar() click to toggle source

Reads a one-character string from the stream. Raises an EOFError at end of file.

# File ext/openssl/lib/openssl/buffering.rb, line 275
def readchar
  raise EOFError if eof?
  getc
end
readline(eol=$/) click to toggle source

Reads a line from the stream which is separated by eol.

Raises EOFError if at end of file.

# File ext/openssl/lib/openssl/buffering.rb, line 249
def readline(eol=$/)
  raise EOFError if eof?
  gets(eol)
end
readlines(eol=$/) click to toggle source

Reads lines from the stream which are separated by eol.

See also gets

# File ext/openssl/lib/openssl/buffering.rb, line 236
def readlines(eol=$/)
  ary = []
  while line = self.gets(eol)
    ary << line
  end
  ary
end
readpartial(maxlen, buf=nil) click to toggle source

Reads at most maxlen bytes from the stream. If buf is provided it must reference a string which will receive the data.

See IO#readpartial for full details.

# File ext/openssl/lib/openssl/buffering.rb, line 114
def readpartial(maxlen, buf=nil)
  if maxlen == 0
    if buf
      buf.clear
      return buf
    else
      return ""
    end
  end
  if @rbuffer.empty?
    begin
      return sysread(maxlen, buf)
    rescue Errno::EAGAIN
      retry
    end
  end
  ret = consume_rbuff(maxlen)
  if buf
    buf.replace(ret)
    ret = buf
  end
  raise EOFError if ret.empty?
  ret
end
ungetc(c) click to toggle source

Pushes character c back onto the stream such that a subsequent buffered character read will return it.

Unlike IO#getc multiple bytes may be pushed back onto the stream.

Has no effect on unbuffered reads (such as sysread).

# File ext/openssl/lib/openssl/buffering.rb, line 288
def ungetc(c)
  @rbuffer[0,0] = c.chr
end
write(s) click to toggle source

Writes s to the stream. If the argument is not a string it will be converted using String#to_s. Returns the number of bytes written.

# File ext/openssl/lib/openssl/buffering.rb, line 339
def write(s)
  do_write(s)
  s.bytesize
end
write_nonblock(s, exception: true) click to toggle source

Writes str in the non-blocking manner.

If there is buffered data, it is flushed first. This may block.

#write_nonblock returns number of bytes written to the SSL connection.

When no data can be written without blocking it raises OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.

IO::WaitReadable means SSL needs to read internally so #write_nonblock should be called again after the underlying IO is readable.

IO::WaitWritable means SSL needs to write internally so #write_nonblock should be called again after underlying IO is writable.

So #write_nonblock needs two rescue clause as follows.

# emulates blocking write.
begin
  result = ssl.write_nonblock(str)
rescue IO::WaitReadable
  IO.select([io])
  retry
rescue IO::WaitWritable
  IO.select(nil, [io])
  retry
end

Note that one reason that #write_nonblock reads from the underlying IO is when the peer requests a new TLS/SSL handshake. See the openssl FAQ for more details. www.openssl.org/support/faq.html

# File ext/openssl/lib/openssl/buffering.rb, line 377
def write_nonblock(s, exception: true)
  flush
  syswrite_nonblock(s, exception: exception)
end

Private Instance Methods

consume_rbuff(size=nil) click to toggle source

Consumes size bytes from the buffer

# File ext/openssl/lib/openssl/buffering.rb, line 68
def consume_rbuff(size=nil)
  if @rbuffer.empty?
    nil
  else
    size = @rbuffer.size unless size
    ret = @rbuffer[0, size]
    @rbuffer[0, size] = ""
    ret
  end
end
fill_rbuff() click to toggle source

Fills the buffer from the underlying SSLSocket

# File ext/openssl/lib/openssl/buffering.rb, line 55
def fill_rbuff
  begin
    @rbuffer << self.sysread(BLOCK_SIZE)
  rescue Errno::EAGAIN
    retry
  rescue EOFError
    @eof = true
  end
end