-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathclient.rb
More file actions
139 lines (109 loc) · 3.9 KB
/
Copy pathclient.rb
File metadata and controls
139 lines (109 loc) · 3.9 KB
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
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
path = (@uri.path == '') ? '/' : @uri.path
@pathname = path + (@uri.query ? '?' + @uri.query : '')
end
def version
"hybi-#{ VERSION }"
end
def proxy(origin, options = {})
Proxy.new(self, origin, options)
end
def start
return false unless @ready_state == -1
@socket.write(handshake_request)
@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
@headers['Host'] = Driver.host_header(@uri)
@headers['Upgrade'] = 'websocket'
@headers['Connection'] = 'Upgrade'
@headers['Sec-WebSocket-Key'] = @key
@headers['Sec-WebSocket-Version'] = VERSION
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
extensions = @extensions.generate_offer
@headers['Sec-WebSocket-Extensions'] = extensions if extensions
start = "GET #{ @pathname } HTTP/1.1"
headers = [start, @headers.merge(@custom_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