faye-websocket 0.1.2 → 0.2.0
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.
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
|
-
|