1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
module WebSocket
class Driver
class Client < Hybi
VALID_SCHEMES = %w[ws wss]
def self.generate_key
Base64.strict_encode64(SecureRandom.random_bytes(16))
end
attr_reader :status, :headers
def initialize(socket, options = {})
super
@ready_state = -1
@key = Client.generate_key
@accept = Hybi.generate_accept(@key)
@http = HTTP::Response.new
uri = URI.parse(@socket.url)
unless VALID_SCHEMES.include?(uri.scheme)
raise URIError, "#{socket.url} is not a valid WebSocket URL"
end
host = uri.host + (uri.port ? ":#{uri.port}" : '')
path = (uri.path == '') ? '/' : uri.path
@pathname = path + (uri.query ? '?' + uri.query : '')
@headers['Host'] = host
@headers['Upgrade'] = 'websocket'
@headers['Connection'] = 'Upgrade'
@headers['Sec-WebSocket-Key'] = @key
@headers['Sec-WebSocket-Version'] = '13'
if @protocols.size > 0
@headers['Sec-WebSocket-Protocol'] = @protocols * ', '
end
if uri.user
auth = Base64.strict_encode64([uri.user, uri.password] * ':')
@headers['Authorization'] = 'Basic ' + auth
end
end
def version
'hybi-13'
end
def proxy(origin, options = {})
Proxy.new(self, origin, options)
end
def start
return false unless @ready_state == -1
@socket.write(Driver.encode(handshake_request, :binary))
@ready_state = 0
true
end
def parse(chunk)
return if @ready_state == 3
return super if @ready_state > 0
@http.parse(chunk)
return fail_handshake('Invalid HTTP response') if @http.error?
return unless @http.complete?
validate_handshake
return if @ready_state == 3
open
parse(@http.body)
end
private
def handshake_request
extensions = @extensions.generate_offer
@headers['Sec-WebSocket-Extensions'] = extensions if extensions
start = "GET #{@pathname} HTTP/1.1"
headers = [start, @headers.to_s, '']
headers.join("\r\n")
end
def fail_handshake(message)
message = "Error during WebSocket handshake: #{message}"
@ready_state = 3
emit(:error, ProtocolError.new(message))
emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
end
def validate_handshake
@status = @http.code
@headers = Headers.new(@http.headers)
unless @http.code == 101
return fail_handshake("Unexpected response code: #{@http.code}")
end
upgrade = @http['Upgrade'] || ''
connection = @http['Connection'] || ''
accept = @http['Sec-WebSocket-Accept'] || ''
protocol = @http['Sec-WebSocket-Protocol'] || ''
if upgrade == ''
return fail_handshake("'Upgrade' header is missing")
elsif upgrade.downcase != 'websocket'
return fail_handshake("'Upgrade' header value is not 'WebSocket'")
end
if connection == ''
return fail_handshake("'Connection' header is missing")
elsif connection.downcase != 'upgrade'
return fail_handshake("'Connection' header value is not 'Upgrade'")
end
unless accept == @accept
return fail_handshake('Sec-WebSocket-Accept mismatch')
end
unless protocol == ''
if @protocols.include?(protocol)
@protocol = protocol
else
return fail_handshake('Sec-WebSocket-Protocol mismatch')
end
end
begin
@extensions.activate(@headers['Sec-WebSocket-Extensions'])
rescue ::WebSocket::Extensions::ExtensionError => error
return fail_handshake(error.message)
end
end
end
end
end
|