123456789_123456789_123456789_123456789_123456789_

Module: OpenSSL::Buffering

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, Enumerable
Defined in: ext/openssl/lib/openssl/buffering.rb

Overview

::OpenSSL IO buffering mix-in module.

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

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

Constant Summary

Instance Attribute Summary

Instance Method Summary

Instance Attribute Details

#eof (readonly)

Alias for #eof?.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 408

alias eof eof?

#sync (rw)

The "sync mode" of the SSLSocket.

See IO#sync for full details.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 46

attr_accessor :sync

Instance Method Details

#<<(s)

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 509

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

#close

Closes the SSLSocket and flushes any unwritten data.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 585

def close
  flush rescue nil
  sysclose
end

#consume_rbuff(size = nil) (private)

Consumes size bytes from the buffer

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 82

def consume_rbuff(size=nil)
  if @rbuffer.empty?
    nil
  else
    size = @rbuffer.size unless size
    @rbuffer.slice!(0, size)
  end
end

#do_write(s) (private)

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 419

def do_write(s)
  @wbuffer = String.new unless defined? @wbuffer
  @wbuffer.append_as_bytes(s)

  @sync ||= false
  buffer_size = @wbuffer.bytesize
  if @sync or buffer_size > BLOCK_SIZE
    nwrote = 0
    begin
      while nwrote < buffer_size do
        chunk = if nwrote > 0
          @wbuffer.byteslice(nwrote, @wbuffer.bytesize)
        else
          @wbuffer
        end

        nwrote += syswrite(chunk)
      end
    ensure
      if nwrote < @wbuffer.bytesize
        @wbuffer[0, nwrote] = ""
      else
        @wbuffer.clear
      end
    end
  end
end

#each(eol = $/, limit = nil, chomp: false) Also known as: #each_line

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

See also #gets

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 302

def each(eol = $/, limit = nil, chomp: false)
  return to_enum(__method__, eol, limit, chomp: chomp) unless block_given?
  if limit.nil? && Integer === eol
    eol, limit = $/, eol
  end
  raise ArgumentError, "invalid limit: 0 for each_line" if limit == 0
  while line = gets(eol, limit, chomp: chomp)
    yield line
  end
  self
end

#each_byte

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 355

def each_byte # :yields: byte
  return to_enum(__method__) unless block_given?
  while c = getbyte
    yield c
  end
  self
end

#each_char

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 344

def each_char
  return to_enum(__method__) unless block_given?
  while c = getc
    yield c
  end
  self
end

#each_line(eol = $/, limit = nil, chomp: false)

Alias for #each.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 313

alias each_line each

#eof? ⇒ Boolean (readonly) Also known as: #eof

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 404

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

#fill_rbuff (private)

Fills the buffer from the underlying SSLSocket

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 71

def fill_rbuff
  begin
    @rbuffer.append_as_bytes(self.sysread(BLOCK_SIZE))
  rescue EOFError
    @eof = true
  end
end

#flush

Flushes buffered data to the SSLSocket.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 573

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

#getbyte ⇒ 1

Get the next 8bit byte from ssl. Returns nil on EOF

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 97

def getbyte
  read(1)&.ord
end

#getc

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 337

def getc
  read(1)
end

#gets(eol = $/, limit = nil, chomp: false)

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. eol defaults to $/.

Note that Regexp eol is an extension to the standard IO#gets. This mode is incompatible with chomp option.

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 215

def gets(eol = $/, limit = nil, chomp: false)
  if limit.nil? && Integer === eol
    eol, limit = $/, eol
  end
  limit = nil if limit && limit < 0
  return String.new if limit == 0

  case eol
  when nil
    gets_slurp(limit)
  when Regexp
    if chomp
      raise ArgumentError,
        "chomp is not supported with Regexp record separator"
    end
    gets_regexp(eol, limit)
  when ""
    swallow_newlines
    ret = gets_string("\n\n", limit, chomp)
    swallow_newlines
    ret
  else
    gets_string(eol, limit, chomp)
  end
end

#gets_regexp(eol, limit) (private)

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 266

private def gets_regexp(eol, limit)
  while true
    break if idx = @rbuffer.index(eol)
    break if limit && @rbuffer.bytesize >= limit
    break if @eof
    fill_rbuff
  end
  if idx
    size = idx + $&.size
    size = [size, limit].min if limit
  else
    size = limit
  end
  consume_rbuff(size)
end

#gets_slurp(limit) (private)

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 282

private def gets_slurp(limit)
  ret = read(limit)
  return nil if ret && ret.empty?
  ret
end

#gets_string(eol, limit, chomp) (private)

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 241

private def gets_string(eol, limit, chomp)
  pos = 0
  while true
    break if idx = @rbuffer.index(eol, pos)
    break if limit && @rbuffer.bytesize >= limit
    pos = [0, @rbuffer.bytesize - eol.bytesize + 1].max
    break if @eof
    fill_rbuff
  end
  if idx
    size = idx + eol.bytesize
    if limit && size > limit
      size = limit
      chomp = false
    end
  else
    size = limit
  end
  line = consume_rbuff(size)
  if chomp && idx
    line.chomp!(eol)
  end
  line
end

#initialize

Creates an instance of OpenSSL's buffering ::IO module.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 56

def initialize(*)
  super
  @eof = false
  @rbuffer = String.new
  @sync = @io.sync
end

#printf(s, *args)

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

See Kernel.sprintf for format string details.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 565

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

#putc(ch)

Writes character ch to the stream.

See IO#putc for full details.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 542

def putc(ch)
  do_write(String === ch ? ch.chr : (ch.to_int & 0xff).chr)
  ch
end

#puts(*args)

Writes args to the stream along with a newline.

See IO#puts for full details.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 519

def puts(*args)
  if args.empty?
    do_write("\n")
    return nil
  end
  # IO#puts writes "[...]" when it encounters a recursion (undocumented).
  # We ignore that for now.
  s = String.new
  args.flatten!
  args.each do |arg|
    b = arg.to_s
    s.append_as_bytes(b)
    s.append_as_bytes("\n") unless b.getbyte(-1) == 0x0a
  end
  do_write(s)
  nil
end

#read(size = nil, buf = nil)

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.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 113

def read(size=nil, buf=nil)
  if size
    return buf&.clear || String.new if size == 0
    raise ArgumentError, "negative length #{size} given" if size < 0
  end
  until @eof
    break if size && size <= @rbuffer.size
    fill_rbuff
  end
  if ret = consume_rbuff(size)
    buf ? buf.replace(ret) : ret
  else
    buf.clear if buf
    return nil if size
    buf || String.new
  end
end

#read_nonblock(maxlen, buf = nil, exception: true)

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

When no data can be read without blocking it raises 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. http://www.openssl.org/support/faq.html

By specifying a keyword argument exception to false, you can indicate that read_nonblock should not raise an IO::Wait*able exception, but return the symbol :wait_writable or :wait_readable instead. At EOF, it will return nil instead of raising EOFError.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 187

def read_nonblock(maxlen, buf=nil, exception: true)
  if maxlen
    return buf&.clear || String.new if maxlen == 0
    raise ArgumentError, "negative length #{maxlen} given" if maxlen < 0
  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

#readbyte

Get the next 8bit byte. Raises EOFError on EOF

Raises:

  • (EOFError)
[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 102

def readbyte
  raise EOFError if eof?
  getbyte
end

#readchar

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

Raises:

  • (EOFError)
[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 367

def readchar
  raise EOFError if eof?
  getc
end

#readline(eol = $/, limit = nil, chomp: false)

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

Raises EOFError if at end of file.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 329

def readline(eol = $/, limit = nil, chomp: false)
  gets(eol, limit, chomp: chomp) or raise EOFError
end

#readlines(eol = $/, limit = nil, chomp: false)

Reads lines from the stream which are separated by eol.

See also #gets

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 320

def readlines(eol = $/, limit = nil, chomp: false)
  each_line(eol, limit, chomp: chomp).to_a
end

#readpartial(maxlen, buf = nil)

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.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 137

def readpartial(maxlen, buf=nil)
  if maxlen
    return buf&.clear || String.new if maxlen == 0
    raise ArgumentError, "negative length #{maxlen} given" if maxlen < 0
  end
  if @rbuffer.empty?
    return sysread(maxlen, buf)
  end
  ret = consume_rbuff(maxlen)
  if buf
    buf.replace(ret)
    ret = buf
  end
  ret
end

#swallow_newlines (private)

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 288

private def swallow_newlines
  while true
    @rbuffer.sub!(/\A\n+/, "")
    break if @eof || !@rbuffer.empty?
    fill_rbuff
  end
end

#ungetbyte(c)

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 391

def ungetbyte(c)
  if Integer === c
    @rbuffer[0, 0] = (c & 0xff).chr
  else
    @rbuffer[0, 0] = c.to_str.b
  end
  nil
end

#ungetc(c)

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

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 378

def ungetc(c)
  if Integer === c
    @rbuffer[0, 0] = c.chr(Encoding::BINARY)
  else
    @rbuffer[0, 0] = c.to_str.b
  end
  nil
end

#write(*s)

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

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 453

def write(*s)
  written = 0
  s.each do |str|
    str = str.to_s
    do_write(str)
    written += str.bytesize
  end
  written
end

#write_nonblock(s, exception: true)

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

By specifying a keyword argument exception to false, you can indicate that write_nonblock should not raise an IO::Wait*able exception, but return the symbol :wait_writable or :wait_readable instead.

[ GitHub ]

  
# File 'ext/openssl/lib/openssl/buffering.rb', line 500

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