em-websocket 0.3.7 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +52 -0
  3. data/Gemfile +6 -0
  4. data/LICENCE +7 -0
  5. data/README.md +105 -40
  6. data/examples/echo.rb +22 -6
  7. data/examples/test.html +5 -6
  8. data/lib/em-websocket.rb +2 -1
  9. data/lib/em-websocket/close03.rb +3 -0
  10. data/lib/em-websocket/close05.rb +3 -0
  11. data/lib/em-websocket/close06.rb +3 -0
  12. data/lib/em-websocket/close75.rb +2 -1
  13. data/lib/em-websocket/connection.rb +154 -48
  14. data/lib/em-websocket/framing03.rb +3 -2
  15. data/lib/em-websocket/framing05.rb +3 -2
  16. data/lib/em-websocket/framing07.rb +16 -4
  17. data/lib/em-websocket/framing76.rb +1 -4
  18. data/lib/em-websocket/handler.rb +61 -15
  19. data/lib/em-websocket/handler03.rb +0 -1
  20. data/lib/em-websocket/handler05.rb +0 -1
  21. data/lib/em-websocket/handler06.rb +0 -1
  22. data/lib/em-websocket/handler07.rb +0 -1
  23. data/lib/em-websocket/handler08.rb +0 -1
  24. data/lib/em-websocket/handler13.rb +0 -1
  25. data/lib/em-websocket/handler76.rb +2 -0
  26. data/lib/em-websocket/handshake.rb +156 -0
  27. data/lib/em-websocket/handshake04.rb +18 -16
  28. data/lib/em-websocket/handshake75.rb +15 -8
  29. data/lib/em-websocket/handshake76.rb +15 -14
  30. data/lib/em-websocket/masking04.rb +3 -6
  31. data/lib/em-websocket/message_processor_03.rb +6 -3
  32. data/lib/em-websocket/message_processor_06.rb +30 -9
  33. data/lib/em-websocket/version.rb +1 -1
  34. data/lib/em-websocket/websocket.rb +24 -15
  35. data/spec/helper.rb +84 -51
  36. data/spec/integration/common_spec.rb +89 -69
  37. data/spec/integration/draft03_spec.rb +84 -56
  38. data/spec/integration/draft05_spec.rb +14 -12
  39. data/spec/integration/draft06_spec.rb +67 -7
  40. data/spec/integration/draft13_spec.rb +30 -19
  41. data/spec/integration/draft75_spec.rb +46 -40
  42. data/spec/integration/draft76_spec.rb +59 -45
  43. data/spec/integration/gte_03_examples.rb +42 -0
  44. data/spec/integration/shared_examples.rb +119 -0
  45. data/spec/unit/framing_spec.rb +24 -4
  46. data/spec/unit/handshake_spec.rb +216 -0
  47. data/spec/unit/masking_spec.rb +2 -0
  48. metadata +32 -86
  49. data/examples/flash_policy_file_server.rb +0 -21
  50. data/examples/js/FABridge.js +0 -604
  51. data/examples/js/WebSocketMain.swf +0 -0
  52. data/examples/js/swfobject.js +0 -4
  53. data/examples/js/web_socket.js +0 -312
  54. data/lib/em-websocket/handler_factory.rb +0 -109
  55. data/spec/unit/handler_spec.rb +0 -159
@@ -0,0 +1,42 @@
1
+ shared_examples_for "a WebSocket server drafts 3 and above" do
2
+ it "should force close connections after a timeout if close handshake is not sent by the client" do
3
+ em {
4
+ server_onerror_fired = false
5
+ server_onclose_fired = false
6
+ client_got_close_handshake = false
7
+
8
+ start_server(:close_timeout => 0.1) { |ws|
9
+ ws.onopen {
10
+ # 1: Send close handshake to client
11
+ EM.next_tick { ws.close(4999, "Close message") }
12
+ }
13
+
14
+ ws.onerror { |e|
15
+ # 3: Client should receive onerror
16
+ e.class.should == EM::WebSocket::WSProtocolError
17
+ e.message.should == "Close handshake un-acked after 0.1s, closing tcp connection"
18
+ server_onerror_fired = true
19
+ }
20
+
21
+ ws.onclose {
22
+ server_onclose_fired = true
23
+ }
24
+ }
25
+ start_client { |client|
26
+ client.onmessage { |msg|
27
+ # 2: Client does not respond to close handshake (the fake client
28
+ # doesn't understand them at all hence this is in onmessage)
29
+ msg.should =~ /Close message/ if version >= 6
30
+ client_got_close_handshake = true
31
+ }
32
+
33
+ client.onclose {
34
+ server_onerror_fired.should == true
35
+ server_onclose_fired.should == true
36
+ client_got_close_handshake.should == true
37
+ done
38
+ }
39
+ }
40
+ }
41
+ end
42
+ end
@@ -3,6 +3,125 @@
3
3
  # These tests are run against all draft versions
4
4
  #
5
5
  shared_examples_for "a websocket server" do
6
+ it "should expose the protocol version" do
7
+ em {
8
+ start_server { |ws|
9
+ ws.onopen { |handshake|
10
+ handshake.protocol_version.should == version
11
+ done
12
+ }
13
+ }
14
+
15
+ start_client
16
+ }
17
+ end
18
+
19
+ it "should expose the origin header" do
20
+ em {
21
+ start_server { |ws|
22
+ ws.onopen { |handshake|
23
+ handshake.origin.should == 'http://example.com'
24
+ done
25
+ }
26
+ }
27
+
28
+ start_client
29
+ }
30
+ end
31
+
32
+ it "should expose the remote IP address" do
33
+ em {
34
+ start_server { |ws|
35
+ ws.onopen {
36
+ ws.remote_ip.should == "127.0.0.1"
37
+ done
38
+ }
39
+ }
40
+
41
+ start_client
42
+ }
43
+ end
44
+
45
+ it "should send messages successfully" do
46
+ em {
47
+ start_server { |ws|
48
+ ws.onmessage { |message|
49
+ message.should == "hello server"
50
+ done
51
+ }
52
+ }
53
+
54
+ start_client { |client|
55
+ client.onopen {
56
+ client.send("hello server")
57
+ }
58
+ }
59
+ }
60
+ end
61
+
62
+ it "should allow connection to be closed with valid close code" do
63
+ em {
64
+ start_server { |ws|
65
+ ws.onopen {
66
+ ws.close(4004, "Bye bye")
67
+ done
68
+ }
69
+ }
70
+
71
+ start_client
72
+ # TODO: Use a real client which understands how to respond to closing
73
+ # handshakes, sending the handshake currently untested
74
+ }
75
+ end
76
+
77
+ it "should raise error if if invalid close code is used" do
78
+ em {
79
+ start_server { |ws|
80
+ ws.onopen {
81
+ lambda {
82
+ ws.close(2000)
83
+ }.should raise_error("Application code may only use codes from 1000, 3000-4999")
84
+ done
85
+ }
86
+ }
87
+
88
+ start_client
89
+ }
90
+ end
91
+
92
+ it "should call onclose with was_clean set to false if connection closed without closing handshake by server" do
93
+ em {
94
+ start_server { |ws|
95
+ ws.onopen {
96
+ # Close tcp connection (no close handshake)
97
+ ws.close_connection
98
+ }
99
+ ws.onclose { |event|
100
+ event.should == {:code => 1006, :was_clean => false}
101
+ done
102
+ }
103
+ }
104
+ start_client
105
+ }
106
+ end
107
+
108
+ it "should call onclose with was_clean set to false if connection closed without closing handshake by client" do
109
+ em {
110
+ start_server { |ws|
111
+ ws.onclose { |event|
112
+ event.should == {:code => 1006, :was_clean => false}
113
+ done
114
+ }
115
+ }
116
+ start_client { |client|
117
+ client.onopen {
118
+ # Close tcp connection (no close handshake)
119
+ client.close_connection
120
+ }
121
+ }
122
+ }
123
+ end
124
+
6
125
  it "should call onerror if an application error raised in onopen" do
7
126
  em {
8
127
  start_server { |ws|
@@ -1,3 +1,5 @@
1
+ # encoding: BINARY
2
+
1
3
  require 'helper'
2
4
 
3
5
  describe EM::WebSocket::Framing03 do
@@ -13,7 +15,7 @@ describe EM::WebSocket::Framing03 do
13
15
 
14
16
  def <<(data)
15
17
  @data << data
16
- process_data(data)
18
+ process_data
17
19
  end
18
20
 
19
21
  def debug(*args); end
@@ -138,7 +140,7 @@ describe EM::WebSocket::Framing04 do
138
140
 
139
141
  def <<(data)
140
142
  @data << data
141
- process_data(data)
143
+ process_data
142
144
  end
143
145
 
144
146
  def debug(*args); end
@@ -207,7 +209,7 @@ describe EM::WebSocket::Framing07 do
207
209
 
208
210
  def <<(data)
209
211
  @data << data
210
- process_data(data)
212
+ process_data
211
213
  end
212
214
 
213
215
  def debug(*args); end
@@ -265,7 +267,7 @@ describe EM::WebSocket::Framing07 do
265
267
  lambda {
266
268
  # Opcode 3 is not supported by this draft
267
269
  @f << "\x83\x05Hello"
268
- }.should raise_error(EventMachine::WebSocket::WSProtocolError, "Unknown opcode")
270
+ }.should raise_error(EventMachine::WebSocket::WSProtocolError, "Unknown opcode 3")
269
271
  end
270
272
 
271
273
  it "should accept a fragmented unmasked text message in 3 frames" do
@@ -274,5 +276,23 @@ describe EM::WebSocket::Framing07 do
274
276
  @f << "\x00\x02lo"
275
277
  @f << "\x80\x06 world"
276
278
  end
279
+
280
+ it "should raise if non-fin frame is followed by a non-continuation data frame (continuation frame would be expected)" do
281
+ lambda {
282
+ @f << 0b00000001 # Not fin, text
283
+ @f << 0b00000001 # Length 1
284
+ @f << 'f'
285
+ @f << 0b10000001 # fin, text (continutation expected)
286
+ @f << 0b00000001 # Length 1
287
+ @f << 'b'
288
+ }.should raise_error(EM::WebSocket::WebSocketError, 'Continuation frame expected')
289
+ end
290
+
291
+ it "should raise on non-fin control frames (control frames must not be fragmented)" do
292
+ lambda {
293
+ @f << 0b00001010 # Not fin, pong (opcode 10)
294
+ @f << 0b00000000 # Length 1
295
+ }.should raise_error(EM::WebSocket::WebSocketError, 'Control frames must not be fragmented')
296
+ end
277
297
  end
278
298
  end
@@ -0,0 +1,216 @@
1
+ require 'helper'
2
+
3
+ describe EM::WebSocket::Handshake do
4
+ def handshake(request, secure = false)
5
+ handshake = EM::WebSocket::Handshake.new(secure)
6
+ handshake.receive_data(format_request(request))
7
+ handshake
8
+ end
9
+
10
+ before :each do
11
+ @request = {
12
+ :port => 80,
13
+ :method => "GET",
14
+ :path => "/demo",
15
+ :headers => {
16
+ 'Host' => 'example.com',
17
+ 'Connection' => 'Upgrade',
18
+ 'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
19
+ 'Sec-WebSocket-Protocol' => 'sample',
20
+ 'Upgrade' => 'WebSocket',
21
+ 'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
22
+ 'Origin' => 'http://example.com'
23
+ },
24
+ :body => '^n:ds[4U'
25
+ }
26
+ @secure_request = @request.merge(:port => 443)
27
+
28
+ @response = {
29
+ :headers => {
30
+ "Upgrade" => "WebSocket",
31
+ "Connection" => "Upgrade",
32
+ "Sec-WebSocket-Location" => "ws://example.com/demo",
33
+ "Sec-WebSocket-Origin" => "http://example.com",
34
+ "Sec-WebSocket-Protocol" => "sample"
35
+ },
36
+ :body => "8jKS\'y:G*Co,Wxa-"
37
+ }
38
+ @secure_response = @response.merge(:headers => @response[:headers].merge('Sec-WebSocket-Location' => "wss://example.com/demo"))
39
+ end
40
+
41
+ it "should handle good request" do
42
+ handshake(@request).should succeed_with_upgrade(@response)
43
+ end
44
+
45
+ it "should handle good request to secure default port if secure mode is enabled" do
46
+ handshake(@secure_request, true).
47
+ should succeed_with_upgrade(@secure_response)
48
+ end
49
+
50
+ it "should not handle good request to secure default port if secure mode is disabled" do
51
+ handshake(@secure_request, false).
52
+ should_not succeed_with_upgrade(@secure_response)
53
+ end
54
+
55
+ it "should handle good request on nondefault port" do
56
+ @request[:port] = 8081
57
+ @request[:headers]['Host'] = 'example.com:8081'
58
+ @response[:headers]['Sec-WebSocket-Location'] =
59
+ 'ws://example.com:8081/demo'
60
+
61
+ handshake(@request).should succeed_with_upgrade(@response)
62
+ end
63
+
64
+ it "should handle good request to secure nondefault port" do
65
+ @secure_request[:port] = 8081
66
+ @secure_request[:headers]['Host'] = 'example.com:8081'
67
+ @secure_response[:headers]['Sec-WebSocket-Location'] = 'wss://example.com:8081/demo'
68
+
69
+ handshake(@secure_request, true).
70
+ should succeed_with_upgrade(@secure_response)
71
+ end
72
+
73
+ it "should handle good request with no protocol" do
74
+ @request[:headers].delete('Sec-WebSocket-Protocol')
75
+ @response[:headers].delete("Sec-WebSocket-Protocol")
76
+
77
+ handshake(@request).should succeed_with_upgrade(@response)
78
+ end
79
+
80
+ it "should handle extra headers by simply ignoring them" do
81
+ @request[:headers]['EmptyValue'] = ""
82
+ @request[:headers]['AKey'] = "AValue"
83
+
84
+ handshake(@request).should succeed_with_upgrade(@response)
85
+ end
86
+
87
+ it "should raise error on HTTP request" do
88
+ @request[:headers] = {
89
+ 'Host' => 'www.google.com',
90
+ 'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB6 GTBA',
91
+ 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
92
+ 'Accept-Language' => 'en-us,en;q=0.5',
93
+ 'Accept-Encoding' => 'gzip,deflate',
94
+ 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
95
+ 'Keep-Alive' => '300',
96
+ 'Connection' => 'keep-alive',
97
+ }
98
+
99
+ handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
100
+ end
101
+
102
+ it "should raise error on wrong method" do
103
+ @request[:method] = 'POST'
104
+
105
+ handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
106
+ end
107
+
108
+ it "should raise error if upgrade header incorrect" do
109
+ @request[:headers]['Upgrade'] = 'NonWebSocket'
110
+
111
+ handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
112
+ end
113
+
114
+ it "should raise error if Sec-WebSocket-Protocol is empty" do
115
+ @request[:headers]['Sec-WebSocket-Protocol'] = ''
116
+
117
+ handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
118
+ end
119
+
120
+ %w[Sec-WebSocket-Key1 Sec-WebSocket-Key2].each do |header|
121
+ it "should raise error if #{header} has zero spaces" do
122
+ @request[:headers][header] = 'nospaces'
123
+
124
+ handshake(@request).
125
+ should fail_with_error(EM::WebSocket::HandshakeError, 'Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack')
126
+ end
127
+ end
128
+
129
+ it "should raise error if Sec-WebSocket-Key1 is missing" do
130
+ @request[:headers].delete("Sec-WebSocket-Key1")
131
+
132
+ # The error message isn't correct since key1 is used to heuristically
133
+ # determine the protocol version in use, however this test at least checks
134
+ # that the handshake does correctly fail
135
+ handshake(@request).
136
+ should fail_with_error(EM::WebSocket::HandshakeError, 'Extra bytes after header')
137
+ end
138
+
139
+ it "should raise error if Sec-WebSocket-Key2 is missing" do
140
+ @request[:headers].delete("Sec-WebSocket-Key2")
141
+
142
+ handshake(@request).
143
+ should fail_with_error(EM::WebSocket::HandshakeError, 'WebSocket key1 or key2 is missing')
144
+ end
145
+
146
+ it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
147
+ @request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
148
+
149
+ handshake(@request).
150
+ should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
151
+ end
152
+
153
+ it "should raise error if the HTTP header is empty" do
154
+ handshake = EM::WebSocket::Handshake.new(false)
155
+ handshake.receive_data("\r\n\r\nfoobar")
156
+
157
+ handshake.
158
+ should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid HTTP header: Could not parse data entirely (4 != 10)')
159
+ end
160
+
161
+ # This might seems crazy, but very occasionally we saw multiple "Upgrade:
162
+ # WebSocket" headers in the wild. RFC 4.2.1 isn't particularly clear on this
163
+ # point, so for now I have decided not to accept --@mloughran
164
+ it "should raise error on multiple upgrade headers" do
165
+ handshake = EM::WebSocket::Handshake.new(false)
166
+
167
+ # Add a duplicate upgrade header
168
+ headers = format_request(@request)
169
+ upgrade_header = "Upgrade: WebSocket\r\n"
170
+ headers.gsub!(upgrade_header, "#{upgrade_header}#{upgrade_header}")
171
+
172
+ handshake.receive_data(headers)
173
+
174
+ handshake.errback { |e|
175
+ e.class.should == EM::WebSocket::HandshakeError
176
+ e.message.should == 'Invalid upgrade header: ["WebSocket", "WebSocket"]'
177
+ }
178
+ end
179
+
180
+ it "should cope with requests where the header is split" do
181
+ request = format_request(@request)
182
+ incomplete_request = request[0...(request.length / 2)]
183
+ rest = request[(request.length / 2)..-1]
184
+ handshake = EM::WebSocket::Handshake.new(false)
185
+ handshake.receive_data(incomplete_request)
186
+
187
+ handshake.instance_variable_get(:@deferred_status).should == nil
188
+
189
+ # Send the remaining header
190
+ handshake.receive_data(rest)
191
+
192
+ handshake(@request).should succeed_with_upgrade(@response)
193
+ end
194
+
195
+ it "should cope with requests where the third key is split" do
196
+ request = format_request(@request)
197
+ # Removes last two bytes of the third key
198
+ incomplete_request = request[0..-3]
199
+ rest = request[-2..-1]
200
+ handshake = EM::WebSocket::Handshake.new(false)
201
+ handshake.receive_data(incomplete_request)
202
+
203
+ handshake.instance_variable_get(:@deferred_status).should == nil
204
+
205
+ # Send the remaining third key
206
+ handshake.receive_data(rest)
207
+
208
+ handshake(@request).should succeed_with_upgrade(@response)
209
+ end
210
+
211
+ it "should fail if the request URI is invalid" do
212
+ @request[:path] = "/%"
213
+ handshake(@request).should \
214
+ fail_with_error(EM::WebSocket::HandshakeError, 'Invalid request URI: /%')
215
+ end
216
+ end
@@ -23,5 +23,7 @@ describe EM::WebSocket::MaskedString do
23
23
  t.getbyte(4).should == 0x03
24
24
  t.read_mask
25
25
  t.getbyte(4).should == 0x01
26
+ t.unset_mask
27
+ t.getbyte(4).should == 0x03
26
28
  end
27
29
  end