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/memory/scoped_vector.h"
13 #include "base/metrics/histogram.h"
14 #include "base/metrics/histogram_samples.h"
15 #include "base/metrics/statistics_recorder.h"
16 #include "base/run_loop.h"
17 #include "base/strings/stringprintf.h"
18 #include "net/base/net_errors.h"
19 #include "net/http/http_request_headers.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/socket/client_socket_handle.h"
22 #include "net/socket/socket_test_util.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "net/websockets/websocket_basic_handshake_stream.h"
25 #include "net/websockets/websocket_frame.h"
26 #include "net/websockets/websocket_handshake_request_info.h"
27 #include "net/websockets/websocket_handshake_response_info.h"
28 #include "net/websockets/websocket_handshake_stream_create_helper.h"
29 #include "net/websockets/websocket_test_util.h"
30 #include "testing/gtest/include/gtest/gtest.h"
32 #include "url/origin.h"
37 typedef std::pair
<std::string
, std::string
> HeaderKeyValuePair
;
39 std::vector
<HeaderKeyValuePair
> ToVector(const HttpRequestHeaders
& headers
) {
40 HttpRequestHeaders::Iterator
it(headers
);
41 std::vector
<HeaderKeyValuePair
> result
;
43 result
.push_back(HeaderKeyValuePair(it
.name(), it
.value()));
47 std::vector
<HeaderKeyValuePair
> ToVector(const HttpResponseHeaders
& headers
) {
49 std::string name
, value
;
50 std::vector
<HeaderKeyValuePair
> result
;
51 while (headers
.EnumerateHeaderLines(&iter
, &name
, &value
))
52 result
.push_back(HeaderKeyValuePair(name
, value
));
56 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
57 // deterministic key to use in the WebSocket handshake.
58 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
59 : public WebSocketHandshakeStreamCreateHelper
{
61 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
62 WebSocketStream::ConnectDelegate
* connect_delegate
,
63 const std::vector
<std::string
>& requested_subprotocols
)
64 : WebSocketHandshakeStreamCreateHelper(connect_delegate
,
65 requested_subprotocols
) {}
67 virtual WebSocketHandshakeStreamBase
* CreateBasicStream(
68 scoped_ptr
<ClientSocketHandle
> connection
,
69 bool using_proxy
) OVERRIDE
{
70 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection
.Pass(),
72 // This will break in an obvious way if the type created by
73 // CreateBasicStream() changes.
74 static_cast<WebSocketBasicHandshakeStream
*>(stream())
75 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
80 class WebSocketStreamCreateTest
: public ::testing::Test
{
82 WebSocketStreamCreateTest(): has_failed_(false) {}
84 void CreateAndConnectCustomResponse(
85 const std::string
& socket_url
,
86 const std::string
& socket_path
,
87 const std::vector
<std::string
>& sub_protocols
,
88 const std::string
& origin
,
89 const std::string
& extra_request_headers
,
90 const std::string
& response_body
) {
91 url_request_context_host_
.SetExpectations(
92 WebSocketStandardRequest(socket_path
, origin
, extra_request_headers
),
94 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
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(const std::string
& socket_url
,
100 const std::string
& socket_path
,
101 const std::vector
<std::string
>& sub_protocols
,
102 const std::string
& origin
,
103 const std::string
& extra_request_headers
,
104 const std::string
& extra_response_headers
) {
105 CreateAndConnectCustomResponse(
110 extra_request_headers
,
111 WebSocketStandardResponse(extra_response_headers
));
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 url_request_context_host_
.SetRawExpectations(socket_data
.Pass());
120 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
123 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
125 void CreateAndConnectStream(const std::string
& socket_url
,
126 const std::vector
<std::string
>& sub_protocols
,
127 const std::string
& origin
) {
128 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate(
129 new TestConnectDelegate(this));
130 WebSocketStream::ConnectDelegate
* delegate
= connect_delegate
.get();
131 stream_request_
= ::net::CreateAndConnectStreamForTesting(
133 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
>(
134 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
135 delegate
, sub_protocols
)),
137 url_request_context_host_
.GetURLRequestContext(),
139 connect_delegate
.Pass());
142 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
144 // A simple function to make the tests more readable. Creates an empty vector.
145 static std::vector
<std::string
> NoSubProtocols() {
146 return std::vector
<std::string
>();
149 const std::string
& failure_message() const { return failure_message_
; }
150 bool has_failed() const { return has_failed_
; }
152 class TestConnectDelegate
: public WebSocketStream::ConnectDelegate
{
154 explicit TestConnectDelegate(WebSocketStreamCreateTest
* owner
)
157 virtual void OnSuccess(scoped_ptr
<WebSocketStream
> stream
) OVERRIDE
{
158 stream
.swap(owner_
->stream_
);
161 virtual void OnFailure(const std::string
& message
) OVERRIDE
{
162 owner_
->has_failed_
= true;
163 owner_
->failure_message_
= message
;
166 virtual void OnStartOpeningHandshake(
167 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) OVERRIDE
{
168 if (owner_
->request_info_
)
170 owner_
->request_info_
= request
.Pass();
172 virtual void OnFinishOpeningHandshake(
173 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) OVERRIDE
{
174 if (owner_
->response_info_
)
176 owner_
->response_info_
= response
.Pass();
180 WebSocketStreamCreateTest
* owner_
;
183 WebSocketTestURLRequestContextHost url_request_context_host_
;
184 scoped_ptr
<WebSocketStreamRequest
> stream_request_
;
185 // Only set if the connection succeeded.
186 scoped_ptr
<WebSocketStream
> stream_
;
187 // Only set if the connection failed.
188 std::string failure_message_
;
190 scoped_ptr
<WebSocketHandshakeRequestInfo
> request_info_
;
191 scoped_ptr
<WebSocketHandshakeResponseInfo
> response_info_
;
194 // There are enough tests of the Sec-WebSocket-Extensions header that they
195 // deserve their own test fixture.
196 class WebSocketStreamCreateExtensionTest
: public WebSocketStreamCreateTest
{
198 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
199 // header in the response set to |extensions_header_value|. Runs the event
200 // loop to allow the connect to complete.
201 void CreateAndConnectWithExtensions(
202 const std::string
& extensions_header_value
) {
203 CreateAndConnectStandard(
204 "ws://localhost/testing_path",
209 "Sec-WebSocket-Extensions: " + extensions_header_value
+ "\r\n");
214 class WebSocketStreamCreateUMATest
: public ::testing::Test
{
216 // This enum should match with the enum in Delegate in websocket_stream.cc.
217 enum HandshakeResult
{
221 NUM_HANDSHAKE_RESULT_TYPES
,
224 class StreamCreation
: public WebSocketStreamCreateTest
{
225 virtual void TestBody() OVERRIDE
{}
228 scoped_ptr
<base::HistogramSamples
> GetSamples(const std::string
& name
) {
229 base::HistogramBase
* histogram
=
230 base::StatisticsRecorder::FindHistogram(name
);
231 return histogram
? histogram
->SnapshotSamples()
232 : scoped_ptr
<base::HistogramSamples
>();
236 // Confirm that the basic case works as expected.
237 TEST_F(WebSocketStreamCreateTest
, SimpleSuccess
) {
238 CreateAndConnectStandard(
239 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
240 EXPECT_FALSE(request_info_
);
241 EXPECT_FALSE(response_info_
);
243 EXPECT_FALSE(has_failed());
244 EXPECT_TRUE(stream_
);
245 EXPECT_TRUE(request_info_
);
246 EXPECT_TRUE(response_info_
);
249 TEST_F(WebSocketStreamCreateTest
, HandshakeInfo
) {
250 static const char kResponse
[] =
251 "HTTP/1.1 101 Switching Protocols\r\n"
252 "Upgrade: websocket\r\n"
253 "Connection: Upgrade\r\n"
254 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
260 CreateAndConnectCustomResponse(
267 EXPECT_FALSE(request_info_
);
268 EXPECT_FALSE(response_info_
);
270 EXPECT_TRUE(stream_
);
271 ASSERT_TRUE(request_info_
);
272 ASSERT_TRUE(response_info_
);
273 std::vector
<HeaderKeyValuePair
> request_headers
=
274 ToVector(request_info_
->headers
);
275 // We examine the contents of request_info_ and response_info_
276 // mainly only in this test case.
277 EXPECT_EQ(GURL("ws://localhost/"), request_info_
->url
);
278 EXPECT_EQ(GURL("ws://localhost/"), response_info_
->url
);
279 EXPECT_EQ(101, response_info_
->status_code
);
280 EXPECT_EQ("Switching Protocols", response_info_
->status_text
);
281 ASSERT_EQ(12u, request_headers
.size());
282 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers
[0]);
283 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers
[1]);
284 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers
[2]);
285 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
287 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers
[4]);
288 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
290 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
292 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers
[7]);
293 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"),
295 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
297 EXPECT_EQ("Sec-WebSocket-Key", request_headers
[10].first
);
298 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
299 "permessage-deflate; client_max_window_bits"),
300 request_headers
[11]);
302 std::vector
<HeaderKeyValuePair
> response_headers
=
303 ToVector(*response_info_
->headers
);
304 ASSERT_EQ(6u, response_headers
.size());
305 // Sort the headers for ease of verification.
306 std::sort(response_headers
.begin(), response_headers
.end());
308 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers
[0]);
309 EXPECT_EQ("Sec-WebSocket-Accept", response_headers
[1].first
);
310 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers
[2]);
311 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers
[3]);
312 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers
[4]);
313 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers
[5]);
316 // Confirm that the stream isn't established until the message loop runs.
317 TEST_F(WebSocketStreamCreateTest
, NeedsToRunLoop
) {
318 CreateAndConnectStandard(
319 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
320 EXPECT_FALSE(has_failed());
321 EXPECT_FALSE(stream_
);
324 // Check the path is used.
325 TEST_F(WebSocketStreamCreateTest
, PathIsUsed
) {
326 CreateAndConnectStandard("ws://localhost/testing_path",
333 EXPECT_FALSE(has_failed());
334 EXPECT_TRUE(stream_
);
337 // Check that the origin is used.
338 TEST_F(WebSocketStreamCreateTest
, OriginIsUsed
) {
339 CreateAndConnectStandard("ws://localhost/testing_path",
346 EXPECT_FALSE(has_failed());
347 EXPECT_TRUE(stream_
);
350 // Check that sub-protocols are sent and parsed.
351 TEST_F(WebSocketStreamCreateTest
, SubProtocolIsUsed
) {
352 std::vector
<std::string
> sub_protocols
;
353 sub_protocols
.push_back("chatv11.chromium.org");
354 sub_protocols
.push_back("chatv20.chromium.org");
355 CreateAndConnectStandard("ws://localhost/testing_path",
359 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
360 "chatv20.chromium.org\r\n",
361 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
363 EXPECT_TRUE(stream_
);
364 EXPECT_FALSE(has_failed());
365 EXPECT_EQ("chatv20.chromium.org", stream_
->GetSubProtocol());
368 // Unsolicited sub-protocols are rejected.
369 TEST_F(WebSocketStreamCreateTest
, UnsolicitedSubProtocol
) {
370 CreateAndConnectStandard("ws://localhost/testing_path",
375 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
377 EXPECT_FALSE(stream_
);
378 EXPECT_TRUE(has_failed());
379 EXPECT_EQ("Error during WebSocket handshake: "
380 "Response must not include 'Sec-WebSocket-Protocol' header "
381 "if not present in request: chatv20.chromium.org",
385 // Missing sub-protocol response is rejected.
386 TEST_F(WebSocketStreamCreateTest
, UnacceptedSubProtocol
) {
387 std::vector
<std::string
> sub_protocols
;
388 sub_protocols
.push_back("chat.example.com");
389 CreateAndConnectStandard("ws://localhost/testing_path",
393 "Sec-WebSocket-Protocol: chat.example.com\r\n",
396 EXPECT_FALSE(stream_
);
397 EXPECT_TRUE(has_failed());
398 EXPECT_EQ("Error during WebSocket handshake: "
399 "Sent non-empty 'Sec-WebSocket-Protocol' header "
400 "but no response was received",
404 // Only one sub-protocol can be accepted.
405 TEST_F(WebSocketStreamCreateTest
, MultipleSubProtocolsInResponse
) {
406 std::vector
<std::string
> sub_protocols
;
407 sub_protocols
.push_back("chatv11.chromium.org");
408 sub_protocols
.push_back("chatv20.chromium.org");
409 CreateAndConnectStandard("ws://localhost/testing_path",
413 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
414 "chatv20.chromium.org\r\n",
415 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
416 "chatv20.chromium.org\r\n");
418 EXPECT_FALSE(stream_
);
419 EXPECT_TRUE(has_failed());
420 EXPECT_EQ("Error during WebSocket handshake: "
421 "'Sec-WebSocket-Protocol' header must not appear "
422 "more than once in a response",
426 // Unmatched sub-protocol should be rejected.
427 TEST_F(WebSocketStreamCreateTest
, UnmatchedSubProtocolInResponse
) {
428 std::vector
<std::string
> sub_protocols
;
429 sub_protocols
.push_back("chatv11.chromium.org");
430 sub_protocols
.push_back("chatv20.chromium.org");
431 CreateAndConnectStandard("ws://localhost/testing_path",
435 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
436 "chatv20.chromium.org\r\n",
437 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n");
439 EXPECT_FALSE(stream_
);
440 EXPECT_TRUE(has_failed());
441 EXPECT_EQ("Error during WebSocket handshake: "
442 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
443 "in response does not match any of sent values",
447 // permessage-deflate extension basic success case.
448 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateSuccess
) {
449 CreateAndConnectWithExtensions("permessage-deflate");
450 EXPECT_TRUE(stream_
);
451 EXPECT_FALSE(has_failed());
454 // permessage-deflate extensions success with all parameters.
455 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateParamsSuccess
) {
456 CreateAndConnectWithExtensions(
457 "permessage-deflate; client_no_context_takeover; "
458 "server_max_window_bits=11; client_max_window_bits=13; "
459 "server_no_context_takeover");
460 EXPECT_TRUE(stream_
);
461 EXPECT_FALSE(has_failed());
464 // Verify that incoming messages are actually decompressed with
465 // permessage-deflate enabled.
466 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateInflates
) {
467 CreateAndConnectCustomResponse(
468 "ws://localhost/testing_path",
473 WebSocketStandardResponse(
474 "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
476 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
477 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
481 ASSERT_TRUE(stream_
);
482 ScopedVector
<WebSocketFrame
> frames
;
483 CompletionCallback callback
;
484 ASSERT_EQ(OK
, stream_
->ReadFrames(&frames
, callback
));
485 ASSERT_EQ(1U, frames
.size());
486 ASSERT_EQ(5U, frames
[0]->header
.payload_length
);
487 EXPECT_EQ("Hello", std::string(frames
[0]->data
->data(), 5));
490 // Unknown extension in the response is rejected
491 TEST_F(WebSocketStreamCreateExtensionTest
, UnknownExtension
) {
492 CreateAndConnectWithExtensions("x-unknown-extension");
493 EXPECT_FALSE(stream_
);
494 EXPECT_TRUE(has_failed());
495 EXPECT_EQ("Error during WebSocket handshake: "
496 "Found an unsupported extension 'x-unknown-extension' "
497 "in 'Sec-WebSocket-Extensions' header",
501 // Malformed extensions are rejected (this file does not cover all possible
502 // parse failures, as the parser is covered thoroughly by its own unit tests).
503 TEST_F(WebSocketStreamCreateExtensionTest
, MalformedExtension
) {
504 CreateAndConnectWithExtensions(";");
505 EXPECT_FALSE(stream_
);
506 EXPECT_TRUE(has_failed());
508 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
509 "value is rejected by the parser: ;",
513 // The permessage-deflate extension may only be specified once.
514 TEST_F(WebSocketStreamCreateExtensionTest
, OnlyOnePerMessageDeflateAllowed
) {
515 CreateAndConnectWithExtensions(
516 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
517 EXPECT_FALSE(stream_
);
518 EXPECT_TRUE(has_failed());
520 "Error during WebSocket handshake: "
521 "Received duplicate permessage-deflate response",
525 // permessage-deflate parameters may not be duplicated.
526 TEST_F(WebSocketStreamCreateExtensionTest
, NoDuplicateParameters
) {
527 CreateAndConnectWithExtensions(
528 "permessage-deflate; client_no_context_takeover; "
529 "client_no_context_takeover");
530 EXPECT_FALSE(stream_
);
531 EXPECT_TRUE(has_failed());
533 "Error during WebSocket handshake: Error in permessage-deflate: "
534 "Received duplicate permessage-deflate extension parameter "
535 "client_no_context_takeover",
539 // permessage-deflate parameters must start with "client_" or "server_"
540 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterPrefix
) {
541 CreateAndConnectWithExtensions(
542 "permessage-deflate; absurd_no_context_takeover");
543 EXPECT_FALSE(stream_
);
544 EXPECT_TRUE(has_failed());
546 "Error during WebSocket handshake: Error in permessage-deflate: "
547 "Received an unexpected permessage-deflate extension parameter",
551 // permessage-deflate parameters must be either *_no_context_takeover or
553 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterSuffix
) {
554 CreateAndConnectWithExtensions(
555 "permessage-deflate; client_max_content_bits=5");
556 EXPECT_FALSE(stream_
);
557 EXPECT_TRUE(has_failed());
559 "Error during WebSocket handshake: Error in permessage-deflate: "
560 "Received an unexpected permessage-deflate extension parameter",
564 // *_no_context_takeover parameters must not have an argument
565 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterValue
) {
566 CreateAndConnectWithExtensions(
567 "permessage-deflate; client_no_context_takeover=true");
568 EXPECT_FALSE(stream_
);
569 EXPECT_TRUE(has_failed());
571 "Error during WebSocket handshake: Error in permessage-deflate: "
572 "Received invalid client_no_context_takeover parameter",
576 // *_max_window_bits must have an argument
577 TEST_F(WebSocketStreamCreateExtensionTest
, NoMaxWindowBitsArgument
) {
578 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
579 EXPECT_FALSE(stream_
);
580 EXPECT_TRUE(has_failed());
582 "Error during WebSocket handshake: Error in permessage-deflate: "
583 "client_max_window_bits must have value",
587 // *_max_window_bits must be an integer
588 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueInteger
) {
589 CreateAndConnectWithExtensions(
590 "permessage-deflate; server_max_window_bits=banana");
591 EXPECT_FALSE(stream_
);
592 EXPECT_TRUE(has_failed());
594 "Error during WebSocket handshake: Error in permessage-deflate: "
595 "Received invalid server_max_window_bits parameter",
599 // *_max_window_bits must be >= 8
600 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooSmall
) {
601 CreateAndConnectWithExtensions(
602 "permessage-deflate; server_max_window_bits=7");
603 EXPECT_FALSE(stream_
);
604 EXPECT_TRUE(has_failed());
606 "Error during WebSocket handshake: Error in permessage-deflate: "
607 "Received invalid server_max_window_bits parameter",
611 // *_max_window_bits must be <= 15
612 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooBig
) {
613 CreateAndConnectWithExtensions(
614 "permessage-deflate; client_max_window_bits=16");
615 EXPECT_FALSE(stream_
);
616 EXPECT_TRUE(has_failed());
618 "Error during WebSocket handshake: Error in permessage-deflate: "
619 "Received invalid client_max_window_bits parameter",
623 // *_max_window_bits must not start with 0
624 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithZero
) {
625 CreateAndConnectWithExtensions(
626 "permessage-deflate; client_max_window_bits=08");
627 EXPECT_FALSE(stream_
);
628 EXPECT_TRUE(has_failed());
630 "Error during WebSocket handshake: Error in permessage-deflate: "
631 "Received invalid client_max_window_bits parameter",
635 // *_max_window_bits must not start with +
636 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithPlus
) {
637 CreateAndConnectWithExtensions(
638 "permessage-deflate; server_max_window_bits=+9");
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 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
648 // arguments from the server. This is difficult because the data written to the
649 // socket is randomly masked.
651 // Additional Sec-WebSocket-Accept headers should be rejected.
652 TEST_F(WebSocketStreamCreateTest
, DoubleAccept
) {
653 CreateAndConnectStandard(
659 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
661 EXPECT_FALSE(stream_
);
662 EXPECT_TRUE(has_failed());
663 EXPECT_EQ("Error during WebSocket handshake: "
664 "'Sec-WebSocket-Accept' header must not appear "
665 "more than once in a response",
669 // Response code 200 must be rejected.
670 TEST_F(WebSocketStreamCreateTest
, InvalidStatusCode
) {
671 static const char kInvalidStatusCodeResponse
[] =
672 "HTTP/1.1 200 OK\r\n"
673 "Upgrade: websocket\r\n"
674 "Connection: Upgrade\r\n"
675 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
677 CreateAndConnectCustomResponse("ws://localhost/",
682 kInvalidStatusCodeResponse
);
684 EXPECT_TRUE(has_failed());
685 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
689 // Redirects are not followed (according to the WHATWG WebSocket API, which
690 // overrides RFC6455 for browser applications).
691 TEST_F(WebSocketStreamCreateTest
, RedirectsRejected
) {
692 static const char kRedirectResponse
[] =
693 "HTTP/1.1 302 Moved Temporarily\r\n"
694 "Content-Type: text/html\r\n"
695 "Content-Length: 34\r\n"
696 "Connection: keep-alive\r\n"
697 "Location: ws://localhost/other\r\n"
699 "<title>Moved</title><h1>Moved</h1>";
700 CreateAndConnectCustomResponse("ws://localhost/",
707 EXPECT_TRUE(has_failed());
708 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
712 // Malformed responses should be rejected. HttpStreamParser will accept just
713 // about any garbage in the middle of the headers. To make it give up, the junk
714 // has to be at the start of the response. Even then, it just gets treated as an
715 // HTTP/0.9 response.
716 TEST_F(WebSocketStreamCreateTest
, MalformedResponse
) {
717 static const char kMalformedResponse
[] =
718 "220 mx.google.com ESMTP\r\n"
719 "HTTP/1.1 101 OK\r\n"
720 "Upgrade: websocket\r\n"
721 "Connection: Upgrade\r\n"
722 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
724 CreateAndConnectCustomResponse("ws://localhost/",
731 EXPECT_TRUE(has_failed());
732 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
736 // Upgrade header must be present.
737 TEST_F(WebSocketStreamCreateTest
, MissingUpgradeHeader
) {
738 static const char kMissingUpgradeResponse
[] =
739 "HTTP/1.1 101 Switching Protocols\r\n"
740 "Connection: Upgrade\r\n"
741 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
743 CreateAndConnectCustomResponse("ws://localhost/",
748 kMissingUpgradeResponse
);
750 EXPECT_TRUE(has_failed());
751 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
755 // There must only be one upgrade header.
756 TEST_F(WebSocketStreamCreateTest
, DoubleUpgradeHeader
) {
757 CreateAndConnectStandard(
762 "", "Upgrade: HTTP/2.0\r\n");
764 EXPECT_TRUE(has_failed());
765 EXPECT_EQ("Error during WebSocket handshake: "
766 "'Upgrade' header must not appear more than once in a response",
770 // There must only be one correct upgrade header.
771 TEST_F(WebSocketStreamCreateTest
, IncorrectUpgradeHeader
) {
772 static const char kMissingUpgradeResponse
[] =
773 "HTTP/1.1 101 Switching Protocols\r\n"
774 "Connection: Upgrade\r\n"
775 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
776 "Upgrade: hogefuga\r\n"
778 CreateAndConnectCustomResponse("ws://localhost/",
783 kMissingUpgradeResponse
);
785 EXPECT_TRUE(has_failed());
786 EXPECT_EQ("Error during WebSocket handshake: "
787 "'Upgrade' header value is not 'WebSocket': hogefuga",
791 // Connection header must be present.
792 TEST_F(WebSocketStreamCreateTest
, MissingConnectionHeader
) {
793 static const char kMissingConnectionResponse
[] =
794 "HTTP/1.1 101 Switching Protocols\r\n"
795 "Upgrade: websocket\r\n"
796 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
798 CreateAndConnectCustomResponse("ws://localhost/",
803 kMissingConnectionResponse
);
805 EXPECT_TRUE(has_failed());
806 EXPECT_EQ("Error during WebSocket handshake: "
807 "'Connection' header is missing",
811 // Connection header must contain "Upgrade".
812 TEST_F(WebSocketStreamCreateTest
, IncorrectConnectionHeader
) {
813 static const char kMissingConnectionResponse
[] =
814 "HTTP/1.1 101 Switching Protocols\r\n"
815 "Upgrade: websocket\r\n"
816 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
817 "Connection: hogefuga\r\n"
819 CreateAndConnectCustomResponse("ws://localhost/",
824 kMissingConnectionResponse
);
826 EXPECT_TRUE(has_failed());
827 EXPECT_EQ("Error during WebSocket handshake: "
828 "'Connection' header value must contain 'Upgrade'",
832 // Connection header is permitted to contain other tokens.
833 TEST_F(WebSocketStreamCreateTest
, AdditionalTokenInConnectionHeader
) {
834 static const char kAdditionalConnectionTokenResponse
[] =
835 "HTTP/1.1 101 Switching Protocols\r\n"
836 "Upgrade: websocket\r\n"
837 "Connection: Upgrade, Keep-Alive\r\n"
838 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
840 CreateAndConnectCustomResponse("ws://localhost/",
845 kAdditionalConnectionTokenResponse
);
847 EXPECT_FALSE(has_failed());
848 EXPECT_TRUE(stream_
);
851 // Sec-WebSocket-Accept header must be present.
852 TEST_F(WebSocketStreamCreateTest
, MissingSecWebSocketAccept
) {
853 static const char kMissingAcceptResponse
[] =
854 "HTTP/1.1 101 Switching Protocols\r\n"
855 "Upgrade: websocket\r\n"
856 "Connection: Upgrade\r\n"
858 CreateAndConnectCustomResponse("ws://localhost/",
863 kMissingAcceptResponse
);
865 EXPECT_TRUE(has_failed());
866 EXPECT_EQ("Error during WebSocket handshake: "
867 "'Sec-WebSocket-Accept' header is missing",
871 // Sec-WebSocket-Accept header must match the key that was sent.
872 TEST_F(WebSocketStreamCreateTest
, WrongSecWebSocketAccept
) {
873 static const char kIncorrectAcceptResponse
[] =
874 "HTTP/1.1 101 Switching Protocols\r\n"
875 "Upgrade: websocket\r\n"
876 "Connection: Upgrade\r\n"
877 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
879 CreateAndConnectCustomResponse("ws://localhost/",
884 kIncorrectAcceptResponse
);
886 EXPECT_TRUE(has_failed());
887 EXPECT_EQ("Error during WebSocket handshake: "
888 "Incorrect 'Sec-WebSocket-Accept' header value",
892 // Cancellation works.
893 TEST_F(WebSocketStreamCreateTest
, Cancellation
) {
894 CreateAndConnectStandard(
895 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
896 stream_request_
.reset();
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(
907 new DeterministicSocketData(NULL
, 0, NULL
, 0));
908 socket_data
->set_connect_data(
909 MockConnect(SYNCHRONOUS
, ERR_CONNECTION_REFUSED
));
910 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
911 "http://localhost", socket_data
.Pass());
913 EXPECT_TRUE(has_failed());
914 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
916 EXPECT_FALSE(request_info_
);
917 EXPECT_FALSE(response_info_
);
920 // Connect timeout must look just like any other failure.
921 TEST_F(WebSocketStreamCreateTest
, ConnectionTimeout
) {
922 scoped_ptr
<DeterministicSocketData
> socket_data(
923 new DeterministicSocketData(NULL
, 0, NULL
, 0));
924 socket_data
->set_connect_data(
925 MockConnect(ASYNC
, ERR_CONNECTION_TIMED_OUT
));
926 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
927 "http://localhost", socket_data
.Pass());
929 EXPECT_TRUE(has_failed());
930 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
934 // Cancellation during connect works.
935 TEST_F(WebSocketStreamCreateTest
, CancellationDuringConnect
) {
936 scoped_ptr
<DeterministicSocketData
> socket_data(
937 new DeterministicSocketData(NULL
, 0, NULL
, 0));
938 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, ERR_IO_PENDING
));
939 CreateAndConnectRawExpectations("ws://localhost/",
943 stream_request_
.reset();
945 EXPECT_FALSE(has_failed());
946 EXPECT_FALSE(stream_
);
949 // Cancellation during write of the request headers works.
950 TEST_F(WebSocketStreamCreateTest
, CancellationDuringWrite
) {
951 // We seem to need at least two operations in order to use SetStop().
952 MockWrite writes
[] = {MockWrite(ASYNC
, 0, "GET / HTTP/"),
953 MockWrite(ASYNC
, 1, "1.1\r\n")};
954 // We keep a copy of the pointer so that we can call RunFor() on it later.
955 DeterministicSocketData
* socket_data(
956 new DeterministicSocketData(NULL
, 0, writes
, arraysize(writes
)));
957 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
958 socket_data
->SetStop(1);
959 CreateAndConnectRawExpectations("ws://localhost/",
962 make_scoped_ptr(socket_data
));
964 stream_request_
.reset();
966 EXPECT_FALSE(has_failed());
967 EXPECT_FALSE(stream_
);
968 EXPECT_TRUE(request_info_
);
969 EXPECT_FALSE(response_info_
);
972 // Cancellation during read of the response headers works.
973 TEST_F(WebSocketStreamCreateTest
, CancellationDuringRead
) {
974 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
975 MockWrite writes
[] = {MockWrite(ASYNC
, 0, request
.c_str())};
977 MockRead(ASYNC
, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
979 DeterministicSocketData
* socket_data(new DeterministicSocketData(
980 reads
, arraysize(reads
), writes
, arraysize(writes
)));
981 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
982 socket_data
->SetStop(1);
983 CreateAndConnectRawExpectations("ws://localhost/",
986 make_scoped_ptr(socket_data
));
988 stream_request_
.reset();
990 EXPECT_FALSE(has_failed());
991 EXPECT_FALSE(stream_
);
992 EXPECT_TRUE(request_info_
);
993 EXPECT_FALSE(response_info_
);
996 // Over-size response headers (> 256KB) should not cause a crash. This is a
997 // regression test for crbug.com/339456. It is based on the layout test
998 // "cookie-flood.html".
999 TEST_F(WebSocketStreamCreateTest
, VeryLargeResponseHeaders
) {
1000 std::string set_cookie_headers
;
1001 set_cookie_headers
.reserve(45 * 10000);
1002 for (int i
= 0; i
< 10000; ++i
) {
1003 set_cookie_headers
+=
1004 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i
);
1006 CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(),
1007 "http://localhost", "", set_cookie_headers
);
1009 EXPECT_TRUE(has_failed());
1010 EXPECT_FALSE(response_info_
);
1013 // If the remote host closes the connection without sending headers, we should
1014 // log the console message "Connection closed before receiving a handshake
1016 TEST_F(WebSocketStreamCreateTest
, NoResponse
) {
1017 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
1018 MockWrite writes
[] = {MockWrite(ASYNC
, request
.data(), request
.size(), 0)};
1019 MockRead reads
[] = {MockRead(ASYNC
, 0, 1)};
1020 DeterministicSocketData
* socket_data(new DeterministicSocketData(
1021 reads
, arraysize(reads
), writes
, arraysize(writes
)));
1022 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
1023 CreateAndConnectRawExpectations("ws://localhost/",
1026 make_scoped_ptr(socket_data
));
1027 socket_data
->RunFor(2);
1028 EXPECT_TRUE(has_failed());
1029 EXPECT_FALSE(stream_
);
1030 EXPECT_FALSE(response_info_
);
1031 EXPECT_EQ("Connection closed before receiving a handshake response",
1035 TEST_F(WebSocketStreamCreateUMATest
, Incomplete
) {
1036 const std::string
name("Net.WebSocket.HandshakeResult");
1037 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1040 StreamCreation creation
;
1041 creation
.CreateAndConnectStandard("ws://localhost/",
1043 creation
.NoSubProtocols(),
1049 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1050 ASSERT_TRUE(samples
);
1052 samples
->Subtract(*original
); // Cancel the original values.
1054 EXPECT_EQ(1, samples
->GetCount(INCOMPLETE
));
1055 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1056 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1059 TEST_F(WebSocketStreamCreateUMATest
, Connected
) {
1060 const std::string
name("Net.WebSocket.HandshakeResult");
1061 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1064 StreamCreation creation
;
1065 creation
.CreateAndConnectStandard("ws://localhost/",
1067 creation
.NoSubProtocols(),
1071 creation
.RunUntilIdle();
1074 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1075 ASSERT_TRUE(samples
);
1077 samples
->Subtract(*original
); // Cancel the original values.
1079 EXPECT_EQ(0, samples
->GetCount(INCOMPLETE
));
1080 EXPECT_EQ(1, samples
->GetCount(CONNECTED
));
1081 EXPECT_EQ(0, samples
->GetCount(FAILED
));
1084 TEST_F(WebSocketStreamCreateUMATest
, Failed
) {
1085 const std::string
name("Net.WebSocket.HandshakeResult");
1086 scoped_ptr
<base::HistogramSamples
> original(GetSamples(name
));
1089 StreamCreation creation
;
1090 static const char kInvalidStatusCodeResponse
[] =
1091 "HTTP/1.1 200 OK\r\n"
1092 "Upgrade: websocket\r\n"
1093 "Connection: Upgrade\r\n"
1094 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
1096 creation
.CreateAndConnectCustomResponse("ws://localhost/",
1098 creation
.NoSubProtocols(),
1101 kInvalidStatusCodeResponse
);
1102 creation
.RunUntilIdle();
1105 scoped_ptr
<base::HistogramSamples
> samples(GetSamples(name
));
1106 ASSERT_TRUE(samples
);
1108 samples
->Subtract(*original
); // Cancel the original values.
1110 EXPECT_EQ(0, samples
->GetCount(INCOMPLETE
));
1111 EXPECT_EQ(0, samples
->GetCount(CONNECTED
));
1112 EXPECT_EQ(1, samples
->GetCount(FAILED
));