websocket 1.0.7 → 1.1.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 (48) hide show
  1. data/CHANGELOG.md +8 -0
  2. data/Gemfile +1 -0
  3. data/README.md +1 -0
  4. data/lib/websocket.rb +15 -4
  5. data/lib/websocket/error.rb +85 -0
  6. data/lib/websocket/exception_handler.rb +36 -0
  7. data/lib/websocket/frame/base.rb +13 -16
  8. data/lib/websocket/frame/data.rb +1 -1
  9. data/lib/websocket/frame/handler/base.rb +6 -2
  10. data/lib/websocket/frame/handler/handler03.rb +49 -55
  11. data/lib/websocket/frame/handler/handler04.rb +1 -3
  12. data/lib/websocket/frame/handler/handler05.rb +2 -6
  13. data/lib/websocket/frame/handler/handler07.rb +5 -7
  14. data/lib/websocket/frame/handler/handler75.rb +15 -19
  15. data/lib/websocket/frame/incoming.rb +2 -1
  16. data/lib/websocket/frame/incoming/client.rb +1 -3
  17. data/lib/websocket/frame/incoming/server.rb +1 -3
  18. data/lib/websocket/frame/outgoing.rb +3 -2
  19. data/lib/websocket/frame/outgoing/client.rb +1 -3
  20. data/lib/websocket/frame/outgoing/server.rb +1 -3
  21. data/lib/websocket/handshake/base.rb +9 -5
  22. data/lib/websocket/handshake/client.rb +13 -12
  23. data/lib/websocket/handshake/handler/base.rb +10 -2
  24. data/lib/websocket/handshake/handler/client.rb +3 -5
  25. data/lib/websocket/handshake/handler/client01.rb +2 -4
  26. data/lib/websocket/handshake/handler/client04.rb +6 -8
  27. data/lib/websocket/handshake/handler/client75.rb +4 -6
  28. data/lib/websocket/handshake/handler/client76.rb +3 -5
  29. data/lib/websocket/handshake/handler/server.rb +1 -3
  30. data/lib/websocket/handshake/handler/server04.rb +7 -5
  31. data/lib/websocket/handshake/handler/server75.rb +3 -5
  32. data/lib/websocket/handshake/handler/server76.rb +9 -11
  33. data/lib/websocket/handshake/server.rb +49 -10
  34. data/lib/websocket/version.rb +1 -1
  35. data/spec/frame/incoming_03_spec.rb +3 -3
  36. data/spec/frame/incoming_04_spec.rb +3 -3
  37. data/spec/frame/incoming_05_spec.rb +3 -3
  38. data/spec/frame/incoming_07_spec.rb +3 -3
  39. data/spec/frame/incoming_75_spec.rb +2 -2
  40. data/spec/handshake/client_04_spec.rb +3 -3
  41. data/spec/handshake/client_76_spec.rb +3 -3
  42. data/spec/spec_helper.rb +1 -0
  43. data/spec/support/all_server_drafts.rb +33 -0
  44. data/spec/support/frames_base.rb +1 -3
  45. data/spec/support/incoming_frames.rb +26 -1
  46. data/spec/support/overwrites.rb +9 -0
  47. data/websocket.gemspec +0 -2
  48. metadata +29 -53
@@ -3,9 +3,7 @@
3
3
  module WebSocket
4
4
  module Frame
5
5
  module Handler
6
- module Handler04
7
-
8
- include Handler03
6
+ class Handler04 < Handler03
9
7
 
10
8
  private
11
9
 
@@ -3,15 +3,11 @@
3
3
  module WebSocket
4
4
  module Frame
5
5
  module Handler
6
- module Handler05
7
-
8
- include Handler04
9
-
10
- private
6
+ class Handler05 < Handler04
11
7
 
12
8
  def encode_frame
13
9
  if @code
14
- @data = Data.new([@code].pack('n') + @data.to_s)
10
+ @frame.data = Data.new([@code].pack('n') + @frame.data.to_s)
15
11
  @code = nil
16
12
  end
17
13
  super
@@ -3,11 +3,7 @@
3
3
  module WebSocket
4
4
  module Frame
5
5
  module Handler
6
- module Handler07
7
-
8
- include Handler05
9
-
10
- private
6
+ class Handler07 < Handler05
11
7
 
12
8
  # Hash of frame names and it's opcodes
13
9
  FRAME_TYPES = {
@@ -22,12 +18,14 @@ module WebSocket
22
18
  # Hash of frame opcodes and it's names
23
19
  FRAME_TYPES_INVERSE = FRAME_TYPES.invert
24
20
 
21
+ private
22
+
25
23
  # Convert frame type name to opcode
26
24
  # @param [Symbol] frame_type Frame type name
27
25
  # @return [Integer] opcode or nil
28
26
  # @raise [WebSocket::Error] if frame opcode is not known
29
27
  def type_to_opcode(frame_type)
30
- FRAME_TYPES[frame_type] || raise(WebSocket::Error, :unknown_frame_type)
28
+ FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
31
29
  end
32
30
 
33
31
  # Convert frame opcode to type name
@@ -35,7 +33,7 @@ module WebSocket
35
33
  # @return [Symbol] Frame type name or nil
36
34
  # @raise [WebSocket::Error] if frame type name is not known
37
35
  def opcode_to_type(opcode)
38
- FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error, :unknown_opcode)
36
+ FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
39
37
  end
40
38
 
41
39
  end
@@ -3,23 +3,19 @@
3
3
  module WebSocket
4
4
  module Frame
5
5
  module Handler
6
- module Handler75
7
-
8
- include Base
6
+ class Handler75 < Base
9
7
 
10
8
  # @see WebSocket::Frame::Base#supported_frames
11
9
  def supported_frames
12
10
  [:text, :close]
13
11
  end
14
12
 
15
- private
16
-
17
13
  # @see WebSocket::Frame::Handler::Base#encode_frame
18
14
  def encode_frame
19
- case @type
15
+ case @frame.type
20
16
  when :close then "\xff\x00"
21
17
  when :text then
22
- ary = ["\x00", @data, "\xff"]
18
+ ary = ["\x00", @frame.data, "\xff"]
23
19
  ary.collect{ |s| s.encode('UTF-8', 'UTF-8', :invalid => :replace) if s.respond_to?(:encode) }
24
20
  ary.join
25
21
  end
@@ -27,10 +23,10 @@ module WebSocket
27
23
 
28
24
  # @see WebSocket::Frame::Handler::Base#decode_frame
29
25
  def decode_frame
30
- return if @data.size == 0
26
+ return if @frame.data.size == 0
31
27
 
32
28
  pointer = 0
33
- frame_type = @data.getbyte(pointer)
29
+ frame_type = @frame.data.getbyte(pointer)
34
30
  pointer += 1
35
31
 
36
32
  if (frame_type & 0x80) == 0x80
@@ -38,40 +34,40 @@ module WebSocket
38
34
  length = 0
39
35
 
40
36
  loop do
41
- return if !@data.getbyte(pointer)
42
- b = @data.getbyte(pointer)
37
+ return if !@frame.data.getbyte(pointer)
38
+ b = @frame.data.getbyte(pointer)
43
39
  pointer += 1
44
40
  b_v = b & 0x7F
45
41
  length = length * 128 + b_v
46
42
  break unless (b & 0x80) == 0x80
47
43
  end
48
44
 
49
- set_error(:frame_too_long) and return if length > ::WebSocket.max_frame_size
45
+ raise WebSocket::Error::Frame::TooLong if length > ::WebSocket.max_frame_size
50
46
 
51
- unless @data.getbyte(pointer+length-1) == nil
47
+ unless @frame.data.getbyte(pointer+length-1) == nil
52
48
  # Straight from spec - I'm sure this isn't crazy...
53
49
  # 6. Read /length/ bytes.
54
50
  # 7. Discard the read bytes.
55
- @data = @data[(pointer+length)..-1]
51
+ @frame.instance_variable_set '@data', @frame.data[(pointer+length)..-1]
56
52
 
57
53
  # If the /frame type/ is 0xFF and the /length/ was 0, then close
58
54
  if length == 0
59
- self.class.new(:version => version, :type => :close, :decoded => true)
55
+ @frame.class.new(:version => @frame.version, :type => :close, :decoded => true)
60
56
  end
61
57
  end
62
58
  else
63
59
  # If the high-order bit of the /frame type/ byte is _not_ set
64
60
 
65
- set_error(:invalid_frame) and return if @data.getbyte(0) != 0x00
61
+ raise WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00
66
62
 
67
63
  # Addition to the spec to protect against malicious requests
68
- set_error(:frame_too_long) and return if @data.size > ::WebSocket.max_frame_size
64
+ raise WebSocket::Error::Frame::TooLong if @frame.data.size > ::WebSocket.max_frame_size
69
65
 
70
- msg = @data.slice!(/\A\x00[^\xff]*\xff/)
66
+ msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/)
71
67
  if msg
72
68
  msg.gsub!(/\A\x00|\xff\z/, '')
73
69
  msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
74
- self.class.new(:version => version, :type => :text, :data => msg, :decoded => true)
70
+ @frame.class.new(:version => @frame.version, :type => :text, :data => msg, :decoded => true)
75
71
  end
76
72
  end
77
73
  end
@@ -37,8 +37,9 @@ module WebSocket
37
37
  # Check #error if nil received to check for eventual parsing errors
38
38
  # @return [WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
39
39
  def next
40
- decode_frame unless decoded?
40
+ @handler.decode_frame unless decoded?
41
41
  end
42
+ rescue_method :next
42
43
 
43
44
  # If decoded then this will return frame content. Otherwise it will return raw frame.
44
45
  # @return [String] Data of frame
@@ -3,14 +3,12 @@ module WebSocket
3
3
  class Incoming
4
4
  class Client < Incoming
5
5
 
6
- private
7
-
8
6
  def incoming_masking?
9
7
  false
10
8
  end
11
9
 
12
10
  def outgoing_masking?
13
- masking?
11
+ @handler.masking?
14
12
  end
15
13
 
16
14
  end
@@ -3,10 +3,8 @@ module WebSocket
3
3
  class Incoming
4
4
  class Server < Incoming
5
5
 
6
- private
7
-
8
6
  def incoming_masking?
9
- masking?
7
+ @handler.masking?
10
8
  end
11
9
 
12
10
  def outgoing_masking?
@@ -25,9 +25,10 @@ module WebSocket
25
25
 
26
26
  # Return raw frame formatted for sending.
27
27
  def to_s
28
- set_error(:unknown_frame_type) and return unless supported?
29
- encode_frame
28
+ raise WebSocket::Error::Frame::UnknownFrameType unless supported?
29
+ @handler.encode_frame
30
30
  end
31
+ rescue_method :to_s
31
32
 
32
33
  end
33
34
  end
@@ -3,14 +3,12 @@ module WebSocket
3
3
  class Outgoing
4
4
  class Client < Outgoing
5
5
 
6
- private
7
-
8
6
  def incoming_masking?
9
7
  false
10
8
  end
11
9
 
12
10
  def outgoing_masking?
13
- masking?
11
+ @handler.masking?
14
12
  end
15
13
 
16
14
  end
@@ -3,10 +3,8 @@ module WebSocket
3
3
  class Outgoing
4
4
  class Server < Outgoing
5
5
 
6
- private
7
-
8
6
  def incoming_masking?
9
- masking?
7
+ @handler.masking?
10
8
  end
11
9
 
12
10
  def outgoing_masking?
@@ -2,13 +2,15 @@ module WebSocket
2
2
  module Handshake
3
3
  # @abstract Subclass and override to implement custom handshakes
4
4
  class Base
5
+ include ExceptionHandler
5
6
 
6
7
  attr_reader :host, :port, :path, :query,
7
- :error, :state, :version, :secure
8
+ :state, :version, :secure, :headers
8
9
 
9
10
  # Initialize new WebSocket Handshake and set it's state to :new
10
11
  def initialize(args = {})
11
12
  @state = :new
13
+ @handler = nil
12
14
 
13
15
  @data = ""
14
16
  @headers = {}
@@ -22,8 +24,9 @@ module WebSocket
22
24
  # Return textual representation of handshake request or response
23
25
  # @return [String] text of response
24
26
  def to_s
25
- ""
27
+ @handler ? @handler.to_s : ""
26
28
  end
29
+ rescue_method :to_s, :return => ""
27
30
 
28
31
  # Recreate inspect as #to_s was overwritten
29
32
  def inspect
@@ -41,8 +44,9 @@ module WebSocket
41
44
  # Is parsed data valid?
42
45
  # @return [Boolean] False if some errors occured. Reason for error could be found in error method
43
46
  def valid?
44
- finished? && @error == nil
47
+ finished? && @error == nil && @handler && @handler.valid?
45
48
  end
49
+ rescue_method :valid?, :return => false
46
50
 
47
51
  # @abstract Should send data after parsing is finished?
48
52
  def should_respond?
@@ -80,7 +84,7 @@ module WebSocket
80
84
  # @param [String] message Error message to set
81
85
  def set_error(message)
82
86
  @state = :error
83
- @error = message
87
+ super
84
88
  end
85
89
 
86
90
  HEADER = /^([^:]+):\s*(.+)$/
@@ -94,7 +98,7 @@ module WebSocket
94
98
  lines = header.split("\r\n")
95
99
 
96
100
  first_line = lines.shift
97
- return false unless parse_first_line(first_line)
101
+ parse_first_line(first_line)
98
102
 
99
103
  lines.each do |line|
100
104
  h = HEADER.match(line)
@@ -33,6 +33,8 @@ module WebSocket
33
33
  #
34
34
  class Client < Base
35
35
 
36
+ attr_reader :origin
37
+
36
38
  # Initialize new WebSocket Client
37
39
  #
38
40
  # @param [Hash] args Arguments for client
@@ -72,10 +74,11 @@ module WebSocket
72
74
 
73
75
  @path = '/' if @path.nil? || @path.empty?
74
76
 
75
- set_error(:no_host_provided) unless @host
77
+ raise WebSocket::Error::Handshake::NoHostProvided unless @host
76
78
 
77
79
  include_version
78
80
  end
81
+ rescue_method :initialize
79
82
 
80
83
  # Add text of response from Server. This method will parse content immediately and update state and error(if neccessary)
81
84
  #
@@ -95,6 +98,7 @@ module WebSocket
95
98
 
96
99
  end
97
100
  end
101
+ rescue_method :<<
98
102
 
99
103
  # Should send content to server after finished parsing?
100
104
  # @return [Boolean] false
@@ -107,14 +111,13 @@ module WebSocket
107
111
  # Include set of methods for selected protocol version
108
112
  # @return [Boolean] false if protocol number is unknown, otherwise true
109
113
  def include_version
110
- case @version
111
- when 75 then extend Handler::Client75
112
- when 76, 0 then extend Handler::Client76
113
- when 1..3 then extend Handler::Client01
114
- when 4..13 then extend Handler::Client04
115
- else set_error(:unknown_protocol_version) and return false
114
+ @handler = case @version
115
+ when 75 then Handler::Client75.new(self)
116
+ when 76, 0 then Handler::Client76.new(self)
117
+ when 1..3 then Handler::Client01.new(self)
118
+ when 4..13 then Handler::Client04.new(self)
119
+ else raise WebSocket::Error::Handshake::UnknownVersion
116
120
  end
117
- return true
118
121
  end
119
122
 
120
123
  FIRST_LINE = /^HTTP\/1\.1 (\d{3})[\w\s]*$/
@@ -124,11 +127,9 @@ module WebSocket
124
127
  # @return [Boolean] True if parsed correctly. False otherwise
125
128
  def parse_first_line(line)
126
129
  line_parts = line.match(FIRST_LINE)
127
- set_error(:invalid_header) and return false unless line_parts
130
+ raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
128
131
  status = line_parts[1]
129
- set_error(:invalid_status_code) and return false unless status == '101'
130
-
131
- return true
132
+ raise WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
132
133
  end
133
134
 
134
135
  end
@@ -1,8 +1,12 @@
1
1
  module WebSocket
2
2
  module Handshake
3
3
  module Handler
4
- # This module and it's descendants are included in client or server handshake in order to extend basic functionality
5
- module Base
4
+ # This class and it's descendants are included in client or server handshake in order to extend basic functionality
5
+ class Base
6
+
7
+ def initialize(handshake)
8
+ @handshake = handshake
9
+ end
6
10
 
7
11
  # @see WebSocket::Handshake::Base#to_s
8
12
  def to_s
@@ -15,6 +19,10 @@ module WebSocket
15
19
  result.join("\r\n")
16
20
  end
17
21
 
22
+ def valid?
23
+ true
24
+ end
25
+
18
26
  private
19
27
 
20
28
  # Set first line of text representation according to specification.
@@ -1,16 +1,14 @@
1
1
  module WebSocket
2
2
  module Handshake
3
3
  module Handler
4
- module Client
5
-
6
- include Base
4
+ class Client < Base
7
5
 
8
6
  private
9
7
 
10
8
  # @see WebSocket::Handshake::Handler::Base#header_line
11
9
  def header_line
12
- path = @path
13
- path += "?" + @query if @query
10
+ path = @handshake.path
11
+ path += "?" + @handshake.query if @handshake.query
14
12
  "GET #{path} HTTP/1.1"
15
13
  end
16
14
 
@@ -3,16 +3,14 @@ require 'digest/md5'
3
3
  module WebSocket
4
4
  module Handshake
5
5
  module Handler
6
- module Client01
7
-
8
- include Client76
6
+ class Client01 < Client76
9
7
 
10
8
  private
11
9
 
12
10
  # @see WebSocket::Handshake::Handler::Base#handshake_keys
13
11
  def handshake_keys
14
12
  keys = super
15
- keys << ['Sec-WebSocket-Draft', @version]
13
+ keys << ['Sec-WebSocket-Draft', @handshake.version]
16
14
  keys
17
15
  end
18
16
 
@@ -4,9 +4,7 @@ require 'base64'
4
4
  module WebSocket
5
5
  module Handshake
6
6
  module Handler
7
- module Client04
8
-
9
- include Client
7
+ class Client04 < Client
10
8
 
11
9
  # @see WebSocket::Handshake::Base#valid?
12
10
  def valid?
@@ -21,11 +19,11 @@ module WebSocket
21
19
  ["Upgrade", "websocket"],
22
20
  ["Connection", "Upgrade"]
23
21
  ]
24
- host = @host
25
- host += ":#{@port}" if @port
22
+ host = @handshake.host
23
+ host += ":#{@handshake.port}" if @handshake.port
26
24
  keys << ["Host", host]
27
- keys << ["Sec-WebSocket-Origin", @origin] if @origin
28
- keys << ["Sec-WebSocket-Version", @version ]
25
+ keys << ["Sec-WebSocket-Origin", @handshake.origin] if @handshake.origin
26
+ keys << ["Sec-WebSocket-Version", @handshake.version ]
29
27
  keys << ["Sec-WebSocket-Key", key]
30
28
  keys
31
29
  end
@@ -45,7 +43,7 @@ module WebSocket
45
43
  # Verify if received header Sec-WebSocket-Accept matches generated one.
46
44
  # @return [Boolean] True if accept is matching. False otherwise(appropriate error is set)
47
45
  def verify_accept
48
- set_error(:invalid_accept) and return false unless @headers['sec-websocket-accept'] == accept
46
+ raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.headers['sec-websocket-accept'] == accept
49
47
  true
50
48
  end
51
49