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 "net/base/net_errors.h"
20 #include "net/base/test_data_directory.h"
21 #include "net/http/http_request_headers.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/socket/client_socket_handle.h"
24 #include "net/socket/socket_test_util.h"
25 #include "net/test/cert_test_util.h"
26 #include "net/url_request/url_request_test_util.h"
27 #include "net/websockets/websocket_basic_handshake_stream.h"
28 #include "net/websockets/websocket_frame.h"
29 #include "net/websockets/websocket_handshake_request_info.h"
30 #include "net/websockets/websocket_handshake_response_info.h"
31 #include "net/websockets/websocket_handshake_stream_create_helper.h"
32 #include "net/websockets/websocket_test_util.h"
33 #include "testing/gtest/include/gtest/gtest.h"
35 #include "url/origin.h"
40 typedef std::pair
<std::string
, std::string
> HeaderKeyValuePair
;
42 std::vector
<HeaderKeyValuePair
> ToVector(const HttpRequestHeaders
& headers
) {
43 HttpRequestHeaders::Iterator
it(headers
);
44 std::vector
<HeaderKeyValuePair
> result
;
46 result
.push_back(HeaderKeyValuePair(it
.name(), it
.value()));
50 std::vector
<HeaderKeyValuePair
> ToVector(const HttpResponseHeaders
& headers
) {
52 std::string name
, value
;
53 std::vector
<HeaderKeyValuePair
> result
;
54 while (headers
.EnumerateHeaderLines(&iter
, &name
, &value
))
55 result
.push_back(HeaderKeyValuePair(name
, value
));
59 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
60 // deterministic key to use in the WebSocket handshake.
61 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
62 : public WebSocketHandshakeStreamCreateHelper
{
64 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
65 WebSocketStream::ConnectDelegate
* connect_delegate
,
66 const std::vector
<std::string
>& requested_subprotocols
)
67 : WebSocketHandshakeStreamCreateHelper(connect_delegate
,
68 requested_subprotocols
) {}
70 virtual void OnStreamCreated(WebSocketBasicHandshakeStream
* stream
) OVERRIDE
{
71 stream
->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
75 class WebSocketStreamCreateTest
: public ::testing::Test
{
77 WebSocketStreamCreateTest() : has_failed_(false), ssl_fatal_(false) {}
79 void CreateAndConnectCustomResponse(
80 const std::string
& socket_url
,
81 const std::string
& socket_path
,
82 const std::vector
<std::string
>& sub_protocols
,
83 const std::string
& origin
,
84 const std::string
& extra_request_headers
,
85 const std::string
& response_body
) {
86 url_request_context_host_
.SetExpectations(
87 WebSocketStandardRequest(socket_path
, origin
, extra_request_headers
),
89 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
92 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or
93 // errors like "Unable to perform synchronous IO while stopped" will occur.
94 void CreateAndConnectStandard(const std::string
& socket_url
,
95 const std::string
& socket_path
,
96 const std::vector
<std::string
>& sub_protocols
,
97 const std::string
& origin
,
98 const std::string
& extra_request_headers
,
99 const std::string
& extra_response_headers
) {
100 CreateAndConnectCustomResponse(
105 extra_request_headers
,
106 WebSocketStandardResponse(extra_response_headers
));
109 void CreateAndConnectRawExpectations(
110 const std::string
& socket_url
,
111 const std::vector
<std::string
>& sub_protocols
,
112 const std::string
& origin
,
113 scoped_ptr
<DeterministicSocketData
> socket_data
) {
114 url_request_context_host_
.AddRawExpectations(socket_data
.Pass());
115 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
118 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
120 void CreateAndConnectStream(const std::string
& socket_url
,
121 const std::vector
<std::string
>& sub_protocols
,
122 const std::string
& origin
) {
123 for (size_t i
= 0; i
< ssl_data_
.size(); ++i
) {
124 scoped_ptr
<SSLSocketDataProvider
> ssl_data(ssl_data_
[i
]);
126 url_request_context_host_
.AddSSLSocketDataProvider(ssl_data
.Pass());
129 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate(
130 new TestConnectDelegate(this));
131 WebSocketStream::ConnectDelegate
* delegate
= connect_delegate
.get();
132 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
> create_helper(
133 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
134 delegate
, sub_protocols
));
135 stream_request_
= ::net::CreateAndConnectStreamForTesting(
137 create_helper
.Pass(),
139 url_request_context_host_
.GetURLRequestContext(),
141 connect_delegate
.Pass());
144 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
146 // A simple function to make the tests more readable. Creates an empty vector.
147 static std::vector
<std::string
> NoSubProtocols() {
148 return std::vector
<std::string
>();
151 const std::string
& failure_message() const { return failure_message_
; }
152 bool has_failed() const { return has_failed_
; }
154 class TestConnectDelegate
: public WebSocketStream::ConnectDelegate
{
156 explicit TestConnectDelegate(WebSocketStreamCreateTest
* owner
)
159 virtual void OnSuccess(scoped_ptr
<WebSocketStream
> stream
) OVERRIDE
{
160 stream
.swap(owner_
->stream_
);
163 virtual void OnFailure(const std::string
& message
) OVERRIDE
{
164 owner_
->has_failed_
= true;
165 owner_
->failure_message_
= message
;
168 virtual void OnStartOpeningHandshake(
169 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) OVERRIDE
{
170 if (owner_
->request_info_
)
172 owner_
->request_info_
= request
.Pass();
174 virtual void OnFinishOpeningHandshake(
175 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) OVERRIDE
{
176 if (owner_
->response_info_
)
178 owner_
->response_info_
= response
.Pass();
180 virtual void OnSSLCertificateError(
181 scoped_ptr
<WebSocketEventInterface::SSLErrorCallbacks
>
183 const SSLInfo
& ssl_info
,
184 bool fatal
) OVERRIDE
{
185 owner_
->ssl_error_callbacks_
= ssl_error_callbacks
.Pass();
186 owner_
->ssl_info_
= ssl_info
;
187 owner_
->ssl_fatal_
= fatal
;
191 WebSocketStreamCreateTest
* owner_
;
194 WebSocketTestURLRequestContextHost url_request_context_host_
;
195 scoped_ptr
<WebSocketStreamRequest
> stream_request_
;
196 // Only set if the connection succeeded.
197 scoped_ptr
<WebSocketStream
> stream_
;
198 // Only set if the connection failed.
199 std::string failure_message_
;
201 scoped_ptr
<WebSocketHandshakeRequestInfo
> request_info_
;
202 scoped_ptr
<WebSocketHandshakeResponseInfo
> response_info_
;
203 scoped_ptr
<WebSocketEventInterface::SSLErrorCallbacks
> ssl_error_callbacks_
;
206 ScopedVector
<SSLSocketDataProvider
> ssl_data_
;
209 // There are enough tests of the Sec-WebSocket-Extensions header that they
210 // deserve their own test fixture.
211 class WebSocketStreamCreateExtensionTest
: public WebSocketStreamCreateTest
{
213 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
214 // header in the response set to |extensions_header_value|. Runs the event
215 // loop to allow the connect to complete.
216 void CreateAndConnectWithExtensions(
217 const std::string
& extensions_header_value
) {
218 CreateAndConnectStandard(
219 "ws://localhost/testing_path",
224 "Sec-WebSocket-Extensions: " + extensions_header_value
+ "\r\n");
229 class WebSocketStreamCreateUMATest
: public ::testing::Test
{
231 // This enum should match with the enum in Delegate in websocket_stream.cc.
232 enum HandshakeResult
{
236 NUM_HANDSHAKE_RESULT_TYPES
,
239 class StreamCreation
: public WebSocketStreamCreateTest
{
240 virtual void TestBody() OVERRIDE
{}
243 scoped_ptr
<base::HistogramSamples
> GetSamples(const std::string
& name
) {
244 base::HistogramBase
* histogram
=
245 base::StatisticsRecorder::FindHistogram(name
);
246 return histogram
? histogram
->SnapshotSamples()
247 : scoped_ptr
<base::HistogramSamples
>();
251 // Confirm that the basic case works as expected.
252 TEST_F(WebSocketStreamCreateTest
, SimpleSuccess
) {
253 CreateAndConnectStandard(
254 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
255 EXPECT_FALSE(request_info_
);
256 EXPECT_FALSE(response_info_
);
258 EXPECT_FALSE(has_failed());
259 EXPECT_TRUE(stream_
);
260 EXPECT_TRUE(request_info_
);
261 EXPECT_TRUE(response_info_
);
264 TEST_F(WebSocketStreamCreateTest
, HandshakeInfo
) {
265 static const char kResponse
[] =
266 "HTTP/1.1 101 Switching Protocols\r\n"
267 "Upgrade: websocket\r\n"
268 "Connection: Upgrade\r\n"
269 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
275 CreateAndConnectCustomResponse(
282 EXPECT_FALSE(request_info_
);
283 EXPECT_FALSE(response_info_
);
285 EXPECT_TRUE(stream_
);
286 ASSERT_TRUE(request_info_
);
287 ASSERT_TRUE(response_info_
);
288 std::vector
<HeaderKeyValuePair
> request_headers
=
289 ToVector(request_info_
->headers
);
290 // We examine the contents of request_info_ and response_info_
291 // mainly only in this test case.
292 EXPECT_EQ(GURL("ws://localhost/"), request_info_
->url
);
293 EXPECT_EQ(GURL("ws://localhost/"), response_info_
->url
);
294 EXPECT_EQ(101, response_info_
->status_code
);
295 EXPECT_EQ("Switching Protocols", response_info_
->status_text
);
296 ASSERT_EQ(12u, request_headers
.size());
297 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers
[0]);
298 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers
[1]);
299 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers
[2]);
300 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
302 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers
[4]);
303 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
305 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
307 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers
[7]);
308 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"),
310 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
312 EXPECT_EQ("Sec-WebSocket-Key", request_headers
[10].first
);
313 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
314 "permessage-deflate; client_max_window_bits"),
315 request_headers
[11]);
317 std::vector
<HeaderKeyValuePair
> response_headers
=
318 ToVector(*response_info_
->headers
);
319 ASSERT_EQ(6u, response_headers
.size());
320 // Sort the headers for ease of verification.
321 std::sort(response_headers
.begin(), response_headers
.end());
323 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers
[0]);
324 EXPECT_EQ("Sec-WebSocket-Accept", response_headers
[1].first
);
325 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers
[2]);
326 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers
[3]);
327 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers
[4]);
328 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers
[5]);
331 // Confirm that the stream isn't established until the message loop runs.
332 TEST_F(WebSocketStreamCreateTest
, NeedsToRunLoop
) {
333 CreateAndConnectStandard(
334 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
335 EXPECT_FALSE(has_failed());
336 EXPECT_FALSE(stream_
);
339 // Check the path is used.
340 TEST_F(WebSocketStreamCreateTest
, PathIsUsed
) {
341 CreateAndConnectStandard("ws://localhost/testing_path",
348 EXPECT_FALSE(has_failed());
349 EXPECT_TRUE(stream_
);
352 // Check that the origin is used.
353 TEST_F(WebSocketStreamCreateTest
, OriginIsUsed
) {
354 CreateAndConnectStandard("ws://localhost/testing_path",
361 EXPECT_FALSE(has_failed());
362 EXPECT_TRUE(stream_
);
365 // Check that sub-protocols are sent and parsed.
366 TEST_F(WebSocketStreamCreateTest
, SubProtocolIsUsed
) {
367 std::vector
<std::string
> sub_protocols
;
368 sub_protocols
.push_back("chatv11.chromium.org");
369 sub_protocols
.push_back("chatv20.chromium.org");
370 CreateAndConnectStandard("ws://localhost/testing_path",
374 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
375 "chatv20.chromium.org\r\n",
376 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
378 EXPECT_TRUE(stream_
);
379 EXPECT_FALSE(has_failed());
380 EXPECT_EQ("chatv20.chromium.org", stream_
->GetSubProtocol());
383 // Unsolicited sub-protocols are rejected.
384 TEST_F(WebSocketStreamCreateTest
, UnsolicitedSubProtocol
) {
385 CreateAndConnectStandard("ws://localhost/testing_path",
390 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
392 EXPECT_FALSE(stream_
);
393 EXPECT_TRUE(has_failed());
394 EXPECT_EQ("Error during WebSocket handshake: "
395 "Response must not include 'Sec-WebSocket-Protocol' header "
396 "if not present in request: chatv20.chromium.org",
400 // Missing sub-protocol response is rejected.
401 TEST_F(WebSocketStreamCreateTest
, UnacceptedSubProtocol
) {
402 std::vector
<std::string
> sub_protocols
;
403 sub_protocols
.push_back("chat.example.com");
404 CreateAndConnectStandard("ws://localhost/testing_path",
408 "Sec-WebSocket-Protocol: chat.example.com\r\n",
411 EXPECT_FALSE(stream_
);
412 EXPECT_TRUE(has_failed());
413 EXPECT_EQ("Error during WebSocket handshake: "
414 "Sent non-empty 'Sec-WebSocket-Protocol' header "
415 "but no response was received",
419 // Only one sub-protocol can be accepted.
420 TEST_F(WebSocketStreamCreateTest
, MultipleSubProtocolsInResponse
) {
421 std::vector
<std::string
> sub_protocols
;
422 sub_protocols
.push_back("chatv11.chromium.org");
423 sub_protocols
.push_back("chatv20.chromium.org");
424 CreateAndConnectStandard("ws://localhost/testing_path",
428 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
429 "chatv20.chromium.org\r\n",
430 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
431 "chatv20.chromium.org\r\n");
433 EXPECT_FALSE(stream_
);
434 EXPECT_TRUE(has_failed());
435 EXPECT_EQ("Error during WebSocket handshake: "
436 "'Sec-WebSocket-Protocol' header must not appear "
437 "more than once in a response",
441 // Unmatched sub-protocol should be rejected.
442 TEST_F(WebSocketStreamCreateTest
, UnmatchedSubProtocolInResponse
) {
443 std::vector
<std::string
> sub_protocols
;
444 sub_protocols
.push_back("chatv11.chromium.org");
445 sub_protocols
.push_back("chatv20.chromium.org");
446 CreateAndConnectStandard("ws://localhost/testing_path",
450 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
451 "chatv20.chromium.org\r\n",
452 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n");
454 EXPECT_FALSE(stream_
);
455 EXPECT_TRUE(has_failed());
456 EXPECT_EQ("Error during WebSocket handshake: "
457 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
458 "in response does not match any of sent values",
462 // permessage-deflate extension basic success case.
463 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateSuccess
) {
464 CreateAndConnectWithExtensions("permessage-deflate");
465 EXPECT_TRUE(stream_
);
466 EXPECT_FALSE(has_failed());
469 // permessage-deflate extensions success with all parameters.
470 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateParamsSuccess
) {
471 CreateAndConnectWithExtensions(
472 "permessage-deflate; client_no_context_takeover; "
473 "server_max_window_bits=11; client_max_window_bits=13; "
474 "server_no_context_takeover");
475 EXPECT_TRUE(stream_
);
476 EXPECT_FALSE(has_failed());
479 // Verify that incoming messages are actually decompressed with
480 // permessage-deflate enabled.
481 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateInflates
) {
482 CreateAndConnectCustomResponse(
483 "ws://localhost/testing_path",
488 WebSocketStandardResponse(
489 "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
491 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
492 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
496 ASSERT_TRUE(stream_
);
497 ScopedVector
<WebSocketFrame
> frames
;
498 CompletionCallback callback
;
499 ASSERT_EQ(OK
, stream_
->ReadFrames(&frames
, callback
));
500 ASSERT_EQ(1U, frames
.size());
501 ASSERT_EQ(5U, frames
[0]->header
.payload_length
);
502 EXPECT_EQ("Hello", std::string(frames
[0]->data
->data(), 5));
505 // Unknown extension in the response is rejected
506 TEST_F(WebSocketStreamCreateExtensionTest
, UnknownExtension
) {
507 CreateAndConnectWithExtensions("x-unknown-extension");
508 EXPECT_FALSE(stream_
);
509 EXPECT_TRUE(has_failed());
510 EXPECT_EQ("Error during WebSocket handshake: "
511 "Found an unsupported extension 'x-unknown-extension' "
512 "in 'Sec-WebSocket-Extensions' header",
516 // Malformed extensions are rejected (this file does not cover all possible
517 // parse failures, as the parser is covered thoroughly by its own unit tests).
518 TEST_F(WebSocketStreamCreateExtensionTest
, MalformedExtension
) {
519 CreateAndConnectWithExtensions(";");
520 EXPECT_FALSE(stream_
);
521 EXPECT_TRUE(has_failed());
523 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
524 "value is rejected by the parser: ;",
528 // The permessage-deflate extension may only be specified once.
529 TEST_F(WebSocketStreamCreateExtensionTest
, OnlyOnePerMessageDeflateAllowed
) {
530 CreateAndConnectWithExtensions(
531 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
532 EXPECT_FALSE(stream_
);
533 EXPECT_TRUE(has_failed());
535 "Error during WebSocket handshake: "
536 "Received duplicate permessage-deflate response",
540 // permessage-deflate parameters may not be duplicated.
541 TEST_F(WebSocketStreamCreateExtensionTest
, NoDuplicateParameters
) {
542 CreateAndConnectWithExtensions(
543 "permessage-deflate; client_no_context_takeover; "
544 "client_no_context_takeover");
545 EXPECT_FALSE(stream_
);
546 EXPECT_TRUE(has_failed());
548 "Error during WebSocket handshake: Error in permessage-deflate: "
549 "Received duplicate permessage-deflate extension parameter "
550 "client_no_context_takeover",
554 // permessage-deflate parameters must start with "client_" or "server_"
555 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterPrefix
) {
556 CreateAndConnectWithExtensions(
557 "permessage-deflate; absurd_no_context_takeover");
558 EXPECT_FALSE(stream_
);
559 EXPECT_TRUE(has_failed());
561 "Error during WebSocket handshake: Error in permessage-deflate: "
562 "Received an unexpected permessage-deflate extension parameter",
566 // permessage-deflate parameters must be either *_no_context_takeover or
568 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterSuffix
) {
569 CreateAndConnectWithExtensions(
570 "permessage-deflate; client_max_content_bits=5");
571 EXPECT_FALSE(stream_
);
572 EXPECT_TRUE(has_failed());
574 "Error during WebSocket handshake: Error in permessage-deflate: "
575 "Received an unexpected permessage-deflate extension parameter",
579 // *_no_context_takeover parameters must not have an argument
580 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterValue
) {
581 CreateAndConnectWithExtensions(
582 "permessage-deflate; client_no_context_takeover=true");
583 EXPECT_FALSE(stream_
);
584 EXPECT_TRUE(has_failed());
586 "Error during WebSocket handshake: Error in permessage-deflate: "
587 "Received invalid client_no_context_takeover parameter",
591 // *_max_window_bits must have an argument
592 TEST_F(WebSocketStreamCreateExtensionTest
, NoMaxWindowBitsArgument
) {
593 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
594 EXPECT_FALSE(stream_
);
595 EXPECT_TRUE(has_failed());
597 "Error during WebSocket handshake: Error in permessage-deflate: "
598 "client_max_window_bits must have value",
602 // *_max_window_bits must be an integer
603 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueInteger
) {
604 CreateAndConnectWithExtensions(
605 "permessage-deflate; server_max_window_bits=banana");
606 EXPECT_FALSE(stream_
);
607 EXPECT_TRUE(has_failed());
609 "Error during WebSocket handshake: Error in permessage-deflate: "
610 "Received invalid server_max_window_bits parameter",
614 // *_max_window_bits must be >= 8
615 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooSmall
) {
616 CreateAndConnectWithExtensions(
617 "permessage-deflate; server_max_window_bits=7");
618 EXPECT_FALSE(stream_
);
619 EXPECT_TRUE(has_failed());
621 "Error during WebSocket handshake: Error in permessage-deflate: "
622 "Received invalid server_max_window_bits parameter",
626 // *_max_window_bits must be <= 15
627 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooBig
) {
628 CreateAndConnectWithExtensions(
629 "permessage-deflate; client_max_window_bits=16");
630 EXPECT_FALSE(stream_
);
631 EXPECT_TRUE(has_failed());
633 "Error during WebSocket handshake: Error in permessage-deflate: "
634 "Received invalid client_max_window_bits parameter",
638 // *_max_window_bits must not start with 0
639 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithZero
) {
640 CreateAndConnectWithExtensions(
641 "permessage-deflate; client_max_window_bits=08");
642 EXPECT_FALSE(stream_
);
643 EXPECT_TRUE(has_failed());
645 "Error during WebSocket handshake: Error in permessage-deflate: "
646 "Received invalid client_max_window_bits parameter",
650 // *_max_window_bits must not start with +
651 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithPlus
) {
652 CreateAndConnectWithExtensions(
653 "permessage-deflate; server_max_window_bits=+9");
654 EXPECT_FALSE(stream_
);
655 EXPECT_TRUE(has_failed());
657 "Error during WebSocket handshake: Error in permessage-deflate: "
658 "Received invalid server_max_window_bits parameter",
662 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
663 // arguments from the server. This is difficult because the data written to the
664 // socket is randomly masked.
666 // Additional Sec-WebSocket-Accept headers should be rejected.
667 TEST_F(WebSocketStreamCreateTest
, DoubleAccept
) {
668 CreateAndConnectStandard(
674 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
676 EXPECT_FALSE(stream_
);
677 EXPECT_TRUE(has_failed());
678 EXPECT_EQ("Error during WebSocket handshake: "
679 "'Sec-WebSocket-Accept' header must not appear "
680 "more than once in a response",
684 // Response code 200 must be rejected.
685 TEST_F(WebSocketStreamCreateTest
, InvalidStatusCode
) {
686 static const char kInvalidStatusCodeResponse
[] =
687 "HTTP/1.1 200 OK\r\n"
688 "Upgrade: websocket\r\n"
689 "Connection: Upgrade\r\n"
690 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
692 CreateAndConnectCustomResponse("ws://localhost/",
697 kInvalidStatusCodeResponse
);
699 EXPECT_TRUE(has_failed());
700 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
704 // Redirects are not followed (according to the WHATWG WebSocket API, which
705 // overrides RFC6455 for browser applications).
706 TEST_F(WebSocketStreamCreateTest
, RedirectsRejected
) {
707 static const char kRedirectResponse
[] =
708 "HTTP/1.1 302 Moved Temporarily\r\n"
709 "Content-Type: text/html\r\n"
710 "Content-Length: 34\r\n"
711 "Connection: keep-alive\r\n"
712 "Location: ws://localhost/other\r\n"
714 "<title>Moved</title><h1>Moved</h1>";
715 CreateAndConnectCustomResponse("ws://localhost/",
722 EXPECT_TRUE(has_failed());
723 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
727 // Malformed responses should be rejected. HttpStreamParser will accept just
728 // about any garbage in the middle of the headers. To make it give up, the junk
729 // has to be at the start of the response. Even then, it just gets treated as an
730 // HTTP/0.9 response.
731 TEST_F(WebSocketStreamCreateTest
, MalformedResponse
) {
732 static const char kMalformedResponse
[] =
733 "220 mx.google.com ESMTP\r\n"
734 "HTTP/1.1 101 OK\r\n"
735 "Upgrade: websocket\r\n"
736 "Connection: Upgrade\r\n"
737 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
739 CreateAndConnectCustomResponse("ws://localhost/",
746 EXPECT_TRUE(has_failed());
747 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
751 // Upgrade header must be present.
752 TEST_F(WebSocketStreamCreateTest
, MissingUpgradeHeader
) {
753 static const char kMissingUpgradeResponse
[] =
754 "HTTP/1.1 101 Switching Protocols\r\n"
755 "Connection: Upgrade\r\n"
756 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
758 CreateAndConnectCustomResponse("ws://localhost/",
763 kMissingUpgradeResponse
);
765 EXPECT_TRUE(has_failed());
766 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
770 // There must only be one upgrade header.
771 TEST_F(WebSocketStreamCreateTest
, DoubleUpgradeHeader
) {
772 CreateAndConnectStandard(
777 "", "Upgrade: HTTP/2.0\r\n");
779 EXPECT_TRUE(has_failed());
780 EXPECT_EQ("Error during WebSocket handshake: "
781 "'Upgrade' header must not appear more than once in a response",
785 // There must only be one correct upgrade header.
786 TEST_F(WebSocketStreamCreateTest
, IncorrectUpgradeHeader
) {
787 static const char kMissingUpgradeResponse
[] =
788 "HTTP/1.1 101 Switching Protocols\r\n"
789 "Connection: Upgrade\r\n"
790 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
791 "Upgrade: hogefuga\r\n"
793 CreateAndConnectCustomResponse("ws://localhost/",
798 kMissingUpgradeResponse
);
800 EXPECT_TRUE(has_failed());
801 EXPECT_EQ("Error during WebSocket handshake: "
802 "'Upgrade' header value is not 'WebSocket': hogefuga",
806 // Connection header must be present.
807 TEST_F(WebSocketStreamCreateTest
, MissingConnectionHeader
) {
808 static const char kMissingConnectionResponse
[] =
809 "HTTP/1.1 101 Switching Protocols\r\n"
810 "Upgrade: websocket\r\n"
811 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
813 CreateAndConnectCustomResponse("ws://localhost/",
818 kMissingConnectionResponse
);
820 EXPECT_TRUE(has_failed());
821 EXPECT_EQ("Error during WebSocket handshake: "
822 "'Connection' header is missing",
826 // Connection header must contain "Upgrade".
827 TEST_F(WebSocketStreamCreateTest
, IncorrectConnectionHeader
) {
828 static const char kMissingConnectionResponse
[] =
829 "HTTP/1.1 101 Switching Protocols\r\n"
830 "Upgrade: websocket\r\n"
831 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
832 "Connection: hogefuga\r\n"
834 CreateAndConnectCustomResponse("ws://localhost/",
839 kMissingConnectionResponse
);
841 EXPECT_TRUE(has_failed());
842 EXPECT_EQ("Error during WebSocket handshake: "
843 "'Connection' header value must contain 'Upgrade'",
847 // Connection header is permitted to contain other tokens.
848 TEST_F(WebSocketStreamCreateTest
, AdditionalTokenInConnectionHeader
) {
849 static const char kAdditionalConnectionTokenResponse
[] =
850 "HTTP/1.1 101 Switching Protocols\r\n"
851 "Upgrade: websocket\r\n"
852 "Connection: Upgrade, Keep-Alive\r\n"
853 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
855 CreateAndConnectCustomResponse("ws://localhost/",
860 kAdditionalConnectionTokenResponse
);
862 EXPECT_FALSE(has_failed());
863 EXPECT_TRUE(stream_
);
866 // Sec-WebSocket-Accept header must be present.
867 TEST_F(WebSocketStreamCreateTest
, MissingSecWebSocketAccept
) {
868 static const char kMissingAcceptResponse
[] =
869 "HTTP/1.1 101 Switching Protocols\r\n"
870 "Upgrade: websocket\r\n"
871 "Connection: Upgrade\r\n"
873 CreateAndConnectCustomResponse("ws://localhost/",
878 kMissingAcceptResponse
);
880 EXPECT_TRUE(has_failed());
881 EXPECT_EQ("Error during WebSocket handshake: "
882 "'Sec-WebSocket-Accept' header is missing",
886 // Sec-WebSocket-Accept header must match the key that was sent.
887 TEST_F(WebSocketStreamCreateTest
, WrongSecWebSocketAccept
) {
888 static const char kIncorrectAcceptResponse
[] =
889 "HTTP/1.1 101 Switching Protocols\r\n"
890 "Upgrade: websocket\r\n"
891 "Connection: Upgrade\r\n"
892 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
894 CreateAndConnectCustomResponse("ws://localhost/",
899 kIncorrectAcceptResponse
);
901 EXPECT_TRUE(has_failed());
902 EXPECT_EQ("Error during WebSocket handshake: "
903 "Incorrect 'Sec-WebSocket-Accept' header value",
907 // Cancellation works.
908 TEST_F(WebSocketStreamCreateTest
, Cancellation
) {
909 CreateAndConnectStandard(
910 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
911 stream_request_
.reset();
913 EXPECT_FALSE(has_failed());
914 EXPECT_FALSE(stream_
);
915 EXPECT_FALSE(request_info_
);
916 EXPECT_FALSE(response_info_
);
919 // Connect failure must look just like negotiation failure.
920 TEST_F(WebSocketStreamCreateTest
, ConnectionFailure
) {
921 scoped_ptr
<DeterministicSocketData
> socket_data(
922 new DeterministicSocketData(NULL
, 0, NULL
, 0));
923 socket_data
->set_connect_data(
924 MockConnect(SYNCHRONOUS
, ERR_CONNECTION_REFUSED
));
925 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
926 "http://localhost", socket_data
.Pass());
928 EXPECT_TRUE(has_failed());
929 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
931 EXPECT_FALSE(request_info_
);
932 EXPECT_FALSE(response_info_
);
935 // Connect timeout must look just like any other failure.
936 TEST_F(WebSocketStreamCreateTest
, ConnectionTimeout
) {
937 scoped_ptr
<DeterministicSocketData
> socket_data(
938 new DeterministicSocketData(NULL
, 0, NULL
, 0));
939 socket_data
->set_connect_data(
940 MockConnect(ASYNC
, ERR_CONNECTION_TIMED_OUT
));
941 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
942 "http://localhost", socket_data
.Pass());
944 EXPECT_TRUE(has_failed());
945 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
949 // Cancellation during connect works.
950 TEST_F(WebSocketStreamCreateTest
, CancellationDuringConnect
) {
951 scoped_ptr
<DeterministicSocketData
> socket_data(
952 new DeterministicSocketData(NULL
, 0, NULL
, 0));
953 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, ERR_IO_PENDING
));
954 CreateAndConnectRawExpectations("ws://localhost/",
958 stream_request_
.reset();
960 EXPECT_FALSE(has_failed());
961 EXPECT_FALSE(stream_
);
964 // Cancellation during write of the request headers works.
965 TEST_F(WebSocketStreamCreateTest
, CancellationDuringWrite
) {
966 // We seem to need at least two operations in order to use SetStop().
967 MockWrite writes
[] = {MockWrite(ASYNC
, 0, "GET / HTTP/"),
968 MockWrite(ASYNC
, 1, "1.1\r\n")};
969 // We keep a copy of the pointer so that we can call RunFor() on it later.
970 DeterministicSocketData
* socket_data(
971 new DeterministicSocketData(NULL
, 0, writes
, arraysize(writes
)));
972 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
973 socket_data
->SetStop(1);
974 CreateAndConnectRawExpectations("ws://localhost/",
977 make_scoped_ptr(socket_data
));
979 stream_request_
.reset();
981 EXPECT_FALSE(has_failed());
982 EXPECT_FALSE(stream_
);
983 EXPECT_TRUE(request_info_
);
984 EXPECT_FALSE(response_info_
);
987 // Cancellation during read of the response headers works.
988 TEST_F(WebSocketStreamCreateTest
, CancellationDuringRead
) {
989 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
990 MockWrite writes
[] = {MockWrite(ASYNC
, 0, request
.c_str())};
992 MockRead(ASYNC
, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
994 DeterministicSocketData
* socket_data(new DeterministicSocketData(
995 reads
, arraysize(reads
), writes
, arraysize(writes
)));
996 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
997 socket_data
->SetStop(1);
998 CreateAndConnectRawExpectations("ws://localhost/",
1001 make_scoped_ptr(socket_data
));
1003 stream_request_
.reset();
1005 EXPECT_FALSE(has_failed());
1006 EXPECT_FALSE(stream_
);
1007 EXPECT_TRUE(request_info_
);
1008 EXPECT_FALSE(response_info_
);
1011 // Over-size response headers (> 256KB) should not cause a crash. This is a
1012 // regression test for crbug.com/339456. It is based on the layout test
1013 // "cookie-flood.html".
1014 TEST_F(WebSocketStreamCreateTest
, VeryLargeResponseHeaders
) {
1015 std::string set_cookie_headers
;
1016 set_cookie_headers
.reserve(45 * 10000);
1017 for (int i
= 0; i
< 10000; ++i
) {
1018 set_cookie_headers
+=
1019 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i
);
1021 CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(),
1022 "http://localhost", "", set_cookie_headers
);
1024 EXPECT_TRUE(has_failed());
1025 EXPECT_FALSE(response_info_
);
1028 // If the remote host closes the connection without sending headers, we should
1029 // log the console message "Connection closed before receiving a handshake
1031 TEST_F(WebSocketStreamCreateTest
, NoResponse
) {
1032 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
1033 MockWrite writes
[] = {MockWrite(ASYNC
, request
.data(), request
.size(), 0)};
1034 MockRead reads
[] = {MockRead(ASYNC
, 0, 1)};
1035 DeterministicSocketData
* socket_data(new DeterministicSocketData(
1036 reads
, arraysize(reads
), writes
, arraysize(writes
)));
1037 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
1038 CreateAndConnectRawExpectations("ws://localhost/",
1041 make_scoped_ptr(socket_data
));
1042 socket_data
->RunFor(2);
1043 EXPECT_TRUE(has_failed());
1044 EXPECT_FALSE(stream_
);
1045 EXPECT_FALSE(response_info_
);
1046 EXPECT_EQ("Connection closed before receiving a handshake response",
1050 TEST_F(WebSocketStreamCreateTest
, SelfSignedCertificateFailure
) {
1051 ssl_data_
.push_back(
1052 new SSLSocketDataProvider(ASYNC
, ERR_CERT_AUTHORITY_INVALID
));
1053 ssl_data_
[0]->cert
=
1054 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1055 ASSERT_TRUE(ssl_data_
[0]->cert
);
1056 scoped_ptr
<DeterministicSocketData
> raw_socket_data(
1057 new DeterministicSocketData(NULL
, 0, NULL
, 0));
1058 CreateAndConnectRawExpectations("wss://localhost/",
1061 raw_socket_data
.Pass());
1063 EXPECT_FALSE(has_failed());
1064 ASSERT_TRUE(ssl_error_callbacks_
);
1065 ssl_error_callbacks_
->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID
,
1068 EXPECT_TRUE(has_failed());
1071 TEST_F(WebSocketStreamCreateTest
, SelfSignedCertificateSuccess
) {
1072 scoped_ptr
<SSLSocketDataProvider
> ssl_data(
1073 new SSLSocketDataProvider(ASYNC
, ERR_CERT_AUTHORITY_INVALID
));
1075 ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
1076 ASSERT_TRUE(ssl_data
->cert
);
1077 ssl_data_
.push_back(ssl_data
.release());
1078 ssl_data
.reset(new SSLSocketDataProvider(ASYNC
, OK
));
1079 ssl_data_
.push_back(ssl_data
.release());
1080 url_request_context_host_
.AddRawExpectations(
1081 make_scoped_ptr(new DeterministicSocketData(NULL
, 0, NULL
, 0)));
1082 CreateAndConnectStandard(
1083 "wss://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
1085 ASSERT_TRUE(ssl_error_callbacks_
);
1086 ssl_error_callbacks_
->ContinueSSLRequest();
1088 EXPECT_FALSE(has_failed());
1089 EXPECT_TRUE(stream_
);
1092 TEST_F(WebSocketStreamCreateUMATest
, Incomplete
) {
1093 const std::string
name("Net.WebSocket.HandshakeResult");
1094 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1097 StreamCreation creation
;
1098 creation
.CreateAndConnectStandard("ws://localhost/",
1100 creation
.NoSubProtocols(),
1106 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1107 ASSERT_TRUE(samples
);
1109 samples
->Subtract(*original
); // Cancel the original values.
1111 EXPECT_EQ(1, samples
->GetCount(INCOMPLETE
));
1112 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1113 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1116 TEST_F(WebSocketStreamCreateUMATest
, Connected
) {
1117 const std::string
name("Net.WebSocket.HandshakeResult");
1118 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1121 StreamCreation creation
;
1122 creation
.CreateAndConnectStandard("ws://localhost/",
1124 creation
.NoSubProtocols(),
1128 creation
.RunUntilIdle();
1131 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1132 ASSERT_TRUE(samples
);
1134 samples
->Subtract(*original
); // Cancel the original values.
1136 EXPECT_EQ(0, samples
->GetCount(INCOMPLETE
));
1137 EXPECT_EQ(1, samples
->GetCount(CONNECTED
));
1138 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1141 TEST_F(WebSocketStreamCreateUMATest
, Failed
) {
1142 const std::string
name("Net.WebSocket.HandshakeResult");
1143 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1146 StreamCreation creation
;
1147 static const char kInvalidStatusCodeResponse
[] =
1148 "HTTP/1.1 200 OK\r\n"
1149 "Upgrade: websocket\r\n"
1150 "Connection: Upgrade\r\n"
1151 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1153 creation
.CreateAndConnectCustomResponse("ws://localhost/",
1155 creation
.NoSubProtocols(),
1158 kInvalidStatusCodeResponse
);
1159 creation
.RunUntilIdle();
1162 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1163 ASSERT_TRUE(samples
);
1165 samples
->Subtract(*original
); // Cancel the original values.
1167 EXPECT_EQ(1, samples
->GetCount(INCOMPLETE
));
1168 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1169 EXPECT_EQ(0, samples
->GetCount(FAILED
));