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,19 @@
1
+ # encoding: binary
2
+
3
+ module WebSocket
4
+ module Frame
5
+ module Handler
6
+ module Handler04
7
+
8
+ include Handler03
9
+
10
+ private
11
+
12
+ # The only difference between draft 03 framing and draft 04 framing is
13
+ # that the MORE bit has been changed to a FIN bit
14
+ def fin; true; end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: binary
2
+
3
+ module WebSocket
4
+ module Frame
5
+ module Handler
6
+ module Handler05
7
+
8
+ include Handler04
9
+
10
+ private
11
+
12
+ def masking?; true; end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: binary
2
+
3
+ module WebSocket
4
+ module Frame
5
+ module Handler
6
+ module Handler07
7
+
8
+ include Handler05
9
+
10
+ private
11
+
12
+ FRAME_TYPES = {
13
+ :continuation => 0,
14
+ :text => 1,
15
+ :binary => 2,
16
+ :close => 8,
17
+ :ping => 9,
18
+ :pong => 10,
19
+ }
20
+
21
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert
22
+
23
+ def type_to_opcode(frame_type)
24
+ FRAME_TYPES[frame_type] || raise(WebSocket::Error, :unknown_frame_type)
25
+ end
26
+
27
+ def opcode_to_type(opcode)
28
+ FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error, :unknown_opcode)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: binary
2
+
3
+ module WebSocket
4
+ module Frame
5
+ module Handler
6
+ module Handler75
7
+
8
+ include Base
9
+
10
+ def supported_frames
11
+ [:text, :close]
12
+ end
13
+
14
+ private
15
+
16
+ def encode_frame
17
+ case @type
18
+ when :close then "\xff\x00"
19
+ when :text then
20
+ ary = ["\x00", @data, "\xff"]
21
+ ary.collect{ |s| s.encode('UTF-8', 'UTF-8', :invalid => :replace) if s.respond_to?(:encode) }
22
+ ary.join
23
+ end
24
+ end
25
+
26
+ def decode_frame
27
+ return if @data.size == 0
28
+
29
+ pointer = 0
30
+ frame_type = @data.getbyte(pointer)
31
+ pointer += 1
32
+
33
+ if (frame_type & 0x80) == 0x80
34
+ # If the high-order bit of the /frame type/ byte is set
35
+ length = 0
36
+
37
+ loop do
38
+ return if !@data.getbyte(pointer)
39
+ b = @data.getbyte(pointer)
40
+ pointer += 1
41
+ b_v = b & 0x7F
42
+ length = length * 128 + b_v
43
+ break unless (b & 0x80) == 0x80
44
+ end
45
+
46
+ set_error(:frame_too_long) and return if length > MAX_FRAME_SIZE
47
+
48
+ unless @data.getbyte(pointer+length-1) == nil
49
+ # Straight from spec - I'm sure this isn't crazy...
50
+ # 6. Read /length/ bytes.
51
+ # 7. Discard the read bytes.
52
+ @data = @data[(pointer+length)..-1]
53
+
54
+ # If the /frame type/ is 0xFF and the /length/ was 0, then close
55
+ if length == 0
56
+ self.class.new(:version => version, :type => :close, :decoded => true)
57
+ end
58
+ end
59
+ else
60
+ # If the high-order bit of the /frame type/ byte is _not_ set
61
+
62
+ set_error(:invalid_frame) and return if @data.getbyte(0) != 0x00
63
+
64
+ # Addition to the spec to protect against malicious requests
65
+ set_error(:frame_too_long) and return if @data.size > MAX_FRAME_SIZE
66
+
67
+ msg = @data.slice!(/\A\x00[^\xff]*\xff/)
68
+ if msg
69
+ msg.gsub!(/\A\x00|\xff\z/, '')
70
+ msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
71
+ self.class.new(:version => version, :type => :text, :data => msg, :decoded => true)
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,43 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Incoming < Base
4
+
5
+ autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/incoming/client"
6
+ autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/incoming/server"
7
+
8
+ def initialize(args = {})
9
+ @decoded = args[:decoded] || false
10
+ super
11
+ end
12
+
13
+ # If data is still encoded after receiving then this is false. After calling "next" you will receive
14
+ # another instance of incoming frame, but with data decoded - this function will return true and
15
+ # to_s will return frame content instead of raw data.
16
+ # @return [Boolean] If frame already decoded?
17
+ def decoded?
18
+ @decoded
19
+ end
20
+
21
+ # Add provided string as raw incoming frame.
22
+ # @param data [String] Raw frame
23
+ def <<(data)
24
+ @data << data
25
+ end
26
+
27
+ # Return next complete frame.
28
+ # This function will merge together splitted frames and return as combined content.
29
+ # Check #error if nil received to check for eventual parsing errors
30
+ # @return [WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
31
+ def next
32
+ decode_frame unless decoded?
33
+ end
34
+
35
+ # If decoded then this will return frame content. Otherwise it will return raw frame.
36
+ # @return [String] Data of frame
37
+ def to_s
38
+ @data
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Incoming
4
+ class Client < Incoming
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Incoming
4
+ class Server < Incoming
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Outgoing < Base
4
+
5
+ autoload :Client, "#{::WebSocket::ROOT}/websocket/frame/outgoing/client"
6
+ autoload :Server, "#{::WebSocket::ROOT}/websocket/frame/outgoing/server"
7
+
8
+ # Is selected type supported by current draft version?
9
+ # @return [Boolean] true if frame type is supported
10
+ def supported?
11
+ support_type?
12
+ end
13
+
14
+ # Should current frame be sent? Exclude empty frames etc.
15
+ # @return [Boolean] true if frame should be sent
16
+ def require_sending?
17
+ !error?
18
+ end
19
+
20
+ # Return raw frame formatted for sending.
21
+ def to_s
22
+ set_error(:unknown_frame_type) and return unless supported?
23
+ encode_frame
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Outgoing
4
+ class Client < Outgoing
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module WebSocket
2
+ module Frame
3
+ class Outgoing
4
+ class Server < Outgoing
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module WebSocket
2
+ module Handshake
3
+
4
+ autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/base"
5
+ autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/client"
6
+ autoload :Handler, "#{::WebSocket::ROOT}/websocket/handshake/handler"
7
+ autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/server"
8
+
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ module WebSocket
2
+ module Handshake
3
+ class Base
4
+
5
+ attr_reader :host, :port, :path, :query,
6
+ :error, :state, :version, :secure
7
+
8
+ def initialize(args = {})
9
+ @state = :new
10
+
11
+ @data = ""
12
+ @headers = {}
13
+ end
14
+
15
+ def <<(data)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def finished?
20
+ @state == :finished || @state == :error
21
+ end
22
+
23
+ def valid?
24
+ finished? && @error == nil
25
+ end
26
+
27
+ def should_respond?
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def leftovers
32
+ @leftovers.split("\n", reserved_leftover_lines + 1)[reserved_leftover_lines]
33
+ end
34
+
35
+ private
36
+
37
+ def reserved_leftover_lines
38
+ 0
39
+ end
40
+
41
+ def set_error(message)
42
+ @state = :error
43
+ @error = message
44
+ end
45
+
46
+ HEADER = /^([^:]+):\s*(.+)$/
47
+
48
+ def parse_data
49
+ header, @leftovers = @data.split("\r\n\r\n", 2)
50
+ return unless @leftovers # The whole header has not been received yet.
51
+
52
+ lines = header.split("\r\n")
53
+
54
+ first_line = lines.shift
55
+ return unless parse_first_line(first_line)
56
+
57
+ lines.each do |line|
58
+ h = HEADER.match(line)
59
+ @headers[h[1].strip.downcase] = h[2].strip if h
60
+ end
61
+
62
+ @state = :finished
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ require 'URI' unless defined?(URI)
2
+
3
+ module WebSocket
4
+ module Handshake
5
+ class Client < Base
6
+
7
+ def initialize(args = {})
8
+ super
9
+
10
+ @version = args[:version] || DEFAULT_VERSION
11
+ @origin = args[:origin]
12
+
13
+ if args[:uri]
14
+ uri = URI.parse(args[:uri])
15
+ @secure = (uri.scheme == 'wss')
16
+ @host = uri.host
17
+ @port = uri.port
18
+ @path = uri.path
19
+ @query = uri.query
20
+ end
21
+
22
+ @secure = args[:secure] if args[:secure]
23
+ @host = args[:host] if args[:host]
24
+ @port = args[:port] if args[:port]
25
+ @path = args[:path] if args[:path]
26
+ @query = args[:query] if args[:query]
27
+
28
+ @path ||= '/'
29
+
30
+ set_error(:no_host_provided) unless @host
31
+
32
+ include_version
33
+ end
34
+
35
+ def <<(data)
36
+ @data << data
37
+ if parse_data
38
+
39
+ end
40
+ end
41
+
42
+ def should_respond?
43
+ false
44
+ end
45
+
46
+ def uri
47
+ uri = @secure ? "wss://" : "ws://"
48
+ uri << @host
49
+ uri << ":#{@port}" if @port
50
+ uri << @path
51
+ uri << "?#{@query}" if @query
52
+ uri
53
+ end
54
+
55
+ private
56
+
57
+ def include_version
58
+ case @version
59
+ when 75 then extend Handler::Client75
60
+ when 76, 0 then extend Handler::Client76
61
+ when 1..3 then extend Handler::Client01
62
+ when 4..13 then extend Handler::Client04
63
+ else set_error(:unknown_protocol_version) and return false
64
+ end
65
+ return true
66
+ end
67
+
68
+ FIRST_LINE = /^HTTP\/1\.1 (\d{3})[\w\s]*$/
69
+
70
+ def parse_first_line(line)
71
+ line_parts = line.match(FIRST_LINE)
72
+ status = line_parts[1]
73
+ set_error(:invalid_status_code) and return unless status == '101'
74
+
75
+ return true
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ module WebSocket
2
+ module Handshake
3
+ module Handler
4
+
5
+ autoload :Base, "#{::WebSocket::ROOT}/websocket/handshake/handler/base"
6
+
7
+ autoload :Client, "#{::WebSocket::ROOT}/websocket/handshake/handler/client"
8
+ autoload :Client01, "#{::WebSocket::ROOT}/websocket/handshake/handler/client01"
9
+ autoload :Client04, "#{::WebSocket::ROOT}/websocket/handshake/handler/client04"
10
+ autoload :Client75, "#{::WebSocket::ROOT}/websocket/handshake/handler/client75"
11
+ autoload :Client76, "#{::WebSocket::ROOT}/websocket/handshake/handler/client76"
12
+
13
+ autoload :Server, "#{::WebSocket::ROOT}/websocket/handshake/handler/server"
14
+ autoload :Server04, "#{::WebSocket::ROOT}/websocket/handshake/handler/server04"
15
+ autoload :Server75, "#{::WebSocket::ROOT}/websocket/handshake/handler/server75"
16
+ autoload :Server76, "#{::WebSocket::ROOT}/websocket/handshake/handler/server76"
17
+
18
+ end
19
+ end
20
+ end