em-websocket 0.3.7 → 0.5.2

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