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