# 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 392
    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 455
    def close
      flush rescue nil
      sysclose
    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 226
    def each(eol=$/)
      while line = self.gets(eol)
        yield line
      end
    end

Also aliased as: each_line
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 267
    def each_byte # :yields: byte
      while c = getc
        yield(c.ord)
      end
    end

each_line(eol=$/)
Alias for: each
eof()
Alias for: eof?
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 298
    def eof?
      fill_rbuff if !@eof && @rbuffer.empty?
      @eof && @rbuffer.empty?
    end

Also aliased as: eof
flush() click to toggle source

Flushes buffered data to the SSLSocket.

    # File ext/openssl/lib/openssl/buffering.rb, line 443
    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 260
    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 202
    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

print(*args) click to toggle source

Writes `args` to the stream.

See `IO#print` for full details.

    # File ext/openssl/lib/openssl/buffering.rb, line 422
    def print(*args)
      s = ""
      args.each{ |arg| s << arg.to_s }
      do_write(s)
      nil
    end

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 435
    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 402
    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

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.

`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. www.openssl.org/support/faq.html

By specifying `exception: false`, the options hash allows you to indicate that `read_nonblock` should not raise an IO::Wait*able exception, but return the symbol :wait_writable or :wait_readable instead.

    # File ext/openssl/lib/openssl/buffering.rb, line 171
    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
      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 277
    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 251
    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 238
    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
      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 290
    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 341
    def write(s)
      do_write(s)
      s.bytesize
    end

write_nonblock(s, exception: true) click to toggle source

Writes `s` 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. www.openssl.org/support/faq.html

By specifying `exception: false`, the options hash allows you to indicate that `write_nonblock` should not raise an IO::Wait*able exception, but return the symbol :wait_writable or :wait_readable instead.

    # File ext/openssl/lib/openssl/buffering.rb, line 383
    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

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 313
    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

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