websocket 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/README.md +117 -0
- data/Rakefile +23 -0
- data/autobahn-client.json +11 -0
- data/autobahn-server.json +10 -0
- data/examples/base.rb +162 -0
- data/examples/client.rb +70 -0
- data/examples/server.rb +56 -0
- data/examples/tests/autobahn_client.rb +52 -0
- data/examples/tests/echo_client.rb +42 -0
- data/examples/tests/echo_server.rb +45 -0
- data/lib/websocket.rb +16 -0
- data/lib/websocket/frame.rb +11 -0
- data/lib/websocket/frame/base.rb +45 -0
- data/lib/websocket/frame/data.rb +52 -0
- data/lib/websocket/frame/handler.rb +15 -0
- data/lib/websocket/frame/handler/base.rb +41 -0
- data/lib/websocket/frame/handler/handler03.rb +162 -0
- data/lib/websocket/frame/handler/handler04.rb +19 -0
- data/lib/websocket/frame/handler/handler05.rb +17 -0
- data/lib/websocket/frame/handler/handler07.rb +34 -0
- data/lib/websocket/frame/handler/handler75.rb +79 -0
- data/lib/websocket/frame/incoming.rb +43 -0
- data/lib/websocket/frame/incoming/client.rb +9 -0
- data/lib/websocket/frame/incoming/server.rb +9 -0
- data/lib/websocket/frame/outgoing.rb +28 -0
- data/lib/websocket/frame/outgoing/client.rb +9 -0
- data/lib/websocket/frame/outgoing/server.rb +9 -0
- data/lib/websocket/handshake.rb +10 -0
- data/lib/websocket/handshake/base.rb +67 -0
- data/lib/websocket/handshake/client.rb +80 -0
- data/lib/websocket/handshake/handler.rb +20 -0
- data/lib/websocket/handshake/handler/base.rb +25 -0
- data/lib/websocket/handshake/handler/client.rb +19 -0
- data/lib/websocket/handshake/handler/client01.rb +19 -0
- data/lib/websocket/handshake/handler/client04.rb +47 -0
- data/lib/websocket/handshake/handler/client75.rb +25 -0
- data/lib/websocket/handshake/handler/client76.rb +85 -0
- data/lib/websocket/handshake/handler/server.rb +30 -0
- data/lib/websocket/handshake/handler/server04.rb +38 -0
- data/lib/websocket/handshake/handler/server75.rb +26 -0
- data/lib/websocket/handshake/handler/server76.rb +71 -0
- data/lib/websocket/handshake/server.rb +56 -0
- data/lib/websocket/version.rb +3 -0
- data/spec/frame/incoming_03_spec.rb +117 -0
- data/spec/frame/incoming_04_spec.rb +117 -0
- data/spec/frame/incoming_05_spec.rb +133 -0
- data/spec/frame/incoming_07_spec.rb +133 -0
- data/spec/frame/incoming_75_spec.rb +59 -0
- data/spec/frame/incoming_common_spec.rb +22 -0
- data/spec/frame/outgoing_03_spec.rb +77 -0
- data/spec/frame/outgoing_04_spec.rb +77 -0
- data/spec/frame/outgoing_05_spec.rb +77 -0
- data/spec/frame/outgoing_07_spec.rb +77 -0
- data/spec/frame/outgoing_75_spec.rb +41 -0
- data/spec/frame/outgoing_common_spec.rb +15 -0
- data/spec/handshake/client_04_spec.rb +20 -0
- data/spec/handshake/client_75_spec.rb +11 -0
- data/spec/handshake/client_76_spec.rb +20 -0
- data/spec/handshake/server_04_spec.rb +18 -0
- data/spec/handshake/server_75_spec.rb +11 -0
- data/spec/handshake/server_76_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/all_client_drafts.rb +77 -0
- data/spec/support/all_server_drafts.rb +86 -0
- data/spec/support/handshake_requests.rb +72 -0
- data/spec/support/incoming_frames.rb +30 -0
- data/spec/support/outgoing_frames.rb +14 -0
- data/websocket.gemspec +21 -0
- metadata +163 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
module Handler
|
4
|
+
module Base
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
result = [ header_line ]
|
8
|
+
handshake_keys.each do |key|
|
9
|
+
result << key.join(': ')
|
10
|
+
end
|
11
|
+
result << ""
|
12
|
+
result << finishing_line
|
13
|
+
result.join("\r\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def finishing_line
|
19
|
+
""
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Handshake
|
5
|
+
module Handler
|
6
|
+
module Client01
|
7
|
+
|
8
|
+
include Client76
|
9
|
+
|
10
|
+
def handshake_keys
|
11
|
+
keys = super
|
12
|
+
keys << ['Sec-WebSocket-Draft', @version]
|
13
|
+
keys
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module WebSocket
|
5
|
+
module Handshake
|
6
|
+
module Handler
|
7
|
+
module Client04
|
8
|
+
|
9
|
+
include Client
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
super && verify_accept
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def handshake_keys
|
18
|
+
keys = [
|
19
|
+
["Upgrade", "websocket"],
|
20
|
+
["Connection", "Upgrade"]
|
21
|
+
]
|
22
|
+
host = @host
|
23
|
+
host += ":#{@port}" if @port
|
24
|
+
keys << ["Host", host]
|
25
|
+
keys << ["Sec-WebSocket-Origin", @origin] if @origin
|
26
|
+
keys << ["Sec-WebSocket-Version", @version]
|
27
|
+
keys << ["Sec-WebSocket-Key", key]
|
28
|
+
keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def key
|
32
|
+
@key ||= Base64.encode64((1..16).map { rand(255).chr } * '').strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def accept
|
36
|
+
@accept ||= Base64.encode64(Digest::SHA1.digest(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')).strip
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify_accept
|
40
|
+
set_error(:invalid_accept) and return false unless @headers['sec-websocket-accept'] == accept
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
module Handler
|
4
|
+
module Client75
|
5
|
+
|
6
|
+
include Client
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def handshake_keys
|
11
|
+
keys = [
|
12
|
+
["Upgrade", "WebSocket"],
|
13
|
+
["Connection", "Upgrade"]
|
14
|
+
]
|
15
|
+
host = @host
|
16
|
+
host += ":#{@port}" if @port
|
17
|
+
keys << ["Host", host]
|
18
|
+
keys << ["Origin", @origin] if @origin
|
19
|
+
keys
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Handshake
|
5
|
+
module Handler
|
6
|
+
module Client76
|
7
|
+
|
8
|
+
include Client75
|
9
|
+
|
10
|
+
def key1
|
11
|
+
@key1 ||= generate_key(:key1)
|
12
|
+
end
|
13
|
+
|
14
|
+
def key2
|
15
|
+
@key2 ||= generate_key(:key2)
|
16
|
+
end
|
17
|
+
|
18
|
+
def key3
|
19
|
+
@key3 ||= generate_key3
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
super && verify_challenge
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def reserved_leftover_lines
|
29
|
+
1
|
30
|
+
end
|
31
|
+
|
32
|
+
def handshake_keys
|
33
|
+
keys = super
|
34
|
+
keys << ['Sec-WebSocket-Key1', key1]
|
35
|
+
keys << ['Sec-WebSocket-Key2', key2]
|
36
|
+
keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify_challenge
|
40
|
+
set_error(:invalid_challenge) and return false unless @leftovers == challenge
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def challenge
|
45
|
+
return @challenge if defined?(@challenge)
|
46
|
+
key1 && key2
|
47
|
+
sum = [@key1_number].pack("N*") +
|
48
|
+
[@key2_number].pack("N*") +
|
49
|
+
key3
|
50
|
+
|
51
|
+
@challenge = Digest::MD5.digest(sum)
|
52
|
+
end
|
53
|
+
|
54
|
+
def finishing_line
|
55
|
+
key3
|
56
|
+
end
|
57
|
+
|
58
|
+
NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
|
59
|
+
|
60
|
+
def generate_key(key)
|
61
|
+
spaces = 1 + rand(12)
|
62
|
+
max = 0xffffffff / spaces
|
63
|
+
number = rand(max + 1)
|
64
|
+
instance_variable_set("@#{key}_number", number)
|
65
|
+
key = (number * spaces).to_s
|
66
|
+
(1 + rand(12)).times() do
|
67
|
+
char = NOISE_CHARS[rand(NOISE_CHARS.size)]
|
68
|
+
pos = rand(key.size + 1)
|
69
|
+
key[pos...pos] = char
|
70
|
+
end
|
71
|
+
spaces.times() do
|
72
|
+
pos = 1 + rand(key.size - 1)
|
73
|
+
key[pos...pos] = " "
|
74
|
+
end
|
75
|
+
return key
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_key3
|
79
|
+
return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
module Handler
|
4
|
+
module Server
|
5
|
+
|
6
|
+
include Base
|
7
|
+
|
8
|
+
def host
|
9
|
+
@headers["host"].to_s.split(":")[0].to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def port
|
13
|
+
@headers["host"].to_s.split(":")[1]
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def handshake_location
|
19
|
+
location = @secure ? "wss://" : "ws://"
|
20
|
+
location << host
|
21
|
+
location << ":#{port}" if port
|
22
|
+
location << path
|
23
|
+
location << "?#{@query}" if @query
|
24
|
+
location
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module WebSocket
|
5
|
+
module Handshake
|
6
|
+
module Handler
|
7
|
+
module Server04
|
8
|
+
|
9
|
+
include Server
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
super && (@headers['sec-websocket-key'] ? true : (set_error(:invalid_handshake_authentication) and false))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def header_line
|
18
|
+
"HTTP/1.1 101 Switching Protocols"
|
19
|
+
end
|
20
|
+
|
21
|
+
def handshake_keys
|
22
|
+
[
|
23
|
+
["Upgrade", "websocket"],
|
24
|
+
["Connection", "Upgrade"],
|
25
|
+
["Sec-WebSocket-Accept", signature]
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def signature
|
30
|
+
return unless key = @headers['sec-websocket-key']
|
31
|
+
string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
32
|
+
Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
module Handler
|
4
|
+
module Server75
|
5
|
+
|
6
|
+
include Server
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def header_line
|
11
|
+
"HTTP/1.1 101 Web Socket Protocol Handshake"
|
12
|
+
end
|
13
|
+
|
14
|
+
def handshake_keys
|
15
|
+
[
|
16
|
+
["Upgrade", "WebSocket"],
|
17
|
+
["Connection", "Upgrade"],
|
18
|
+
["WebSocket-Origin", @headers['origin']],
|
19
|
+
["WebSocket-Location", handshake_location]
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module WebSocket
|
4
|
+
module Handshake
|
5
|
+
module Handler
|
6
|
+
module Server76
|
7
|
+
|
8
|
+
include Server
|
9
|
+
|
10
|
+
def valid?
|
11
|
+
super && !!finishing_line
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def reserved_leftover_lines
|
17
|
+
1
|
18
|
+
end
|
19
|
+
|
20
|
+
def header_line
|
21
|
+
"HTTP/1.1 101 WebSocket Protocol Handshake"
|
22
|
+
end
|
23
|
+
|
24
|
+
def handshake_keys
|
25
|
+
[
|
26
|
+
["Upgrade", "WebSocket"],
|
27
|
+
["Connection", "Upgrade"],
|
28
|
+
["Sec-WebSocket-Origin", @headers['origin']],
|
29
|
+
["Sec-WebSocket-Location", handshake_location]
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def finishing_line
|
34
|
+
@finishing_line ||= challenge_response
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def challenge_response
|
40
|
+
# Refer to 5.2 4-9 of the draft 76
|
41
|
+
first = numbers_over_spaces(@headers['sec-websocket-key1']) || return
|
42
|
+
second = numbers_over_spaces(@headers['sec-websocket-key2']) || return
|
43
|
+
third = @leftovers.strip
|
44
|
+
|
45
|
+
sum = [first].pack("N*") +
|
46
|
+
[second].pack("N*") +
|
47
|
+
third
|
48
|
+
Digest::MD5.digest(sum)
|
49
|
+
end
|
50
|
+
|
51
|
+
def numbers_over_spaces(string)
|
52
|
+
numbers = string.scan(/[0-9]/).join.to_i
|
53
|
+
|
54
|
+
spaces = string.scan(/ /).size
|
55
|
+
# As per 5.2.5, abort the connection if spaces are zero.
|
56
|
+
set_error(:invalid_handshake_authentication) and return if spaces == 0
|
57
|
+
|
58
|
+
# As per 5.2.6, abort if numbers is not an integral multiple of spaces
|
59
|
+
set_error(:invalid_handshake_authentication) and return if numbers % spaces != 0
|
60
|
+
|
61
|
+
quotient = numbers / spaces
|
62
|
+
|
63
|
+
set_error(:invalid_handshake_authentication) and return if quotient > 2**32-1
|
64
|
+
|
65
|
+
return quotient
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Handshake
|
3
|
+
class Server < Base
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
super
|
7
|
+
@secure = !!args[:secure]
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(data)
|
11
|
+
@data << data
|
12
|
+
if parse_data
|
13
|
+
set_version
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def should_respond?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_version
|
24
|
+
@version = @headers['sec-websocket-version'].to_i if @headers['sec-websocket-version']
|
25
|
+
@version ||= @headers['sec-websocket-draft'].to_i if @headers['sec-websocket-draft']
|
26
|
+
@version ||= 76 if @leftovers != ""
|
27
|
+
@version ||= 75
|
28
|
+
include_version
|
29
|
+
end
|
30
|
+
|
31
|
+
def include_version
|
32
|
+
case @version
|
33
|
+
when 75 then extend Handler::Server75
|
34
|
+
when 76, 0..3 then extend Handler::Server76
|
35
|
+
when 4..13 then extend Handler::Server04
|
36
|
+
else set_error(:unknown_protocol_version) and return false
|
37
|
+
end
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
|
41
|
+
PATH = /^(\w+) (\/[^\s]*) HTTP\/1\.1$/
|
42
|
+
|
43
|
+
def parse_first_line(line)
|
44
|
+
line_parts = line.match(PATH)
|
45
|
+
method = line_parts[1].strip
|
46
|
+
set_error(:get_request_required) and return unless method == "GET"
|
47
|
+
|
48
|
+
resource_name = line_parts[2].strip
|
49
|
+
@path, @query = resource_name.split('?', 2)
|
50
|
+
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|