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
@@ -1,9 +1,7 @@
1
1
  module WebSocket
2
2
  module Handshake
3
3
  module Handler
4
- module Client75
5
-
6
- include Client
4
+ class Client75 < Client
7
5
 
8
6
  private
9
7
 
@@ -13,10 +11,10 @@ module WebSocket
13
11
  ["Upgrade", "WebSocket"],
14
12
  ["Connection", "Upgrade"]
15
13
  ]
16
- host = @host
17
- host += ":#{@port}" if @port
14
+ host = @handshake.host
15
+ host += ":#{@handshake.port}" if @handshake.port
18
16
  keys << ["Host", host]
19
- keys << ["Origin", @origin] if @origin
17
+ keys << ["Origin", @handshake.origin] if @handshake.origin
20
18
  keys
21
19
  end
22
20
 
@@ -3,9 +3,7 @@ require 'digest/md5'
3
3
  module WebSocket
4
4
  module Handshake
5
5
  module Handler
6
- module Client76
7
-
8
- include Client75
6
+ class Client76 < Client75
9
7
 
10
8
  # @see WebSocket::Handshake::Base#valid?
11
9
  def valid?
@@ -65,7 +63,7 @@ module WebSocket
65
63
  # Verify if challenge sent by server match generated one
66
64
  # @return [Boolena] True if challenge matches, false otherwise(sets appropriate error)
67
65
  def verify_challenge
68
- set_error(:invalid_challenge) and return false unless @leftovers == challenge
66
+ raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.instance_variable_get('@leftovers') == challenge
69
67
  true
70
68
  end
71
69
 
@@ -94,7 +92,7 @@ module WebSocket
94
92
 
95
93
  # Generate third key
96
94
  def generate_key3
97
- return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
95
+ [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
98
96
  end
99
97
 
100
98
  end
@@ -1,9 +1,7 @@
1
1
  module WebSocket
2
2
  module Handshake
3
3
  module Handler
4
- module Server
5
-
6
- include Base
4
+ class Server < Base
7
5
 
8
6
  end
9
7
  end
@@ -4,13 +4,11 @@ require 'base64'
4
4
  module WebSocket
5
5
  module Handshake
6
6
  module Handler
7
- module Server04
8
-
9
- include Server
7
+ class Server04 < Server
10
8
 
11
9
  # @see WebSocket::Handshake::Base#valid?
12
10
  def valid?
13
- super && (@headers['sec-websocket-key'] ? true : (set_error(:invalid_handshake_authentication) and false))
11
+ super && verify_key
14
12
  end
15
13
 
16
14
  private
@@ -32,11 +30,15 @@ module WebSocket
32
30
  # Signature of response, created from client request Sec-WebSocket-Key
33
31
  # @return [String] signature
34
32
  def signature
35
- return unless key = @headers['sec-websocket-key']
33
+ return unless key = @handshake.headers['sec-websocket-key']
36
34
  string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
37
35
  Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
38
36
  end
39
37
 
38
+ def verify_key
39
+ (@handshake.headers['sec-websocket-key'] ? true : raise(WebSocket::Error::Handshake::InvalidAuthentication))
40
+ end
41
+
40
42
  end
41
43
  end
42
44
  end
@@ -1,9 +1,7 @@
1
1
  module WebSocket
2
2
  module Handshake
3
3
  module Handler
4
- module Server75
5
-
6
- include Server
4
+ class Server75 < Server
7
5
 
8
6
  private
9
7
 
@@ -17,8 +15,8 @@ module WebSocket
17
15
  [
18
16
  ["Upgrade", "WebSocket"],
19
17
  ["Connection", "Upgrade"],
20
- ["WebSocket-Origin", @headers['origin']],
21
- ["WebSocket-Location", uri]
18
+ ["WebSocket-Origin", @handshake.headers['origin']],
19
+ ["WebSocket-Location", @handshake.uri]
22
20
  ]
23
21
  end
24
22
 
@@ -3,9 +3,7 @@ require 'digest/md5'
3
3
  module WebSocket
4
4
  module Handshake
5
5
  module Handler
6
- module Server76
7
-
8
- include Server
6
+ class Server76 < Server
9
7
 
10
8
  # @see WebSocket::Handshake::Base#valid?
11
9
  def valid?
@@ -29,8 +27,8 @@ module WebSocket
29
27
  [
30
28
  ["Upgrade", "WebSocket"],
31
29
  ["Connection", "Upgrade"],
32
- ["Sec-WebSocket-Origin", @headers['origin']],
33
- ["Sec-WebSocket-Location", uri]
30
+ ["Sec-WebSocket-Origin", @handshake.headers['origin']],
31
+ ["Sec-WebSocket-Location", @handshake.uri]
34
32
  ]
35
33
  end
36
34
 
@@ -45,9 +43,9 @@ module WebSocket
45
43
  # @return [String] Challenge response or nil if error occured
46
44
  def challenge_response
47
45
  # Refer to 5.2 4-9 of the draft 76
48
- first = numbers_over_spaces(@headers['sec-websocket-key1']) || return
49
- second = numbers_over_spaces(@headers['sec-websocket-key2']) || return
50
- third = @leftovers.strip
46
+ first = numbers_over_spaces(@handshake.headers['sec-websocket-key1'].to_s)
47
+ second = numbers_over_spaces(@handshake.headers['sec-websocket-key2'].to_s)
48
+ third = @handshake.instance_variable_get('@leftovers').strip
51
49
 
52
50
  sum = [first].pack("N*") +
53
51
  [second].pack("N*") +
@@ -63,14 +61,14 @@ module WebSocket
63
61
 
64
62
  spaces = string.scan(/ /).size
65
63
  # As per 5.2.5, abort the connection if spaces are zero.
66
- set_error(:invalid_handshake_authentication) and return if spaces == 0
64
+ raise WebSocket::Error::Handshake::InvalidAuthentication if spaces == 0
67
65
 
68
66
  # As per 5.2.6, abort if numbers is not an integral multiple of spaces
69
- set_error(:invalid_handshake_authentication) and return if numbers % spaces != 0
67
+ raise WebSocket::Error::Handshake::InvalidAuthentication if numbers % spaces != 0
70
68
 
71
69
  quotient = numbers / spaces
72
70
 
73
- set_error(:invalid_handshake_authentication) and return if quotient > 2**32-1
71
+ raise WebSocket::Error::Handshake::InvalidAuthentication if quotient > 2**32-1
74
72
 
75
73
  return quotient
76
74
  end
@@ -65,6 +65,48 @@ module WebSocket
65
65
  set_version
66
66
  end
67
67
  end
68
+ rescue_method :<<
69
+
70
+ # Parse the request from a rack environment
71
+ # @param env Rack Environment
72
+ #
73
+ # @example
74
+ # @handshake.from_rack(env)
75
+ def from_rack(env)
76
+ @headers = env.select {|key, value|
77
+ key =~ /\AHTTP_/
78
+ }.inject({}) {|memo, tuple|
79
+ key, value = tuple
80
+ memo[key.gsub(/\AHTTP_/, '').gsub('_', '-').downcase] = value
81
+ memo
82
+ }
83
+
84
+ @path = env["REQUEST_PATH"]
85
+ @query = env["QUERY_STRING"]
86
+ @leftovers = env['rack.input'].read
87
+
88
+ set_version
89
+ @state = :finished
90
+ end
91
+
92
+ # Parse the request from hash
93
+ # @param hash Hash to import data
94
+ # @option hash [Hash] :headers HTTP headers of request, downcased
95
+ # @option hash [String] :path Path for request(without host and query string)
96
+ # @option hash [String] :query Query string for request
97
+ # @option hash [String] :body Body of request(if exists)
98
+ #
99
+ # @example
100
+ # @handshake.from_hash(hash)
101
+ def from_hash(hash)
102
+ @headers = hash[:headers] || {}
103
+ @path = hash[:path] || "/"
104
+ @query = hash[:query] || ""
105
+ @leftovers = hash[:body]
106
+
107
+ set_version
108
+ @state = :finished
109
+ end
68
110
 
69
111
  # Should send content to client after finished parsing?
70
112
  # @return [Boolean] true
@@ -98,13 +140,12 @@ module WebSocket
98
140
  # Include set of methods for selected protocol version
99
141
  # @return [Boolean] false if protocol number is unknown, otherwise true
100
142
  def include_version
101
- case @version
102
- when 75 then extend Handler::Server75
103
- when 76, 0..3 then extend Handler::Server76
104
- when 4..13 then extend Handler::Server04
105
- else set_error(:unknown_protocol_version) and return false
143
+ @handler = case @version
144
+ when 75 then Handler::Server75.new(self)
145
+ when 76, 0..3 then Handler::Server76.new(self)
146
+ when 4..13 then Handler::Server04.new(self)
147
+ else raise WebSocket::Error::Handshake::UnknownVersion
106
148
  end
107
- return true
108
149
  end
109
150
 
110
151
  PATH = /^(\w+) (\/[^\s]*) HTTP\/1\.1$/
@@ -114,14 +155,12 @@ module WebSocket
114
155
  # @return [Boolean] True if parsed correctly. False otherwise
115
156
  def parse_first_line(line)
116
157
  line_parts = line.match(PATH)
117
- set_error(:invalid_header) and return unless line_parts
158
+ raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
118
159
  method = line_parts[1].strip
119
- set_error(:get_request_required) and return unless method == "GET"
160
+ raise WebSocket::Error::Handshake::GetRequestRequired unless method == "GET"
120
161
 
121
162
  resource_name = line_parts[2].strip
122
163
  @path, @query = resource_name.split('?', 2)
123
-
124
- return true
125
164
  end
126
165
 
127
166
  end
@@ -1,3 +1,3 @@
1
1
  module WebSocket
2
- VERSION = '1.0.7'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -94,7 +94,7 @@ describe 'Incoming frame draft 03' do
94
94
  context "should raise error with invalid opcode" do
95
95
  let(:encoded_text) { "\x09\x05Hello" }
96
96
  let(:decoded_text) { nil }
97
- let(:error) { :unknown_opcode }
97
+ let(:error) { WebSocket::Error::Frame::UnknownOpcode }
98
98
 
99
99
  it_should_behave_like 'valid_incoming_frame'
100
100
  end
@@ -102,7 +102,7 @@ describe 'Incoming frame draft 03' do
102
102
  context "should raise error with too long frame" do
103
103
  let(:encoded_text) { "\x04\x7F" + "a" * WebSocket.max_frame_size }
104
104
  let(:decoded_text) { nil }
105
- let(:error) { :frame_too_long }
105
+ let(:error) { WebSocket::Error::Frame::TooLong }
106
106
 
107
107
  it_should_behave_like 'valid_incoming_frame'
108
108
  end
@@ -110,7 +110,7 @@ describe 'Incoming frame draft 03' do
110
110
  context "should raise error with continuation frame without more frame earlier" do
111
111
  let(:encoded_text) { "\x00\x05Hello" }
112
112
  let(:decoded_text) { nil }
113
- let(:error) { :unexpected_continuation_frame }
113
+ let(:error) { WebSocket::Error::Frame::UnexpectedContinuationFrame }
114
114
 
115
115
  it_should_behave_like 'valid_incoming_frame'
116
116
  end
@@ -94,7 +94,7 @@ describe 'Incoming frame draft 04' do
94
94
  context "should raise error with invalid opcode" do
95
95
  let(:encoded_text) { "\x89\x05Hello" }
96
96
  let(:decoded_text) { nil }
97
- let(:error) { :unknown_opcode }
97
+ let(:error) { WebSocket::Error::Frame::UnknownOpcode }
98
98
 
99
99
  it_should_behave_like 'valid_incoming_frame'
100
100
  end
@@ -102,7 +102,7 @@ describe 'Incoming frame draft 04' do
102
102
  context "should raise error with too long frame" do
103
103
  let(:encoded_text) { "\x84\x7F" + "a" * WebSocket.max_frame_size }
104
104
  let(:decoded_text) { nil }
105
- let(:error) { :frame_too_long }
105
+ let(:error) { WebSocket::Error::Frame::TooLong }
106
106
 
107
107
  it_should_behave_like 'valid_incoming_frame'
108
108
  end
@@ -110,7 +110,7 @@ describe 'Incoming frame draft 04' do
110
110
  context "should raise error with continuation frame without more frame earlier" do
111
111
  let(:encoded_text) { "\x80\x05Hello" }
112
112
  let(:decoded_text) { nil }
113
- let(:error) { :unexpected_continuation_frame }
113
+ let(:error) { WebSocket::Error::Frame::UnexpectedContinuationFrame }
114
114
 
115
115
  it_should_behave_like 'valid_incoming_frame'
116
116
  end
@@ -110,7 +110,7 @@ describe 'Incoming frame draft 05' do
110
110
  context "should raise error with invalid opcode" do
111
111
  let(:encoded_text) { "\x89\x05Hello" }
112
112
  let(:decoded_text) { nil }
113
- let(:error) { :unknown_opcode }
113
+ let(:error) { WebSocket::Error::Frame::UnknownOpcode }
114
114
 
115
115
  it_should_behave_like 'valid_incoming_frame'
116
116
  end
@@ -118,7 +118,7 @@ describe 'Incoming frame draft 05' do
118
118
  context "should raise error with too long frame" do
119
119
  let(:encoded_text) { "\x84\x7F" + "a" * WebSocket.max_frame_size }
120
120
  let(:decoded_text) { nil }
121
- let(:error) { :frame_too_long }
121
+ let(:error) { WebSocket::Error::Frame::TooLong }
122
122
 
123
123
  it_should_behave_like 'valid_incoming_frame'
124
124
  end
@@ -126,7 +126,7 @@ describe 'Incoming frame draft 05' do
126
126
  context "should raise error with continuation frame without more frame earlier" do
127
127
  let(:encoded_text) { "\x80\x05Hello" }
128
128
  let(:decoded_text) { nil }
129
- let(:error) { :unexpected_continuation_frame }
129
+ let(:error) { WebSocket::Error::Frame::UnexpectedContinuationFrame }
130
130
 
131
131
  it_should_behave_like 'valid_incoming_frame'
132
132
  end
@@ -110,7 +110,7 @@ describe 'Incoming frame draft 07' do
110
110
  context "should raise error with invalid opcode" do
111
111
  let(:encoded_text) { "\x85\x05Hello" }
112
112
  let(:decoded_text) { nil }
113
- let(:error) { :unknown_opcode }
113
+ let(:error) { WebSocket::Error::Frame::UnknownOpcode }
114
114
 
115
115
  it_should_behave_like 'valid_incoming_frame'
116
116
  end
@@ -118,7 +118,7 @@ describe 'Incoming frame draft 07' do
118
118
  context "should raise error with too long frame" do
119
119
  let(:encoded_text) { "\x81\x7F" + "a" * WebSocket.max_frame_size }
120
120
  let(:decoded_text) { nil }
121
- let(:error) { :frame_too_long }
121
+ let(:error) { WebSocket::Error::Frame::TooLong }
122
122
 
123
123
  it_should_behave_like 'valid_incoming_frame'
124
124
  end
@@ -126,7 +126,7 @@ describe 'Incoming frame draft 07' do
126
126
  context "should raise error with continuation frame without more frame earlier" do
127
127
  let(:encoded_text) { "\x80\x05Hello" }
128
128
  let(:decoded_text) { nil }
129
- let(:error) { :unexpected_continuation_frame }
129
+ let(:error) { WebSocket::Error::Frame::UnexpectedContinuationFrame }
130
130
 
131
131
  it_should_behave_like 'valid_incoming_frame'
132
132
  end
@@ -45,14 +45,14 @@ describe 'Incoming frame draft 75' do
45
45
 
46
46
  context "with invalid frame" do
47
47
  let(:encoded_text) { "invalid" }
48
- let(:error) { :invalid_frame }
48
+ let(:error) { WebSocket::Error::Frame::Invalid }
49
49
 
50
50
  it_should_behave_like 'valid_incoming_frame'
51
51
  end
52
52
 
53
53
  context "with too long frame" do
54
54
  let(:encoded_text) { "\x00" + "a" * WebSocket.max_frame_size + "\xFF" }
55
- let(:error) { :frame_too_long }
55
+ let(:error) { WebSocket::Error::Frame::TooLong }
56
56
 
57
57
  it_should_behave_like 'valid_incoming_frame'
58
58
  end
@@ -4,8 +4,8 @@ describe 'Client draft 4 handshake' do
4
4
  let(:handshake) { WebSocket::Handshake::Client.new({ :uri => 'ws://example.com/demo', :origin => 'http://example.com', :version => version }.merge(@request_params || {})) }
5
5
 
6
6
  let(:version) { 4 }
7
- let(:client_request) { client_handshake_04({ :key => handshake.send(:key), :version => version }.merge(@request_params || {})) }
8
- let(:server_response) { server_handshake_04({ :accept => handshake.send(:accept) }.merge(@request_params || {})) }
7
+ let(:client_request) { client_handshake_04({ :key => handshake.handler.send(:key), :version => version }.merge(@request_params || {})) }
8
+ let(:server_response) { server_handshake_04({ :accept => handshake.handler.send(:accept) }.merge(@request_params || {})) }
9
9
 
10
10
  it_should_behave_like 'all client drafts'
11
11
 
@@ -15,6 +15,6 @@ describe 'Client draft 4 handshake' do
15
15
 
16
16
  handshake.should be_finished
17
17
  handshake.should_not be_valid
18
- handshake.error.should eql(:invalid_accept)
18
+ handshake.error.should eql(:invalid_handshake_authentication)
19
19
  end
20
20
  end
@@ -4,8 +4,8 @@ describe 'Client draft 76 handshake' do
4
4
  let(:handshake) { WebSocket::Handshake::Client.new({ :uri => 'ws://example.com/demo', :origin => 'http://example.com', :version => version }.merge(@request_params || {})) }
5
5
 
6
6
  let(:version) { 76 }
7
- let(:client_request) { client_handshake_76({ :key1 => handshake.send(:key1), :key2 => handshake.send(:key2), :key3 => handshake.send(:key3) }.merge(@request_params || {})) }
8
- let(:server_response) { server_handshake_76({ :challenge => handshake.send(:challenge) }.merge(@request_params || {})) }
7
+ let(:client_request) { client_handshake_76({ :key1 => handshake.handler.send(:key1), :key2 => handshake.handler.send(:key2), :key3 => handshake.handler.send(:key3) }.merge(@request_params || {})) }
8
+ let(:server_response) { server_handshake_76({ :challenge => handshake.handler.send(:challenge) }.merge(@request_params || {})) }
9
9
 
10
10
  it_should_behave_like 'all client drafts'
11
11
 
@@ -15,6 +15,6 @@ describe 'Client draft 76 handshake' do
15
15
 
16
16
  handshake.should be_finished
17
17
  handshake.should_not be_valid
18
- handshake.error.should eql(:invalid_challenge)
18
+ handshake.error.should eql(:invalid_handshake_authentication)
19
19
  end
20
20
  end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rspec'
2
+ require 'webrick'
2
3
 
3
4
  require 'websocket'
4
5
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
@@ -83,4 +83,37 @@ shared_examples_for 'all server drafts' do
83
83
  handshake.should_not be_valid
84
84
  handshake.error.should eql(:get_request_required)
85
85
  end
86
+
87
+ it "should parse a rack request" do
88
+ request = WEBrick::HTTPRequest.new( :ServerSoftware => "rspec" )
89
+ request.parse(StringIO.new(client_request)).should be_true
90
+ rest = client_request.slice((request.to_s.length..-1))
91
+
92
+ handshake.from_rack(request.meta_vars.merge(
93
+ "rack.input" => StringIO.new(rest)
94
+ ))
95
+ validate_request
96
+ end
97
+
98
+ it "should parse a hash request" do
99
+ request = WEBrick::HTTPRequest.new( :ServerSoftware => "rspec" )
100
+ request.parse(StringIO.new(client_request)).should be_true
101
+ body = client_request.slice((request.to_s.length..-1))
102
+
103
+ path = request.path
104
+ query = request.query_string
105
+ headers = request.header.inject({}) do |hash, header|
106
+ hash[header[0]] = header[1].first if header[0] && header[1]
107
+ hash
108
+ end
109
+
110
+ handshake.from_hash({
111
+ :headers => headers,
112
+ :path => path,
113
+ :query => query,
114
+ :body => body
115
+ })
116
+
117
+ validate_request
118
+ end
86
119
  end