faye-websocket 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of faye-websocket might be problematic. Click here for more details.
- data/CHANGELOG.txt +22 -0
- data/README.rdoc +24 -2
- data/examples/autobahn_client.rb +1 -2
- data/examples/index.html +7 -2
- data/examples/server.rb +2 -2
- data/ext/faye_websocket_mask/extconf.rb +6 -0
- data/ext/faye_websocket_mask/faye_websocket_mask.c +35 -0
- data/lib/faye/websocket.rb +9 -4
- data/lib/faye/websocket/client.rb +7 -5
- data/lib/faye/websocket/draft75_parser.rb +53 -24
- data/lib/faye/websocket/draft76_parser.rb +15 -1
- data/lib/faye/websocket/{protocol8_parser.rb → hybi_parser.rb} +147 -168
- data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
- data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
- data/spec/faye/websocket/client_spec.rb +19 -7
- data/spec/faye/websocket/draft75_parser_spec.rb +43 -13
- data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
- data/spec/faye/websocket/{protocol8_parser_spec.rb → hybi_parser_spec.rb} +39 -28
- data/spec/spec_helper.rb +1 -1
- metadata +79 -95
data/CHANGELOG.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
=== 0.2.0 / 2011-12-21
|
2
|
+
|
3
|
+
* Add support for Sec-WebSocket-Protocol negotiation
|
4
|
+
* Support hixie-76 close frames and 75/76 ignored segments
|
5
|
+
* Improve performance of HyBi parsing/framing functions
|
6
|
+
* Write masking function in C
|
7
|
+
|
8
|
+
|
9
|
+
=== 0.1.2 / 2011-12-05
|
10
|
+
|
11
|
+
* Make hixie-76 sockets work through HAProxy
|
12
|
+
|
13
|
+
|
14
|
+
=== 0.1.1 / 2011-11-30
|
15
|
+
|
16
|
+
* Fix add_event_listener() interface methods
|
17
|
+
|
18
|
+
|
19
|
+
=== 0.1.0 / 2011-11-27
|
20
|
+
|
21
|
+
* Initial release, based on WebSocket components from Faye
|
22
|
+
|
data/README.rdoc
CHANGED
@@ -59,8 +59,8 @@ is how you'd implement an echo server:
|
|
59
59
|
|
60
60
|
The client supports both the plain-text +ws+ protocol and the encrypted +wss+
|
61
61
|
protocol, and has exactly the same interface as a socket you would use in a web
|
62
|
-
browser. On the wire it identifies itself as hybi-
|
63
|
-
with servers speaking later versions of the protocol
|
62
|
+
browser. On the wire it identifies itself as hybi-13, though it's compatible
|
63
|
+
with servers speaking later versions of the protocol.
|
64
64
|
|
65
65
|
require 'faye/websocket'
|
66
66
|
require 'eventmachine'
|
@@ -84,6 +84,26 @@ with servers speaking later versions of the protocol, at least up to version 17.
|
|
84
84
|
}
|
85
85
|
|
86
86
|
|
87
|
+
== Subprotocol negotiation
|
88
|
+
|
89
|
+
The WebSocket protocol allows peers to select and identify the application
|
90
|
+
protocol to use over the connection. On the client side, you can set which
|
91
|
+
protocols the client accepts by passing a list of protocol names when you
|
92
|
+
construct the socket:
|
93
|
+
|
94
|
+
ws = Faye::WebSocket::Client.new('ws://www.example.com/', ['irc', 'amqp'])
|
95
|
+
|
96
|
+
On the server side, you can likewise pass in the list of protocols the server
|
97
|
+
supports after the other constructor arguments:
|
98
|
+
|
99
|
+
ws = Faye::WebSocket.new(env, ['irc', 'amqp'])
|
100
|
+
|
101
|
+
If the client and server agree on a protocol, both the client- and server-side
|
102
|
+
socket objects expose the selected protocol through the <tt>ws.protocol</tt>
|
103
|
+
property. If they cannot agree on a protocol to use, the client closes the
|
104
|
+
connection.
|
105
|
+
|
106
|
+
|
87
107
|
== WebSocket API
|
88
108
|
|
89
109
|
The WebSocket API consists of several event handlers and a method for sending
|
@@ -105,6 +125,8 @@ messages.
|
|
105
125
|
the other peer.
|
106
126
|
* <b><tt>close(code, reason)</tt></b> closes the connection, sending the given
|
107
127
|
status code and reason text, both of which are optional.
|
128
|
+
* <b><tt>protocol</tt></b> is a string or +nil+ identifying the subprotocol th
|
129
|
+
socket is using.
|
108
130
|
|
109
131
|
|
110
132
|
== License
|
data/examples/autobahn_client.rb
CHANGED
data/examples/index.html
CHANGED
@@ -12,14 +12,19 @@
|
|
12
12
|
<script type="text/javascript">
|
13
13
|
var logger = document.getElementsByTagName('ul')[0],
|
14
14
|
Socket = window.MozWebSocket || window.WebSocket,
|
15
|
-
|
15
|
+
protos = ['foo', 'bar', 'xmpp'],
|
16
|
+
socket = new Socket('ws://' + location.hostname + ':' + location.port + '/', protos),
|
16
17
|
index = 0;
|
17
18
|
|
18
19
|
socket.onopen = function() {
|
19
|
-
logger.innerHTML += '<li>OPEN</li>';
|
20
|
+
logger.innerHTML += '<li>OPEN: ' + socket.protocol + '</li>';
|
20
21
|
socket.send('Hello, world');
|
21
22
|
};
|
22
23
|
|
24
|
+
socket.onerror = function(event) {
|
25
|
+
logger.innerHTML += '<li>ERROR: ' + error.message + '</li>';
|
26
|
+
};
|
27
|
+
|
23
28
|
socket.addEventListener('message', function(event) {
|
24
29
|
logger.innerHTML += '<li>MESSAGE: ' + event.data + '</li>';
|
25
30
|
setTimeout(function() { socket.send(++index + ' ' + event.data) }, 2000);
|
data/examples/server.rb
CHANGED
@@ -10,8 +10,8 @@ static = Rack::File.new(File.dirname(__FILE__))
|
|
10
10
|
|
11
11
|
app = lambda do |env|
|
12
12
|
if env['HTTP_UPGRADE']
|
13
|
-
socket = Faye::WebSocket.new(env)
|
14
|
-
p [:open, socket.url, socket.version]
|
13
|
+
socket = Faye::WebSocket.new(env, ['irc', 'xmpp'])
|
14
|
+
p [:open, socket.url, socket.version, socket.protocol]
|
15
15
|
|
16
16
|
socket.onmessage = lambda do |event|
|
17
17
|
socket.send(event.data)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
VALUE Faye = Qnil;
|
4
|
+
VALUE FayeWebSocket = Qnil;
|
5
|
+
VALUE FayeWebSocketMask = Qnil;
|
6
|
+
|
7
|
+
void Init_faye_websocket_mask();
|
8
|
+
VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask);
|
9
|
+
|
10
|
+
void Init_faye_websocket_mask() {
|
11
|
+
Faye = rb_define_module("Faye");
|
12
|
+
FayeWebSocket = rb_define_class_under(Faye, "WebSocket", rb_cObject);
|
13
|
+
FayeWebSocketMask = rb_define_module_under(FayeWebSocket, "Mask");
|
14
|
+
rb_define_singleton_method(FayeWebSocketMask, "mask", method_faye_websocket_mask, 2);
|
15
|
+
}
|
16
|
+
|
17
|
+
VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
|
18
|
+
int n = RARRAY_LEN(payload), i, p, m;
|
19
|
+
VALUE unmasked = rb_ary_new2(n);
|
20
|
+
|
21
|
+
int mask_array[] = {
|
22
|
+
NUM2INT(rb_ary_entry(mask, 0)),
|
23
|
+
NUM2INT(rb_ary_entry(mask, 1)),
|
24
|
+
NUM2INT(rb_ary_entry(mask, 2)),
|
25
|
+
NUM2INT(rb_ary_entry(mask, 3))
|
26
|
+
};
|
27
|
+
|
28
|
+
for (i = 0; i < n; i++) {
|
29
|
+
p = NUM2INT(rb_ary_entry(payload, i));
|
30
|
+
m = mask_array[i % 4];
|
31
|
+
rb_ary_store(unmasked, i, INT2NUM(p ^ m));
|
32
|
+
}
|
33
|
+
return unmasked;
|
34
|
+
}
|
35
|
+
|
data/lib/faye/websocket.rb
CHANGED
@@ -22,12 +22,13 @@ module Faye
|
|
22
22
|
class WebSocket
|
23
23
|
|
24
24
|
root = File.expand_path('../websocket', __FILE__)
|
25
|
+
require root + '/../../faye_websocket_mask'
|
25
26
|
|
26
27
|
autoload :API, root + '/api'
|
27
28
|
autoload :Client, root + '/client'
|
28
29
|
autoload :Draft75Parser, root + '/draft75_parser'
|
29
30
|
autoload :Draft76Parser, root + '/draft76_parser'
|
30
|
-
autoload :
|
31
|
+
autoload :HybiParser, root + '/hybi_parser'
|
31
32
|
|
32
33
|
# http://www.w3.org/International/questions/qa-forms-utf-8.en.php
|
33
34
|
UTF8_MATCH = /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/
|
@@ -47,7 +48,7 @@ module Faye
|
|
47
48
|
|
48
49
|
def self.parser(env)
|
49
50
|
if env['HTTP_SEC_WEBSOCKET_VERSION']
|
50
|
-
|
51
|
+
HybiParser
|
51
52
|
elsif env['HTTP_SEC_WEBSOCKET_KEY1']
|
52
53
|
Draft76Parser
|
53
54
|
else
|
@@ -61,7 +62,7 @@ module Faye
|
|
61
62
|
attr_reader :env
|
62
63
|
include API
|
63
64
|
|
64
|
-
def initialize(env)
|
65
|
+
def initialize(env, supported_protos = nil)
|
65
66
|
@env = env
|
66
67
|
@callback = @env['async.callback']
|
67
68
|
@stream = Stream.new(self, @env['em.connection'])
|
@@ -71,7 +72,7 @@ module Faye
|
|
71
72
|
@ready_state = CONNECTING
|
72
73
|
@buffered_amount = 0
|
73
74
|
|
74
|
-
@parser = WebSocket.parser(@env).new(self)
|
75
|
+
@parser = WebSocket.parser(@env).new(self, :protocols => supported_protos)
|
75
76
|
@stream.write(@parser.handshake_response)
|
76
77
|
|
77
78
|
@ready_state = OPEN
|
@@ -86,6 +87,10 @@ module Faye
|
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
90
|
+
def protocol
|
91
|
+
@parser.protocol || ''
|
92
|
+
end
|
93
|
+
|
89
94
|
private
|
90
95
|
|
91
96
|
def determine_url
|
@@ -3,13 +3,14 @@ module Faye
|
|
3
3
|
|
4
4
|
class Client
|
5
5
|
include API
|
6
|
-
attr_reader :uri
|
6
|
+
attr_reader :protocol, :uri
|
7
7
|
|
8
|
-
def initialize(url)
|
9
|
-
@parser =
|
8
|
+
def initialize(url, protocols = nil)
|
9
|
+
@parser = HybiParser.new(self, :masking => true, :protocols => protocols)
|
10
10
|
@url = url
|
11
11
|
@uri = URI.parse(url)
|
12
12
|
|
13
|
+
@protocol = ''
|
13
14
|
@ready_state = CONNECTING
|
14
15
|
@buffered_amount = 0
|
15
16
|
|
@@ -39,6 +40,7 @@ module Faye
|
|
39
40
|
return unless @handshake.complete?
|
40
41
|
|
41
42
|
if @handshake.valid?
|
43
|
+
@protocol = @handshake.protocol || ''
|
42
44
|
@ready_state = OPEN
|
43
45
|
event = Event.new('open')
|
44
46
|
event.init_event('open', false, false)
|
@@ -47,8 +49,8 @@ module Faye
|
|
47
49
|
receive_data(@message)
|
48
50
|
else
|
49
51
|
@ready_state = CLOSED
|
50
|
-
event = Event.new('
|
51
|
-
event.init_event('
|
52
|
+
event = Event.new('close', :code => 1006, :reason => '')
|
53
|
+
event.init_event('close', false, false)
|
52
54
|
dispatch_event(event)
|
53
55
|
end
|
54
56
|
|
@@ -2,14 +2,15 @@ module Faye
|
|
2
2
|
class WebSocket
|
3
3
|
|
4
4
|
class Draft75Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@
|
5
|
+
attr_reader :protocol
|
6
|
+
|
7
|
+
def initialize(web_socket, options = {})
|
8
|
+
@socket = web_socket
|
9
|
+
@stage = 0
|
9
10
|
end
|
10
11
|
|
11
12
|
def version
|
12
|
-
'
|
13
|
+
'hixie-75'
|
13
14
|
end
|
14
15
|
|
15
16
|
def handshake_response
|
@@ -22,30 +23,58 @@ module Faye
|
|
22
23
|
upgrade
|
23
24
|
end
|
24
25
|
|
25
|
-
def parse(
|
26
|
-
|
26
|
+
def parse(buffer)
|
27
|
+
buffer.each_byte do |data|
|
28
|
+
case @stage
|
29
|
+
when 0 then
|
30
|
+
parse_leading_byte(data)
|
31
|
+
|
32
|
+
when 1 then
|
33
|
+
value = (data & 0x7F)
|
34
|
+
@length = value + 128 * @length
|
35
|
+
|
36
|
+
if @closing and @length.zero?
|
37
|
+
@socket.close(nil, nil, false)
|
38
|
+
elsif (0x80 & data) != 0x80
|
39
|
+
if @length.zero?
|
40
|
+
@socket.receive('')
|
41
|
+
@stage = 0
|
42
|
+
else
|
43
|
+
@buffer = []
|
44
|
+
@stage = 2
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
when 2 then
|
49
|
+
if data == 0xFF
|
50
|
+
@socket.receive(WebSocket.encode(@buffer))
|
51
|
+
@stage = 0
|
52
|
+
else
|
53
|
+
@buffer << data
|
54
|
+
if @length and @buffer.size == @length
|
55
|
+
@stage = 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
27
61
|
nil
|
28
62
|
end
|
29
63
|
|
30
|
-
def
|
31
|
-
|
64
|
+
def parse_leading_byte(data)
|
65
|
+
if (0x80 & data) == 0x80
|
66
|
+
@length = 0
|
67
|
+
@stage = 1
|
68
|
+
else
|
69
|
+
@length = nil
|
70
|
+
@buffer = []
|
71
|
+
@stage = 2
|
72
|
+
end
|
32
73
|
end
|
33
74
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
case data
|
38
|
-
when 0x00 then
|
39
|
-
@buffering = true
|
40
|
-
|
41
|
-
when 0xFF then
|
42
|
-
@socket.receive(WebSocket.encode(@buffer))
|
43
|
-
@buffer = []
|
44
|
-
@buffering = false
|
45
|
-
|
46
|
-
else
|
47
|
-
@buffer.push(data) if @buffering
|
48
|
-
end
|
75
|
+
def frame(data, type = nil, error_type = nil)
|
76
|
+
return WebSocket.encode(data) if Array === data
|
77
|
+
["\x00", data, "\xFF"].map(&WebSocket.method(:encode)) * ''
|
49
78
|
end
|
50
79
|
end
|
51
80
|
|
@@ -3,7 +3,7 @@ module Faye
|
|
3
3
|
|
4
4
|
class Draft76Parser < Draft75Parser
|
5
5
|
def version
|
6
|
-
'
|
6
|
+
'hixie-76'
|
7
7
|
end
|
8
8
|
|
9
9
|
def handshake_response
|
@@ -42,8 +42,22 @@ module Faye
|
|
42
42
|
handshake_signature(data)
|
43
43
|
end
|
44
44
|
|
45
|
+
def close(code = nil, reason = nil, &callback)
|
46
|
+
return if @closed
|
47
|
+
@socket.send([0xFF, 0x00]) if @closing
|
48
|
+
@closed = true
|
49
|
+
callback.call if callback
|
50
|
+
end
|
51
|
+
|
45
52
|
private
|
46
53
|
|
54
|
+
def parse_leading_byte(data)
|
55
|
+
return super unless data == 0xFF
|
56
|
+
@closing = true
|
57
|
+
@length = 0
|
58
|
+
@stage = 1
|
59
|
+
end
|
60
|
+
|
47
61
|
def number_from_key(key)
|
48
62
|
key.scan(/[0-9]/).join('').to_i(10)
|
49
63
|
end
|
@@ -1,16 +1,19 @@
|
|
1
1
|
module Faye
|
2
2
|
class WebSocket
|
3
|
-
|
4
|
-
class
|
5
|
-
|
6
|
-
|
3
|
+
|
4
|
+
class HybiParser
|
5
|
+
root = File.expand_path('../hybi_parser', __FILE__)
|
6
|
+
autoload :Handshake, root + '/handshake'
|
7
|
+
autoload :StreamReader, root + '/stream_reader'
|
8
|
+
|
9
|
+
BYTE = 0b11111111
|
7
10
|
FIN = MASK = 0b10000000
|
8
11
|
RSV1 = 0b01000000
|
9
12
|
RSV2 = 0b00100000
|
10
13
|
RSV3 = 0b00010000
|
11
14
|
OPCODE = 0b00001111
|
12
15
|
LENGTH = 0b01111111
|
13
|
-
|
16
|
+
|
14
17
|
OPCODES = {
|
15
18
|
:continuation => 0,
|
16
19
|
:text => 1,
|
@@ -19,10 +22,10 @@ module Faye
|
|
19
22
|
:ping => 9,
|
20
23
|
:pong => 10
|
21
24
|
}
|
22
|
-
|
25
|
+
|
23
26
|
FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
|
24
27
|
OPENING_OPCODES = OPCODES.values_at(:text, :binary)
|
25
|
-
|
28
|
+
|
26
29
|
ERRORS = {
|
27
30
|
:normal_closure => 1000,
|
28
31
|
:going_away => 1001,
|
@@ -33,214 +36,201 @@ module Faye
|
|
33
36
|
:too_large => 1009,
|
34
37
|
:extension_error => 1010
|
35
38
|
}
|
36
|
-
|
39
|
+
|
37
40
|
ERROR_CODES = ERRORS.values
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@uri = uri
|
42
|
-
@key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
|
43
|
-
@accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
|
44
|
-
@buffer = []
|
45
|
-
end
|
46
|
-
|
47
|
-
def request_data
|
48
|
-
hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
|
49
|
-
|
50
|
-
handshake = "GET #{@uri.path}#{@uri.query ? '?' : ''}#{@uri.query} HTTP/1.1\r\n"
|
51
|
-
handshake << "Host: #{hostname}\r\n"
|
52
|
-
handshake << "Upgrade: websocket\r\n"
|
53
|
-
handshake << "Connection: Upgrade\r\n"
|
54
|
-
handshake << "Sec-WebSocket-Key: #{@key}\r\n"
|
55
|
-
handshake << "Sec-WebSocket-Version: 8\r\n"
|
56
|
-
handshake << "\r\n"
|
57
|
-
|
58
|
-
handshake
|
59
|
-
end
|
60
|
-
|
61
|
-
def parse(data)
|
62
|
-
message = []
|
63
|
-
complete = false
|
64
|
-
data.each_byte do |byte|
|
65
|
-
if complete
|
66
|
-
message << byte
|
67
|
-
else
|
68
|
-
@buffer << byte
|
69
|
-
complete ||= complete?
|
70
|
-
end
|
71
|
-
end
|
72
|
-
message
|
73
|
-
end
|
74
|
-
|
75
|
-
def complete?
|
76
|
-
@buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
|
77
|
-
end
|
78
|
-
|
79
|
-
def valid?
|
80
|
-
data = WebSocket.encode(@buffer)
|
81
|
-
response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
|
82
|
-
return false unless response.code.to_i == 101
|
83
|
-
|
84
|
-
upgrade, connection = response['Upgrade'], response['Connection']
|
85
|
-
|
86
|
-
upgrade and upgrade =~ /^websocket$/i and
|
87
|
-
connection and connection.split(/\s*,\s*/).include?('Upgrade') and
|
88
|
-
response['Sec-WebSocket-Accept'] == @accept
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
41
|
+
|
42
|
+
attr_reader :protocol
|
43
|
+
|
92
44
|
def initialize(web_socket, options = {})
|
93
45
|
reset
|
94
|
-
@socket
|
95
|
-
@
|
96
|
-
@
|
46
|
+
@socket = web_socket
|
47
|
+
@reader = StreamReader.new
|
48
|
+
@stage = 0
|
49
|
+
@masking = options[:masking]
|
50
|
+
@protocols = options[:protocols]
|
51
|
+
|
52
|
+
@protocols = @protocols.split(/\s*,\s*/) if String === @protocols
|
97
53
|
end
|
98
|
-
|
54
|
+
|
99
55
|
def version
|
100
|
-
"
|
56
|
+
"hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
|
101
57
|
end
|
102
|
-
|
58
|
+
|
103
59
|
def handshake_response
|
104
60
|
sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
|
105
61
|
return '' unless String === sec_key
|
106
|
-
|
107
|
-
accept
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
62
|
+
|
63
|
+
accept = Base64.encode64(Digest::SHA1.digest(sec_key + Handshake::GUID)).strip
|
64
|
+
protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
65
|
+
supported = @protocols
|
66
|
+
proto = nil
|
67
|
+
|
68
|
+
headers = [
|
69
|
+
"HTTP/1.1 101 Switching Protocols",
|
70
|
+
"Upgrade: websocket",
|
71
|
+
"Connection: Upgrade",
|
72
|
+
"Sec-WebSocket-Accept: #{accept}"
|
73
|
+
]
|
74
|
+
|
75
|
+
if protos and supported
|
76
|
+
protos = protos.split(/\s*,\s*/) if String === protos
|
77
|
+
proto = protos.find { |p| supported.include?(p) }
|
78
|
+
if proto
|
79
|
+
@protocol = proto
|
80
|
+
headers << "Sec-WebSocket-Protocol: #{proto}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
(headers + ['','']).join("\r\n")
|
115
85
|
end
|
116
|
-
|
86
|
+
|
117
87
|
def create_handshake
|
118
|
-
Handshake.new(@socket.uri)
|
88
|
+
Handshake.new(@socket.uri, @protocols)
|
119
89
|
end
|
120
|
-
|
90
|
+
|
121
91
|
def parse(data)
|
122
|
-
data.
|
92
|
+
@reader.put(data.bytes.to_a)
|
93
|
+
buffer = true
|
94
|
+
while buffer
|
123
95
|
case @stage
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
96
|
+
when 0 then
|
97
|
+
buffer = @reader.read(1)
|
98
|
+
parse_opcode(buffer[0]) if buffer
|
99
|
+
|
100
|
+
when 1 then
|
101
|
+
buffer = @reader.read(1)
|
102
|
+
parse_length(buffer[0]) if buffer
|
103
|
+
|
104
|
+
when 2 then
|
105
|
+
buffer = @reader.read(@length_size)
|
106
|
+
parse_extended_length(buffer) if buffer
|
107
|
+
|
108
|
+
when 3 then
|
109
|
+
buffer = @reader.read(4)
|
110
|
+
if buffer
|
111
|
+
@mask = buffer
|
112
|
+
@stage = 4
|
113
|
+
end
|
114
|
+
|
115
|
+
when 4 then
|
116
|
+
buffer = @reader.read(@length)
|
117
|
+
if buffer
|
118
|
+
@payload = buffer
|
119
|
+
emit_frame
|
120
|
+
@stage = 0
|
121
|
+
end
|
129
122
|
end
|
130
|
-
emit_frame if @stage == 4 and @length == 0
|
131
123
|
end
|
124
|
+
|
132
125
|
nil
|
133
126
|
end
|
134
|
-
|
127
|
+
|
135
128
|
def frame(data, type = nil, code = nil)
|
136
129
|
return nil if @closed
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
130
|
+
|
131
|
+
is_text = (String === data)
|
132
|
+
opcode = OPCODES[type || (is_text ? :text : :binary)]
|
133
|
+
buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
|
134
|
+
insert = code ? 2 : 0
|
135
|
+
length = buffer.size + insert
|
136
|
+
header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
|
137
|
+
offset = header + (@masking ? 4 : 0)
|
138
|
+
masked = @masking ? MASK : 0
|
139
|
+
frame = Array.new(offset)
|
140
|
+
|
141
|
+
frame[0] = FIN | opcode
|
142
|
+
|
143
|
+
if length <= 125
|
144
|
+
frame[1] = masked | length
|
145
|
+
elsif length <= 65535
|
146
|
+
frame[1] = masked | 126
|
147
|
+
frame[2] = (length >> 8) & BYTE
|
148
|
+
frame[3] = length & BYTE
|
149
|
+
else
|
150
|
+
frame[1] = masked | 127
|
151
|
+
frame[2] = (length >> 56) & BYTE
|
152
|
+
frame[3] = (length >> 48) & BYTE
|
153
|
+
frame[4] = (length >> 40) & BYTE
|
154
|
+
frame[5] = (length >> 32) & BYTE
|
155
|
+
frame[6] = (length >> 24) & BYTE
|
156
|
+
frame[7] = (length >> 16) & BYTE
|
157
|
+
frame[8] = (length >> 8) & BYTE
|
158
|
+
frame[9] = length & BYTE
|
143
159
|
end
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
masked = @masking ? MASK : 0
|
148
|
-
|
149
|
-
case length
|
150
|
-
when 0..125 then
|
151
|
-
frame << (masked | length).chr
|
152
|
-
when 126..65535 then
|
153
|
-
frame << (masked | 126).chr
|
154
|
-
frame << [length].pack('n')
|
155
|
-
else
|
156
|
-
frame << (masked | 127).chr
|
157
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack('NN')
|
160
|
+
|
161
|
+
if code
|
162
|
+
buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
|
158
163
|
end
|
159
|
-
|
164
|
+
|
160
165
|
if @masking
|
161
|
-
mask = (
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
frame << mask.pack('C*')
|
166
|
+
mask = [rand(256), rand(256), rand(256), rand(256)]
|
167
|
+
frame[header...offset] = mask
|
168
|
+
buffer = Mask.mask(buffer, mask)
|
166
169
|
end
|
167
|
-
|
168
|
-
|
170
|
+
|
171
|
+
frame.concat(buffer)
|
172
|
+
|
173
|
+
WebSocket.encode(frame)
|
169
174
|
end
|
170
|
-
|
175
|
+
|
171
176
|
def close(code = nil, reason = nil, &callback)
|
172
177
|
return if @closed
|
173
178
|
@closing_callback ||= callback
|
174
179
|
@socket.send(reason || '', :close, code || ERRORS[:normal_closure])
|
175
180
|
@closed = true
|
176
181
|
end
|
177
|
-
|
182
|
+
|
178
183
|
private
|
179
|
-
|
184
|
+
|
180
185
|
def parse_opcode(data)
|
181
186
|
if [RSV1, RSV2, RSV3].any? { |rsv| (data & rsv) == rsv }
|
182
187
|
return @socket.close(ERRORS[:protocol_error], nil, false)
|
183
188
|
end
|
184
|
-
|
189
|
+
|
185
190
|
@final = (data & FIN) == FIN
|
186
191
|
@opcode = (data & OPCODE)
|
187
192
|
@mask = []
|
188
193
|
@payload = []
|
189
|
-
|
194
|
+
|
190
195
|
unless OPCODES.values.include?(@opcode)
|
191
196
|
return @socket.close(ERRORS[:protocol_error], nil, false)
|
192
197
|
end
|
193
|
-
|
198
|
+
|
194
199
|
unless FRAGMENTED_OPCODES.include?(@opcode) or @final
|
195
200
|
return @socket.close(ERRORS[:protocol_error], nil, false)
|
196
201
|
end
|
197
|
-
|
202
|
+
|
198
203
|
if @mode and OPENING_OPCODES.include?(@opcode)
|
199
204
|
return @socket.close(ERRORS[:protocol_error], nil, false)
|
200
205
|
end
|
201
|
-
|
206
|
+
|
202
207
|
@stage = 1
|
203
208
|
end
|
204
|
-
|
209
|
+
|
205
210
|
def parse_length(data)
|
206
211
|
@masked = (data & MASK) == MASK
|
207
212
|
@length = (data & LENGTH)
|
208
|
-
|
213
|
+
|
209
214
|
if @length <= 125
|
210
215
|
@stage = @masked ? 3 : 4
|
211
216
|
else
|
212
|
-
@
|
213
|
-
@
|
214
|
-
@stage = 2
|
217
|
+
@length_size = (@length == 126) ? 2 : 8
|
218
|
+
@stage = 2
|
215
219
|
end
|
216
220
|
end
|
217
|
-
|
218
|
-
def parse_extended_length(
|
219
|
-
@
|
220
|
-
return unless @length_buffer.size == @length_size
|
221
|
-
@length = integer(@length_buffer)
|
221
|
+
|
222
|
+
def parse_extended_length(buffer)
|
223
|
+
@length = integer(buffer)
|
222
224
|
@stage = @masked ? 3 : 4
|
223
225
|
end
|
224
|
-
|
225
|
-
def parse_mask(data)
|
226
|
-
@mask << data
|
227
|
-
return if @mask.size < 4
|
228
|
-
@stage = 4
|
229
|
-
end
|
230
|
-
|
231
|
-
def parse_payload(data)
|
232
|
-
@payload << data
|
233
|
-
return if @payload.size < @length
|
234
|
-
emit_frame
|
235
|
-
end
|
236
|
-
|
226
|
+
|
237
227
|
def emit_frame
|
238
|
-
payload =
|
239
|
-
|
228
|
+
payload = @masked ? Mask.mask(@payload, @mask) : @payload
|
229
|
+
|
240
230
|
case @opcode
|
241
231
|
when OPCODES[:continuation] then
|
242
232
|
return @socket.close(ERRORS[:protocol_error], nil, false) unless @mode
|
243
|
-
@buffer
|
233
|
+
@buffer.concat(payload)
|
244
234
|
if @final
|
245
235
|
message = @buffer
|
246
236
|
message = WebSocket.encode(message, true) if @mode == :text
|
@@ -262,7 +252,7 @@ module Faye
|
|
262
252
|
end
|
263
253
|
else
|
264
254
|
@mode = :text
|
265
|
-
@buffer
|
255
|
+
@buffer.concat(payload)
|
266
256
|
end
|
267
257
|
|
268
258
|
when OPCODES[:binary] then
|
@@ -270,22 +260,22 @@ module Faye
|
|
270
260
|
@socket.receive(payload)
|
271
261
|
else
|
272
262
|
@mode = :binary
|
273
|
-
@buffer
|
263
|
+
@buffer.concat(payload)
|
274
264
|
end
|
275
265
|
|
276
266
|
when OPCODES[:close] then
|
277
267
|
code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
|
278
|
-
|
268
|
+
|
279
269
|
unless (payload.size == 0) or
|
280
270
|
(code && code >= 3000 && code < 5000) or
|
281
271
|
ERROR_CODES.include?(code)
|
282
272
|
code = ERRORS[:protocol_error]
|
283
273
|
end
|
284
|
-
|
274
|
+
|
285
275
|
if payload.size > 125 or not WebSocket.valid_utf8?(payload[2..-1] || [])
|
286
276
|
code = ERRORS[:protocol_error]
|
287
277
|
end
|
288
|
-
|
278
|
+
|
289
279
|
reason = (payload.size > 2) ? WebSocket.encode(payload[2..-1], true) : nil
|
290
280
|
@socket.close(code, reason, false)
|
291
281
|
@closing_callback.call if @closing_callback
|
@@ -294,14 +284,13 @@ module Faye
|
|
294
284
|
return @socket.close(ERRORS[:protocol_error], nil, false) if payload.size > 125
|
295
285
|
@socket.send(payload, :pong)
|
296
286
|
end
|
297
|
-
@stage = 0
|
298
287
|
end
|
299
288
|
|
300
289
|
def reset
|
301
290
|
@buffer = []
|
302
291
|
@mode = nil
|
303
292
|
end
|
304
|
-
|
293
|
+
|
305
294
|
def integer(bytes)
|
306
295
|
number = 0
|
307
296
|
bytes.each_with_index do |data, i|
|
@@ -309,17 +298,7 @@ module Faye
|
|
309
298
|
end
|
310
299
|
number
|
311
300
|
end
|
312
|
-
|
313
|
-
def unmask(payload, mask)
|
314
|
-
unmasked = []
|
315
|
-
payload.each_with_index do |byte, i|
|
316
|
-
byte = byte ^ mask[i % 4] if mask.size > 0
|
317
|
-
unmasked << byte
|
318
|
-
end
|
319
|
-
unmasked
|
320
|
-
end
|
321
301
|
end
|
322
|
-
|
302
|
+
|
323
303
|
end
|
324
304
|
end
|
325
|
-
|