123456789_123456789_123456789_123456789_123456789_

Class: Puma::MiniSSL::Socket

Relationships & Source Files
Inherits: Object
Defined in: lib/puma/minissl.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(socket, engine) ⇒ Socket

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 23

def initialize(socket, engine)
  @socket = socket
  @engine = engine
  @peercert = nil
  @reuse = nil
  @write_buffer = +''
  @write_owed = 0
  @write_owed_data = nil
end

Instance Attribute Details

#bad_tlsv1_3? ⇒ Boolean (readonly, private)

Used to check the handshake status, in particular when a TCP connection is made with TLSv1.3 as an available protocol

Version:

  • 5.0.0

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 58

def bad_tlsv1_3?
  HAS_TLS1_3 && ssl_version_state == ['TLSv1.3', 'SSLERR']
end

#closed? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 38

def closed?
  @socket.closed?
end

#peeraddr (readonly)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 227

def peeraddr
  @socket.peeraddr
end

#peercert ⇒ OpenSSL::X509::Certificate? (readonly)

OpenSSL is loaded in ContextBuilder when Context#verify_mode is not VERIFY_NONE. When VERIFY_NONE, Engine#peercert is nil, regardless of whether the client sends a cert.

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 237

def peercert
  return @peercert if @peercert

  raw = @engine.peercert
  return nil unless raw

  @peercert = OpenSSL::X509::Certificate.new raw
end

#ssl_version_state (readonly)

Returns a two element array, first is protocol version (SSL_get_version), second is 'handshake' state (SSL_state_string)

Used for dropping tcp connections to ssl. See OpenSSL ssl/ssl_stat.c SSL_state_string for info

Version:

  • 5.0.0

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 51

def ssl_version_state
  IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
end

#to_io (readonly)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 34

def to_io
  @socket
end

#write_pending? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 121

def write_pending?
  !@write_buffer.empty?
end

Instance Method Details

#<<(data)

Alias for #write.

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 154

alias_method :<<, :write

#close

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 189

def close
  begin
    queue_ciphertext unless @engine.shutdown
    flush_write_buffer_nonblock
  rescue IOError, SystemCallError
    # nothing
  ensure
    @socket.close
  end
end

#engine_read_all

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 79

def engine_read_all
  output = @engine.read
  while output and additional_output = @engine.read
    output << additional_output
  end
  output
end

#flush

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 184

def flush
  flush_write_buffer
  @socket.flush
end

#flush_write_buffer (private)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 207

def flush_write_buffer
  return if @write_buffer.empty?

  @socket.write @write_buffer
  @write_buffer = +''
end

#flush_write_buffer_nonblock (private)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 215

def flush_write_buffer_nonblock
  until @write_buffer.empty?
    n = @socket.write_nonblock @write_buffer, exception: false
    return false if n == :wait_writable

    @write_buffer = @write_buffer.byteslice(n..-1)
  end
  true
end

#queue_ciphertext (private)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 200

def queue_ciphertext
  while (enc = @engine.extract)
    @write_buffer << enc
  end
end

#read_nonblock(size, *_)

Raises:

  • (IO::EAGAINWaitReadable)
[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 87

def read_nonblock(size, *_)
  # *_ is to deal with keyword args that were added
  # at some point (and being used in the wild)
  output = engine_read_all
  return output if output

  raise IO::EAGAINWaitReadable unless flush_write_buffer_nonblock

  data = @socket.read_nonblock(size, exception: false)
  if data == :wait_readable || data == :wait_writable
    # It would make more sense to let @socket.read_nonblock raise
    # EAGAIN if necessary but it seems like it'll misbehave on Windows.
    # I don't have a Windows machine to debug this so I can't explain
    # exactly what's happening in that OS. Please let me know if you
    # find out!
    #
    # In the meantime, we can emulate the correct behavior by
    # capturing :wait_readable & :wait_writable and raising EAGAIN
    # ourselves.
    raise IO::EAGAINWaitReadable
  elsif data.nil?
    raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
    return nil
  end

  @engine.inject(data)
  output = engine_read_all
  return output if output

  queue_ciphertext
  flush_write_buffer_nonblock
  raise IO::EAGAINWaitReadable
end

#readpartial(size)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 63

def readpartial(size)
  while true
    output = @engine.read
    return output if output

    data = @socket.readpartial(size)
    @engine.inject(data)
    output = @engine.read

    return output if output

    queue_ciphertext
    flush_write_buffer
  end
end

#syswrite(data)

Alias for #write.

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 153

alias_method :syswrite, :write

#wait_writable(timeout = nil)

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 180

def wait_writable(timeout = nil)
  @socket.wait_writable timeout
end

#write(data) Also known as: #syswrite, #<<

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 125

def write(data)
  return 0 if data.empty?

  data_size = data.bytesize

  flush_write_buffer
  if @write_owed_data && data.byteslice(0, @write_owed_data.bytesize).b == @write_owed_data
    data = data.byteslice(@write_owed..-1)
    @write_owed = 0
    @write_owed_data = nil
    return data_size if data.empty?
  end

  need = data.bytesize

  while true
    wrote = @engine.write data
    queue_ciphertext
    flush_write_buffer

    need -= wrote

    return data_size if need == 0

    data = data.byteslice(wrote..-1)
  end
end

#write_nonblock(data, exception: true)

As with SSL_write, retry with the same data after IO::EAGAINWaitWritable

[ GitHub ]

  
# File 'lib/puma/minissl.rb', line 157

def write_nonblock(data, exception: true)
  return 0 if data.empty?

  @write_owed = 0 if @write_owed_data && data.byteslice(0, @write_owed_data.bytesize).b != @write_owed_data

  if @write_owed == 0
    @write_owed = @engine.write data
    @write_owed_data = nil
    queue_ciphertext
  end

  unless flush_write_buffer_nonblock
    @write_owed_data ||= data.byteslice(0, @write_owed).b
    raise IO::EAGAINWaitWritable if exception
    return :wait_writable
  end

  wrote = @write_owed
  @write_owed = 0
  @write_owed_data = nil
  wrote
end