| OLD | NEW |
| (Empty) |
| 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 <algorithm> | |
| 8 #include <string> | |
| 9 #include <utility> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/compiler_specific.h" | |
| 13 #include "base/memory/scoped_vector.h" | |
| 14 #include "base/metrics/histogram.h" | |
| 15 #include "base/metrics/histogram_samples.h" | |
| 16 #include "base/metrics/statistics_recorder.h" | |
| 17 #include "base/run_loop.h" | |
| 18 #include "base/strings/stringprintf.h" | |
| 19 #include "base/timer/mock_timer.h" | |
| 20 #include "base/timer/timer.h" | |
| 21 #include "net/base/net_errors.h" | |
| 22 #include "net/base/test_data_directory.h" | |
| 23 #include "net/http/http_request_headers.h" | |
| 24 #include "net/http/http_response_headers.h" | |
| 25 #include "net/proxy/proxy_service.h" | |
| 26 #include "net/socket/client_socket_handle.h" | |
| 27 #include "net/socket/socket_test_util.h" | |
| 28 #include "net/test/cert_test_util.h" | |
| 29 #include "net/url_request/url_request_test_util.h" | |
| 30 #include "net/websockets/websocket_basic_handshake_stream.h" | |
| 31 #include "net/websockets/websocket_frame.h" | |
| 32 #include "net/websockets/websocket_stream_create_test_base.h" | |
| 33 #include "net/websockets/websocket_test_util.h" | |
| 34 #include "testing/gtest/include/gtest/gtest.h" | |
| 35 #include "url/gurl.h" | |
| 36 #include "url/origin.h" | |
| 37 | |
| 38 namespace net { | |
| 39 namespace { | |
| 40 | |
| 41 // Simple builder for a DeterministicSocketData object to save repetitive code. | |
| 42 // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot | |
| 43 // be used in tests where the connect fails. In practice, those tests never have | |
| 44 // any read/write data and so can't benefit from it anyway. The arrays are not | |
| 45 // copied. It is up to the caller to ensure they stay in scope until the test | |
| 46 // ends. | |
| 47 template <size_t reads_count, size_t writes_count> | |
| 48 scoped_ptr<DeterministicSocketData> BuildSocketData( | |
| 49 MockRead (&reads)[reads_count], | |
| 50 MockWrite (&writes)[writes_count]) { | |
| 51 scoped_ptr<DeterministicSocketData> socket_data( | |
| 52 new DeterministicSocketData(reads, reads_count, writes, writes_count)); | |
| 53 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
| 54 socket_data->SetStop(reads_count + writes_count); | |
| 55 return socket_data.Pass(); | |
| 56 } | |
| 57 | |
| 58 // Builder for a DeterministicSocketData that expects nothing. This does not | |
| 59 // set the connect data, so the calling code must do that explicitly. | |
| 60 scoped_ptr<DeterministicSocketData> BuildNullSocketData() { | |
| 61 return make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0)); | |
| 62 } | |
| 63 | |
| 64 class MockWeakTimer : public base::MockTimer, | |
| 65 public base::SupportsWeakPtr<MockWeakTimer> { | |
| 66 public: | |
| 67 MockWeakTimer(bool retain_user_task, bool is_repeating) | |
| 68 : MockTimer(retain_user_task, is_repeating) {} | |
| 69 }; | |
| 70 | |
| 71 class WebSocketStreamCreateTest : public ::testing::Test, | |
| 72 public WebSocketStreamCreateTestBase { | |
| 73 public: | |
| 74 ~WebSocketStreamCreateTest() override { | |
| 75 // Permit any endpoint locks to be released. | |
| 76 stream_request_.reset(); | |
| 77 stream_.reset(); | |
| 78 base::RunLoop().RunUntilIdle(); | |
| 79 } | |
| 80 | |
| 81 void CreateAndConnectCustomResponse( | |
| 82 const std::string& socket_url, | |
| 83 const std::string& socket_host, | |
| 84 const std::string& socket_path, | |
| 85 const std::vector<std::string>& sub_protocols, | |
| 86 const std::string& origin, | |
| 87 const std::string& extra_request_headers, | |
| 88 const std::string& response_body, | |
| 89 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
| 90 url_request_context_host_.SetExpectations( | |
| 91 WebSocketStandardRequest(socket_path, socket_host, origin, | |
| 92 extra_request_headers), | |
| 93 response_body); | |
| 94 CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); | |
| 95 } | |
| 96 | |
| 97 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or | |
| 98 // errors like "Unable to perform synchronous IO while stopped" will occur. | |
| 99 void CreateAndConnectStandard( | |
| 100 const std::string& socket_url, | |
| 101 const std::string& socket_host, | |
| 102 const std::string& socket_path, | |
| 103 const std::vector<std::string>& sub_protocols, | |
| 104 const std::string& origin, | |
| 105 const std::string& extra_request_headers, | |
| 106 const std::string& extra_response_headers, | |
| 107 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
| 108 CreateAndConnectCustomResponse( | |
| 109 socket_url, socket_host, socket_path, sub_protocols, origin, | |
| 110 extra_request_headers, | |
| 111 WebSocketStandardResponse(extra_response_headers), timer.Pass()); | |
| 112 } | |
| 113 | |
| 114 void CreateAndConnectRawExpectations( | |
| 115 const std::string& socket_url, | |
| 116 const std::vector<std::string>& sub_protocols, | |
| 117 const std::string& origin, | |
| 118 scoped_ptr<DeterministicSocketData> socket_data, | |
| 119 scoped_ptr<base::Timer> timer = scoped_ptr<base::Timer>()) { | |
| 120 AddRawExpectations(socket_data.Pass()); | |
| 121 CreateAndConnectStream(socket_url, sub_protocols, origin, timer.Pass()); | |
| 122 } | |
| 123 | |
| 124 // Add additional raw expectations for sockets created before the final one. | |
| 125 void AddRawExpectations(scoped_ptr<DeterministicSocketData> socket_data) { | |
| 126 url_request_context_host_.AddRawExpectations(socket_data.Pass()); | |
| 127 } | |
| 128 }; | |
| 129 | |
| 130 // There are enough tests of the Sec-WebSocket-Extensions header that they | |
| 131 // deserve their own test fixture. | |
| 132 class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest { | |
| 133 public: | |
| 134 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions | |
| 135 // header in the response set to |extensions_header_value|. Runs the event | |
| 136 // loop to allow the connect to complete. | |
| 137 void CreateAndConnectWithExtensions( | |
| 138 const std::string& extensions_header_value) { | |
| 139 CreateAndConnectStandard( | |
| 140 "ws://localhost/testing_path", "localhost", "/testing_path", | |
| 141 NoSubProtocols(), "http://localhost", "", | |
| 142 "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n"); | |
| 143 WaitUntilConnectDone(); | |
| 144 } | |
| 145 }; | |
| 146 | |
| 147 // Common code to construct expectations for authentication tests that receive | |
| 148 // the auth challenge on one connection and then create a second connection to | |
| 149 // send the authenticated request on. | |
| 150 class CommonAuthTestHelper { | |
| 151 public: | |
| 152 CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {} | |
| 153 | |
| 154 scoped_ptr<DeterministicSocketData> BuildSocketData1( | |
| 155 const std::string& response) { | |
| 156 request1_ = | |
| 157 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
| 158 writes1_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str()); | |
| 159 response1_ = response; | |
| 160 reads1_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str()); | |
| 161 reads1_[1] = MockRead(SYNCHRONOUS, OK, 2); // Close connection | |
| 162 | |
| 163 return BuildSocketData(reads1_, writes1_); | |
| 164 } | |
| 165 | |
| 166 scoped_ptr<DeterministicSocketData> BuildSocketData2( | |
| 167 const std::string& request, | |
| 168 const std::string& response) { | |
| 169 request2_ = request; | |
| 170 response2_ = response; | |
| 171 writes2_[0] = MockWrite(SYNCHRONOUS, 0, request2_.c_str()); | |
| 172 reads2_[0] = MockRead(SYNCHRONOUS, 1, response2_.c_str()); | |
| 173 return BuildSocketData(reads2_, writes2_); | |
| 174 } | |
| 175 | |
| 176 private: | |
| 177 // These need to be object-scoped since they have to remain valid until all | |
| 178 // socket operations in the test are complete. | |
| 179 std::string request1_; | |
| 180 std::string request2_; | |
| 181 std::string response1_; | |
| 182 std::string response2_; | |
| 183 MockRead reads1_[2]; | |
| 184 MockWrite writes1_[1]; | |
| 185 MockRead reads2_[1]; | |
| 186 MockWrite writes2_[1]; | |
| 187 | |
| 188 DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper); | |
| 189 }; | |
| 190 | |
| 191 // Data and methods for BasicAuth tests. | |
| 192 class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest { | |
| 193 protected: | |
| 194 void CreateAndConnectAuthHandshake(const std::string& url, | |
| 195 const std::string& base64_user_pass, | |
| 196 const std::string& response2) { | |
| 197 AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | |
| 198 | |
| 199 static const char request2format[] = | |
| 200 "GET / HTTP/1.1\r\n" | |
| 201 "Host: localhost\r\n" | |
| 202 "Connection: Upgrade\r\n" | |
| 203 "Pragma: no-cache\r\n" | |
| 204 "Cache-Control: no-cache\r\n" | |
| 205 "Authorization: Basic %s\r\n" | |
| 206 "Upgrade: websocket\r\n" | |
| 207 "Origin: http://localhost\r\n" | |
| 208 "Sec-WebSocket-Version: 13\r\n" | |
| 209 "User-Agent:\r\n" | |
| 210 "Accept-Encoding: gzip, deflate\r\n" | |
| 211 "Accept-Language: en-us,fr\r\n" | |
| 212 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
| 213 "Sec-WebSocket-Extensions: permessage-deflate; " | |
| 214 "client_max_window_bits\r\n" | |
| 215 "\r\n"; | |
| 216 const std::string request = | |
| 217 base::StringPrintf(request2format, base64_user_pass.c_str()); | |
| 218 CreateAndConnectRawExpectations( | |
| 219 url, | |
| 220 NoSubProtocols(), | |
| 221 "http://localhost", | |
| 222 helper_.BuildSocketData2(request, response2)); | |
| 223 } | |
| 224 | |
| 225 static const char kUnauthorizedResponse[]; | |
| 226 | |
| 227 CommonAuthTestHelper helper_; | |
| 228 }; | |
| 229 | |
| 230 class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest { | |
| 231 protected: | |
| 232 static const char kUnauthorizedResponse[]; | |
| 233 static const char kAuthorizedRequest[]; | |
| 234 | |
| 235 CommonAuthTestHelper helper_; | |
| 236 }; | |
| 237 | |
| 238 const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] = | |
| 239 "HTTP/1.1 401 Unauthorized\r\n" | |
| 240 "Content-Length: 0\r\n" | |
| 241 "WWW-Authenticate: Basic realm=\"camelot\"\r\n" | |
| 242 "\r\n"; | |
| 243 | |
| 244 // These negotiation values are borrowed from | |
| 245 // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if | |
| 246 // you are bored. Only the weakest (no qop) variants of Digest authentication | |
| 247 // can be tested by this method, because the others involve random input. | |
| 248 const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] = | |
| 249 "HTTP/1.1 401 Unauthorized\r\n" | |
| 250 "Content-Length: 0\r\n" | |
| 251 "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n" | |
| 252 "\r\n"; | |
| 253 | |
| 254 const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] = | |
| 255 "GET / HTTP/1.1\r\n" | |
| 256 "Host: localhost\r\n" | |
| 257 "Connection: Upgrade\r\n" | |
| 258 "Pragma: no-cache\r\n" | |
| 259 "Cache-Control: no-cache\r\n" | |
| 260 "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", " | |
| 261 "nonce=\"nonce-value\", uri=\"/\", " | |
| 262 "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n" | |
| 263 "Upgrade: websocket\r\n" | |
| 264 "Origin: http://localhost\r\n" | |
| 265 "Sec-WebSocket-Version: 13\r\n" | |
| 266 "User-Agent:\r\n" | |
| 267 "Accept-Encoding: gzip, deflate\r\n" | |
| 268 "Accept-Language: en-us,fr\r\n" | |
| 269 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | |
| 270 "Sec-WebSocket-Extensions: permessage-deflate; " | |
| 271 "client_max_window_bits\r\n" | |
| 272 "\r\n"; | |
| 273 | |
| 274 class WebSocketStreamCreateUMATest : public ::testing::Test { | |
| 275 public: | |
| 276 // This enum should match with the enum in Delegate in websocket_stream.cc. | |
| 277 enum HandshakeResult { | |
| 278 INCOMPLETE, | |
| 279 CONNECTED, | |
| 280 FAILED, | |
| 281 NUM_HANDSHAKE_RESULT_TYPES, | |
| 282 }; | |
| 283 | |
| 284 class StreamCreation : public WebSocketStreamCreateTest { | |
| 285 void TestBody() override {} | |
| 286 }; | |
| 287 | |
| 288 scoped_ptr<base::HistogramSamples> GetSamples(const std::string& name) { | |
| 289 base::HistogramBase* histogram = | |
| 290 base::StatisticsRecorder::FindHistogram(name); | |
| 291 return histogram ? histogram->SnapshotSamples() | |
| 292 : scoped_ptr<base::HistogramSamples>(); | |
| 293 } | |
| 294 }; | |
| 295 | |
| 296 // Confirm that the basic case works as expected. | |
| 297 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) { | |
| 298 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 299 NoSubProtocols(), "http://localhost", "", ""); | |
| 300 EXPECT_FALSE(request_info_); | |
| 301 EXPECT_FALSE(response_info_); | |
| 302 WaitUntilConnectDone(); | |
| 303 EXPECT_FALSE(has_failed()); | |
| 304 EXPECT_TRUE(stream_); | |
| 305 EXPECT_TRUE(request_info_); | |
| 306 EXPECT_TRUE(response_info_); | |
| 307 } | |
| 308 | |
| 309 TEST_F(WebSocketStreamCreateTest, HandshakeInfo) { | |
| 310 static const char kResponse[] = | |
| 311 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 312 "Upgrade: websocket\r\n" | |
| 313 "Connection: Upgrade\r\n" | |
| 314 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 315 "foo: bar, baz\r\n" | |
| 316 "hoge: fuga\r\n" | |
| 317 "hoge: piyo\r\n" | |
| 318 "\r\n"; | |
| 319 | |
| 320 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 321 NoSubProtocols(), "http://localhost", "", | |
| 322 kResponse); | |
| 323 EXPECT_FALSE(request_info_); | |
| 324 EXPECT_FALSE(response_info_); | |
| 325 WaitUntilConnectDone(); | |
| 326 EXPECT_TRUE(stream_); | |
| 327 ASSERT_TRUE(request_info_); | |
| 328 ASSERT_TRUE(response_info_); | |
| 329 std::vector<HeaderKeyValuePair> request_headers = | |
| 330 RequestHeadersToVector(request_info_->headers); | |
| 331 // We examine the contents of request_info_ and response_info_ | |
| 332 // mainly only in this test case. | |
| 333 EXPECT_EQ(GURL("ws://localhost/"), request_info_->url); | |
| 334 EXPECT_EQ(GURL("ws://localhost/"), response_info_->url); | |
| 335 EXPECT_EQ(101, response_info_->status_code); | |
| 336 EXPECT_EQ("Switching Protocols", response_info_->status_text); | |
| 337 ASSERT_EQ(12u, request_headers.size()); | |
| 338 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]); | |
| 339 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); | |
| 340 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]); | |
| 341 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"), | |
| 342 request_headers[3]); | |
| 343 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]); | |
| 344 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"), | |
| 345 request_headers[5]); | |
| 346 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), | |
| 347 request_headers[6]); | |
| 348 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]); | |
| 349 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"), | |
| 350 request_headers[8]); | |
| 351 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), | |
| 352 request_headers[9]); | |
| 353 EXPECT_EQ("Sec-WebSocket-Key", request_headers[10].first); | |
| 354 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions", | |
| 355 "permessage-deflate; client_max_window_bits"), | |
| 356 request_headers[11]); | |
| 357 | |
| 358 std::vector<HeaderKeyValuePair> response_headers = | |
| 359 ResponseHeadersToVector(*response_info_->headers.get()); | |
| 360 ASSERT_EQ(6u, response_headers.size()); | |
| 361 // Sort the headers for ease of verification. | |
| 362 std::sort(response_headers.begin(), response_headers.end()); | |
| 363 | |
| 364 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); | |
| 365 EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); | |
| 366 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); | |
| 367 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]); | |
| 368 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]); | |
| 369 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]); | |
| 370 } | |
| 371 | |
| 372 // Confirm that the stream isn't established until the message loop runs. | |
| 373 TEST_F(WebSocketStreamCreateTest, NeedsToRunLoop) { | |
| 374 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 375 NoSubProtocols(), "http://localhost", "", ""); | |
| 376 EXPECT_FALSE(has_failed()); | |
| 377 EXPECT_FALSE(stream_); | |
| 378 } | |
| 379 | |
| 380 // Check the path is used. | |
| 381 TEST_F(WebSocketStreamCreateTest, PathIsUsed) { | |
| 382 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 383 "/testing_path", NoSubProtocols(), | |
| 384 "http://localhost", "", ""); | |
| 385 WaitUntilConnectDone(); | |
| 386 EXPECT_FALSE(has_failed()); | |
| 387 EXPECT_TRUE(stream_); | |
| 388 } | |
| 389 | |
| 390 // Check that the origin is used. | |
| 391 TEST_F(WebSocketStreamCreateTest, OriginIsUsed) { | |
| 392 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 393 "/testing_path", NoSubProtocols(), | |
| 394 "http://google.com", "", ""); | |
| 395 WaitUntilConnectDone(); | |
| 396 EXPECT_FALSE(has_failed()); | |
| 397 EXPECT_TRUE(stream_); | |
| 398 } | |
| 399 | |
| 400 // Check that sub-protocols are sent and parsed. | |
| 401 TEST_F(WebSocketStreamCreateTest, SubProtocolIsUsed) { | |
| 402 std::vector<std::string> sub_protocols; | |
| 403 sub_protocols.push_back("chatv11.chromium.org"); | |
| 404 sub_protocols.push_back("chatv20.chromium.org"); | |
| 405 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 406 "/testing_path", sub_protocols, "http://google.com", | |
| 407 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
| 408 "chatv20.chromium.org\r\n", | |
| 409 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | |
| 410 WaitUntilConnectDone(); | |
| 411 EXPECT_TRUE(stream_); | |
| 412 EXPECT_FALSE(has_failed()); | |
| 413 EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); | |
| 414 } | |
| 415 | |
| 416 // Unsolicited sub-protocols are rejected. | |
| 417 TEST_F(WebSocketStreamCreateTest, UnsolicitedSubProtocol) { | |
| 418 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 419 "/testing_path", NoSubProtocols(), | |
| 420 "http://google.com", "", | |
| 421 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n"); | |
| 422 WaitUntilConnectDone(); | |
| 423 EXPECT_FALSE(stream_); | |
| 424 EXPECT_TRUE(has_failed()); | |
| 425 EXPECT_EQ("Error during WebSocket handshake: " | |
| 426 "Response must not include 'Sec-WebSocket-Protocol' header " | |
| 427 "if not present in request: chatv20.chromium.org", | |
| 428 failure_message()); | |
| 429 } | |
| 430 | |
| 431 // Missing sub-protocol response is rejected. | |
| 432 TEST_F(WebSocketStreamCreateTest, UnacceptedSubProtocol) { | |
| 433 std::vector<std::string> sub_protocols; | |
| 434 sub_protocols.push_back("chat.example.com"); | |
| 435 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 436 "/testing_path", sub_protocols, "http://localhost", | |
| 437 "Sec-WebSocket-Protocol: chat.example.com\r\n", ""); | |
| 438 WaitUntilConnectDone(); | |
| 439 EXPECT_FALSE(stream_); | |
| 440 EXPECT_TRUE(has_failed()); | |
| 441 EXPECT_EQ("Error during WebSocket handshake: " | |
| 442 "Sent non-empty 'Sec-WebSocket-Protocol' header " | |
| 443 "but no response was received", | |
| 444 failure_message()); | |
| 445 } | |
| 446 | |
| 447 // Only one sub-protocol can be accepted. | |
| 448 TEST_F(WebSocketStreamCreateTest, MultipleSubProtocolsInResponse) { | |
| 449 std::vector<std::string> sub_protocols; | |
| 450 sub_protocols.push_back("chatv11.chromium.org"); | |
| 451 sub_protocols.push_back("chatv20.chromium.org"); | |
| 452 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 453 "/testing_path", sub_protocols, "http://google.com", | |
| 454 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
| 455 "chatv20.chromium.org\r\n", | |
| 456 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
| 457 "chatv20.chromium.org\r\n"); | |
| 458 WaitUntilConnectDone(); | |
| 459 EXPECT_FALSE(stream_); | |
| 460 EXPECT_TRUE(has_failed()); | |
| 461 EXPECT_EQ("Error during WebSocket handshake: " | |
| 462 "'Sec-WebSocket-Protocol' header must not appear " | |
| 463 "more than once in a response", | |
| 464 failure_message()); | |
| 465 } | |
| 466 | |
| 467 // Unmatched sub-protocol should be rejected. | |
| 468 TEST_F(WebSocketStreamCreateTest, UnmatchedSubProtocolInResponse) { | |
| 469 std::vector<std::string> sub_protocols; | |
| 470 sub_protocols.push_back("chatv11.chromium.org"); | |
| 471 sub_protocols.push_back("chatv20.chromium.org"); | |
| 472 CreateAndConnectStandard("ws://localhost/testing_path", "localhost", | |
| 473 "/testing_path", sub_protocols, "http://google.com", | |
| 474 "Sec-WebSocket-Protocol: chatv11.chromium.org, " | |
| 475 "chatv20.chromium.org\r\n", | |
| 476 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n"); | |
| 477 WaitUntilConnectDone(); | |
| 478 EXPECT_FALSE(stream_); | |
| 479 EXPECT_TRUE(has_failed()); | |
| 480 EXPECT_EQ("Error during WebSocket handshake: " | |
| 481 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " | |
| 482 "in response does not match any of sent values", | |
| 483 failure_message()); | |
| 484 } | |
| 485 | |
| 486 // permessage-deflate extension basic success case. | |
| 487 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) { | |
| 488 CreateAndConnectWithExtensions("permessage-deflate"); | |
| 489 EXPECT_TRUE(stream_); | |
| 490 EXPECT_FALSE(has_failed()); | |
| 491 } | |
| 492 | |
| 493 // permessage-deflate extensions success with all parameters. | |
| 494 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) { | |
| 495 CreateAndConnectWithExtensions( | |
| 496 "permessage-deflate; client_no_context_takeover; " | |
| 497 "server_max_window_bits=11; client_max_window_bits=13; " | |
| 498 "server_no_context_takeover"); | |
| 499 EXPECT_TRUE(stream_); | |
| 500 EXPECT_FALSE(has_failed()); | |
| 501 } | |
| 502 | |
| 503 // Verify that incoming messages are actually decompressed with | |
| 504 // permessage-deflate enabled. | |
| 505 TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) { | |
| 506 CreateAndConnectCustomResponse( | |
| 507 "ws://localhost/testing_path", "localhost", "/testing_path", | |
| 508 NoSubProtocols(), "http://localhost", "", | |
| 509 WebSocketStandardResponse( | |
| 510 "Sec-WebSocket-Extensions: permessage-deflate\r\n") + | |
| 511 std::string( | |
| 512 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes) | |
| 513 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed | |
| 514 9)); | |
| 515 WaitUntilConnectDone(); | |
| 516 | |
| 517 ASSERT_TRUE(stream_); | |
| 518 ScopedVector<WebSocketFrame> frames; | |
| 519 CompletionCallback callback; | |
| 520 ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback)); | |
| 521 ASSERT_EQ(1U, frames.size()); | |
| 522 ASSERT_EQ(5U, frames[0]->header.payload_length); | |
| 523 EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5)); | |
| 524 } | |
| 525 | |
| 526 // Unknown extension in the response is rejected | |
| 527 TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) { | |
| 528 CreateAndConnectWithExtensions("x-unknown-extension"); | |
| 529 EXPECT_FALSE(stream_); | |
| 530 EXPECT_TRUE(has_failed()); | |
| 531 EXPECT_EQ("Error during WebSocket handshake: " | |
| 532 "Found an unsupported extension 'x-unknown-extension' " | |
| 533 "in 'Sec-WebSocket-Extensions' header", | |
| 534 failure_message()); | |
| 535 } | |
| 536 | |
| 537 // Malformed extensions are rejected (this file does not cover all possible | |
| 538 // parse failures, as the parser is covered thoroughly by its own unit tests). | |
| 539 TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) { | |
| 540 CreateAndConnectWithExtensions(";"); | |
| 541 EXPECT_FALSE(stream_); | |
| 542 EXPECT_TRUE(has_failed()); | |
| 543 EXPECT_EQ( | |
| 544 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header " | |
| 545 "value is rejected by the parser: ;", | |
| 546 failure_message()); | |
| 547 } | |
| 548 | |
| 549 // The permessage-deflate extension may only be specified once. | |
| 550 TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) { | |
| 551 CreateAndConnectWithExtensions( | |
| 552 "permessage-deflate, permessage-deflate; client_max_window_bits=10"); | |
| 553 EXPECT_FALSE(stream_); | |
| 554 EXPECT_TRUE(has_failed()); | |
| 555 EXPECT_EQ( | |
| 556 "Error during WebSocket handshake: " | |
| 557 "Received duplicate permessage-deflate response", | |
| 558 failure_message()); | |
| 559 } | |
| 560 | |
| 561 // permessage-deflate parameters may not be duplicated. | |
| 562 TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) { | |
| 563 CreateAndConnectWithExtensions( | |
| 564 "permessage-deflate; client_no_context_takeover; " | |
| 565 "client_no_context_takeover"); | |
| 566 EXPECT_FALSE(stream_); | |
| 567 EXPECT_TRUE(has_failed()); | |
| 568 EXPECT_EQ( | |
| 569 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 570 "Received duplicate permessage-deflate extension parameter " | |
| 571 "client_no_context_takeover", | |
| 572 failure_message()); | |
| 573 } | |
| 574 | |
| 575 // permessage-deflate parameters must start with "client_" or "server_" | |
| 576 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) { | |
| 577 CreateAndConnectWithExtensions( | |
| 578 "permessage-deflate; absurd_no_context_takeover"); | |
| 579 EXPECT_FALSE(stream_); | |
| 580 EXPECT_TRUE(has_failed()); | |
| 581 EXPECT_EQ( | |
| 582 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 583 "Received an unexpected permessage-deflate extension parameter", | |
| 584 failure_message()); | |
| 585 } | |
| 586 | |
| 587 // permessage-deflate parameters must be either *_no_context_takeover or | |
| 588 // *_max_window_bits | |
| 589 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) { | |
| 590 CreateAndConnectWithExtensions( | |
| 591 "permessage-deflate; client_max_content_bits=5"); | |
| 592 EXPECT_FALSE(stream_); | |
| 593 EXPECT_TRUE(has_failed()); | |
| 594 EXPECT_EQ( | |
| 595 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 596 "Received an unexpected permessage-deflate extension parameter", | |
| 597 failure_message()); | |
| 598 } | |
| 599 | |
| 600 // *_no_context_takeover parameters must not have an argument | |
| 601 TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) { | |
| 602 CreateAndConnectWithExtensions( | |
| 603 "permessage-deflate; client_no_context_takeover=true"); | |
| 604 EXPECT_FALSE(stream_); | |
| 605 EXPECT_TRUE(has_failed()); | |
| 606 EXPECT_EQ( | |
| 607 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 608 "Received invalid client_no_context_takeover parameter", | |
| 609 failure_message()); | |
| 610 } | |
| 611 | |
| 612 // *_max_window_bits must have an argument | |
| 613 TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) { | |
| 614 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits"); | |
| 615 EXPECT_FALSE(stream_); | |
| 616 EXPECT_TRUE(has_failed()); | |
| 617 EXPECT_EQ( | |
| 618 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 619 "client_max_window_bits must have value", | |
| 620 failure_message()); | |
| 621 } | |
| 622 | |
| 623 // *_max_window_bits must be an integer | |
| 624 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) { | |
| 625 CreateAndConnectWithExtensions( | |
| 626 "permessage-deflate; server_max_window_bits=banana"); | |
| 627 EXPECT_FALSE(stream_); | |
| 628 EXPECT_TRUE(has_failed()); | |
| 629 EXPECT_EQ( | |
| 630 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 631 "Received invalid server_max_window_bits parameter", | |
| 632 failure_message()); | |
| 633 } | |
| 634 | |
| 635 // *_max_window_bits must be >= 8 | |
| 636 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) { | |
| 637 CreateAndConnectWithExtensions( | |
| 638 "permessage-deflate; server_max_window_bits=7"); | |
| 639 EXPECT_FALSE(stream_); | |
| 640 EXPECT_TRUE(has_failed()); | |
| 641 EXPECT_EQ( | |
| 642 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 643 "Received invalid server_max_window_bits parameter", | |
| 644 failure_message()); | |
| 645 } | |
| 646 | |
| 647 // *_max_window_bits must be <= 15 | |
| 648 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) { | |
| 649 CreateAndConnectWithExtensions( | |
| 650 "permessage-deflate; client_max_window_bits=16"); | |
| 651 EXPECT_FALSE(stream_); | |
| 652 EXPECT_TRUE(has_failed()); | |
| 653 EXPECT_EQ( | |
| 654 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 655 "Received invalid client_max_window_bits parameter", | |
| 656 failure_message()); | |
| 657 } | |
| 658 | |
| 659 // *_max_window_bits must not start with 0 | |
| 660 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) { | |
| 661 CreateAndConnectWithExtensions( | |
| 662 "permessage-deflate; client_max_window_bits=08"); | |
| 663 EXPECT_FALSE(stream_); | |
| 664 EXPECT_TRUE(has_failed()); | |
| 665 EXPECT_EQ( | |
| 666 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 667 "Received invalid client_max_window_bits parameter", | |
| 668 failure_message()); | |
| 669 } | |
| 670 | |
| 671 // *_max_window_bits must not start with + | |
| 672 TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) { | |
| 673 CreateAndConnectWithExtensions( | |
| 674 "permessage-deflate; server_max_window_bits=+9"); | |
| 675 EXPECT_FALSE(stream_); | |
| 676 EXPECT_TRUE(has_failed()); | |
| 677 EXPECT_EQ( | |
| 678 "Error during WebSocket handshake: Error in permessage-deflate: " | |
| 679 "Received invalid server_max_window_bits parameter", | |
| 680 failure_message()); | |
| 681 } | |
| 682 | |
| 683 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the | |
| 684 // arguments from the server. This is difficult because the data written to the | |
| 685 // socket is randomly masked. | |
| 686 | |
| 687 // Additional Sec-WebSocket-Accept headers should be rejected. | |
| 688 TEST_F(WebSocketStreamCreateTest, DoubleAccept) { | |
| 689 CreateAndConnectStandard( | |
| 690 "ws://localhost/", "localhost", "/", NoSubProtocols(), "http://localhost", | |
| 691 "", "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); | |
| 692 WaitUntilConnectDone(); | |
| 693 EXPECT_FALSE(stream_); | |
| 694 EXPECT_TRUE(has_failed()); | |
| 695 EXPECT_EQ("Error during WebSocket handshake: " | |
| 696 "'Sec-WebSocket-Accept' header must not appear " | |
| 697 "more than once in a response", | |
| 698 failure_message()); | |
| 699 } | |
| 700 | |
| 701 // Response code 200 must be rejected. | |
| 702 TEST_F(WebSocketStreamCreateTest, InvalidStatusCode) { | |
| 703 static const char kInvalidStatusCodeResponse[] = | |
| 704 "HTTP/1.1 200 OK\r\n" | |
| 705 "Upgrade: websocket\r\n" | |
| 706 "Connection: Upgrade\r\n" | |
| 707 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 708 "\r\n"; | |
| 709 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 710 NoSubProtocols(), "http://localhost", "", | |
| 711 kInvalidStatusCodeResponse); | |
| 712 WaitUntilConnectDone(); | |
| 713 EXPECT_TRUE(has_failed()); | |
| 714 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200", | |
| 715 failure_message()); | |
| 716 } | |
| 717 | |
| 718 // Redirects are not followed (according to the WHATWG WebSocket API, which | |
| 719 // overrides RFC6455 for browser applications). | |
| 720 TEST_F(WebSocketStreamCreateTest, RedirectsRejected) { | |
| 721 static const char kRedirectResponse[] = | |
| 722 "HTTP/1.1 302 Moved Temporarily\r\n" | |
| 723 "Content-Type: text/html\r\n" | |
| 724 "Content-Length: 34\r\n" | |
| 725 "Connection: keep-alive\r\n" | |
| 726 "Location: ws://localhost/other\r\n" | |
| 727 "\r\n" | |
| 728 "<title>Moved</title><h1>Moved</h1>"; | |
| 729 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 730 NoSubProtocols(), "http://localhost", "", | |
| 731 kRedirectResponse); | |
| 732 WaitUntilConnectDone(); | |
| 733 EXPECT_TRUE(has_failed()); | |
| 734 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302", | |
| 735 failure_message()); | |
| 736 } | |
| 737 | |
| 738 // Malformed responses should be rejected. HttpStreamParser will accept just | |
| 739 // about any garbage in the middle of the headers. To make it give up, the junk | |
| 740 // has to be at the start of the response. Even then, it just gets treated as an | |
| 741 // HTTP/0.9 response. | |
| 742 TEST_F(WebSocketStreamCreateTest, MalformedResponse) { | |
| 743 static const char kMalformedResponse[] = | |
| 744 "220 mx.google.com ESMTP\r\n" | |
| 745 "HTTP/1.1 101 OK\r\n" | |
| 746 "Upgrade: websocket\r\n" | |
| 747 "Connection: Upgrade\r\n" | |
| 748 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 749 "\r\n"; | |
| 750 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 751 NoSubProtocols(), "http://localhost", "", | |
| 752 kMalformedResponse); | |
| 753 WaitUntilConnectDone(); | |
| 754 EXPECT_TRUE(has_failed()); | |
| 755 EXPECT_EQ("Error during WebSocket handshake: Invalid status line", | |
| 756 failure_message()); | |
| 757 } | |
| 758 | |
| 759 // Upgrade header must be present. | |
| 760 TEST_F(WebSocketStreamCreateTest, MissingUpgradeHeader) { | |
| 761 static const char kMissingUpgradeResponse[] = | |
| 762 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 763 "Connection: Upgrade\r\n" | |
| 764 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 765 "\r\n"; | |
| 766 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 767 NoSubProtocols(), "http://localhost", "", | |
| 768 kMissingUpgradeResponse); | |
| 769 WaitUntilConnectDone(); | |
| 770 EXPECT_TRUE(has_failed()); | |
| 771 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", | |
| 772 failure_message()); | |
| 773 } | |
| 774 | |
| 775 // There must only be one upgrade header. | |
| 776 TEST_F(WebSocketStreamCreateTest, DoubleUpgradeHeader) { | |
| 777 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 778 NoSubProtocols(), "http://localhost", "", | |
| 779 "Upgrade: HTTP/2.0\r\n"); | |
| 780 WaitUntilConnectDone(); | |
| 781 EXPECT_TRUE(has_failed()); | |
| 782 EXPECT_EQ("Error during WebSocket handshake: " | |
| 783 "'Upgrade' header must not appear more than once in a response", | |
| 784 failure_message()); | |
| 785 } | |
| 786 | |
| 787 // There must only be one correct upgrade header. | |
| 788 TEST_F(WebSocketStreamCreateTest, IncorrectUpgradeHeader) { | |
| 789 static const char kMissingUpgradeResponse[] = | |
| 790 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 791 "Connection: Upgrade\r\n" | |
| 792 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 793 "Upgrade: hogefuga\r\n" | |
| 794 "\r\n"; | |
| 795 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 796 NoSubProtocols(), "http://localhost", "", | |
| 797 kMissingUpgradeResponse); | |
| 798 WaitUntilConnectDone(); | |
| 799 EXPECT_TRUE(has_failed()); | |
| 800 EXPECT_EQ("Error during WebSocket handshake: " | |
| 801 "'Upgrade' header value is not 'WebSocket': hogefuga", | |
| 802 failure_message()); | |
| 803 } | |
| 804 | |
| 805 // Connection header must be present. | |
| 806 TEST_F(WebSocketStreamCreateTest, MissingConnectionHeader) { | |
| 807 static const char kMissingConnectionResponse[] = | |
| 808 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 809 "Upgrade: websocket\r\n" | |
| 810 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 811 "\r\n"; | |
| 812 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 813 NoSubProtocols(), "http://localhost", "", | |
| 814 kMissingConnectionResponse); | |
| 815 WaitUntilConnectDone(); | |
| 816 EXPECT_TRUE(has_failed()); | |
| 817 EXPECT_EQ("Error during WebSocket handshake: " | |
| 818 "'Connection' header is missing", | |
| 819 failure_message()); | |
| 820 } | |
| 821 | |
| 822 // Connection header must contain "Upgrade". | |
| 823 TEST_F(WebSocketStreamCreateTest, IncorrectConnectionHeader) { | |
| 824 static const char kMissingConnectionResponse[] = | |
| 825 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 826 "Upgrade: websocket\r\n" | |
| 827 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 828 "Connection: hogefuga\r\n" | |
| 829 "\r\n"; | |
| 830 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 831 NoSubProtocols(), "http://localhost", "", | |
| 832 kMissingConnectionResponse); | |
| 833 WaitUntilConnectDone(); | |
| 834 EXPECT_TRUE(has_failed()); | |
| 835 EXPECT_EQ("Error during WebSocket handshake: " | |
| 836 "'Connection' header value must contain 'Upgrade'", | |
| 837 failure_message()); | |
| 838 } | |
| 839 | |
| 840 // Connection header is permitted to contain other tokens. | |
| 841 TEST_F(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { | |
| 842 static const char kAdditionalConnectionTokenResponse[] = | |
| 843 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 844 "Upgrade: websocket\r\n" | |
| 845 "Connection: Upgrade, Keep-Alive\r\n" | |
| 846 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 847 "\r\n"; | |
| 848 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 849 NoSubProtocols(), "http://localhost", "", | |
| 850 kAdditionalConnectionTokenResponse); | |
| 851 WaitUntilConnectDone(); | |
| 852 EXPECT_FALSE(has_failed()); | |
| 853 EXPECT_TRUE(stream_); | |
| 854 } | |
| 855 | |
| 856 // Sec-WebSocket-Accept header must be present. | |
| 857 TEST_F(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { | |
| 858 static const char kMissingAcceptResponse[] = | |
| 859 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 860 "Upgrade: websocket\r\n" | |
| 861 "Connection: Upgrade\r\n" | |
| 862 "\r\n"; | |
| 863 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 864 NoSubProtocols(), "http://localhost", "", | |
| 865 kMissingAcceptResponse); | |
| 866 WaitUntilConnectDone(); | |
| 867 EXPECT_TRUE(has_failed()); | |
| 868 EXPECT_EQ("Error during WebSocket handshake: " | |
| 869 "'Sec-WebSocket-Accept' header is missing", | |
| 870 failure_message()); | |
| 871 } | |
| 872 | |
| 873 // Sec-WebSocket-Accept header must match the key that was sent. | |
| 874 TEST_F(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { | |
| 875 static const char kIncorrectAcceptResponse[] = | |
| 876 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 877 "Upgrade: websocket\r\n" | |
| 878 "Connection: Upgrade\r\n" | |
| 879 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" | |
| 880 "\r\n"; | |
| 881 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 882 NoSubProtocols(), "http://localhost", "", | |
| 883 kIncorrectAcceptResponse); | |
| 884 WaitUntilConnectDone(); | |
| 885 EXPECT_TRUE(has_failed()); | |
| 886 EXPECT_EQ("Error during WebSocket handshake: " | |
| 887 "Incorrect 'Sec-WebSocket-Accept' header value", | |
| 888 failure_message()); | |
| 889 } | |
| 890 | |
| 891 // Cancellation works. | |
| 892 TEST_F(WebSocketStreamCreateTest, Cancellation) { | |
| 893 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 894 NoSubProtocols(), "http://localhost", "", ""); | |
| 895 stream_request_.reset(); | |
| 896 // WaitUntilConnectDone doesn't work in this case. | |
| 897 base::RunLoop().RunUntilIdle(); | |
| 898 EXPECT_FALSE(has_failed()); | |
| 899 EXPECT_FALSE(stream_); | |
| 900 EXPECT_FALSE(request_info_); | |
| 901 EXPECT_FALSE(response_info_); | |
| 902 } | |
| 903 | |
| 904 // Connect failure must look just like negotiation failure. | |
| 905 TEST_F(WebSocketStreamCreateTest, ConnectionFailure) { | |
| 906 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
| 907 socket_data->set_connect_data( | |
| 908 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | |
| 909 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
| 910 "http://localhost", socket_data.Pass()); | |
| 911 WaitUntilConnectDone(); | |
| 912 EXPECT_TRUE(has_failed()); | |
| 913 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | |
| 914 failure_message()); | |
| 915 EXPECT_FALSE(request_info_); | |
| 916 EXPECT_FALSE(response_info_); | |
| 917 } | |
| 918 | |
| 919 // Connect timeout must look just like any other failure. | |
| 920 TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) { | |
| 921 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
| 922 socket_data->set_connect_data( | |
| 923 MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); | |
| 924 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
| 925 "http://localhost", socket_data.Pass()); | |
| 926 WaitUntilConnectDone(); | |
| 927 EXPECT_TRUE(has_failed()); | |
| 928 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", | |
| 929 failure_message()); | |
| 930 } | |
| 931 | |
| 932 // The server doesn't respond to the opening handshake. | |
| 933 TEST_F(WebSocketStreamCreateTest, HandshakeTimeout) { | |
| 934 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
| 935 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | |
| 936 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
| 937 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
| 938 CreateAndConnectRawExpectations("ws://localhost/", | |
| 939 NoSubProtocols(), | |
| 940 "http://localhost", | |
| 941 socket_data.Pass(), | |
| 942 timer.Pass()); | |
| 943 EXPECT_FALSE(has_failed()); | |
| 944 ASSERT_TRUE(weak_timer.get()); | |
| 945 EXPECT_TRUE(weak_timer->IsRunning()); | |
| 946 | |
| 947 weak_timer->Fire(); | |
| 948 WaitUntilConnectDone(); | |
| 949 | |
| 950 EXPECT_TRUE(has_failed()); | |
| 951 EXPECT_EQ("WebSocket opening handshake timed out", failure_message()); | |
| 952 ASSERT_TRUE(weak_timer.get()); | |
| 953 EXPECT_FALSE(weak_timer->IsRunning()); | |
| 954 } | |
| 955 | |
| 956 // When the connection establishes the timer should be stopped. | |
| 957 TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) { | |
| 958 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
| 959 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
| 960 | |
| 961 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 962 NoSubProtocols(), "http://localhost", "", "", | |
| 963 timer.Pass()); | |
| 964 ASSERT_TRUE(weak_timer); | |
| 965 EXPECT_TRUE(weak_timer->IsRunning()); | |
| 966 | |
| 967 WaitUntilConnectDone(); | |
| 968 EXPECT_FALSE(has_failed()); | |
| 969 EXPECT_TRUE(stream_); | |
| 970 ASSERT_TRUE(weak_timer); | |
| 971 EXPECT_FALSE(weak_timer->IsRunning()); | |
| 972 } | |
| 973 | |
| 974 // When the connection fails the timer should be stopped. | |
| 975 TEST_F(WebSocketStreamCreateTest, HandshakeTimerOnFailure) { | |
| 976 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
| 977 socket_data->set_connect_data( | |
| 978 MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | |
| 979 scoped_ptr<MockWeakTimer> timer(new MockWeakTimer(false, false)); | |
| 980 base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | |
| 981 CreateAndConnectRawExpectations("ws://localhost/", | |
| 982 NoSubProtocols(), | |
| 983 "http://localhost", | |
| 984 socket_data.Pass(), | |
| 985 timer.Pass()); | |
| 986 ASSERT_TRUE(weak_timer.get()); | |
| 987 EXPECT_TRUE(weak_timer->IsRunning()); | |
| 988 | |
| 989 WaitUntilConnectDone(); | |
| 990 EXPECT_TRUE(has_failed()); | |
| 991 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | |
| 992 failure_message()); | |
| 993 ASSERT_TRUE(weak_timer.get()); | |
| 994 EXPECT_FALSE(weak_timer->IsRunning()); | |
| 995 } | |
| 996 | |
| 997 // Cancellation during connect works. | |
| 998 TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) { | |
| 999 scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData()); | |
| 1000 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | |
| 1001 CreateAndConnectRawExpectations("ws://localhost/", | |
| 1002 NoSubProtocols(), | |
| 1003 "http://localhost", | |
| 1004 socket_data.Pass()); | |
| 1005 stream_request_.reset(); | |
| 1006 // WaitUntilConnectDone doesn't work in this case. | |
| 1007 base::RunLoop().RunUntilIdle(); | |
| 1008 EXPECT_FALSE(has_failed()); | |
| 1009 EXPECT_FALSE(stream_); | |
| 1010 } | |
| 1011 | |
| 1012 // Cancellation during write of the request headers works. | |
| 1013 TEST_F(WebSocketStreamCreateTest, CancellationDuringWrite) { | |
| 1014 // We seem to need at least two operations in order to use SetStop(). | |
| 1015 MockWrite writes[] = {MockWrite(ASYNC, 0, "GET / HTTP/"), | |
| 1016 MockWrite(ASYNC, 1, "1.1\r\n")}; | |
| 1017 // We keep a copy of the pointer so that we can call RunFor() on it later. | |
| 1018 DeterministicSocketData* socket_data( | |
| 1019 new DeterministicSocketData(NULL, 0, writes, arraysize(writes))); | |
| 1020 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
| 1021 socket_data->SetStop(1); | |
| 1022 CreateAndConnectRawExpectations("ws://localhost/", | |
| 1023 NoSubProtocols(), | |
| 1024 "http://localhost", | |
| 1025 make_scoped_ptr(socket_data)); | |
| 1026 socket_data->Run(); | |
| 1027 stream_request_.reset(); | |
| 1028 // WaitUntilConnectDone doesn't work in this case. | |
| 1029 base::RunLoop().RunUntilIdle(); | |
| 1030 EXPECT_FALSE(has_failed()); | |
| 1031 EXPECT_FALSE(stream_); | |
| 1032 EXPECT_TRUE(request_info_); | |
| 1033 EXPECT_FALSE(response_info_); | |
| 1034 } | |
| 1035 | |
| 1036 // Cancellation during read of the response headers works. | |
| 1037 TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) { | |
| 1038 std::string request = | |
| 1039 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
| 1040 MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; | |
| 1041 MockRead reads[] = { | |
| 1042 MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"), | |
| 1043 }; | |
| 1044 scoped_ptr<DeterministicSocketData> socket_data( | |
| 1045 BuildSocketData(reads, writes)); | |
| 1046 socket_data->SetStop(1); | |
| 1047 DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); | |
| 1048 CreateAndConnectRawExpectations("ws://localhost/", | |
| 1049 NoSubProtocols(), | |
| 1050 "http://localhost", | |
| 1051 socket_data.Pass()); | |
| 1052 socket_data_raw_ptr->Run(); | |
| 1053 stream_request_.reset(); | |
| 1054 // WaitUntilConnectDone doesn't work in this case. | |
| 1055 base::RunLoop().RunUntilIdle(); | |
| 1056 EXPECT_FALSE(has_failed()); | |
| 1057 EXPECT_FALSE(stream_); | |
| 1058 EXPECT_TRUE(request_info_); | |
| 1059 EXPECT_FALSE(response_info_); | |
| 1060 } | |
| 1061 | |
| 1062 // Over-size response headers (> 256KB) should not cause a crash. This is a | |
| 1063 // regression test for crbug.com/339456. It is based on the layout test | |
| 1064 // "cookie-flood.html". | |
| 1065 TEST_F(WebSocketStreamCreateTest, VeryLargeResponseHeaders) { | |
| 1066 std::string set_cookie_headers; | |
| 1067 set_cookie_headers.reserve(45 * 10000); | |
| 1068 for (int i = 0; i < 10000; ++i) { | |
| 1069 set_cookie_headers += | |
| 1070 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i); | |
| 1071 } | |
| 1072 CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 1073 NoSubProtocols(), "http://localhost", "", | |
| 1074 set_cookie_headers); | |
| 1075 WaitUntilConnectDone(); | |
| 1076 EXPECT_TRUE(has_failed()); | |
| 1077 EXPECT_FALSE(response_info_); | |
| 1078 } | |
| 1079 | |
| 1080 // If the remote host closes the connection without sending headers, we should | |
| 1081 // log the console message "Connection closed before receiving a handshake | |
| 1082 // response". | |
| 1083 TEST_F(WebSocketStreamCreateTest, NoResponse) { | |
| 1084 std::string request = | |
| 1085 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
| 1086 MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)}; | |
| 1087 MockRead reads[] = {MockRead(ASYNC, 0, 1)}; | |
| 1088 scoped_ptr<DeterministicSocketData> socket_data( | |
| 1089 BuildSocketData(reads, writes)); | |
| 1090 DeterministicSocketData* socket_data_raw_ptr = socket_data.get(); | |
| 1091 CreateAndConnectRawExpectations("ws://localhost/", | |
| 1092 NoSubProtocols(), | |
| 1093 "http://localhost", | |
| 1094 socket_data.Pass()); | |
| 1095 socket_data_raw_ptr->RunFor(2); | |
| 1096 EXPECT_TRUE(has_failed()); | |
| 1097 EXPECT_FALSE(stream_); | |
| 1098 EXPECT_FALSE(response_info_); | |
| 1099 EXPECT_EQ("Connection closed before receiving a handshake response", | |
| 1100 failure_message()); | |
| 1101 } | |
| 1102 | |
| 1103 TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateFailure) { | |
| 1104 ssl_data_.push_back( | |
| 1105 new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); | |
| 1106 ssl_data_[0]->cert = | |
| 1107 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | |
| 1108 ASSERT_TRUE(ssl_data_[0]->cert.get()); | |
| 1109 scoped_ptr<DeterministicSocketData> raw_socket_data(BuildNullSocketData()); | |
| 1110 CreateAndConnectRawExpectations("wss://localhost/", | |
| 1111 NoSubProtocols(), | |
| 1112 "http://localhost", | |
| 1113 raw_socket_data.Pass()); | |
| 1114 // WaitUntilConnectDone doesn't work in this case. | |
| 1115 base::RunLoop().RunUntilIdle(); | |
| 1116 EXPECT_FALSE(has_failed()); | |
| 1117 ASSERT_TRUE(ssl_error_callbacks_); | |
| 1118 ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID, | |
| 1119 &ssl_info_); | |
| 1120 WaitUntilConnectDone(); | |
| 1121 EXPECT_TRUE(has_failed()); | |
| 1122 } | |
| 1123 | |
| 1124 TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { | |
| 1125 scoped_ptr<SSLSocketDataProvider> ssl_data( | |
| 1126 new SSLSocketDataProvider(ASYNC, ERR_CERT_AUTHORITY_INVALID)); | |
| 1127 ssl_data->cert = | |
| 1128 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | |
| 1129 ASSERT_TRUE(ssl_data->cert.get()); | |
| 1130 ssl_data_.push_back(ssl_data.release()); | |
| 1131 ssl_data.reset(new SSLSocketDataProvider(ASYNC, OK)); | |
| 1132 ssl_data_.push_back(ssl_data.release()); | |
| 1133 url_request_context_host_.AddRawExpectations(BuildNullSocketData()); | |
| 1134 CreateAndConnectStandard("wss://localhost/", "localhost", "/", | |
| 1135 NoSubProtocols(), "http://localhost", "", ""); | |
| 1136 // WaitUntilConnectDone doesn't work in this case. | |
| 1137 base::RunLoop().RunUntilIdle(); | |
| 1138 ASSERT_TRUE(ssl_error_callbacks_); | |
| 1139 ssl_error_callbacks_->ContinueSSLRequest(); | |
| 1140 WaitUntilConnectDone(); | |
| 1141 EXPECT_FALSE(has_failed()); | |
| 1142 EXPECT_TRUE(stream_); | |
| 1143 } | |
| 1144 | |
| 1145 // If the server requests authorisation, but we have no credentials, the | |
| 1146 // connection should fail cleanly. | |
| 1147 TEST_F(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) { | |
| 1148 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/", | |
| 1149 NoSubProtocols(), "http://localhost", "", | |
| 1150 kUnauthorizedResponse); | |
| 1151 WaitUntilConnectDone(); | |
| 1152 EXPECT_TRUE(has_failed()); | |
| 1153 EXPECT_EQ("HTTP Authentication failed; no valid credentials available", | |
| 1154 failure_message()); | |
| 1155 EXPECT_TRUE(response_info_); | |
| 1156 } | |
| 1157 | |
| 1158 TEST_F(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) { | |
| 1159 CreateAndConnectAuthHandshake("ws://foo:bar@localhost/", | |
| 1160 "Zm9vOmJhcg==", | |
| 1161 WebSocketStandardResponse(std::string())); | |
| 1162 WaitUntilConnectDone(); | |
| 1163 EXPECT_FALSE(has_failed()); | |
| 1164 EXPECT_TRUE(stream_); | |
| 1165 ASSERT_TRUE(response_info_); | |
| 1166 EXPECT_EQ(101, response_info_->status_code); | |
| 1167 } | |
| 1168 | |
| 1169 TEST_F(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) { | |
| 1170 CreateAndConnectAuthHandshake( | |
| 1171 "ws://foo:baz@localhost/", "Zm9vOmJheg==", kUnauthorizedResponse); | |
| 1172 WaitUntilConnectDone(); | |
| 1173 EXPECT_TRUE(has_failed()); | |
| 1174 EXPECT_TRUE(response_info_); | |
| 1175 } | |
| 1176 | |
| 1177 // Digest auth has the same connection semantics as Basic auth, so we can | |
| 1178 // generally assume that whatever works for Basic auth will also work for | |
| 1179 // Digest. There's just one test here, to confirm that it works at all. | |
| 1180 TEST_F(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) { | |
| 1181 AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | |
| 1182 | |
| 1183 CreateAndConnectRawExpectations( | |
| 1184 "ws://FooBar:pass@localhost/", | |
| 1185 NoSubProtocols(), | |
| 1186 "http://localhost", | |
| 1187 helper_.BuildSocketData2(kAuthorizedRequest, | |
| 1188 WebSocketStandardResponse(std::string()))); | |
| 1189 WaitUntilConnectDone(); | |
| 1190 EXPECT_FALSE(has_failed()); | |
| 1191 EXPECT_TRUE(stream_); | |
| 1192 ASSERT_TRUE(response_info_); | |
| 1193 EXPECT_EQ(101, response_info_->status_code); | |
| 1194 } | |
| 1195 | |
| 1196 TEST_F(WebSocketStreamCreateUMATest, Incomplete) { | |
| 1197 const std::string name("Net.WebSocket.HandshakeResult"); | |
| 1198 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
| 1199 | |
| 1200 { | |
| 1201 StreamCreation creation; | |
| 1202 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 1203 creation.NoSubProtocols(), | |
| 1204 "http://localhost", "", ""); | |
| 1205 } | |
| 1206 | |
| 1207 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
| 1208 ASSERT_TRUE(samples); | |
| 1209 if (original) { | |
| 1210 samples->Subtract(*original); // Cancel the original values. | |
| 1211 } | |
| 1212 EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); | |
| 1213 EXPECT_EQ(0, samples->GetCount(CONNECTED)); | |
| 1214 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
| 1215 } | |
| 1216 | |
| 1217 TEST_F(WebSocketStreamCreateUMATest, Connected) { | |
| 1218 const std::string name("Net.WebSocket.HandshakeResult"); | |
| 1219 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
| 1220 | |
| 1221 { | |
| 1222 StreamCreation creation; | |
| 1223 creation.CreateAndConnectStandard("ws://localhost/", "localhost", "/", | |
| 1224 creation.NoSubProtocols(), | |
| 1225 "http://localhost", "", ""); | |
| 1226 creation.WaitUntilConnectDone(); | |
| 1227 } | |
| 1228 | |
| 1229 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
| 1230 ASSERT_TRUE(samples); | |
| 1231 if (original) { | |
| 1232 samples->Subtract(*original); // Cancel the original values. | |
| 1233 } | |
| 1234 EXPECT_EQ(0, samples->GetCount(INCOMPLETE)); | |
| 1235 EXPECT_EQ(1, samples->GetCount(CONNECTED)); | |
| 1236 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
| 1237 } | |
| 1238 | |
| 1239 TEST_F(WebSocketStreamCreateUMATest, Failed) { | |
| 1240 const std::string name("Net.WebSocket.HandshakeResult"); | |
| 1241 scoped_ptr<base::HistogramSamples> original(GetSamples(name)); | |
| 1242 | |
| 1243 { | |
| 1244 StreamCreation creation; | |
| 1245 static const char kInvalidStatusCodeResponse[] = | |
| 1246 "HTTP/1.1 200 OK\r\n" | |
| 1247 "Upgrade: websocket\r\n" | |
| 1248 "Connection: Upgrade\r\n" | |
| 1249 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 1250 "\r\n"; | |
| 1251 creation.CreateAndConnectCustomResponse( | |
| 1252 "ws://localhost/", "localhost", "/", creation.NoSubProtocols(), | |
| 1253 "http://localhost", "", kInvalidStatusCodeResponse); | |
| 1254 creation.WaitUntilConnectDone(); | |
| 1255 } | |
| 1256 | |
| 1257 scoped_ptr<base::HistogramSamples> samples(GetSamples(name)); | |
| 1258 ASSERT_TRUE(samples); | |
| 1259 if (original) { | |
| 1260 samples->Subtract(*original); // Cancel the original values. | |
| 1261 } | |
| 1262 EXPECT_EQ(1, samples->GetCount(INCOMPLETE)); | |
| 1263 EXPECT_EQ(0, samples->GetCount(CONNECTED)); | |
| 1264 EXPECT_EQ(0, samples->GetCount(FAILED)); | |
| 1265 } | |
| 1266 | |
| 1267 TEST_F(WebSocketStreamCreateTest, HandleErrConnectionClosed) { | |
| 1268 static const char kTruncatedResponse[] = | |
| 1269 "HTTP/1.1 101 Switching Protocols\r\n" | |
| 1270 "Upgrade: websocket\r\n" | |
| 1271 "Connection: Upgrade\r\n" | |
| 1272 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | |
| 1273 "Cache-Control: no-sto"; | |
| 1274 | |
| 1275 std::string request = | |
| 1276 WebSocketStandardRequest("/", "localhost", "http://localhost", ""); | |
| 1277 MockRead reads[] = { | |
| 1278 MockRead(SYNCHRONOUS, 1, kTruncatedResponse), | |
| 1279 MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2), | |
| 1280 }; | |
| 1281 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())}; | |
| 1282 scoped_ptr<DeterministicSocketData> socket_data( | |
| 1283 BuildSocketData(reads, writes)); | |
| 1284 socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | |
| 1285 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
| 1286 "http://localhost", socket_data.Pass()); | |
| 1287 WaitUntilConnectDone(); | |
| 1288 EXPECT_TRUE(has_failed()); | |
| 1289 } | |
| 1290 | |
| 1291 TEST_F(WebSocketStreamCreateTest, HandleErrTunnelConnectionFailed) { | |
| 1292 static const char kConnectRequest[] = | |
| 1293 "CONNECT localhost:80 HTTP/1.1\r\n" | |
| 1294 "Host: localhost\r\n" | |
| 1295 "Proxy-Connection: keep-alive\r\n" | |
| 1296 "\r\n"; | |
| 1297 | |
| 1298 static const char kProxyResponse[] = | |
| 1299 "HTTP/1.1 403 Forbidden\r\n" | |
| 1300 "Content-Type: text/html\r\n" | |
| 1301 "Content-Length: 9\r\n" | |
| 1302 "Connection: keep-alive\r\n" | |
| 1303 "\r\n" | |
| 1304 "Forbidden"; | |
| 1305 | |
| 1306 MockRead reads[] = {MockRead(SYNCHRONOUS, 1, kProxyResponse)}; | |
| 1307 MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, kConnectRequest)}; | |
| 1308 scoped_ptr<DeterministicSocketData> socket_data( | |
| 1309 BuildSocketData(reads, writes)); | |
| 1310 url_request_context_host_.SetProxyConfig("https=proxy:8000"); | |
| 1311 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(), | |
| 1312 "http://localhost", socket_data.Pass()); | |
| 1313 WaitUntilConnectDone(); | |
| 1314 EXPECT_TRUE(has_failed()); | |
| 1315 EXPECT_EQ("Establishing a tunnel via proxy server failed.", | |
| 1316 failure_message()); | |
| 1317 } | |
| 1318 | |
| 1319 } // namespace | |
| 1320 } // namespace net | |
| OLD | NEW |