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: 29 DeterministicKeyWebSocketHandshakeStreamCreateHelper( 30 const std::vector<std::string>& requested_subprotocols) 31 : WebSocketHandshakeStreamCreateHelper(requested_subprotocols) {} 32 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: 48 WebSocketStreamCreateTest() : websocket_error_(0) {} 49 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. 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 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. 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 106 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } 107 108 // A simple function to make the tests more readable. Creates an empty vector. 109 static std::vector<std::string> NoSubProtocols() { 110 return std::vector<std::string>(); 111 } 112 113 uint16 error() const { return websocket_error_; } 114 115 class TestConnectDelegate : public WebSocketStream::ConnectDelegate { 116 public: 117 TestConnectDelegate(WebSocketStreamCreateTest* owner) : owner_(owner) {} 118 119 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE { 120 stream.swap(owner_->stream_); 121 } 122 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. 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. 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. 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. 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. 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. 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. 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. 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 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. 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. 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). 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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