websocket 1.0.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.
- 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
|