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.
5 #include "net/websockets/websocket_stream.h"
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"
36 #include "url/origin.h"
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
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();
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));
64 class MockWeakTimer
: public base::MockTimer
,
65 public base::SupportsWeakPtr
<MockWeakTimer
> {
67 MockWeakTimer(bool retain_user_task
, bool is_repeating
)
68 : MockTimer(retain_user_task
, is_repeating
) {}
71 class WebSocketStreamCreateTest
: public ::testing::Test
,
72 public WebSocketStreamCreateTestBase
{
74 ~WebSocketStreamCreateTest() override
{
75 // Permit any endpoint locks to be released.
76 stream_request_
.reset();
78 base::RunLoop().RunUntilIdle();
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
),
94 CreateAndConnectStream(socket_url
, sub_protocols
, origin
, timer
.Pass());
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());
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());
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());
130 // There are enough tests of the Sec-WebSocket-Extensions header that they
131 // deserve their own test fixture.
132 class WebSocketStreamCreateExtensionTest
: public WebSocketStreamCreateTest
{
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();
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
{
152 CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {}
154 scoped_ptr
<DeterministicSocketData
> BuildSocketData1(
155 const std::string
& response
) {
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
163 return BuildSocketData(reads1_
, writes1_
);
166 scoped_ptr
<DeterministicSocketData
> BuildSocketData2(
167 const std::string
& request
,
168 const std::string
& response
) {
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_
);
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_
;
184 MockWrite writes1_
[1];
186 MockWrite writes2_
[1];
188 DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper
);
191 // Data and methods for BasicAuth tests.
192 class WebSocketStreamCreateBasicAuthTest
: public WebSocketStreamCreateTest
{
194 void CreateAndConnectAuthHandshake(const std::string
& url
,
195 const std::string
& base64_user_pass
,
196 const std::string
& response2
) {
197 AddRawExpectations(helper_
.BuildSocketData1(kUnauthorizedResponse
));
199 static const char request2format
[] =
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"
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"
216 const std::string request
=
217 base::StringPrintf(request2format
, base64_user_pass
.c_str());
218 CreateAndConnectRawExpectations(
222 helper_
.BuildSocketData2(request
, response2
));
225 static const char kUnauthorizedResponse
[];
227 CommonAuthTestHelper helper_
;
230 class WebSocketStreamCreateDigestAuthTest
: public WebSocketStreamCreateTest
{
232 static const char kUnauthorizedResponse
[];
233 static const char kAuthorizedRequest
[];
235 CommonAuthTestHelper helper_
;
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"
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"
254 const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest
[] =
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"
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"
274 class WebSocketStreamCreateUMATest
: public ::testing::Test
{
276 // This enum should match with the enum in Delegate in websocket_stream.cc.
277 enum HandshakeResult
{
281 NUM_HANDSHAKE_RESULT_TYPES
,
284 class StreamCreation
: public WebSocketStreamCreateTest
{
285 void TestBody() override
{}
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
>();
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_
);
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"
320 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/",
321 NoSubProtocols(), "http://localhost", "",
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"),
343 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers
[4]);
344 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
346 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
348 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers
[7]);
349 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"),
351 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
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]);
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());
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]);
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_
);
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_
);
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_
);
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());
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",
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",
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",
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",
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());
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());
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") +
512 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
513 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
515 WaitUntilConnectDone();
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));
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",
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());
544 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
545 "value is rejected by the parser: ;",
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());
556 "Error during WebSocket handshake: "
557 "Received duplicate permessage-deflate response",
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());
569 "Error during WebSocket handshake: Error in permessage-deflate: "
570 "Received duplicate permessage-deflate extension parameter "
571 "client_no_context_takeover",
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());
582 "Error during WebSocket handshake: Error in permessage-deflate: "
583 "Received an unexpected permessage-deflate extension parameter",
587 // permessage-deflate parameters must be either *_no_context_takeover or
589 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterSuffix
) {
590 CreateAndConnectWithExtensions(
591 "permessage-deflate; client_max_content_bits=5");
592 EXPECT_FALSE(stream_
);
593 EXPECT_TRUE(has_failed());
595 "Error during WebSocket handshake: Error in permessage-deflate: "
596 "Received an unexpected permessage-deflate extension parameter",
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());
607 "Error during WebSocket handshake: Error in permessage-deflate: "
608 "Received invalid client_no_context_takeover parameter",
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());
618 "Error during WebSocket handshake: Error in permessage-deflate: "
619 "client_max_window_bits must have value",
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());
630 "Error during WebSocket handshake: Error in permessage-deflate: "
631 "Received invalid server_max_window_bits parameter",
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());
642 "Error during WebSocket handshake: Error in permessage-deflate: "
643 "Received invalid server_max_window_bits parameter",
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());
654 "Error during WebSocket handshake: Error in permessage-deflate: "
655 "Received invalid client_max_window_bits parameter",
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());
666 "Error during WebSocket handshake: Error in permessage-deflate: "
667 "Received invalid client_max_window_bits parameter",
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());
678 "Error during WebSocket handshake: Error in permessage-deflate: "
679 "Received invalid server_max_window_bits parameter",
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.
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",
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"
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",
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"
728 "<title>Moved</title><h1>Moved</h1>";
729 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/",
730 NoSubProtocols(), "http://localhost", "",
732 WaitUntilConnectDone();
733 EXPECT_TRUE(has_failed());
734 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
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"
750 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/",
751 NoSubProtocols(), "http://localhost", "",
753 WaitUntilConnectDone();
754 EXPECT_TRUE(has_failed());
755 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
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"
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",
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",
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"
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",
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"
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",
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"
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'",
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"
848 CreateAndConnectCustomResponse("ws://localhost/", "localhost", "/",
849 NoSubProtocols(), "http://localhost", "",
850 kAdditionalConnectionTokenResponse
);
851 WaitUntilConnectDone();
852 EXPECT_FALSE(has_failed());
853 EXPECT_TRUE(stream_
);
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"
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",
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"
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",
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_
);
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",
915 EXPECT_FALSE(request_info_
);
916 EXPECT_FALSE(response_info_
);
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",
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/",
943 EXPECT_FALSE(has_failed());
944 ASSERT_TRUE(weak_timer
.get());
945 EXPECT_TRUE(weak_timer
->IsRunning());
948 WaitUntilConnectDone();
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());
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();
961 CreateAndConnectStandard("ws://localhost/", "localhost", "/",
962 NoSubProtocols(), "http://localhost", "", "",
964 ASSERT_TRUE(weak_timer
);
965 EXPECT_TRUE(weak_timer
->IsRunning());
967 WaitUntilConnectDone();
968 EXPECT_FALSE(has_failed());
969 EXPECT_TRUE(stream_
);
970 ASSERT_TRUE(weak_timer
);
971 EXPECT_FALSE(weak_timer
->IsRunning());
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/",
986 ASSERT_TRUE(weak_timer
.get());
987 EXPECT_TRUE(weak_timer
->IsRunning());
989 WaitUntilConnectDone();
990 EXPECT_TRUE(has_failed());
991 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
993 ASSERT_TRUE(weak_timer
.get());
994 EXPECT_FALSE(weak_timer
->IsRunning());
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/",
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_
);
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/",
1025 make_scoped_ptr(socket_data
));
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_
);
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"),
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/",
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_
);
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
);
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_
);
1080 // If the remote host closes the connection without sending headers, we should
1081 // log the console message "Connection closed before receiving a handshake
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/",
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",
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/",
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
,
1120 WaitUntilConnectDone();
1121 EXPECT_TRUE(has_failed());
1124 TEST_F(WebSocketStreamCreateTest
, SelfSignedCertificateSuccess
) {
1125 scoped_ptr
<SSLSocketDataProvider
> ssl_data(
1126 new SSLSocketDataProvider(ASYNC
, ERR_CERT_AUTHORITY_INVALID
));
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_
);
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",
1155 EXPECT_TRUE(response_info_
);
1158 TEST_F(WebSocketStreamCreateBasicAuthTest
, SuccessPasswordInUrl
) {
1159 CreateAndConnectAuthHandshake("ws://foo:bar@localhost/",
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
);
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_
);
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
));
1183 CreateAndConnectRawExpectations(
1184 "ws://FooBar:pass@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
);
1196 TEST_F(WebSocketStreamCreateUMATest
, Incomplete
) {
1197 const std::string
name("Net.WebSocket.HandshakeResult");
1198 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1201 StreamCreation creation
;
1202 creation
.CreateAndConnectStandard("ws://localhost/", "localhost", "/",
1203 creation
.NoSubProtocols(),
1204 "http://localhost", "", "");
1207 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1208 ASSERT_TRUE(samples
);
1210 samples
->Subtract(*original
); // Cancel the original values.
1212 EXPECT_EQ(1, samples
->GetCount(INCOMPLETE
));
1213 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1214 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1217 TEST_F(WebSocketStreamCreateUMATest
, Connected
) {
1218 const std::string
name("Net.WebSocket.HandshakeResult");
1219 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1222 StreamCreation creation
;
1223 creation
.CreateAndConnectStandard("ws://localhost/", "localhost", "/",
1224 creation
.NoSubProtocols(),
1225 "http://localhost", "", "");
1226 creation
.WaitUntilConnectDone();
1229 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1230 ASSERT_TRUE(samples
);
1232 samples
->Subtract(*original
); // Cancel the original values.
1234 EXPECT_EQ(0, samples
->GetCount(INCOMPLETE
));
1235 EXPECT_EQ(1, samples
->GetCount(CONNECTED
));
1236 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1239 TEST_F(WebSocketStreamCreateUMATest
, Failed
) {
1240 const std::string
name("Net.WebSocket.HandshakeResult");
1241 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
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"
1251 creation
.CreateAndConnectCustomResponse(
1252 "ws://localhost/", "localhost", "/", creation
.NoSubProtocols(),
1253 "http://localhost", "", kInvalidStatusCodeResponse
);
1254 creation
.WaitUntilConnectDone();
1257 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1258 ASSERT_TRUE(samples
);
1260 samples
->Subtract(*original
); // Cancel the original values.
1262 EXPECT_EQ(1, samples
->GetCount(INCOMPLETE
));
1263 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1264 EXPECT_EQ(0, samples
->GetCount(FAILED
));
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";
1275 std::string request
=
1276 WebSocketStandardRequest("/", "localhost", "http://localhost", "");
1277 MockRead reads
[] = {
1278 MockRead(SYNCHRONOUS
, 1, kTruncatedResponse
),
1279 MockRead(SYNCHRONOUS
, ERR_CONNECTION_CLOSED
, 2),
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());
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"
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"
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.",