1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/websockets/websocket_stream.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/run_loop.h"
11 #include "net/base/net_errors.h"
12 #include "net/socket/client_socket_handle.h"
13 #include "net/socket/socket_test_util.h"
14 #include "net/url_request/url_request_test_util.h"
15 #include "net/websockets/websocket_basic_handshake_stream.h"
16 #include "net/websockets/websocket_handshake_stream_create_helper.h"
17 #include "net/websockets/websocket_test_util.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "url/gurl.h"
20
21 namespace net {
22 namespace {
23
24 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
25 // deterministic key to use in the WebSocket handshake.
26 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
27 : public WebSocketHandshakeStreamCreateHelper {
28 public:
DeterministicKeyWebSocketHandshakeStreamCreateHelper(const std::vector<std::string> & requested_subprotocols)29 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
30 const std::vector<std::string>& requested_subprotocols)
31 : WebSocketHandshakeStreamCreateHelper(requested_subprotocols) {}
32
CreateBasicStream(scoped_ptr<ClientSocketHandle> connection,bool using_proxy)33 virtual WebSocketHandshakeStreamBase* CreateBasicStream(
34 scoped_ptr<ClientSocketHandle> connection,
35 bool using_proxy) OVERRIDE {
36 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection.Pass(),
37 using_proxy);
38 // This will break in an obvious way if the type created by
39 // CreateBasicStream() changes.
40 static_cast<WebSocketBasicHandshakeStream*>(stream())
41 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
42 return stream();
43 }
44 };
45
46 class WebSocketStreamCreateTest : public ::testing::Test {
47 protected:
WebSocketStreamCreateTest()48 WebSocketStreamCreateTest() : websocket_error_(0) {}
49
CreateAndConnectCustomResponse(const std::string & socket_url,const std::string & socket_path,const std::vector<std::string> & sub_protocols,const std::string & origin,const std::string & extra_request_headers,const std::string & response_body)50 void CreateAndConnectCustomResponse(
51 const std::string& socket_url,
52 const std::string& socket_path,
53 const std::vector<std::string>& sub_protocols,
54 const std::string& origin,
55 const std::string& extra_request_headers,
56 const std::string& response_body) {
57 url_request_context_host_.SetExpectations(
58 WebSocketStandardRequest(socket_path, origin, extra_request_headers),
59 response_body);
60 CreateAndConnectStream(socket_url, sub_protocols, origin);
61 }
62
63 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or
64 // errors like "Unable to perform synchronous IO while stopped" will occur.
CreateAndConnectStandard(const std::string & socket_url,const std::string & socket_path,const std::vector<std::string> & sub_protocols,const std::string & origin,const std::string & extra_request_headers,const std::string & extra_response_headers)65 void CreateAndConnectStandard(const std::string& socket_url,
66 const std::string& socket_path,
67 const std::vector<std::string>& sub_protocols,
68 const std::string& origin,
69 const std::string& extra_request_headers,
70 const std::string& extra_response_headers) {
71 CreateAndConnectCustomResponse(
72 socket_url,
73 socket_path,
74 sub_protocols,
75 origin,
76 extra_request_headers,
77 WebSocketStandardResponse(extra_response_headers));
78 }
79
CreateAndConnectRawExpectations(const std::string & socket_url,const std::vector<std::string> & sub_protocols,const std::string & origin,scoped_ptr<DeterministicSocketData> socket_data)80 void CreateAndConnectRawExpectations(
81 const std::string& socket_url,
82 const std::vector<std::string>& sub_protocols,
83 const std::string& origin,
84 scoped_ptr<DeterministicSocketData> socket_data) {
85 url_request_context_host_.SetRawExpectations(socket_data.Pass());
86 CreateAndConnectStream(socket_url, sub_protocols, origin);
87 }
88
89 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
90 // parameters.
CreateAndConnectStream(const std::string & socket_url,const std::vector<std::string> & sub_protocols,const std::string & origin)91 void CreateAndConnectStream(const std::string& socket_url,
92 const std::vector<std::string>& sub_protocols,
93 const std::string& origin) {
94 stream_request_ = ::net::CreateAndConnectStreamForTesting(
95 GURL(socket_url),
96 scoped_ptr<WebSocketHandshakeStreamCreateHelper>(
97 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
98 sub_protocols)),
99 GURL(origin),
100 url_request_context_host_.GetURLRequestContext(),
101 BoundNetLog(),
102 scoped_ptr<WebSocketStream::ConnectDelegate>(
103 new TestConnectDelegate(this)));
104 }
105
RunUntilIdle()106 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
107
108 // A simple function to make the tests more readable. Creates an empty vector.
NoSubProtocols()109 static std::vector<std::string> NoSubProtocols() {
110 return std::vector<std::string>();
111 }
112
error() const113 uint16 error() const { return websocket_error_; }
114
115 class TestConnectDelegate : public WebSocketStream::ConnectDelegate {
116 public:
TestConnectDelegate(WebSocketStreamCreateTest * owner)117 TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {}
118
OnSuccess(scoped_ptr<WebSocketStream> stream)119 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
120 stream.swap(owner_->stream_);
121 }
122
OnFailure(uint16 websocket_error)123 virtual void OnFailure(uint16 websocket_error) OVERRIDE {
124 owner_->websocket_error_ = websocket_error;
125 }
126
127 private:
128 WebSocketStreamCreateTest* owner_;
129 };
130
131 WebSocketTestURLRequestContextHost url_request_context_host_;
132 scoped_ptr<WebSocketStreamRequest> stream_request_;
133 // Only set if the connection succeeded.
134 scoped_ptr<WebSocketStream> stream_;
135 // Only set if the connection failed. 0 otherwise.
136 uint16 websocket_error_;
137 };
138
139 // Confirm that the basic case works as expected.
TEST_F(WebSocketStreamCreateTest,SimpleSuccess)140 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
141 CreateAndConnectStandard(
142 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
143 RunUntilIdle();
144 EXPECT_TRUE(stream_);
145 }
146
147 // Confirm that the stream isn't established until the message loop runs.
TEST_F(WebSocketStreamCreateTest,NeedsToRunLoop)148 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) {
149 CreateAndConnectStandard(
150 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
151 EXPECT_FALSE(stream_);
152 }
153
154 // Check the path is used.
TEST_F(WebSocketStreamCreateTest,PathIsUsed)155 TEST_F(WebSocketStreamCreateTest, PathIsUsed) {
156 CreateAndConnectStandard("ws://localhost/testing_path",
157 "/testing_path",
158 NoSubProtocols(),
159 "http://localhost/",
160 "",
161 "");
162 RunUntilIdle();
163 EXPECT_TRUE(stream_);
164 }
165
166 // Check that the origin is used.
TEST_F(WebSocketStreamCreateTest,OriginIsUsed)167 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) {
168 CreateAndConnectStandard("ws://localhost/testing_path",
169 "/testing_path",
170 NoSubProtocols(),
171 "http://google.com/",
172 "",
173 "");
174 RunUntilIdle();
175 EXPECT_TRUE(stream_);
176 }
177
178 // Check that sub-protocols are sent and parsed.
TEST_F(WebSocketStreamCreateTest,SubProtocolIsUsed)179 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) {
180 std::vector<std::string> sub_protocols;
181 sub_protocols.push_back("chatv11.chromium.org");
182 sub_protocols.push_back("chatv20.chromium.org");
183 CreateAndConnectStandard("ws://localhost/testing_path",
184 "/testing_path",
185 sub_protocols,
186 "http://google.com/",
187 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
188 "chatv20.chromium.org\r\n",
189 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
190 RunUntilIdle();
191 EXPECT_TRUE(stream_);
192 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol());
193 }
194
195 // Unsolicited sub-protocols are rejected.
TEST_F(WebSocketStreamCreateTest,UnsolicitedSubProtocol)196 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) {
197 CreateAndConnectStandard("ws://localhost/testing_path",
198 "/testing_path",
199 NoSubProtocols(),
200 "http://google.com/",
201 "",
202 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
203 RunUntilIdle();
204 EXPECT_FALSE(stream_);
205 EXPECT_EQ(1006, error());
206 }
207
208 // Missing sub-protocol response is rejected.
TEST_F(WebSocketStreamCreateTest,UnacceptedSubProtocol)209 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) {
210 CreateAndConnectStandard("ws://localhost/testing_path",
211 "/testing_path",
212 std::vector<std::string>(1, "chat.example.com"),
213 "http://localhost/",
214 "Sec-WebSocket-Protocol: chat.example.com\r\n",
215 "");
216 RunUntilIdle();
217 EXPECT_FALSE(stream_);
218 EXPECT_EQ(1006, error());
219 }
220
221 // Only one sub-protocol can be accepted.
TEST_F(WebSocketStreamCreateTest,MultipleSubProtocolsInResponse)222 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) {
223 std::vector<std::string> sub_protocols;
224 sub_protocols.push_back("chatv11.chromium.org");
225 sub_protocols.push_back("chatv20.chromium.org");
226 CreateAndConnectStandard("ws://localhost/testing_path",
227 "/testing_path",
228 sub_protocols,
229 "http://google.com/",
230 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
231 "chatv20.chromium.org\r\n",
232 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
233 "chatv20.chromium.org\r\n");
234 RunUntilIdle();
235 EXPECT_FALSE(stream_);
236 EXPECT_EQ(1006, error());
237 }
238
239 // Unknown extension in the response is rejected
TEST_F(WebSocketStreamCreateTest,UnknownExtension)240 TEST_F(WebSocketStreamCreateTest, UnknownExtension) {
241 CreateAndConnectStandard("ws://localhost/testing_path",
242 "/testing_path",
243 NoSubProtocols(),
244 "http://localhost/",
245 "",
246 "Sec-WebSocket-Extensions: x-unknown-extension\r\n");
247 RunUntilIdle();
248 EXPECT_FALSE(stream_);
249 EXPECT_EQ(1006, error());
250 }
251
252 // Additional Sec-WebSocket-Accept headers should be rejected.
TEST_F(WebSocketStreamCreateTest,DoubleAccept)253 TEST_F(WebSocketStreamCreateTest, DoubleAccept) {
254 CreateAndConnectStandard(
255 "ws://localhost/",
256 "/",
257 NoSubProtocols(),
258 "http://localhost/",
259 "",
260 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
261 RunUntilIdle();
262 EXPECT_FALSE(stream_);
263 EXPECT_EQ(1006, error());
264 }
265
266 // Response code 200 must be rejected.
TEST_F(WebSocketStreamCreateTest,InvalidStatusCode)267 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) {
268 static const char kInvalidStatusCodeResponse[] =
269 "HTTP/1.1 200 OK\r\n"
270 "Upgrade: websocket\r\n"
271 "Connection: Upgrade\r\n"
272 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
273 "\r\n";
274 CreateAndConnectCustomResponse("ws://localhost/",
275 "/",
276 NoSubProtocols(),
277 "http://localhost/",
278 "",
279 kInvalidStatusCodeResponse);
280 RunUntilIdle();
281 EXPECT_EQ(1006, error());
282 }
283
284 // Redirects are not followed (according to the WHATWG WebSocket API, which
285 // overrides RFC6455 for browser applications).
TEST_F(WebSocketStreamCreateTest,RedirectsRejected)286 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) {
287 static const char kRedirectResponse[] =
288 "HTTP/1.1 302 Moved Temporarily\r\n"
289 "Content-Type: text/html\r\n"
290 "Content-Length: 34\r\n"
291 "Connection: keep-alive\r\n"
292 "Location: ws://localhost/other\r\n"
293 "\r\n"
294 "<title>Moved</title><h1>Moved</h1>";
295 CreateAndConnectCustomResponse("ws://localhost/",
296 "/",
297 NoSubProtocols(),
298 "http://localhost/",
299 "",
300 kRedirectResponse);
301 RunUntilIdle();
302 EXPECT_EQ(1006, error());
303 }
304
305 // Malformed responses should be rejected. HttpStreamParser will accept just
306 // about any garbage in the middle of the headers. To make it give up, the junk
307 // has to be at the start of the response. Even then, it just gets treated as an
308 // HTTP/0.9 response.
TEST_F(WebSocketStreamCreateTest,MalformedResponse)309 TEST_F(WebSocketStreamCreateTest, MalformedResponse) {
310 static const char kMalformedResponse[] =
311 "220 mx.google.com ESMTP\r\n"
312 "HTTP/1.1 101 OK\r\n"
313 "Upgrade: websocket\r\n"
314 "Connection: Upgrade\r\n"
315 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
316 "\r\n";
317 CreateAndConnectCustomResponse("ws://localhost/",
318 "/",
319 NoSubProtocols(),
320 "http://localhost/",
321 "",
322 kMalformedResponse);
323 RunUntilIdle();
324 EXPECT_EQ(1006, error());
325 }
326
327 // Upgrade header must be present.
TEST_F(WebSocketStreamCreateTest,MissingUpgradeHeader)328 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) {
329 static const char kMissingUpgradeResponse[] =
330 "HTTP/1.1 101 Switching Protocols\r\n"
331 "Connection: Upgrade\r\n"
332 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
333 "\r\n";
334 CreateAndConnectCustomResponse("ws://localhost/",
335 "/",
336 NoSubProtocols(),
337 "http://localhost/",
338 "",
339 kMissingUpgradeResponse);
340 RunUntilIdle();
341 EXPECT_EQ(1006, error());
342 }
343
344 // There must only be one upgrade header.
TEST_F(WebSocketStreamCreateTest,DoubleUpgradeHeader)345 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) {
346 CreateAndConnectStandard(
347 "ws://localhost/",
348 "/",
349 NoSubProtocols(),
350 "http://localhost/",
351 "", "Upgrade: HTTP/2.0\r\n");
352 RunUntilIdle();
353 EXPECT_EQ(1006, error());
354 }
355
356 // Connection header must be present.
TEST_F(WebSocketStreamCreateTest,MissingConnectionHeader)357 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) {
358 static const char kMissingConnectionResponse[] =
359 "HTTP/1.1 101 Switching Protocols\r\n"
360 "Upgrade: websocket\r\n"
361 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
362 "\r\n";
363 CreateAndConnectCustomResponse("ws://localhost/",
364 "/",
365 NoSubProtocols(),
366 "http://localhost/",
367 "",
368 kMissingConnectionResponse);
369 RunUntilIdle();
370 EXPECT_EQ(1006, error());
371 }
372
373 // Connection header is permitted to contain other tokens.
TEST_F(WebSocketStreamCreateTest,AdditionalTokenInConnectionHeader)374 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) {
375 static const char kAdditionalConnectionTokenResponse[] =
376 "HTTP/1.1 101 Switching Protocols\r\n"
377 "Upgrade: websocket\r\n"
378 "Connection: Upgrade, Keep-Alive\r\n"
379 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
380 "\r\n";
381 CreateAndConnectCustomResponse("ws://localhost/",
382 "/",
383 NoSubProtocols(),
384 "http://localhost/",
385 "",
386 kAdditionalConnectionTokenResponse);
387 RunUntilIdle();
388 EXPECT_TRUE(stream_);
389 }
390
391 // Sec-WebSocket-Accept header must be present.
TEST_F(WebSocketStreamCreateTest,MissingSecWebSocketAccept)392 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) {
393 static const char kMissingAcceptResponse[] =
394 "HTTP/1.1 101 Switching Protocols\r\n"
395 "Upgrade: websocket\r\n"
396 "Connection: Upgrade\r\n"
397 "\r\n";
398 CreateAndConnectCustomResponse("ws://localhost/",
399 "/",
400 NoSubProtocols(),
401 "http://localhost/",
402 "",
403 kMissingAcceptResponse);
404 RunUntilIdle();
405 EXPECT_EQ(1006, error());
406 }
407
408 // Sec-WebSocket-Accept header must match the key that was sent.
TEST_F(WebSocketStreamCreateTest,WrongSecWebSocketAccept)409 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) {
410 static const char kIncorrectAcceptResponse[] =
411 "HTTP/1.1 101 Switching Protocols\r\n"
412 "Upgrade: websocket\r\n"
413 "Connection: Upgrade\r\n"
414 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
415 "\r\n";
416 CreateAndConnectCustomResponse("ws://localhost/",
417 "/",
418 NoSubProtocols(),
419 "http://localhost/",
420 "",
421 kIncorrectAcceptResponse);
422 RunUntilIdle();
423 EXPECT_EQ(1006, error());
424 }
425
426 // Cancellation works.
TEST_F(WebSocketStreamCreateTest,Cancellation)427 TEST_F(WebSocketStreamCreateTest, Cancellation) {
428 CreateAndConnectStandard(
429 "ws://localhost/", "/", NoSubProtocols(), "http://localhost/", "", "");
430 stream_request_.reset();
431 RunUntilIdle();
432 EXPECT_FALSE(stream_);
433 }
434
435 // Connect failure must look just like negotiation failure.
TEST_F(WebSocketStreamCreateTest,ConnectionFailure)436 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) {
437 scoped_ptr<DeterministicSocketData> socket_data(
438 new DeterministicSocketData(NULL, 0, NULL, 0));
439 socket_data->set_connect_data(
440 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
441 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
442 "http://localhost/", socket_data.Pass());
443 RunUntilIdle();
444 EXPECT_EQ(1006, error());
445 }
446
447 // Connect timeout must look just like any other failure.
TEST_F(WebSocketStreamCreateTest,ConnectionTimeout)448 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
449 scoped_ptr<DeterministicSocketData> socket_data(
450 new DeterministicSocketData(NULL, 0, NULL, 0));
451 socket_data->set_connect_data(
452 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
453 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
454 "http://localhost/", socket_data.Pass());
455 RunUntilIdle();
456 EXPECT_EQ(1006, error());
457 }
458
459 // Cancellation during connect works.
TEST_F(WebSocketStreamCreateTest,CancellationDuringConnect)460 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) {
461 scoped_ptr<DeterministicSocketData> socket_data(
462 new DeterministicSocketData(NULL, 0, NULL, 0));
463 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
464 CreateAndConnectRawExpectations("ws://localhost/",
465 NoSubProtocols(),
466 "http://localhost/",
467 socket_data.Pass());
468 stream_request_.reset();
469 RunUntilIdle();
470 EXPECT_FALSE(stream_);
471 }
472
473 // Cancellation during write of the request headers works.
TEST_F(WebSocketStreamCreateTest,CancellationDuringWrite)474 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) {
475 // We seem to need at least two operations in order to use SetStop().
476 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"),
477 MockWrite(ASYNC, 1, "1.1\r\n")};
478 // We keep a copy of the pointer so that we can call RunFor() on it later.
479 DeterministicSocketData* socket_data(
480 new DeterministicSocketData(NULL, 0, writes, arraysize(writes)));
481 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
482 socket_data->SetStop(1);
483 CreateAndConnectRawExpectations("ws://localhost/",
484 NoSubProtocols(),
485 "http://localhost/",
486 make_scoped_ptr(socket_data));
487 socket_data->Run();
488 stream_request_.reset();
489 RunUntilIdle();
490 EXPECT_FALSE(stream_);
491 }
492
493 // Cancellation during read of the response headers works.
TEST_F(WebSocketStreamCreateTest,CancellationDuringRead)494 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) {
495 std::string request = WebSocketStandardRequest("/", "http://localhost/", "");
496 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())};
497 MockRead reads[] = {
498 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
499 };
500 DeterministicSocketData* socket_data(new DeterministicSocketData(
501 reads, arraysize(reads), writes, arraysize(writes)));
502 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
503 socket_data->SetStop(1);
504 CreateAndConnectRawExpectations("ws://localhost/",
505 NoSubProtocols(),
506 "http://localhost/",
507 make_scoped_ptr(socket_data));
508 socket_data->Run();
509 stream_request_.reset();
510 RunUntilIdle();
511 EXPECT_FALSE(stream_);
512 }
513
514 } // namespace
515 } // namespace net
516