websocket-driver 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +136 -0
- data/LICENSE.md +12 -0
- data/README.md +380 -0
- data/ext/websocket-driver/WebsocketMaskService.java +57 -0
- data/ext/websocket-driver/extconf.rb +4 -0
- data/ext/websocket-driver/websocket_mask.c +32 -0
- data/lib/websocket/driver.rb +233 -0
- data/lib/websocket/driver/client.rb +140 -0
- data/lib/websocket/driver/draft75.rb +102 -0
- data/lib/websocket/driver/draft76.rb +98 -0
- data/lib/websocket/driver/event_emitter.rb +54 -0
- data/lib/websocket/driver/headers.rb +45 -0
- data/lib/websocket/driver/hybi.rb +414 -0
- data/lib/websocket/driver/hybi/frame.rb +20 -0
- data/lib/websocket/driver/hybi/message.rb +31 -0
- data/lib/websocket/driver/proxy.rb +68 -0
- data/lib/websocket/driver/server.rb +80 -0
- data/lib/websocket/driver/stream_reader.rb +55 -0
- data/lib/websocket/http.rb +15 -0
- data/lib/websocket/http/headers.rb +112 -0
- data/lib/websocket/http/request.rb +45 -0
- data/lib/websocket/http/response.rb +29 -0
- data/lib/websocket/mask.rb +14 -0
- data/lib/websocket/websocket_mask.rb +2 -0
- metadata +142 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Draft75 < Driver
|
5
|
+
def initialize(socket, options = {})
|
6
|
+
super
|
7
|
+
|
8
|
+
@stage = 0
|
9
|
+
@closing = false
|
10
|
+
|
11
|
+
@headers['Upgrade'] = 'WebSocket'
|
12
|
+
@headers['Connection'] = 'Upgrade'
|
13
|
+
@headers['WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
|
14
|
+
@headers['WebSocket-Location'] = @socket.url
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
'hixie-75'
|
19
|
+
end
|
20
|
+
|
21
|
+
def close(reason = nil, code = nil)
|
22
|
+
return false if @ready_state == 3
|
23
|
+
@ready_state = 3
|
24
|
+
emit(:close, CloseEvent.new(nil, nil))
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse(chunk)
|
29
|
+
return if @ready_state > 1
|
30
|
+
|
31
|
+
@reader.put(chunk)
|
32
|
+
|
33
|
+
@reader.each_byte do |octet|
|
34
|
+
case @stage
|
35
|
+
when -1 then
|
36
|
+
@body << octet
|
37
|
+
send_handshake_body
|
38
|
+
|
39
|
+
when 0 then
|
40
|
+
parse_leading_byte(octet)
|
41
|
+
|
42
|
+
when 1 then
|
43
|
+
@length = (octet & 0x7F) + 128 * @length
|
44
|
+
|
45
|
+
if @closing and @length.zero?
|
46
|
+
return close
|
47
|
+
elsif (octet & 0x80) != 0x80
|
48
|
+
if @length.zero?
|
49
|
+
@stage = 0
|
50
|
+
else
|
51
|
+
@skipped = 0
|
52
|
+
@stage = 2
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
when 2 then
|
57
|
+
if octet == 0xFF
|
58
|
+
@stage = 0
|
59
|
+
emit(:message, MessageEvent.new(Driver.encode(@buffer, UNICODE)))
|
60
|
+
else
|
61
|
+
if @length
|
62
|
+
@skipped += 1
|
63
|
+
@stage = 0 if @skipped == @length
|
64
|
+
else
|
65
|
+
@buffer << octet
|
66
|
+
return close if @buffer.size > @max_length
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def frame(buffer, type = nil, error_type = nil)
|
74
|
+
return queue([buffer, type, error_type]) if @ready_state == 0
|
75
|
+
frame = [0x00, buffer, 0xFF].pack('CA*C')
|
76
|
+
@socket.write(frame)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def handshake_response
|
83
|
+
start = 'HTTP/1.1 101 Web Socket Protocol Handshake'
|
84
|
+
headers = [start, @headers.to_s, '']
|
85
|
+
headers.join("\r\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_leading_byte(octet)
|
89
|
+
if (octet & 0x80) == 0x80
|
90
|
+
@length = 0
|
91
|
+
@stage = 1
|
92
|
+
else
|
93
|
+
@length = nil
|
94
|
+
@skipped = nil
|
95
|
+
@buffer = []
|
96
|
+
@stage = 2
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Draft76 < Draft75
|
5
|
+
BODY_SIZE = 8
|
6
|
+
|
7
|
+
def initialize(socket, options = {})
|
8
|
+
super
|
9
|
+
input = @socket.env['rack.input']
|
10
|
+
@stage = -1
|
11
|
+
@body = (input ? input.read : String.new('')).force_encoding(BINARY)
|
12
|
+
|
13
|
+
@headers.clear
|
14
|
+
@headers['Upgrade'] = 'WebSocket'
|
15
|
+
@headers['Connection'] = 'Upgrade'
|
16
|
+
@headers['Sec-WebSocket-Origin'] = @socket.env['HTTP_ORIGIN']
|
17
|
+
@headers['Sec-WebSocket-Location'] = @socket.url
|
18
|
+
end
|
19
|
+
|
20
|
+
def version
|
21
|
+
'hixie-76'
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
return false unless super
|
26
|
+
send_handshake_body
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def close(reason = nil, code = nil)
|
31
|
+
return false if @ready_state == 3
|
32
|
+
@socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
|
33
|
+
@ready_state = 3
|
34
|
+
emit(:close, CloseEvent.new(nil, nil))
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def handshake_response
|
41
|
+
env = @socket.env
|
42
|
+
key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
|
43
|
+
key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
|
44
|
+
|
45
|
+
raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
|
46
|
+
raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
|
47
|
+
|
48
|
+
number1 = number_from_key(key1)
|
49
|
+
spaces1 = spaces_in_key(key1)
|
50
|
+
|
51
|
+
number2 = number_from_key(key2)
|
52
|
+
spaces2 = spaces_in_key(key2)
|
53
|
+
|
54
|
+
if number1 % spaces1 != 0 or number2 % spaces2 != 0
|
55
|
+
raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
|
56
|
+
end
|
57
|
+
|
58
|
+
@key_values = [number1 / spaces1, number2 / spaces2]
|
59
|
+
|
60
|
+
start = 'HTTP/1.1 101 WebSocket Protocol Handshake'
|
61
|
+
headers = [start, @headers.to_s, '']
|
62
|
+
headers.join("\r\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
def handshake_signature
|
66
|
+
return nil unless @body.bytesize >= BODY_SIZE
|
67
|
+
|
68
|
+
head = @body[0...BODY_SIZE]
|
69
|
+
Digest::MD5.digest((@key_values + [head]).pack('N2A*'))
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_handshake_body
|
73
|
+
return unless signature = handshake_signature
|
74
|
+
@socket.write(signature)
|
75
|
+
@stage = 0
|
76
|
+
open
|
77
|
+
parse(@body[BODY_SIZE..-1]) if @body.bytesize > BODY_SIZE
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_leading_byte(octet)
|
81
|
+
return super unless octet == 0xFF
|
82
|
+
@closing = true
|
83
|
+
@length = 0
|
84
|
+
@stage = 1
|
85
|
+
end
|
86
|
+
|
87
|
+
def number_from_key(key)
|
88
|
+
number = key.scan(/[0-9]/).join('')
|
89
|
+
number == '' ? Float::NAN : number.to_i(10)
|
90
|
+
end
|
91
|
+
|
92
|
+
def spaces_in_key(key)
|
93
|
+
key.scan(/ /).size
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
module EventEmitter
|
5
|
+
def initialize
|
6
|
+
@listeners = Hash.new { |h,k| h[k] = [] }
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_listener(event, callable = nil, &block)
|
10
|
+
listener = callable || block
|
11
|
+
@listeners[event.to_s] << listener
|
12
|
+
listener
|
13
|
+
end
|
14
|
+
|
15
|
+
def on(event, callable = nil, &block)
|
16
|
+
if callable
|
17
|
+
add_listener(event, callable)
|
18
|
+
else
|
19
|
+
add_listener(event, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_listener(event, callable = nil, &block)
|
24
|
+
listener = callable || block
|
25
|
+
@listeners[event.to_s].delete(listener)
|
26
|
+
listener
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_all_listeners(event = nil)
|
30
|
+
if event
|
31
|
+
@listeners.delete(event.to_s)
|
32
|
+
else
|
33
|
+
@listeners.clear
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def emit(event, *args)
|
38
|
+
@listeners[event.to_s].dup.each do |listener|
|
39
|
+
listener.call(*args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def listener_count(event)
|
44
|
+
return 0 unless @listeners.has_key?(event.to_s)
|
45
|
+
@listeners[event.to_s].size
|
46
|
+
end
|
47
|
+
|
48
|
+
def listeners(event)
|
49
|
+
@listeners[event.to_s]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Headers
|
5
|
+
ALLOWED_DUPLICATES = %w[set-cookie set-cookie2 warning www-authenticate]
|
6
|
+
|
7
|
+
def initialize(received = {})
|
8
|
+
@raw = received
|
9
|
+
clear
|
10
|
+
|
11
|
+
@received = {}
|
12
|
+
@raw.each { |k,v| @received[HTTP.normalize_header(k)] = v }
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear
|
16
|
+
@sent = Set.new
|
17
|
+
@lines = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
@received[HTTP.normalize_header(name)]
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(name, value)
|
25
|
+
return if value.nil?
|
26
|
+
key = HTTP.normalize_header(name)
|
27
|
+
return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
|
28
|
+
@lines << "#{name.strip}: #{value.to_s.strip}\r\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
@raw.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_h
|
36
|
+
@raw.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
@lines.join('')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,414 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Hybi < Driver
|
5
|
+
root = File.expand_path('../hybi', __FILE__)
|
6
|
+
|
7
|
+
autoload :Frame, root + '/frame'
|
8
|
+
autoload :Message, root + '/message'
|
9
|
+
|
10
|
+
def self.generate_accept(key)
|
11
|
+
Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
|
12
|
+
end
|
13
|
+
|
14
|
+
VERSION = '13'
|
15
|
+
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
16
|
+
|
17
|
+
BYTE = 0b11111111
|
18
|
+
FIN = MASK = 0b10000000
|
19
|
+
RSV1 = 0b01000000
|
20
|
+
RSV2 = 0b00100000
|
21
|
+
RSV3 = 0b00010000
|
22
|
+
OPCODE = 0b00001111
|
23
|
+
LENGTH = 0b01111111
|
24
|
+
|
25
|
+
OPCODES = {
|
26
|
+
:continuation => 0,
|
27
|
+
:text => 1,
|
28
|
+
:binary => 2,
|
29
|
+
:close => 8,
|
30
|
+
:ping => 9,
|
31
|
+
:pong => 10
|
32
|
+
}
|
33
|
+
|
34
|
+
OPCODE_CODES = OPCODES.values
|
35
|
+
MESSAGE_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
|
36
|
+
OPENING_OPCODES = OPCODES.values_at(:text, :binary)
|
37
|
+
|
38
|
+
ERRORS = {
|
39
|
+
:normal_closure => 1000,
|
40
|
+
:going_away => 1001,
|
41
|
+
:protocol_error => 1002,
|
42
|
+
:unacceptable => 1003,
|
43
|
+
:encoding_error => 1007,
|
44
|
+
:policy_violation => 1008,
|
45
|
+
:too_large => 1009,
|
46
|
+
:extension_error => 1010,
|
47
|
+
:unexpected_condition => 1011
|
48
|
+
}
|
49
|
+
|
50
|
+
ERROR_CODES = ERRORS.values
|
51
|
+
DEFAULT_ERROR_CODE = 1000
|
52
|
+
MIN_RESERVED_ERROR = 3000
|
53
|
+
MAX_RESERVED_ERROR = 4999
|
54
|
+
|
55
|
+
PACK_FORMATS = {2 => 'n', 8 => 'Q>'}
|
56
|
+
|
57
|
+
def initialize(socket, options = {})
|
58
|
+
super
|
59
|
+
|
60
|
+
@extensions = ::WebSocket::Extensions.new
|
61
|
+
@stage = 0
|
62
|
+
@masking = options[:masking]
|
63
|
+
@protocols = options[:protocols] || []
|
64
|
+
@protocols = @protocols.strip.split(/ *, */) if String === @protocols
|
65
|
+
@require_masking = options[:require_masking]
|
66
|
+
@ping_callbacks = {}
|
67
|
+
|
68
|
+
@frame = @message = nil
|
69
|
+
|
70
|
+
return unless @socket.respond_to?(:env)
|
71
|
+
|
72
|
+
if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
73
|
+
protos = protos.split(/ *, */) if String === protos
|
74
|
+
@protocol = protos.find { |p| @protocols.include?(p) }
|
75
|
+
else
|
76
|
+
@protocol = nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def version
|
81
|
+
"hybi-#{VERSION}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_extension(extension)
|
85
|
+
@extensions.add(extension)
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse(chunk)
|
90
|
+
@reader.put(chunk)
|
91
|
+
buffer = true
|
92
|
+
while buffer
|
93
|
+
case @stage
|
94
|
+
when 0 then
|
95
|
+
buffer = @reader.read(1)
|
96
|
+
parse_opcode(buffer.getbyte(0)) if buffer
|
97
|
+
|
98
|
+
when 1 then
|
99
|
+
buffer = @reader.read(1)
|
100
|
+
parse_length(buffer.getbyte(0)) if buffer
|
101
|
+
|
102
|
+
when 2 then
|
103
|
+
buffer = @reader.read(@frame.length_bytes)
|
104
|
+
parse_extended_length(buffer) if buffer
|
105
|
+
|
106
|
+
when 3 then
|
107
|
+
buffer = @reader.read(4)
|
108
|
+
if buffer
|
109
|
+
@stage = 4
|
110
|
+
@frame.masking_key = buffer
|
111
|
+
end
|
112
|
+
|
113
|
+
when 4 then
|
114
|
+
buffer = @reader.read(@frame.length)
|
115
|
+
|
116
|
+
if buffer
|
117
|
+
@stage = 0
|
118
|
+
emit_frame(buffer)
|
119
|
+
end
|
120
|
+
|
121
|
+
else
|
122
|
+
buffer = nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def binary(message)
|
128
|
+
frame(message, :binary)
|
129
|
+
end
|
130
|
+
|
131
|
+
def ping(message = '', &callback)
|
132
|
+
@ping_callbacks[message] = callback if callback
|
133
|
+
frame(message, :ping)
|
134
|
+
end
|
135
|
+
|
136
|
+
def pong(message = '')
|
137
|
+
frame(message, :pong)
|
138
|
+
end
|
139
|
+
|
140
|
+
def close(reason = nil, code = nil)
|
141
|
+
reason ||= ''
|
142
|
+
code ||= ERRORS[:normal_closure]
|
143
|
+
|
144
|
+
if @ready_state <= 0
|
145
|
+
@ready_state = 3
|
146
|
+
emit(:close, CloseEvent.new(code, reason))
|
147
|
+
true
|
148
|
+
elsif @ready_state == 1
|
149
|
+
frame(reason, :close, code)
|
150
|
+
@ready_state = 2
|
151
|
+
true
|
152
|
+
else
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def frame(buffer, type = nil, code = nil)
|
158
|
+
return queue([buffer, type, code]) if @ready_state <= 0
|
159
|
+
return false unless @ready_state == 1
|
160
|
+
|
161
|
+
message = Message.new
|
162
|
+
frame = Frame.new
|
163
|
+
is_text = String === buffer
|
164
|
+
|
165
|
+
message.rsv1 = message.rsv2 = message.rsv3 = false
|
166
|
+
message.opcode = OPCODES[type || (is_text ? :text : :binary)]
|
167
|
+
|
168
|
+
payload = is_text ? buffer.bytes.to_a : buffer
|
169
|
+
payload = [code].pack(PACK_FORMATS[2]).bytes.to_a + payload if code
|
170
|
+
message.data = payload.pack('C*')
|
171
|
+
|
172
|
+
if MESSAGE_OPCODES.include?(message.opcode)
|
173
|
+
message = @extensions.process_outgoing_message(message)
|
174
|
+
end
|
175
|
+
|
176
|
+
frame.final = true
|
177
|
+
frame.rsv1 = message.rsv1
|
178
|
+
frame.rsv2 = message.rsv2
|
179
|
+
frame.rsv3 = message.rsv3
|
180
|
+
frame.opcode = message.opcode
|
181
|
+
frame.masked = !!@masking
|
182
|
+
frame.masking_key = SecureRandom.random_bytes(4) if frame.masked
|
183
|
+
frame.length = message.data.bytesize
|
184
|
+
frame.payload = message.data
|
185
|
+
|
186
|
+
send_frame(frame)
|
187
|
+
true
|
188
|
+
|
189
|
+
rescue ::WebSocket::Extensions::ExtensionError => error
|
190
|
+
fail(:extension_error, error.message)
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def send_frame(frame)
|
196
|
+
length = frame.length
|
197
|
+
buffer = []
|
198
|
+
masked = frame.masked ? MASK : 0
|
199
|
+
|
200
|
+
buffer[0] = (frame.final ? FIN : 0) |
|
201
|
+
(frame.rsv1 ? RSV1 : 0) |
|
202
|
+
(frame.rsv2 ? RSV2 : 0) |
|
203
|
+
(frame.rsv3 ? RSV3 : 0) |
|
204
|
+
frame.opcode
|
205
|
+
|
206
|
+
if length <= 125
|
207
|
+
buffer[1] = masked | length
|
208
|
+
elsif length <= 65535
|
209
|
+
buffer[1] = masked | 126
|
210
|
+
buffer[2..3] = [length].pack(PACK_FORMATS[2]).bytes.to_a
|
211
|
+
else
|
212
|
+
buffer[1] = masked | 127
|
213
|
+
buffer[2..9] = [length].pack(PACK_FORMATS[8]).bytes.to_a
|
214
|
+
end
|
215
|
+
|
216
|
+
if frame.masked
|
217
|
+
buffer.concat(frame.masking_key.bytes.to_a)
|
218
|
+
buffer.concat(Mask.mask(frame.payload, frame.masking_key).bytes.to_a)
|
219
|
+
else
|
220
|
+
buffer.concat(frame.payload.bytes.to_a)
|
221
|
+
end
|
222
|
+
|
223
|
+
@socket.write(buffer.pack('C*'))
|
224
|
+
end
|
225
|
+
|
226
|
+
def handshake_response
|
227
|
+
sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
|
228
|
+
version = @socket.env['HTTP_SEC_WEBSOCKET_VERSION']
|
229
|
+
|
230
|
+
unless version == VERSION
|
231
|
+
raise ProtocolError.new("Unsupported WebSocket version: #{VERSION}")
|
232
|
+
end
|
233
|
+
|
234
|
+
unless sec_key
|
235
|
+
raise ProtocolError.new('Missing handshake request header: Sec-WebSocket-Key')
|
236
|
+
end
|
237
|
+
|
238
|
+
@headers['Upgrade'] = 'websocket'
|
239
|
+
@headers['Connection'] = 'Upgrade'
|
240
|
+
@headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
|
241
|
+
|
242
|
+
@headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
|
243
|
+
|
244
|
+
extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
|
245
|
+
@headers['Sec-WebSocket-Extensions'] = extensions if extensions
|
246
|
+
|
247
|
+
start = 'HTTP/1.1 101 Switching Protocols'
|
248
|
+
headers = [start, @headers.to_s, '']
|
249
|
+
headers.join("\r\n")
|
250
|
+
end
|
251
|
+
|
252
|
+
def shutdown(code, reason, error = false)
|
253
|
+
@frame = @message = nil
|
254
|
+
@stage = 5
|
255
|
+
@extensions.close
|
256
|
+
|
257
|
+
frame(reason, :close, code) if @ready_state < 2
|
258
|
+
@ready_state = 3
|
259
|
+
|
260
|
+
emit(:error, ProtocolError.new(reason)) if error
|
261
|
+
emit(:close, CloseEvent.new(code, reason))
|
262
|
+
end
|
263
|
+
|
264
|
+
def fail(type, message)
|
265
|
+
return if @ready_state > 1
|
266
|
+
shutdown(ERRORS[type], message, true)
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse_opcode(octet)
|
270
|
+
rsvs = [RSV1, RSV2, RSV3].map { |rsv| (octet & rsv) == rsv }
|
271
|
+
|
272
|
+
@frame = Frame.new
|
273
|
+
|
274
|
+
@frame.final = (octet & FIN) == FIN
|
275
|
+
@frame.rsv1 = rsvs[0]
|
276
|
+
@frame.rsv2 = rsvs[1]
|
277
|
+
@frame.rsv3 = rsvs[2]
|
278
|
+
@frame.opcode = (octet & OPCODE)
|
279
|
+
|
280
|
+
@stage = 1
|
281
|
+
|
282
|
+
unless @extensions.valid_frame_rsv?(@frame)
|
283
|
+
return fail(:protocol_error,
|
284
|
+
"One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
|
285
|
+
", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
|
286
|
+
", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
|
287
|
+
end
|
288
|
+
|
289
|
+
unless OPCODES.values.include?(@frame.opcode)
|
290
|
+
return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
|
291
|
+
end
|
292
|
+
|
293
|
+
unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
|
294
|
+
return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
|
295
|
+
end
|
296
|
+
|
297
|
+
if @message and OPENING_OPCODES.include?(@frame.opcode)
|
298
|
+
return fail(:protocol_error, 'Received new data frame but previous continuous frame is unfinished')
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def parse_length(octet)
|
303
|
+
@frame.masked = (octet & MASK) == MASK
|
304
|
+
@frame.length = (octet & LENGTH)
|
305
|
+
|
306
|
+
if @frame.length >= 0 and @frame.length <= 125
|
307
|
+
@stage = @frame.masked ? 3 : 4
|
308
|
+
return unless check_frame_length
|
309
|
+
else
|
310
|
+
@stage = 2
|
311
|
+
@frame.length_bytes = (@frame.length == 126) ? 2 : 8
|
312
|
+
end
|
313
|
+
|
314
|
+
if @require_masking and not @frame.masked
|
315
|
+
return fail(:unacceptable, 'Received unmasked frame but masking is required')
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def parse_extended_length(buffer)
|
320
|
+
@frame.length = buffer.unpack(PACK_FORMATS[buffer.bytesize]).first
|
321
|
+
@stage = @frame.masked ? 3 : 4
|
322
|
+
|
323
|
+
unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
|
324
|
+
return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
|
325
|
+
end
|
326
|
+
|
327
|
+
return unless check_frame_length
|
328
|
+
end
|
329
|
+
|
330
|
+
def check_frame_length
|
331
|
+
length = @message ? @message.data.bytesize : 0
|
332
|
+
|
333
|
+
if length + @frame.length > @max_length
|
334
|
+
fail(:too_large, 'WebSocket frame length too large')
|
335
|
+
false
|
336
|
+
else
|
337
|
+
true
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def emit_frame(buffer)
|
342
|
+
frame = @frame
|
343
|
+
opcode = frame.opcode
|
344
|
+
payload = frame.payload = Mask.mask(buffer, @frame.masking_key)
|
345
|
+
bytesize = payload.bytesize
|
346
|
+
bytes = payload.bytes.to_a
|
347
|
+
|
348
|
+
@frame = nil
|
349
|
+
|
350
|
+
case opcode
|
351
|
+
when OPCODES[:continuation] then
|
352
|
+
return fail(:protocol_error, 'Received unexpected continuation frame') unless @message
|
353
|
+
@message << frame
|
354
|
+
|
355
|
+
when OPCODES[:text], OPCODES[:binary] then
|
356
|
+
@message = Message.new
|
357
|
+
@message << frame
|
358
|
+
|
359
|
+
when OPCODES[:close] then
|
360
|
+
code = (bytesize >= 2) ? payload.unpack(PACK_FORMATS[2]).first : nil
|
361
|
+
reason = (bytesize > 2) ? Driver.encode(bytes[2..-1] || [], UNICODE) : nil
|
362
|
+
|
363
|
+
unless (bytesize == 0) or
|
364
|
+
(code && code >= MIN_RESERVED_ERROR && code <= MAX_RESERVED_ERROR) or
|
365
|
+
ERROR_CODES.include?(code)
|
366
|
+
code = ERRORS[:protocol_error]
|
367
|
+
end
|
368
|
+
|
369
|
+
if bytesize > 125 or (bytesize > 2 and reason.nil?)
|
370
|
+
code = ERRORS[:protocol_error]
|
371
|
+
end
|
372
|
+
|
373
|
+
shutdown(code || DEFAULT_ERROR_CODE, reason || '')
|
374
|
+
|
375
|
+
when OPCODES[:ping] then
|
376
|
+
frame(payload, :pong)
|
377
|
+
emit(:ping, PingEvent.new(payload))
|
378
|
+
|
379
|
+
when OPCODES[:pong] then
|
380
|
+
message = Driver.encode(payload, UNICODE)
|
381
|
+
callback = @ping_callbacks[message]
|
382
|
+
@ping_callbacks.delete(message)
|
383
|
+
callback.call if callback
|
384
|
+
emit(:pong, PongEvent.new(payload))
|
385
|
+
end
|
386
|
+
|
387
|
+
emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
|
388
|
+
end
|
389
|
+
|
390
|
+
def emit_message
|
391
|
+
message = @extensions.process_incoming_message(@message)
|
392
|
+
@message = nil
|
393
|
+
|
394
|
+
payload = message.data
|
395
|
+
|
396
|
+
case message.opcode
|
397
|
+
when OPCODES[:text] then
|
398
|
+
payload = Driver.encode(payload, UNICODE)
|
399
|
+
when OPCODES[:binary]
|
400
|
+
payload = payload.bytes.to_a
|
401
|
+
end
|
402
|
+
|
403
|
+
if payload
|
404
|
+
emit(:message, MessageEvent.new(payload))
|
405
|
+
else
|
406
|
+
fail(:encoding_error, 'Could not decode a text frame as UTF-8')
|
407
|
+
end
|
408
|
+
rescue ::WebSocket::Extensions::ExtensionError => error
|
409
|
+
fail(:extension_error, error.message)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
end
|