websocket 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +11 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +5 -0
  5. data/README.md +117 -0
  6. data/Rakefile +23 -0
  7. data/autobahn-client.json +11 -0
  8. data/autobahn-server.json +10 -0
  9. data/examples/base.rb +162 -0
  10. data/examples/client.rb +70 -0
  11. data/examples/server.rb +56 -0
  12. data/examples/tests/autobahn_client.rb +52 -0
  13. data/examples/tests/echo_client.rb +42 -0
  14. data/examples/tests/echo_server.rb +45 -0
  15. data/lib/websocket.rb +16 -0
  16. data/lib/websocket/frame.rb +11 -0
  17. data/lib/websocket/frame/base.rb +45 -0
  18. data/lib/websocket/frame/data.rb +52 -0
  19. data/lib/websocket/frame/handler.rb +15 -0
  20. data/lib/websocket/frame/handler/base.rb +41 -0
  21. data/lib/websocket/frame/handler/handler03.rb +162 -0
  22. data/lib/websocket/frame/handler/handler04.rb +19 -0
  23. data/lib/websocket/frame/handler/handler05.rb +17 -0
  24. data/lib/websocket/frame/handler/handler07.rb +34 -0
  25. data/lib/websocket/frame/handler/handler75.rb +79 -0
  26. data/lib/websocket/frame/incoming.rb +43 -0
  27. data/lib/websocket/frame/incoming/client.rb +9 -0
  28. data/lib/websocket/frame/incoming/server.rb +9 -0
  29. data/lib/websocket/frame/outgoing.rb +28 -0
  30. data/lib/websocket/frame/outgoing/client.rb +9 -0
  31. data/lib/websocket/frame/outgoing/server.rb +9 -0
  32. data/lib/websocket/handshake.rb +10 -0
  33. data/lib/websocket/handshake/base.rb +67 -0
  34. data/lib/websocket/handshake/client.rb +80 -0
  35. data/lib/websocket/handshake/handler.rb +20 -0
  36. data/lib/websocket/handshake/handler/base.rb +25 -0
  37. data/lib/websocket/handshake/handler/client.rb +19 -0
  38. data/lib/websocket/handshake/handler/client01.rb +19 -0
  39. data/lib/websocket/handshake/handler/client04.rb +47 -0
  40. data/lib/websocket/handshake/handler/client75.rb +25 -0
  41. data/lib/websocket/handshake/handler/client76.rb +85 -0
  42. data/lib/websocket/handshake/handler/server.rb +30 -0
  43. data/lib/websocket/handshake/handler/server04.rb +38 -0
  44. data/lib/websocket/handshake/handler/server75.rb +26 -0
  45. data/lib/websocket/handshake/handler/server76.rb +71 -0
  46. data/lib/websocket/handshake/server.rb +56 -0
  47. data/lib/websocket/version.rb +3 -0
  48. data/spec/frame/incoming_03_spec.rb +117 -0
  49. data/spec/frame/incoming_04_spec.rb +117 -0
  50. data/spec/frame/incoming_05_spec.rb +133 -0
  51. data/spec/frame/incoming_07_spec.rb +133 -0
  52. data/spec/frame/incoming_75_spec.rb +59 -0
  53. data/spec/frame/incoming_common_spec.rb +22 -0
  54. data/spec/frame/outgoing_03_spec.rb +77 -0
  55. data/spec/frame/outgoing_04_spec.rb +77 -0
  56. data/spec/frame/outgoing_05_spec.rb +77 -0
  57. data/spec/frame/outgoing_07_spec.rb +77 -0
  58. data/spec/frame/outgoing_75_spec.rb +41 -0
  59. data/spec/frame/outgoing_common_spec.rb +15 -0
  60. data/spec/handshake/client_04_spec.rb +20 -0
  61. data/spec/handshake/client_75_spec.rb +11 -0
  62. data/spec/handshake/client_76_spec.rb +20 -0
  63. data/spec/handshake/server_04_spec.rb +18 -0
  64. data/spec/handshake/server_75_spec.rb +11 -0
  65. data/spec/handshake/server_76_spec.rb +46 -0
  66. data/spec/spec_helper.rb +4 -0
  67. data/spec/support/all_client_drafts.rb +77 -0
  68. data/spec/support/all_server_drafts.rb +86 -0
  69. data/spec/support/handshake_requests.rb +72 -0
  70. data/spec/support/incoming_frames.rb +30 -0
  71. data/spec/support/outgoing_frames.rb +14 -0
  72. data/websocket.gemspec +21 -0
  73. 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
+ module WebSocket
2
+ module Handshake
3
+ module Handler
4
+ module Client
5
+
6
+ include Base
7
+
8
+ private
9
+
10
+ def header_line
11
+ path = @path
12
+ path += "?" + @query if @query
13
+ "GET #{path} HTTP/1.1"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ 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