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/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "net/base/net_errors.h"
16 #include "net/http/http_request_headers.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/socket/client_socket_handle.h"
19 #include "net/socket/socket_test_util.h"
20 #include "net/url_request/url_request_test_util.h"
21 #include "net/websockets/websocket_basic_handshake_stream.h"
22 #include "net/websockets/websocket_frame.h"
23 #include "net/websockets/websocket_handshake_request_info.h"
24 #include "net/websockets/websocket_handshake_response_info.h"
25 #include "net/websockets/websocket_handshake_stream_create_helper.h"
26 #include "net/websockets/websocket_test_util.h"
27 #include "testing/gtest/include/gtest/gtest.h"
29 #include "url/origin.h"
34 typedef std::pair
<std::string
, std::string
> HeaderKeyValuePair
;
36 std::vector
<HeaderKeyValuePair
> ToVector(const HttpRequestHeaders
& headers
) {
37 HttpRequestHeaders::Iterator
it(headers
);
38 std::vector
<HeaderKeyValuePair
> result
;
40 result
.push_back(HeaderKeyValuePair(it
.name(), it
.value()));
44 std::vector
<HeaderKeyValuePair
> ToVector(const HttpResponseHeaders
& headers
) {
46 std::string name
, value
;
47 std::vector
<HeaderKeyValuePair
> result
;
48 while (headers
.EnumerateHeaderLines(&iter
, &name
, &value
))
49 result
.push_back(HeaderKeyValuePair(name
, value
));
53 // A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
54 // deterministic key to use in the WebSocket handshake.
55 class DeterministicKeyWebSocketHandshakeStreamCreateHelper
56 : public WebSocketHandshakeStreamCreateHelper
{
58 DeterministicKeyWebSocketHandshakeStreamCreateHelper(
59 WebSocketStream::ConnectDelegate
* connect_delegate
,
60 const std::vector
<std::string
>& requested_subprotocols
)
61 : WebSocketHandshakeStreamCreateHelper(connect_delegate
,
62 requested_subprotocols
) {}
64 virtual WebSocketHandshakeStreamBase
* CreateBasicStream(
65 scoped_ptr
<ClientSocketHandle
> connection
,
66 bool using_proxy
) OVERRIDE
{
67 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(connection
.Pass(),
69 // This will break in an obvious way if the type created by
70 // CreateBasicStream() changes.
71 static_cast<WebSocketBasicHandshakeStream
*>(stream())
72 ->SetWebSocketKeyForTesting("dGhlIHNhbXBsZSBub25jZQ==");
77 class WebSocketStreamCreateTest
: public ::testing::Test
{
79 WebSocketStreamCreateTest(): has_failed_(false) {}
81 void CreateAndConnectCustomResponse(
82 const std::string
& socket_url
,
83 const std::string
& socket_path
,
84 const std::vector
<std::string
>& sub_protocols
,
85 const std::string
& origin
,
86 const std::string
& extra_request_headers
,
87 const std::string
& response_body
) {
88 url_request_context_host_
.SetExpectations(
89 WebSocketStandardRequest(socket_path
, origin
, extra_request_headers
),
91 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
94 // |extra_request_headers| and |extra_response_headers| must end in "\r\n" or
95 // errors like "Unable to perform synchronous IO while stopped" will occur.
96 void CreateAndConnectStandard(const std::string
& socket_url
,
97 const std::string
& socket_path
,
98 const std::vector
<std::string
>& sub_protocols
,
99 const std::string
& origin
,
100 const std::string
& extra_request_headers
,
101 const std::string
& extra_response_headers
) {
102 CreateAndConnectCustomResponse(
107 extra_request_headers
,
108 WebSocketStandardResponse(extra_response_headers
));
111 void CreateAndConnectRawExpectations(
112 const std::string
& socket_url
,
113 const std::vector
<std::string
>& sub_protocols
,
114 const std::string
& origin
,
115 scoped_ptr
<DeterministicSocketData
> socket_data
) {
116 url_request_context_host_
.SetRawExpectations(socket_data
.Pass());
117 CreateAndConnectStream(socket_url
, sub_protocols
, origin
);
120 // A wrapper for CreateAndConnectStreamForTesting that knows about our default
122 void CreateAndConnectStream(const std::string
& socket_url
,
123 const std::vector
<std::string
>& sub_protocols
,
124 const std::string
& origin
) {
125 scoped_ptr
<WebSocketStream::ConnectDelegate
> connect_delegate(
126 new TestConnectDelegate(this));
127 WebSocketStream::ConnectDelegate
* delegate
= connect_delegate
.get();
128 stream_request_
= ::net::CreateAndConnectStreamForTesting(
130 scoped_ptr
<WebSocketHandshakeStreamCreateHelper
>(
131 new DeterministicKeyWebSocketHandshakeStreamCreateHelper(
132 delegate
, sub_protocols
)),
134 url_request_context_host_
.GetURLRequestContext(),
136 connect_delegate
.Pass());
139 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
141 // A simple function to make the tests more readable. Creates an empty vector.
142 static std::vector
<std::string
> NoSubProtocols() {
143 return std::vector
<std::string
>();
146 const std::string
& failure_message() const { return failure_message_
; }
147 bool has_failed() const { return has_failed_
; }
149 class TestConnectDelegate
: public WebSocketStream::ConnectDelegate
{
151 explicit TestConnectDelegate(WebSocketStreamCreateTest
* owner
)
154 virtual void OnSuccess(scoped_ptr
<WebSocketStream
> stream
) OVERRIDE
{
155 stream
.swap(owner_
->stream_
);
158 virtual void OnFailure(const std::string
& message
) OVERRIDE
{
159 owner_
->has_failed_
= true;
160 owner_
->failure_message_
= message
;
163 virtual void OnStartOpeningHandshake(
164 scoped_ptr
<WebSocketHandshakeRequestInfo
> request
) OVERRIDE
{
165 if (owner_
->request_info_
)
167 owner_
->request_info_
= request
.Pass();
169 virtual void OnFinishOpeningHandshake(
170 scoped_ptr
<WebSocketHandshakeResponseInfo
> response
) OVERRIDE
{
171 if (owner_
->response_info_
)
173 owner_
->response_info_
= response
.Pass();
177 WebSocketStreamCreateTest
* owner_
;
180 WebSocketTestURLRequestContextHost url_request_context_host_
;
181 scoped_ptr
<WebSocketStreamRequest
> stream_request_
;
182 // Only set if the connection succeeded.
183 scoped_ptr
<WebSocketStream
> stream_
;
184 // Only set if the connection failed.
185 std::string failure_message_
;
187 scoped_ptr
<WebSocketHandshakeRequestInfo
> request_info_
;
188 scoped_ptr
<WebSocketHandshakeResponseInfo
> response_info_
;
191 // There are enough tests of the Sec-WebSocket-Extensions header that they
192 // deserve their own test fixture.
193 class WebSocketStreamCreateExtensionTest
: public WebSocketStreamCreateTest
{
195 // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
196 // header in the response set to |extensions_header_value|. Runs the event
197 // loop to allow the connect to complete.
198 void CreateAndConnectWithExtensions(
199 const std::string
& extensions_header_value
) {
200 CreateAndConnectStandard(
201 "ws://localhost/testing_path",
206 "Sec-WebSocket-Extensions: " + extensions_header_value
+ "\r\n");
211 // Confirm that the basic case works as expected.
212 TEST_F(WebSocketStreamCreateTest
, SimpleSuccess
) {
213 CreateAndConnectStandard(
214 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
215 EXPECT_FALSE(request_info_
);
216 EXPECT_FALSE(response_info_
);
218 EXPECT_FALSE(has_failed());
219 EXPECT_TRUE(stream_
);
220 EXPECT_TRUE(request_info_
);
221 EXPECT_TRUE(response_info_
);
224 TEST_F(WebSocketStreamCreateTest
, HandshakeInfo
) {
225 static const char kResponse
[] =
226 "HTTP/1.1 101 Switching Protocols\r\n"
227 "Upgrade: websocket\r\n"
228 "Connection: Upgrade\r\n"
229 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
235 CreateAndConnectCustomResponse(
242 EXPECT_FALSE(request_info_
);
243 EXPECT_FALSE(response_info_
);
245 EXPECT_TRUE(stream_
);
246 ASSERT_TRUE(request_info_
);
247 ASSERT_TRUE(response_info_
);
248 std::vector
<HeaderKeyValuePair
> request_headers
=
249 ToVector(request_info_
->headers
);
250 // We examine the contents of request_info_ and response_info_
251 // mainly only in this test case.
252 EXPECT_EQ(GURL("ws://localhost/"), request_info_
->url
);
253 EXPECT_EQ(GURL("ws://localhost/"), response_info_
->url
);
254 EXPECT_EQ(101, response_info_
->status_code
);
255 EXPECT_EQ("Switching Protocols", response_info_
->status_text
);
256 ASSERT_EQ(12u, request_headers
.size());
257 EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers
[0]);
258 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers
[1]);
259 EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers
[2]);
260 EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"),
262 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers
[4]);
263 EXPECT_EQ(HeaderKeyValuePair("Origin", "http://localhost"),
265 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"),
267 EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers
[7]);
268 EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip,deflate"),
270 EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
272 EXPECT_EQ("Sec-WebSocket-Key", request_headers
[10].first
);
273 EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
274 "permessage-deflate; client_max_window_bits"),
275 request_headers
[11]);
277 std::vector
<HeaderKeyValuePair
> response_headers
=
278 ToVector(*response_info_
->headers
);
279 ASSERT_EQ(6u, response_headers
.size());
280 // Sort the headers for ease of verification.
281 std::sort(response_headers
.begin(), response_headers
.end());
283 EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers
[0]);
284 EXPECT_EQ("Sec-WebSocket-Accept", response_headers
[1].first
);
285 EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers
[2]);
286 EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers
[3]);
287 EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers
[4]);
288 EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers
[5]);
291 // Confirm that the stream isn't established until the message loop runs.
292 TEST_F(WebSocketStreamCreateTest
, NeedsToRunLoop
) {
293 CreateAndConnectStandard(
294 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
295 EXPECT_FALSE(has_failed());
296 EXPECT_FALSE(stream_
);
299 // Check the path is used.
300 TEST_F(WebSocketStreamCreateTest
, PathIsUsed
) {
301 CreateAndConnectStandard("ws://localhost/testing_path",
308 EXPECT_FALSE(has_failed());
309 EXPECT_TRUE(stream_
);
312 // Check that the origin is used.
313 TEST_F(WebSocketStreamCreateTest
, OriginIsUsed
) {
314 CreateAndConnectStandard("ws://localhost/testing_path",
321 EXPECT_FALSE(has_failed());
322 EXPECT_TRUE(stream_
);
325 // Check that sub-protocols are sent and parsed.
326 TEST_F(WebSocketStreamCreateTest
, SubProtocolIsUsed
) {
327 std::vector
<std::string
> sub_protocols
;
328 sub_protocols
.push_back("chatv11.chromium.org");
329 sub_protocols
.push_back("chatv20.chromium.org");
330 CreateAndConnectStandard("ws://localhost/testing_path",
334 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
335 "chatv20.chromium.org\r\n",
336 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
338 EXPECT_TRUE(stream_
);
339 EXPECT_FALSE(has_failed());
340 EXPECT_EQ("chatv20.chromium.org", stream_
->GetSubProtocol());
343 // Unsolicited sub-protocols are rejected.
344 TEST_F(WebSocketStreamCreateTest
, UnsolicitedSubProtocol
) {
345 CreateAndConnectStandard("ws://localhost/testing_path",
350 "Sec-WebSocket-Protocol: chatv20.chromium.org\r\n");
352 EXPECT_FALSE(stream_
);
353 EXPECT_TRUE(has_failed());
354 EXPECT_EQ("Error during WebSocket handshake: "
355 "Response must not include 'Sec-WebSocket-Protocol' header "
356 "if not present in request: chatv20.chromium.org",
360 // Missing sub-protocol response is rejected.
361 TEST_F(WebSocketStreamCreateTest
, UnacceptedSubProtocol
) {
362 std::vector
<std::string
> sub_protocols
;
363 sub_protocols
.push_back("chat.example.com");
364 CreateAndConnectStandard("ws://localhost/testing_path",
368 "Sec-WebSocket-Protocol: chat.example.com\r\n",
371 EXPECT_FALSE(stream_
);
372 EXPECT_TRUE(has_failed());
373 EXPECT_EQ("Error during WebSocket handshake: "
374 "Sent non-empty 'Sec-WebSocket-Protocol' header "
375 "but no response was received",
379 // Only one sub-protocol can be accepted.
380 TEST_F(WebSocketStreamCreateTest
, MultipleSubProtocolsInResponse
) {
381 std::vector
<std::string
> sub_protocols
;
382 sub_protocols
.push_back("chatv11.chromium.org");
383 sub_protocols
.push_back("chatv20.chromium.org");
384 CreateAndConnectStandard("ws://localhost/testing_path",
388 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
389 "chatv20.chromium.org\r\n",
390 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
391 "chatv20.chromium.org\r\n");
393 EXPECT_FALSE(stream_
);
394 EXPECT_TRUE(has_failed());
395 EXPECT_EQ("Error during WebSocket handshake: "
396 "'Sec-WebSocket-Protocol' header must not appear "
397 "more than once in a response",
401 // Unmatched sub-protocol should be rejected.
402 TEST_F(WebSocketStreamCreateTest
, UnmatchedSubProtocolInResponse
) {
403 std::vector
<std::string
> sub_protocols
;
404 sub_protocols
.push_back("chatv11.chromium.org");
405 sub_protocols
.push_back("chatv20.chromium.org");
406 CreateAndConnectStandard("ws://localhost/testing_path",
410 "Sec-WebSocket-Protocol: chatv11.chromium.org, "
411 "chatv20.chromium.org\r\n",
412 "Sec-WebSocket-Protocol: chatv21.chromium.org\r\n");
414 EXPECT_FALSE(stream_
);
415 EXPECT_TRUE(has_failed());
416 EXPECT_EQ("Error during WebSocket handshake: "
417 "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' "
418 "in response does not match any of sent values",
422 // permessage-deflate extension basic success case.
423 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateSuccess
) {
424 CreateAndConnectWithExtensions("permessage-deflate");
425 EXPECT_TRUE(stream_
);
426 EXPECT_FALSE(has_failed());
429 // permessage-deflate extensions success with all parameters.
430 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateParamsSuccess
) {
431 CreateAndConnectWithExtensions(
432 "permessage-deflate; client_no_context_takeover; "
433 "server_max_window_bits=11; client_max_window_bits=13; "
434 "server_no_context_takeover");
435 EXPECT_TRUE(stream_
);
436 EXPECT_FALSE(has_failed());
439 // Verify that incoming messages are actually decompressed with
440 // permessage-deflate enabled.
441 TEST_F(WebSocketStreamCreateExtensionTest
, PerMessageDeflateInflates
) {
442 CreateAndConnectCustomResponse(
443 "ws://localhost/testing_path",
448 WebSocketStandardResponse(
449 "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
451 "\xc1\x07" // WebSocket header (FIN + RSV1, Text payload 7 bytes)
452 "\xf2\x48\xcd\xc9\xc9\x07\x00", // "Hello" DEFLATE compressed
456 ASSERT_TRUE(stream_
);
457 ScopedVector
<WebSocketFrame
> frames
;
458 CompletionCallback callback
;
459 ASSERT_EQ(OK
, stream_
->ReadFrames(&frames
, callback
));
460 ASSERT_EQ(1U, frames
.size());
461 ASSERT_EQ(5U, frames
[0]->header
.payload_length
);
462 EXPECT_EQ("Hello", std::string(frames
[0]->data
->data(), 5));
465 // Unknown extension in the response is rejected
466 TEST_F(WebSocketStreamCreateExtensionTest
, UnknownExtension
) {
467 CreateAndConnectWithExtensions("x-unknown-extension");
468 EXPECT_FALSE(stream_
);
469 EXPECT_TRUE(has_failed());
470 EXPECT_EQ("Error during WebSocket handshake: "
471 "Found an unsupported extension 'x-unknown-extension' "
472 "in 'Sec-WebSocket-Extensions' header",
476 // Malformed extensions are rejected (this file does not cover all possible
477 // parse failures, as the parser is covered thoroughly by its own unit tests).
478 TEST_F(WebSocketStreamCreateExtensionTest
, MalformedExtension
) {
479 CreateAndConnectWithExtensions(";");
480 EXPECT_FALSE(stream_
);
481 EXPECT_TRUE(has_failed());
483 "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
484 "value is rejected by the parser: ;",
488 // The permessage-deflate extension may only be specified once.
489 TEST_F(WebSocketStreamCreateExtensionTest
, OnlyOnePerMessageDeflateAllowed
) {
490 CreateAndConnectWithExtensions(
491 "permessage-deflate, permessage-deflate; client_max_window_bits=10");
492 EXPECT_FALSE(stream_
);
493 EXPECT_TRUE(has_failed());
495 "Error during WebSocket handshake: "
496 "Received duplicate permessage-deflate response",
500 // permessage-deflate parameters may not be duplicated.
501 TEST_F(WebSocketStreamCreateExtensionTest
, NoDuplicateParameters
) {
502 CreateAndConnectWithExtensions(
503 "permessage-deflate; client_no_context_takeover; "
504 "client_no_context_takeover");
505 EXPECT_FALSE(stream_
);
506 EXPECT_TRUE(has_failed());
508 "Error during WebSocket handshake: Error in permessage-deflate: "
509 "Received duplicate permessage-deflate extension parameter "
510 "client_no_context_takeover",
514 // permessage-deflate parameters must start with "client_" or "server_"
515 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterPrefix
) {
516 CreateAndConnectWithExtensions(
517 "permessage-deflate; absurd_no_context_takeover");
518 EXPECT_FALSE(stream_
);
519 EXPECT_TRUE(has_failed());
521 "Error during WebSocket handshake: Error in permessage-deflate: "
522 "Received an unexpected permessage-deflate extension parameter",
526 // permessage-deflate parameters must be either *_no_context_takeover or
528 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterSuffix
) {
529 CreateAndConnectWithExtensions(
530 "permessage-deflate; client_max_content_bits=5");
531 EXPECT_FALSE(stream_
);
532 EXPECT_TRUE(has_failed());
534 "Error during WebSocket handshake: Error in permessage-deflate: "
535 "Received an unexpected permessage-deflate extension parameter",
539 // *_no_context_takeover parameters must not have an argument
540 TEST_F(WebSocketStreamCreateExtensionTest
, BadParameterValue
) {
541 CreateAndConnectWithExtensions(
542 "permessage-deflate; client_no_context_takeover=true");
543 EXPECT_FALSE(stream_
);
544 EXPECT_TRUE(has_failed());
546 "Error during WebSocket handshake: Error in permessage-deflate: "
547 "Received invalid client_no_context_takeover parameter",
551 // *_max_window_bits must have an argument
552 TEST_F(WebSocketStreamCreateExtensionTest
, NoMaxWindowBitsArgument
) {
553 CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
554 EXPECT_FALSE(stream_
);
555 EXPECT_TRUE(has_failed());
557 "Error during WebSocket handshake: Error in permessage-deflate: "
558 "client_max_window_bits must have value",
562 // *_max_window_bits must be an integer
563 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueInteger
) {
564 CreateAndConnectWithExtensions(
565 "permessage-deflate; server_max_window_bits=banana");
566 EXPECT_FALSE(stream_
);
567 EXPECT_TRUE(has_failed());
569 "Error during WebSocket handshake: Error in permessage-deflate: "
570 "Received invalid server_max_window_bits parameter",
574 // *_max_window_bits must be >= 8
575 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooSmall
) {
576 CreateAndConnectWithExtensions(
577 "permessage-deflate; server_max_window_bits=7");
578 EXPECT_FALSE(stream_
);
579 EXPECT_TRUE(has_failed());
581 "Error during WebSocket handshake: Error in permessage-deflate: "
582 "Received invalid server_max_window_bits parameter",
586 // *_max_window_bits must be <= 15
587 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueTooBig
) {
588 CreateAndConnectWithExtensions(
589 "permessage-deflate; client_max_window_bits=16");
590 EXPECT_FALSE(stream_
);
591 EXPECT_TRUE(has_failed());
593 "Error during WebSocket handshake: Error in permessage-deflate: "
594 "Received invalid client_max_window_bits parameter",
598 // *_max_window_bits must not start with 0
599 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithZero
) {
600 CreateAndConnectWithExtensions(
601 "permessage-deflate; client_max_window_bits=08");
602 EXPECT_FALSE(stream_
);
603 EXPECT_TRUE(has_failed());
605 "Error during WebSocket handshake: Error in permessage-deflate: "
606 "Received invalid client_max_window_bits parameter",
610 // *_max_window_bits must not start with +
611 TEST_F(WebSocketStreamCreateExtensionTest
, MaxWindowBitsValueStartsWithPlus
) {
612 CreateAndConnectWithExtensions(
613 "permessage-deflate; server_max_window_bits=+9");
614 EXPECT_FALSE(stream_
);
615 EXPECT_TRUE(has_failed());
617 "Error during WebSocket handshake: Error in permessage-deflate: "
618 "Received invalid server_max_window_bits parameter",
622 // TODO(ricea): Check that WebSocketDeflateStream is initialised with the
623 // arguments from the server. This is difficult because the data written to the
624 // socket is randomly masked.
626 // Additional Sec-WebSocket-Accept headers should be rejected.
627 TEST_F(WebSocketStreamCreateTest
, DoubleAccept
) {
628 CreateAndConnectStandard(
634 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
636 EXPECT_FALSE(stream_
);
637 EXPECT_TRUE(has_failed());
638 EXPECT_EQ("Error during WebSocket handshake: "
639 "'Sec-WebSocket-Accept' header must not appear "
640 "more than once in a response",
644 // Response code 200 must be rejected.
645 TEST_F(WebSocketStreamCreateTest
, InvalidStatusCode
) {
646 static const char kInvalidStatusCodeResponse
[] =
647 "HTTP/1.1 200 OK\r\n"
648 "Upgrade: websocket\r\n"
649 "Connection: Upgrade\r\n"
650 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
652 CreateAndConnectCustomResponse("ws://localhost/",
657 kInvalidStatusCodeResponse
);
659 EXPECT_TRUE(has_failed());
660 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200",
664 // Redirects are not followed (according to the WHATWG WebSocket API, which
665 // overrides RFC6455 for browser applications).
666 TEST_F(WebSocketStreamCreateTest
, RedirectsRejected
) {
667 static const char kRedirectResponse
[] =
668 "HTTP/1.1 302 Moved Temporarily\r\n"
669 "Content-Type: text/html\r\n"
670 "Content-Length: 34\r\n"
671 "Connection: keep-alive\r\n"
672 "Location: ws://localhost/other\r\n"
674 "<title>Moved</title><h1>Moved</h1>";
675 CreateAndConnectCustomResponse("ws://localhost/",
682 EXPECT_TRUE(has_failed());
683 EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302",
687 // Malformed responses should be rejected. HttpStreamParser will accept just
688 // about any garbage in the middle of the headers. To make it give up, the junk
689 // has to be at the start of the response. Even then, it just gets treated as an
690 // HTTP/0.9 response.
691 TEST_F(WebSocketStreamCreateTest
, MalformedResponse
) {
692 static const char kMalformedResponse
[] =
693 "220 mx.google.com ESMTP\r\n"
694 "HTTP/1.1 101 OK\r\n"
695 "Upgrade: websocket\r\n"
696 "Connection: Upgrade\r\n"
697 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
699 CreateAndConnectCustomResponse("ws://localhost/",
706 EXPECT_TRUE(has_failed());
707 EXPECT_EQ("Error during WebSocket handshake: Invalid status line",
711 // Upgrade header must be present.
712 TEST_F(WebSocketStreamCreateTest
, MissingUpgradeHeader
) {
713 static const char kMissingUpgradeResponse
[] =
714 "HTTP/1.1 101 Switching Protocols\r\n"
715 "Connection: Upgrade\r\n"
716 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
718 CreateAndConnectCustomResponse("ws://localhost/",
723 kMissingUpgradeResponse
);
725 EXPECT_TRUE(has_failed());
726 EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing",
730 // There must only be one upgrade header.
731 TEST_F(WebSocketStreamCreateTest
, DoubleUpgradeHeader
) {
732 CreateAndConnectStandard(
737 "", "Upgrade: HTTP/2.0\r\n");
739 EXPECT_TRUE(has_failed());
740 EXPECT_EQ("Error during WebSocket handshake: "
741 "'Upgrade' header must not appear more than once in a response",
745 // There must only be one correct upgrade header.
746 TEST_F(WebSocketStreamCreateTest
, IncorrectUpgradeHeader
) {
747 static const char kMissingUpgradeResponse
[] =
748 "HTTP/1.1 101 Switching Protocols\r\n"
749 "Connection: Upgrade\r\n"
750 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
751 "Upgrade: hogefuga\r\n"
753 CreateAndConnectCustomResponse("ws://localhost/",
758 kMissingUpgradeResponse
);
760 EXPECT_TRUE(has_failed());
761 EXPECT_EQ("Error during WebSocket handshake: "
762 "'Upgrade' header value is not 'WebSocket': hogefuga",
766 // Connection header must be present.
767 TEST_F(WebSocketStreamCreateTest
, MissingConnectionHeader
) {
768 static const char kMissingConnectionResponse
[] =
769 "HTTP/1.1 101 Switching Protocols\r\n"
770 "Upgrade: websocket\r\n"
771 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
773 CreateAndConnectCustomResponse("ws://localhost/",
778 kMissingConnectionResponse
);
780 EXPECT_TRUE(has_failed());
781 EXPECT_EQ("Error during WebSocket handshake: "
782 "'Connection' header is missing",
786 // Connection header must contain "Upgrade".
787 TEST_F(WebSocketStreamCreateTest
, IncorrectConnectionHeader
) {
788 static const char kMissingConnectionResponse
[] =
789 "HTTP/1.1 101 Switching Protocols\r\n"
790 "Upgrade: websocket\r\n"
791 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
792 "Connection: hogefuga\r\n"
794 CreateAndConnectCustomResponse("ws://localhost/",
799 kMissingConnectionResponse
);
801 EXPECT_TRUE(has_failed());
802 EXPECT_EQ("Error during WebSocket handshake: "
803 "'Connection' header value must contain 'Upgrade'",
807 // Connection header is permitted to contain other tokens.
808 TEST_F(WebSocketStreamCreateTest
, AdditionalTokenInConnectionHeader
) {
809 static const char kAdditionalConnectionTokenResponse
[] =
810 "HTTP/1.1 101 Switching Protocols\r\n"
811 "Upgrade: websocket\r\n"
812 "Connection: Upgrade, Keep-Alive\r\n"
813 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
815 CreateAndConnectCustomResponse("ws://localhost/",
820 kAdditionalConnectionTokenResponse
);
822 EXPECT_FALSE(has_failed());
823 EXPECT_TRUE(stream_
);
826 // Sec-WebSocket-Accept header must be present.
827 TEST_F(WebSocketStreamCreateTest
, MissingSecWebSocketAccept
) {
828 static const char kMissingAcceptResponse
[] =
829 "HTTP/1.1 101 Switching Protocols\r\n"
830 "Upgrade: websocket\r\n"
831 "Connection: Upgrade\r\n"
833 CreateAndConnectCustomResponse("ws://localhost/",
838 kMissingAcceptResponse
);
840 EXPECT_TRUE(has_failed());
841 EXPECT_EQ("Error during WebSocket handshake: "
842 "'Sec-WebSocket-Accept' header is missing",
846 // Sec-WebSocket-Accept header must match the key that was sent.
847 TEST_F(WebSocketStreamCreateTest
, WrongSecWebSocketAccept
) {
848 static const char kIncorrectAcceptResponse
[] =
849 "HTTP/1.1 101 Switching Protocols\r\n"
850 "Upgrade: websocket\r\n"
851 "Connection: Upgrade\r\n"
852 "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n"
854 CreateAndConnectCustomResponse("ws://localhost/",
859 kIncorrectAcceptResponse
);
861 EXPECT_TRUE(has_failed());
862 EXPECT_EQ("Error during WebSocket handshake: "
863 "Incorrect 'Sec-WebSocket-Accept' header value",
867 // Cancellation works.
868 TEST_F(WebSocketStreamCreateTest
, Cancellation
) {
869 CreateAndConnectStandard(
870 "ws://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
871 stream_request_
.reset();
873 EXPECT_FALSE(has_failed());
874 EXPECT_FALSE(stream_
);
875 EXPECT_FALSE(request_info_
);
876 EXPECT_FALSE(response_info_
);
879 // Connect failure must look just like negotiation failure.
880 TEST_F(WebSocketStreamCreateTest
, ConnectionFailure
) {
881 scoped_ptr
<DeterministicSocketData
> socket_data(
882 new DeterministicSocketData(NULL
, 0, NULL
, 0));
883 socket_data
->set_connect_data(
884 MockConnect(SYNCHRONOUS
, ERR_CONNECTION_REFUSED
));
885 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
886 "http://localhost", socket_data
.Pass());
888 EXPECT_TRUE(has_failed());
889 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED",
891 EXPECT_FALSE(request_info_
);
892 EXPECT_FALSE(response_info_
);
895 // Connect timeout must look just like any other failure.
896 TEST_F(WebSocketStreamCreateTest
, ConnectionTimeout
) {
897 scoped_ptr
<DeterministicSocketData
> socket_data(
898 new DeterministicSocketData(NULL
, 0, NULL
, 0));
899 socket_data
->set_connect_data(
900 MockConnect(ASYNC
, ERR_CONNECTION_TIMED_OUT
));
901 CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
902 "http://localhost", socket_data
.Pass());
904 EXPECT_TRUE(has_failed());
905 EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT",
909 // Cancellation during connect works.
910 TEST_F(WebSocketStreamCreateTest
, CancellationDuringConnect
) {
911 scoped_ptr
<DeterministicSocketData
> socket_data(
912 new DeterministicSocketData(NULL
, 0, NULL
, 0));
913 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, ERR_IO_PENDING
));
914 CreateAndConnectRawExpectations("ws://localhost/",
918 stream_request_
.reset();
920 EXPECT_FALSE(has_failed());
921 EXPECT_FALSE(stream_
);
924 // Cancellation during write of the request headers works.
925 TEST_F(WebSocketStreamCreateTest
, CancellationDuringWrite
) {
926 // We seem to need at least two operations in order to use SetStop().
927 MockWrite writes
[] = {MockWrite(ASYNC
, 0, "GET / HTTP/"),
928 MockWrite(ASYNC
, 1, "1.1\r\n")};
929 // We keep a copy of the pointer so that we can call RunFor() on it later.
930 DeterministicSocketData
* socket_data(
931 new DeterministicSocketData(NULL
, 0, writes
, arraysize(writes
)));
932 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
933 socket_data
->SetStop(1);
934 CreateAndConnectRawExpectations("ws://localhost/",
937 make_scoped_ptr(socket_data
));
939 stream_request_
.reset();
941 EXPECT_FALSE(has_failed());
942 EXPECT_FALSE(stream_
);
943 EXPECT_TRUE(request_info_
);
944 EXPECT_FALSE(response_info_
);
947 // Cancellation during read of the response headers works.
948 TEST_F(WebSocketStreamCreateTest
, CancellationDuringRead
) {
949 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
950 MockWrite writes
[] = {MockWrite(ASYNC
, 0, request
.c_str())};
952 MockRead(ASYNC
, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
954 DeterministicSocketData
* socket_data(new DeterministicSocketData(
955 reads
, arraysize(reads
), writes
, arraysize(writes
)));
956 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
957 socket_data
->SetStop(1);
958 CreateAndConnectRawExpectations("ws://localhost/",
961 make_scoped_ptr(socket_data
));
963 stream_request_
.reset();
965 EXPECT_FALSE(has_failed());
966 EXPECT_FALSE(stream_
);
967 EXPECT_TRUE(request_info_
);
968 EXPECT_FALSE(response_info_
);
971 // Over-size response headers (> 256KB) should not cause a crash. This is a
972 // regression test for crbug.com/339456. It is based on the layout test
973 // "cookie-flood.html".
974 TEST_F(WebSocketStreamCreateTest
, VeryLargeResponseHeaders
) {
975 std::string set_cookie_headers
;
976 set_cookie_headers
.reserve(45 * 10000);
977 for (int i
= 0; i
< 10000; ++i
) {
978 set_cookie_headers
+=
979 base::StringPrintf("Set-Cookie: WK-websocket-test-flood-%d=1\r\n", i
);
981 CreateAndConnectStandard("ws://localhost/", "/", NoSubProtocols(),
982 "http://localhost", "", set_cookie_headers
);
984 EXPECT_TRUE(has_failed());
985 EXPECT_FALSE(response_info_
);
988 // If the remote host closes the connection without sending headers, we should
989 // log the console message "Connection closed before receiving a handshake
991 TEST_F(WebSocketStreamCreateTest
, NoResponse
) {
992 std::string request
= WebSocketStandardRequest("/", "http://localhost", "");
993 MockWrite writes
[] = {MockWrite(ASYNC
, request
.data(), request
.size(), 0)};
994 MockRead reads
[] = {MockRead(ASYNC
, 0, 1)};
995 DeterministicSocketData
* socket_data(new DeterministicSocketData(
996 reads
, arraysize(reads
), writes
, arraysize(writes
)));
997 socket_data
->set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
998 CreateAndConnectRawExpectations("ws://localhost/",
1001 make_scoped_ptr(socket_data
));
1002 socket_data
->RunFor(2);
1003 EXPECT_TRUE(has_failed());
1004 EXPECT_FALSE(stream_
);
1005 EXPECT_FALSE(response_info_
);
1006 EXPECT_EQ("Connection closed before receiving a handshake response",