websocket 1.0.7 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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