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