|  | // Copyright 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "net/websockets/websocket_stream.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/containers/span.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/metrics/histogram.h" | 
|  | #include "base/metrics/histogram_samples.h" | 
|  | #include "base/metrics/statistics_recorder.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/test/metrics/histogram_tester.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/timer/mock_timer.h" | 
|  | #include "base/timer/timer.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/base/url_util.h" | 
|  | #include "net/http/http_request_headers.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/proxy_resolution/proxy_resolution_service.h" | 
|  | #include "net/socket/client_socket_handle.h" | 
|  | #include "net/socket/socket_test_util.h" | 
|  | #include "net/spdy/spdy_test_util_common.h" | 
|  | #include "net/test/cert_test_util.h" | 
|  | #include "net/test/gtest_util.h" | 
|  | #include "net/test/test_data_directory.h" | 
|  | #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" | 
|  | #include "net/url_request/url_request_test_util.h" | 
|  | #include "net/websockets/websocket_basic_handshake_stream.h" | 
|  | #include "net/websockets/websocket_frame.h" | 
|  | #include "net/websockets/websocket_stream_create_test_base.h" | 
|  | #include "net/websockets/websocket_test_util.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "url/gurl.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | using ::net::test::IsError; | 
|  | using ::net::test::IsOk; | 
|  | using ::testing::TestWithParam; | 
|  | using ::testing::Values; | 
|  |  | 
|  | namespace net { | 
|  | namespace { | 
|  |  | 
|  | enum HandshakeStreamType { BASIC_HANDSHAKE_STREAM, HTTP2_HANDSHAKE_STREAM }; | 
|  |  | 
|  | // Simple builder for a SequencedSocketData object to save repetitive code. | 
|  | // It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot | 
|  | // be used in tests where the connect fails. In practice, those tests never have | 
|  | // any read/write data and so can't benefit from it anyway.  The arrays are not | 
|  | // copied. It is up to the caller to ensure they stay in scope until the test | 
|  | // ends. | 
|  | std::unique_ptr<SequencedSocketData> BuildSocketData( | 
|  | base::span<MockRead> reads, | 
|  | base::span<MockWrite> writes) { | 
|  | auto socket_data = std::make_unique<SequencedSocketData>(reads, writes); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
|  | return socket_data; | 
|  | } | 
|  |  | 
|  | // Builder for a SequencedSocketData that expects nothing. This does not | 
|  | // set the connect data, so the calling code must do that explicitly. | 
|  | std::unique_ptr<SequencedSocketData> BuildNullSocketData() { | 
|  | return std::make_unique<SequencedSocketData>(); | 
|  | } | 
|  |  | 
|  | class MockWeakTimer : public base::MockOneShotTimer, | 
|  | public base::SupportsWeakPtr<MockWeakTimer> { | 
|  | public: | 
|  | MockWeakTimer() {} | 
|  | }; | 
|  |  | 
|  | const char kOrigin[] = "http://www.example.org"; | 
|  |  | 
|  | static url::Origin Origin() { | 
|  | return url::Origin::Create(GURL(kOrigin)); | 
|  | } | 
|  |  | 
|  | static GURL SiteForCookies() { | 
|  | return GURL("http://www.example.org/foobar"); | 
|  | } | 
|  |  | 
|  | class WebSocketStreamCreateTest : public TestWithParam<HandshakeStreamType>, | 
|  | public WebSocketStreamCreateTestBase { | 
|  | protected: | 
|  | WebSocketStreamCreateTest() | 
|  | : stream_type_(GetParam()), | 
|  | http2_response_status_("200"), | 
|  | reset_websocket_http2_stream_(false), | 
|  | sequence_number_(0) {} | 
|  | ~WebSocketStreamCreateTest() override { | 
|  | // Permit any endpoint locks to be released. | 
|  | stream_request_.reset(); | 
|  | stream_.reset(); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Normally it's easier to use CreateAndConnectRawExpectations() instead. This | 
|  | // method is only needed when multiple sockets are involved. | 
|  | void AddRawExpectations(std::unique_ptr<SequencedSocketData> socket_data) { | 
|  | url_request_context_host_.AddRawExpectations(std::move(socket_data)); | 
|  | } | 
|  |  | 
|  | void AddSSLData() { | 
|  | auto ssl_data = std::make_unique<SSLSocketDataProvider>(ASYNC, OK); | 
|  | ssl_data->ssl_info.cert = | 
|  | ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"); | 
|  | if (stream_type_ == HTTP2_HANDSHAKE_STREAM) | 
|  | ssl_data->next_proto = kProtoHTTP2; | 
|  | ASSERT_TRUE(ssl_data->ssl_info.cert.get()); | 
|  | url_request_context_host_.AddSSLSocketDataProvider(std::move(ssl_data)); | 
|  | } | 
|  |  | 
|  | void SetTimer(std::unique_ptr<base::OneShotTimer> timer) { | 
|  | timer_ = std::move(timer); | 
|  | } | 
|  |  | 
|  | void SetAdditionalResponseData(std::string additional_data) { | 
|  | additional_data_ = std::move(additional_data); | 
|  | } | 
|  |  | 
|  | void SetHttp2ResponseStatus(const char* const http2_response_status) { | 
|  | http2_response_status_ = http2_response_status; | 
|  | } | 
|  |  | 
|  | void SetResetWebSocketHttp2Stream(bool reset_websocket_http2_stream) { | 
|  | reset_websocket_http2_stream_ = reset_websocket_http2_stream; | 
|  | } | 
|  |  | 
|  | // Set up mock data and start websockets request, either for WebSocket | 
|  | // upgraded from an HTTP/1 connection, or for a WebSocket request over HTTP/2. | 
|  | void CreateAndConnectStandard( | 
|  | base::StringPiece url, | 
|  | const std::vector<std::string>& sub_protocols, | 
|  | const WebSocketExtraHeaders& send_additional_request_headers, | 
|  | const WebSocketExtraHeaders& extra_request_headers, | 
|  | const WebSocketExtraHeaders& extra_response_headers) { | 
|  | const GURL socket_url(url); | 
|  | const std::string socket_host = GetHostAndOptionalPort(socket_url); | 
|  | const std::string socket_path = socket_url.path(); | 
|  |  | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | url_request_context_host_.SetExpectations( | 
|  | WebSocketStandardRequest( | 
|  | socket_path, socket_host, Origin(), | 
|  | WebSocketExtraHeadersToString(send_additional_request_headers), | 
|  | WebSocketExtraHeadersToString(extra_request_headers)), | 
|  | WebSocketStandardResponse( | 
|  | WebSocketExtraHeadersToString(extra_response_headers)) + | 
|  | additional_data_); | 
|  | CreateAndConnectStream(socket_url, sub_protocols, Origin(), | 
|  | SiteForCookies(), | 
|  | WebSocketExtraHeadersToHttpRequestHeaders( | 
|  | send_additional_request_headers), | 
|  | std::move(timer_)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  |  | 
|  | // TODO(bnc): Find a way to clear | 
|  | // spdy_session_pool.enable_sending_initial_data_ to avoid sending | 
|  | // connection preface, initial settings, and window update. | 
|  |  | 
|  | // HTTP/2 connection preface. | 
|  | frames_.push_back(spdy::SpdySerializedFrame( | 
|  | const_cast<char*>(spdy::kHttp2ConnectionHeaderPrefix), | 
|  | spdy::kHttp2ConnectionHeaderPrefixSize, | 
|  | /* owns_buffer = */ false)); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | // Server advertises WebSockets over HTTP/2 support. | 
|  | spdy::SettingsMap read_settings; | 
|  | read_settings[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1; | 
|  | frames_.push_back(spdy_util_.ConstructSpdySettings(read_settings)); | 
|  | AddRead(&frames_.back()); | 
|  |  | 
|  | // Initial SETTINGS frame. | 
|  | spdy::SettingsMap write_settings; | 
|  | write_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize; | 
|  | write_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = | 
|  | kSpdyMaxConcurrentPushedStreams; | 
|  | write_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = 6 * 1024 * 1024; | 
|  | frames_.push_back(spdy_util_.ConstructSpdySettings(write_settings)); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | // Initial window update frame. | 
|  | frames_.push_back(spdy_util_.ConstructSpdyWindowUpdate(0, 0x00ef0001)); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | // SETTINGS ACK sent as a response to server's SETTINGS frame. | 
|  | frames_.push_back(spdy_util_.ConstructSpdySettingsAck()); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | // First request.  This is necessary, because a WebSockets request currently | 
|  | // does not open a new HTTP/2 connection, it only uses an existing one. | 
|  | const char* const kExtraRequestHeaders[] = { | 
|  | "user-agent",      "",        "accept-encoding", "gzip, deflate", | 
|  | "accept-language", "en-us,fr"}; | 
|  | frames_.push_back(spdy_util_.ConstructSpdyGet( | 
|  | kExtraRequestHeaders, arraysize(kExtraRequestHeaders) / 2, 1, | 
|  | DEFAULT_PRIORITY)); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | // SETTINGS ACK frame sent by the server in response to the client's | 
|  | // initial SETTINGS frame. | 
|  | frames_.push_back(spdy_util_.ConstructSpdySettingsAck()); | 
|  | AddRead(&frames_.back()); | 
|  |  | 
|  | // Response headers to first request. | 
|  | frames_.push_back(spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); | 
|  | AddRead(&frames_.back()); | 
|  |  | 
|  | // Response body to first request. | 
|  | frames_.push_back(spdy_util_.ConstructSpdyDataFrame(1, true)); | 
|  | AddRead(&frames_.back()); | 
|  |  | 
|  | // First request is closed. | 
|  | spdy_util_.UpdateWithStreamDestruction(1); | 
|  |  | 
|  | // WebSocket request. | 
|  | spdy::SpdyHeaderBlock request_headers = WebSocketHttp2Request( | 
|  | socket_path, socket_host, kOrigin, extra_request_headers); | 
|  | frames_.push_back(spdy_util_.ConstructSpdyHeaders( | 
|  | 3, std::move(request_headers), DEFAULT_PRIORITY, false)); | 
|  | AddWrite(&frames_.back()); | 
|  |  | 
|  | if (reset_websocket_http2_stream_) { | 
|  | frames_.push_back( | 
|  | spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL)); | 
|  | AddRead(&frames_.back()); | 
|  | } else { | 
|  | // Response to WebSocket request. | 
|  | std::vector<std::string> extra_response_header_keys; | 
|  | std::vector<const char*> extra_response_headers_vector; | 
|  | for (const auto& extra_header : extra_response_headers) { | 
|  | // Save a lowercase copy of the header key. | 
|  | extra_response_header_keys.push_back( | 
|  | base::ToLowerASCII(extra_header.first)); | 
|  | // Save a pointer to this lowercase copy. | 
|  | extra_response_headers_vector.push_back( | 
|  | extra_response_header_keys.back().c_str()); | 
|  | // Save a pointer to the original header value provided by the caller. | 
|  | extra_response_headers_vector.push_back(extra_header.second.c_str()); | 
|  | } | 
|  | frames_.push_back(spdy_util_.ConstructSpdyReplyError( | 
|  | http2_response_status_, extra_response_headers_vector.data(), | 
|  | extra_response_headers_vector.size() / 2, 3)); | 
|  | AddRead(&frames_.back()); | 
|  |  | 
|  | // WebSocket data received. | 
|  | if (!additional_data_.empty()) { | 
|  | frames_.push_back( | 
|  | spdy_util_.ConstructSpdyDataFrame(3, additional_data_, true)); | 
|  | AddRead(&frames_.back()); | 
|  | } | 
|  |  | 
|  | // Client cancels HTTP/2 stream when request is destroyed. | 
|  | frames_.push_back( | 
|  | spdy_util_.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL)); | 
|  | AddWrite(&frames_.back()); | 
|  | } | 
|  |  | 
|  | // EOF. | 
|  | reads_.push_back(MockRead(ASYNC, 0, sequence_number_++)); | 
|  |  | 
|  | auto socket_data = std::make_unique<SequencedSocketData>(reads_, writes_); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
|  | AddRawExpectations(std::move(socket_data)); | 
|  |  | 
|  | // Send first request.  This makes sure server's | 
|  | // spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL advertisement is read. | 
|  | TestURLRequestContext* context = | 
|  | url_request_context_host_.GetURLRequestContext(); | 
|  | TestDelegate delegate; | 
|  | std::unique_ptr<URLRequest> request = context->CreateRequest( | 
|  | GURL("https://www.example.org/"), DEFAULT_PRIORITY, &delegate, | 
|  | TRAFFIC_ANNOTATION_FOR_TESTS); | 
|  | request->Start(); | 
|  | EXPECT_TRUE(request->is_pending()); | 
|  | delegate.RunUntilComplete(); | 
|  | EXPECT_FALSE(request->is_pending()); | 
|  |  | 
|  | CreateAndConnectStream(socket_url, sub_protocols, Origin(), | 
|  | SiteForCookies(), | 
|  | WebSocketExtraHeadersToHttpRequestHeaders( | 
|  | send_additional_request_headers), | 
|  | std::move(timer_)); | 
|  | } | 
|  |  | 
|  | // Like CreateAndConnectStandard(), but allow for arbitrary response body. | 
|  | // Only for HTTP/1-based WebSockets. | 
|  | void CreateAndConnectCustomResponse( | 
|  | base::StringPiece url, | 
|  | const std::vector<std::string>& sub_protocols, | 
|  | const WebSocketExtraHeaders& send_additional_request_headers, | 
|  | const WebSocketExtraHeaders& extra_request_headers, | 
|  | const std::string& response_body) { | 
|  | ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_); | 
|  |  | 
|  | const GURL socket_url(url); | 
|  | const std::string socket_host = GetHostAndOptionalPort(socket_url); | 
|  | const std::string socket_path = socket_url.path(); | 
|  |  | 
|  | url_request_context_host_.SetExpectations( | 
|  | WebSocketStandardRequest( | 
|  | socket_path, socket_host, Origin(), | 
|  | WebSocketExtraHeadersToString(send_additional_request_headers), | 
|  | WebSocketExtraHeadersToString(extra_request_headers)), | 
|  | response_body); | 
|  | CreateAndConnectStream(socket_url, sub_protocols, Origin(), | 
|  | SiteForCookies(), | 
|  | WebSocketExtraHeadersToHttpRequestHeaders( | 
|  | send_additional_request_headers), | 
|  | nullptr); | 
|  | } | 
|  |  | 
|  | // Like CreateAndConnectStandard(), but take extra response headers as a | 
|  | // string.  This can save space in case of a very large response. | 
|  | // Only for HTTP/1-based WebSockets. | 
|  | void CreateAndConnectStringResponse( | 
|  | base::StringPiece url, | 
|  | const std::vector<std::string>& sub_protocols, | 
|  | const std::string& extra_response_headers) { | 
|  | ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_); | 
|  |  | 
|  | const GURL socket_url(url); | 
|  | const std::string socket_host = GetHostAndOptionalPort(socket_url); | 
|  | const std::string socket_path = socket_url.path(); | 
|  |  | 
|  | url_request_context_host_.SetExpectations( | 
|  | WebSocketStandardRequest(socket_path, socket_host, Origin(), "", ""), | 
|  | WebSocketStandardResponse(extra_response_headers)); | 
|  | CreateAndConnectStream(socket_url, sub_protocols, Origin(), | 
|  | SiteForCookies(), HttpRequestHeaders(), nullptr); | 
|  | } | 
|  |  | 
|  | // Like CreateAndConnectStandard(), but take raw mock data. | 
|  | void CreateAndConnectRawExpectations( | 
|  | base::StringPiece url, | 
|  | const std::vector<std::string>& sub_protocols, | 
|  | const HttpRequestHeaders& additional_headers, | 
|  | std::unique_ptr<SequencedSocketData> socket_data) { | 
|  | ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_); | 
|  |  | 
|  | AddRawExpectations(std::move(socket_data)); | 
|  | CreateAndConnectStream(GURL(url), sub_protocols, Origin(), SiteForCookies(), | 
|  | additional_headers, std::move(timer_)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void AddWrite(const spdy::SpdySerializedFrame* frame) { | 
|  | writes_.push_back( | 
|  | MockWrite(ASYNC, frame->data(), frame->size(), sequence_number_++)); | 
|  | } | 
|  |  | 
|  | void AddRead(const spdy::SpdySerializedFrame* frame) { | 
|  | reads_.push_back( | 
|  | MockRead(ASYNC, frame->data(), frame->size(), sequence_number_++)); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | const HandshakeStreamType stream_type_; | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<base::OneShotTimer> timer_; | 
|  | std::string additional_data_; | 
|  | const char* http2_response_status_; | 
|  | bool reset_websocket_http2_stream_; | 
|  | SpdyTestUtil spdy_util_; | 
|  | NetLogWithSource log_; | 
|  |  | 
|  | int sequence_number_; | 
|  |  | 
|  | // Store mock HTTP/2 data. | 
|  | std::vector<spdy::SpdySerializedFrame> frames_; | 
|  |  | 
|  | // Store MockRead and MockWrite objects that have pointers to above data. | 
|  | std::vector<MockRead> reads_; | 
|  | std::vector<MockWrite> writes_; | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | WebSocketStreamCreateTest, | 
|  | Values(BASIC_HANDSHAKE_STREAM)); | 
|  |  | 
|  | using WebSocketMultiProtocolStreamCreateTest = WebSocketStreamCreateTest; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | WebSocketMultiProtocolStreamCreateTest, | 
|  | Values(BASIC_HANDSHAKE_STREAM, HTTP2_HANDSHAKE_STREAM)); | 
|  |  | 
|  | // There are enough tests of the Sec-WebSocket-Extensions header that they | 
|  | // deserve their own test fixture. | 
|  | class WebSocketStreamCreateExtensionTest | 
|  | : public WebSocketMultiProtocolStreamCreateTest { | 
|  | protected: | 
|  | // Performs a standard connect, with the value of the Sec-WebSocket-Extensions | 
|  | // header in the response set to |extensions_header_value|. Runs the event | 
|  | // loop to allow the connect to complete. | 
|  | void CreateAndConnectWithExtensions( | 
|  | const std::string& extensions_header_value) { | 
|  | AddSSLData(); | 
|  | CreateAndConnectStandard( | 
|  | "wss://www.example.org/testing_path", NoSubProtocols(), {}, {}, | 
|  | {{"Sec-WebSocket-Extensions", extensions_header_value}}); | 
|  | WaitUntilConnectDone(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | WebSocketStreamCreateExtensionTest, | 
|  | Values(BASIC_HANDSHAKE_STREAM, HTTP2_HANDSHAKE_STREAM)); | 
|  |  | 
|  | // Common code to construct expectations for authentication tests that receive | 
|  | // the auth challenge on one connection and then create a second connection to | 
|  | // send the authenticated request on. | 
|  | class CommonAuthTestHelper { | 
|  | public: | 
|  | CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {} | 
|  |  | 
|  | std::unique_ptr<SequencedSocketData> BuildSocketData1( | 
|  | const std::string& response) { | 
|  | request1_ = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | writes1_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str()); | 
|  | response1_ = response; | 
|  | reads1_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str()); | 
|  | reads1_[1] = MockRead(SYNCHRONOUS, OK, 2);  // Close connection | 
|  |  | 
|  | return BuildSocketData(reads1_, writes1_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SequencedSocketData> BuildSocketData2( | 
|  | const std::string& request, | 
|  | const std::string& response) { | 
|  | request2_ = request; | 
|  | response2_ = response; | 
|  | writes2_[0] = MockWrite(SYNCHRONOUS, 0, request2_.c_str()); | 
|  | reads2_[0] = MockRead(SYNCHRONOUS, 1, response2_.c_str()); | 
|  | return BuildSocketData(reads2_, writes2_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // These need to be object-scoped since they have to remain valid until all | 
|  | // socket operations in the test are complete. | 
|  | std::string request1_; | 
|  | std::string request2_; | 
|  | std::string response1_; | 
|  | std::string response2_; | 
|  | MockRead reads1_[2]; | 
|  | MockWrite writes1_[1]; | 
|  | MockRead reads2_[1]; | 
|  | MockWrite writes2_[1]; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper); | 
|  | }; | 
|  |  | 
|  | // Data and methods for BasicAuth tests. | 
|  | class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest { | 
|  | protected: | 
|  | void CreateAndConnectAuthHandshake(base::StringPiece url, | 
|  | base::StringPiece base64_user_pass, | 
|  | base::StringPiece response2) { | 
|  | AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | 
|  |  | 
|  | CreateAndConnectRawExpectations( | 
|  | url, NoSubProtocols(), HttpRequestHeaders(), | 
|  | helper_.BuildSocketData2(RequestExpectation(base64_user_pass), | 
|  | response2.as_string())); | 
|  | } | 
|  |  | 
|  | static std::string RequestExpectation(base::StringPiece base64_user_pass) { | 
|  | static const char request2format[] = | 
|  | "GET / HTTP/1.1\r\n" | 
|  | "Host: www.example.org\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Pragma: no-cache\r\n" | 
|  | "Cache-Control: no-cache\r\n" | 
|  | "Authorization: Basic %s\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Origin: http://www.example.org\r\n" | 
|  | "Sec-WebSocket-Version: 13\r\n" | 
|  | "User-Agent: \r\n" | 
|  | "Accept-Encoding: gzip, deflate\r\n" | 
|  | "Accept-Language: en-us,fr\r\n" | 
|  | "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | 
|  | "Sec-WebSocket-Extensions: permessage-deflate; " | 
|  | "client_max_window_bits\r\n" | 
|  | "\r\n"; | 
|  | return base::StringPrintf(request2format, base64_user_pass.data()); | 
|  | } | 
|  |  | 
|  | static const char kUnauthorizedResponse[]; | 
|  |  | 
|  | CommonAuthTestHelper helper_; | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | WebSocketStreamCreateBasicAuthTest, | 
|  | Values(BASIC_HANDSHAKE_STREAM)); | 
|  |  | 
|  | class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest { | 
|  | protected: | 
|  | static const char kUnauthorizedResponse[]; | 
|  | static const char kAuthorizedRequest[]; | 
|  |  | 
|  | CommonAuthTestHelper helper_; | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(, | 
|  | WebSocketStreamCreateDigestAuthTest, | 
|  | Values(BASIC_HANDSHAKE_STREAM)); | 
|  |  | 
|  | const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] = | 
|  | "HTTP/1.1 401 Unauthorized\r\n" | 
|  | "Content-Length: 0\r\n" | 
|  | "WWW-Authenticate: Basic realm=\"camelot\"\r\n" | 
|  | "\r\n"; | 
|  |  | 
|  | // These negotiation values are borrowed from | 
|  | // http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if | 
|  | // you are bored. Only the weakest (no qop) variants of Digest authentication | 
|  | // can be tested by this method, because the others involve random input. | 
|  | const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] = | 
|  | "HTTP/1.1 401 Unauthorized\r\n" | 
|  | "Content-Length: 0\r\n" | 
|  | "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n" | 
|  | "\r\n"; | 
|  |  | 
|  | const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] = | 
|  | "GET / HTTP/1.1\r\n" | 
|  | "Host: www.example.org\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Pragma: no-cache\r\n" | 
|  | "Cache-Control: no-cache\r\n" | 
|  | "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", " | 
|  | "nonce=\"nonce-value\", uri=\"/\", " | 
|  | "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Origin: http://www.example.org\r\n" | 
|  | "Sec-WebSocket-Version: 13\r\n" | 
|  | "User-Agent: \r\n" | 
|  | "Accept-Encoding: gzip, deflate\r\n" | 
|  | "Accept-Language: en-us,fr\r\n" | 
|  | "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" | 
|  | "Sec-WebSocket-Extensions: permessage-deflate; " | 
|  | "client_max_window_bits\r\n" | 
|  | "\r\n"; | 
|  |  | 
|  | // Confirm that the basic case works as expected. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, SimpleSuccess) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | AddSSLData(); | 
|  | EXPECT_FALSE(url_request_); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | EXPECT_TRUE(url_request_); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | EXPECT_TRUE(request_info_); | 
|  | EXPECT_TRUE(response_info_); | 
|  | EXPECT_EQ(ERR_WS_UPGRADE, | 
|  | url_request_context_host_.network_delegate().last_error()); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | EXPECT_EQ(1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::CONNECTED))); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | EXPECT_EQ( | 
|  | 1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_CONNECTED))); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateTest, HandshakeInfo) { | 
|  | static const char kResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "foo: bar, baz\r\n" | 
|  | "hoge: fuga\r\n" | 
|  | "hoge: piyo\r\n" | 
|  | "\r\n"; | 
|  |  | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kResponse); | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(stream_); | 
|  | ASSERT_TRUE(request_info_); | 
|  | ASSERT_TRUE(response_info_); | 
|  | std::vector<HeaderKeyValuePair> request_headers = | 
|  | RequestHeadersToVector(request_info_->headers); | 
|  | // We examine the contents of request_info_ and response_info_ | 
|  | // mainly only in this test case. | 
|  | EXPECT_EQ(GURL("ws://www.example.org/"), request_info_->url); | 
|  | EXPECT_EQ(GURL("ws://www.example.org/"), response_info_->url); | 
|  | EXPECT_EQ(101, response_info_->headers->response_code()); | 
|  | EXPECT_EQ("Switching Protocols", response_info_->headers->GetStatusText()); | 
|  | ASSERT_EQ(12u, request_headers.size()); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Host", "www.example.org"), request_headers[0]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Pragma", "no-cache"), request_headers[2]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Cache-Control", "no-cache"), | 
|  | request_headers[3]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[4]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Origin", "http://www.example.org"), | 
|  | request_headers[5]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Version", "13"), | 
|  | request_headers[6]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("User-Agent", ""), request_headers[7]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Accept-Encoding", "gzip, deflate"), | 
|  | request_headers[8]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"), | 
|  | request_headers[9]); | 
|  | EXPECT_EQ("Sec-WebSocket-Key",  request_headers[10].first); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions", | 
|  | "permessage-deflate; client_max_window_bits"), | 
|  | request_headers[11]); | 
|  |  | 
|  | std::vector<HeaderKeyValuePair> response_headers = | 
|  | ResponseHeadersToVector(*response_info_->headers.get()); | 
|  | ASSERT_EQ(6u, response_headers.size()); | 
|  | // Sort the headers for ease of verification. | 
|  | std::sort(response_headers.begin(), response_headers.end()); | 
|  |  | 
|  | EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), response_headers[0]); | 
|  | EXPECT_EQ("Sec-WebSocket-Accept", response_headers[1].first); | 
|  | EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), response_headers[2]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("foo", "bar, baz"), response_headers[3]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("hoge", "fuga"), response_headers[4]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("hoge", "piyo"), response_headers[5]); | 
|  | } | 
|  |  | 
|  | // Confirms that request headers are overriden/added after handshake | 
|  | TEST_P(WebSocketStreamCreateTest, HandshakeOverrideHeaders) { | 
|  | WebSocketExtraHeaders additional_headers( | 
|  | {{"User-Agent", "OveRrIde"}, {"rAnDomHeader", "foobar"}}); | 
|  | CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), | 
|  | additional_headers, additional_headers, {}); | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | EXPECT_TRUE(request_info_); | 
|  | EXPECT_TRUE(response_info_); | 
|  |  | 
|  | std::vector<HeaderKeyValuePair> request_headers = | 
|  | RequestHeadersToVector(request_info_->headers); | 
|  | EXPECT_EQ(HeaderKeyValuePair("User-Agent", "OveRrIde"), request_headers[4]); | 
|  | EXPECT_EQ(HeaderKeyValuePair("rAnDomHeader", "foobar"), request_headers[5]); | 
|  | } | 
|  |  | 
|  | // Confirm that the stream isn't established until the message loop runs. | 
|  | TEST_P(WebSocketStreamCreateTest, NeedsToRunLoop) { | 
|  | CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | } | 
|  |  | 
|  | // Check the path is used. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, PathIsUsed) { | 
|  | AddSSLData(); | 
|  | CreateAndConnectStandard("wss://www.example.org/testing_path", | 
|  | NoSubProtocols(), {}, {}, {}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | } | 
|  |  | 
|  | // Check that sub-protocols are sent and parsed. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, SubProtocolIsUsed) { | 
|  | AddSSLData(); | 
|  | std::vector<std::string> sub_protocols; | 
|  | sub_protocols.push_back("chatv11.chromium.org"); | 
|  | sub_protocols.push_back("chatv20.chromium.org"); | 
|  | CreateAndConnectStandard( | 
|  | "wss://www.example.org/testing_path", sub_protocols, {}, | 
|  | {{"Sec-WebSocket-Protocol", | 
|  | "chatv11.chromium.org, chatv20.chromium.org"}}, | 
|  | {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}}); | 
|  | WaitUntilConnectDone(); | 
|  | ASSERT_TRUE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_EQ("chatv20.chromium.org", stream_->GetSubProtocol()); | 
|  | } | 
|  |  | 
|  | // Unsolicited sub-protocols are rejected. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, UnsolicitedSubProtocol) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | AddSSLData(); | 
|  | CreateAndConnectStandard( | 
|  | "wss://www.example.org/testing_path", NoSubProtocols(), {}, {}, | 
|  | {{"Sec-WebSocket-Protocol", "chatv20.chromium.org"}}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "Response must not include 'Sec-WebSocket-Protocol' header " | 
|  | "if not present in request: chatv20.chromium.org", | 
|  | failure_message()); | 
|  | EXPECT_EQ(ERR_INVALID_RESPONSE, | 
|  | url_request_context_host_.network_delegate().last_error()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | EXPECT_EQ( | 
|  | 1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED_SUBPROTO))); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | EXPECT_EQ(1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult:: | 
|  | HTTP2_FAILED_SUBPROTO))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Missing sub-protocol response is rejected. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, UnacceptedSubProtocol) { | 
|  | AddSSLData(); | 
|  | std::vector<std::string> sub_protocols; | 
|  | sub_protocols.push_back("chat.example.com"); | 
|  | CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols, | 
|  | {}, {{"Sec-WebSocket-Protocol", "chat.example.com"}}, | 
|  | {}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "Sent non-empty 'Sec-WebSocket-Protocol' header " | 
|  | "but no response was received", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Only one sub-protocol can be accepted. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, MultipleSubProtocolsInResponse) { | 
|  | AddSSLData(); | 
|  | std::vector<std::string> sub_protocols; | 
|  | sub_protocols.push_back("chatv11.chromium.org"); | 
|  | sub_protocols.push_back("chatv20.chromium.org"); | 
|  | CreateAndConnectStandard("wss://www.example.org/testing_path", sub_protocols, | 
|  | {}, | 
|  | {{"Sec-WebSocket-Protocol", | 
|  | "chatv11.chromium.org, chatv20.chromium.org"}}, | 
|  | {{"Sec-WebSocket-Protocol", | 
|  | "chatv11.chromium.org, chatv20.chromium.org"}}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ( | 
|  | "Error during WebSocket handshake: " | 
|  | "'Sec-WebSocket-Protocol' header must not appear " | 
|  | "more than once in a response", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Unmatched sub-protocol should be rejected. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, UnmatchedSubProtocolInResponse) { | 
|  | AddSSLData(); | 
|  | std::vector<std::string> sub_protocols; | 
|  | sub_protocols.push_back("chatv11.chromium.org"); | 
|  | sub_protocols.push_back("chatv20.chromium.org"); | 
|  | CreateAndConnectStandard( | 
|  | "wss://www.example.org/testing_path", sub_protocols, {}, | 
|  | {{"Sec-WebSocket-Protocol", | 
|  | "chatv11.chromium.org, chatv20.chromium.org"}}, | 
|  | {{"Sec-WebSocket-Protocol", "chatv21.chromium.org"}}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Sec-WebSocket-Protocol' header value 'chatv21.chromium.org' " | 
|  | "in response does not match any of sent values", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // permessage-deflate extension basic success case. | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) { | 
|  | CreateAndConnectWithExtensions("permessage-deflate"); | 
|  | EXPECT_TRUE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | } | 
|  |  | 
|  | // permessage-deflate extensions success with all parameters. | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) { | 
|  | CreateAndConnectWithExtensions( | 
|  | "permessage-deflate; client_no_context_takeover; " | 
|  | "server_max_window_bits=11; client_max_window_bits=13; " | 
|  | "server_no_context_takeover"); | 
|  | EXPECT_TRUE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | } | 
|  |  | 
|  | // Verify that incoming messages are actually decompressed with | 
|  | // permessage-deflate enabled. | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) { | 
|  | AddSSLData(); | 
|  | SetAdditionalResponseData(std::string( | 
|  | "\xc1\x07"  // WebSocket header (FIN + RSV1, Text payload 7 bytes) | 
|  | "\xf2\x48\xcd\xc9\xc9\x07\x00",  // "Hello" DEFLATE compressed | 
|  | 9)); | 
|  | CreateAndConnectStandard( | 
|  | "wss://www.example.org/testing_path", NoSubProtocols(), {}, {}, | 
|  | {{"Sec-WebSocket-Extensions", "permessage-deflate"}}); | 
|  | WaitUntilConnectDone(); | 
|  |  | 
|  | ASSERT_TRUE(stream_); | 
|  | std::vector<std::unique_ptr<WebSocketFrame>> frames; | 
|  | TestCompletionCallback callback; | 
|  | int rv = stream_->ReadFrames(&frames, callback.callback()); | 
|  | rv = callback.GetResult(rv); | 
|  | ASSERT_THAT(rv, IsOk()); | 
|  | ASSERT_EQ(1U, frames.size()); | 
|  | ASSERT_EQ(5U, frames[0]->header.payload_length); | 
|  | EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5)); | 
|  | } | 
|  |  | 
|  | // Unknown extension in the response is rejected | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, UnknownExtension) { | 
|  | CreateAndConnectWithExtensions("x-unknown-extension"); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "Found an unsupported extension 'x-unknown-extension' " | 
|  | "in 'Sec-WebSocket-Extensions' header", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Malformed extensions are rejected (this file does not cover all possible | 
|  | // parse failures, as the parser is covered thoroughly by its own unit tests). | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, MalformedExtension) { | 
|  | CreateAndConnectWithExtensions(";"); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ( | 
|  | "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header " | 
|  | "value is rejected by the parser: ;", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // The permessage-deflate extension may only be specified once. | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | CreateAndConnectWithExtensions( | 
|  | "permessage-deflate, permessage-deflate; client_max_window_bits=10"); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ( | 
|  | "Error during WebSocket handshake: " | 
|  | "Received duplicate permessage-deflate response", | 
|  | failure_message()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | EXPECT_EQ( | 
|  | 1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED_EXTENSIONS))); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | EXPECT_EQ(1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult:: | 
|  | HTTP2_FAILED_EXTENSIONS))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // client_max_window_bits must have an argument | 
|  | TEST_P(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) { | 
|  | CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits"); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ( | 
|  | "Error during WebSocket handshake: Error in permessage-deflate: " | 
|  | "client_max_window_bits must have value", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Other cases for permessage-deflate parameters are tested in | 
|  | // websocket_deflate_parameters_test.cc. | 
|  |  | 
|  | // TODO(ricea): Check that WebSocketDeflateStream is initialised with the | 
|  | // arguments from the server. This is difficult because the data written to the | 
|  | // socket is randomly masked. | 
|  |  | 
|  | // Additional Sec-WebSocket-Accept headers should be rejected. | 
|  | TEST_P(WebSocketStreamCreateTest, DoubleAccept) { | 
|  | CreateAndConnectStandard( | 
|  | "ws://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {{"Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Sec-WebSocket-Accept' header must not appear " | 
|  | "more than once in a response", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // When upgrading an HTTP/1 connection, response code 200 is invalid and must be | 
|  | // rejected.  Response code 101 means success.  On the other hand, when | 
|  | // requesting a WebSocket stream over HTTP/2, response code 101 is invalid and | 
|  | // must be rejected.  Response code 200 means success. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, InvalidStatusCode) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | AddSSLData(); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | static const char kInvalidStatusCodeResponse[] = | 
|  | "HTTP/1.1 200 OK\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(), | 
|  | {}, {}, kInvalidStatusCodeResponse); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | SetHttp2ResponseStatus("101"); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | } | 
|  |  | 
|  | WaitUntilConnectDone(); | 
|  | stream_request_.reset(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  |  | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 200", | 
|  | failure_message()); | 
|  | EXPECT_EQ( | 
|  | 1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::INVALID_STATUS))); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 101", | 
|  | failure_message()); | 
|  | EXPECT_EQ(1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult:: | 
|  | HTTP2_INVALID_STATUS))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Redirects are not followed (according to the WHATWG WebSocket API, which | 
|  | // overrides RFC6455 for browser applications). | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, RedirectsRejected) { | 
|  | AddSSLData(); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | static const char kRedirectResponse[] = | 
|  | "HTTP/1.1 302 Moved Temporarily\r\n" | 
|  | "Content-Type: text/html\r\n" | 
|  | "Content-Length: 34\r\n" | 
|  | "Connection: keep-alive\r\n" | 
|  | "Location: wss://www.example.org/other\r\n" | 
|  | "\r\n" | 
|  | "<title>Moved</title><h1>Moved</h1>"; | 
|  | CreateAndConnectCustomResponse("wss://www.example.org/", NoSubProtocols(), | 
|  | {}, {}, kRedirectResponse); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | SetHttp2ResponseStatus("302"); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | } | 
|  | WaitUntilConnectDone(); | 
|  |  | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 302", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Malformed responses should be rejected. HttpStreamParser will accept just | 
|  | // about any garbage in the middle of the headers. To make it give up, the junk | 
|  | // has to be at the start of the response. Even then, it just gets treated as an | 
|  | // HTTP/0.9 response. | 
|  | TEST_P(WebSocketStreamCreateTest, MalformedResponse) { | 
|  | static const char kMalformedResponse[] = | 
|  | "220 mx.google.com ESMTP\r\n" | 
|  | "HTTP/1.1 101 OK\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMalformedResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: Invalid status line", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Upgrade header must be present. | 
|  | TEST_P(WebSocketStreamCreateTest, MissingUpgradeHeader) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | static const char kMissingUpgradeResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMissingUpgradeResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: 'Upgrade' header is missing", | 
|  | failure_message()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ( | 
|  | 1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED_UPGRADE))); | 
|  | } | 
|  |  | 
|  | // There must only be one upgrade header. | 
|  | TEST_P(WebSocketStreamCreateTest, DoubleUpgradeHeader) { | 
|  | CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {{"Upgrade", "HTTP/2.0"}}); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Upgrade' header must not appear more than once in a response", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // There must only be one correct upgrade header. | 
|  | TEST_P(WebSocketStreamCreateTest, IncorrectUpgradeHeader) { | 
|  | static const char kMissingUpgradeResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "Upgrade: hogefuga\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMissingUpgradeResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Upgrade' header value is not 'WebSocket': hogefuga", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Connection header must be present. | 
|  | TEST_P(WebSocketStreamCreateTest, MissingConnectionHeader) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | static const char kMissingConnectionResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMissingConnectionResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Connection' header is missing", | 
|  | failure_message()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ( | 
|  | 1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED_CONNECTION))); | 
|  | } | 
|  |  | 
|  | // Connection header must contain "Upgrade". | 
|  | TEST_P(WebSocketStreamCreateTest, IncorrectConnectionHeader) { | 
|  | static const char kMissingConnectionResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "Connection: hogefuga\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMissingConnectionResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Connection' header value must contain 'Upgrade'", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Connection header is permitted to contain other tokens. | 
|  | TEST_P(WebSocketStreamCreateTest, AdditionalTokenInConnectionHeader) { | 
|  | static const char kAdditionalConnectionTokenResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade, Keep-Alive\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kAdditionalConnectionTokenResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | } | 
|  |  | 
|  | // Sec-WebSocket-Accept header must be present. | 
|  | TEST_P(WebSocketStreamCreateTest, MissingSecWebSocketAccept) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | static const char kMissingAcceptResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kMissingAcceptResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "'Sec-WebSocket-Accept' header is missing", | 
|  | failure_message()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ(1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED_ACCEPT))); | 
|  | } | 
|  |  | 
|  | // Sec-WebSocket-Accept header must match the key that was sent. | 
|  | TEST_P(WebSocketStreamCreateTest, WrongSecWebSocketAccept) { | 
|  | static const char kIncorrectAcceptResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: x/byyPZ2tOFvJCGkkugcKvqhhPk=\r\n" | 
|  | "\r\n"; | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kIncorrectAcceptResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error during WebSocket handshake: " | 
|  | "Incorrect 'Sec-WebSocket-Accept' header value", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // Cancellation works. | 
|  | TEST_P(WebSocketStreamCreateTest, Cancellation) { | 
|  | CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | stream_request_.reset(); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | } | 
|  |  | 
|  | // Connect failure must look just like negotiation failure. | 
|  | TEST_P(WebSocketStreamCreateTest, ConnectionFailure) { | 
|  | std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData()); | 
|  | socket_data->set_connect_data( | 
|  | MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | 
|  | failure_message()); | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | } | 
|  |  | 
|  | // Connect timeout must look just like any other failure. | 
|  | TEST_P(WebSocketStreamCreateTest, ConnectionTimeout) { | 
|  | std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData()); | 
|  | socket_data->set_connect_data( | 
|  | MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_TIMED_OUT", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | // The server doesn't respond to the opening handshake. | 
|  | TEST_P(WebSocketStreamCreateTest, HandshakeTimeout) { | 
|  | std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData()); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | 
|  | auto timer = std::make_unique<MockWeakTimer>(); | 
|  | base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | 
|  | SetTimer(std::move(timer)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | ASSERT_TRUE(weak_timer.get()); | 
|  | EXPECT_TRUE(weak_timer->IsRunning()); | 
|  |  | 
|  | weak_timer->Fire(); | 
|  | WaitUntilConnectDone(); | 
|  |  | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("WebSocket opening handshake timed out", failure_message()); | 
|  | ASSERT_TRUE(weak_timer.get()); | 
|  | EXPECT_FALSE(weak_timer->IsRunning()); | 
|  | } | 
|  |  | 
|  | // When the connection establishes the timer should be stopped. | 
|  | TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnSuccess) { | 
|  | auto timer = std::make_unique<MockWeakTimer>(); | 
|  | base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | 
|  |  | 
|  | SetTimer(std::move(timer)); | 
|  | CreateAndConnectStandard("ws://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | ASSERT_TRUE(weak_timer); | 
|  | EXPECT_TRUE(weak_timer->IsRunning()); | 
|  |  | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | ASSERT_TRUE(weak_timer); | 
|  | EXPECT_FALSE(weak_timer->IsRunning()); | 
|  | } | 
|  |  | 
|  | // When the connection fails the timer should be stopped. | 
|  | TEST_P(WebSocketStreamCreateTest, HandshakeTimerOnFailure) { | 
|  | std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData()); | 
|  | socket_data->set_connect_data( | 
|  | MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); | 
|  | auto timer = std::make_unique<MockWeakTimer>(); | 
|  | base::WeakPtr<MockWeakTimer> weak_timer = timer->AsWeakPtr(); | 
|  | SetTimer(std::move(timer)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | ASSERT_TRUE(weak_timer.get()); | 
|  | EXPECT_TRUE(weak_timer->IsRunning()); | 
|  |  | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Error in connection establishment: net::ERR_CONNECTION_REFUSED", | 
|  | failure_message()); | 
|  | ASSERT_TRUE(weak_timer.get()); | 
|  | EXPECT_FALSE(weak_timer->IsRunning()); | 
|  | } | 
|  |  | 
|  | // Cancellation during connect works. | 
|  | TEST_P(WebSocketStreamCreateTest, CancellationDuringConnect) { | 
|  | std::unique_ptr<SequencedSocketData> socket_data(BuildNullSocketData()); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | stream_request_.reset(); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | } | 
|  |  | 
|  | // Cancellation during write of the request headers works. | 
|  | TEST_P(WebSocketStreamCreateTest, CancellationDuringWrite) { | 
|  | // First write never completes. | 
|  | MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)}; | 
|  | SequencedSocketData* socket_data( | 
|  | new SequencedSocketData(base::span<MockRead>(), writes)); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), | 
|  | base::WrapUnique(socket_data)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_TRUE(socket_data->AllWriteDataConsumed()); | 
|  | stream_request_.reset(); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | } | 
|  |  | 
|  | // Cancellation during read of the response headers works. | 
|  | TEST_P(WebSocketStreamCreateTest, CancellationDuringRead) { | 
|  | std::string request = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | MockWrite writes[] = {MockWrite(ASYNC, 0, request.c_str())}; | 
|  | MockRead reads[] = { | 
|  | MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1), | 
|  | }; | 
|  | std::unique_ptr<SequencedSocketData> socket_data( | 
|  | BuildSocketData(reads, writes)); | 
|  | SequencedSocketData* socket_data_raw_ptr = socket_data.get(); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed()); | 
|  | stream_request_.reset(); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | } | 
|  |  | 
|  | // Over-size response headers (> 256KB) should not cause a crash.  This is a | 
|  | // regression test for crbug.com/339456. It is based on the layout test | 
|  | // "cookie-flood.html". | 
|  | TEST_P(WebSocketStreamCreateTest, VeryLargeResponseHeaders) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | std::string set_cookie_headers; | 
|  | set_cookie_headers.reserve(24 * 20000); | 
|  | for (int i = 0; i < 20000; ++i) { | 
|  | set_cookie_headers += base::StringPrintf("Set-Cookie: ws-%d=1\r\n", i); | 
|  | } | 
|  | ASSERT_GT(set_cookie_headers.size(), 256U * 1024U); | 
|  | CreateAndConnectStringResponse("ws://www.example.org/", NoSubProtocols(), | 
|  | set_cookie_headers); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_FALSE(response_info_); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ(1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::FAILED))); | 
|  | } | 
|  |  | 
|  | // If the remote host closes the connection without sending headers, we should | 
|  | // log the console message "Connection closed before receiving a handshake | 
|  | // response". | 
|  | TEST_P(WebSocketStreamCreateTest, NoResponse) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | std::string request = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)}; | 
|  | MockRead reads[] = {MockRead(ASYNC, 0, 1)}; | 
|  | std::unique_ptr<SequencedSocketData> socket_data( | 
|  | BuildSocketData(reads, writes)); | 
|  | SequencedSocketData* socket_data_raw_ptr = socket_data.get(); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_TRUE(socket_data_raw_ptr->AllReadDataConsumed()); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | EXPECT_EQ("Connection closed before receiving a handshake response", | 
|  | failure_message()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ( | 
|  | 1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::EMPTY_RESPONSE))); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateFailure) { | 
|  | auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>( | 
|  | ASYNC, ERR_CERT_AUTHORITY_INVALID); | 
|  | ssl_socket_data->ssl_info.cert = | 
|  | ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | 
|  | ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get()); | 
|  | url_request_context_host_.AddSSLSocketDataProvider( | 
|  | std::move(ssl_socket_data)); | 
|  | std::unique_ptr<SequencedSocketData> raw_socket_data(BuildNullSocketData()); | 
|  | CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), | 
|  | std::move(raw_socket_data)); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | ASSERT_TRUE(ssl_error_callbacks_); | 
|  | ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID, | 
|  | &ssl_info_); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) { | 
|  | auto ssl_socket_data = std::make_unique<SSLSocketDataProvider>( | 
|  | ASYNC, ERR_CERT_AUTHORITY_INVALID); | 
|  | ssl_socket_data->ssl_info.cert = | 
|  | ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der"); | 
|  | ASSERT_TRUE(ssl_socket_data->ssl_info.cert.get()); | 
|  | url_request_context_host_.AddSSLSocketDataProvider( | 
|  | std::move(ssl_socket_data)); | 
|  | url_request_context_host_.AddSSLSocketDataProvider( | 
|  | std::make_unique<SSLSocketDataProvider>(ASYNC, OK)); | 
|  | AddRawExpectations(BuildNullSocketData()); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | // WaitUntilConnectDone doesn't work in this case. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | ASSERT_TRUE(ssl_error_callbacks_); | 
|  | ssl_error_callbacks_->ContinueSSLRequest(); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | } | 
|  |  | 
|  | // If the server requests authorisation, but we have no credentials, the | 
|  | // connection should fail cleanly. | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) { | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kUnauthorizedResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("HTTP Authentication failed; no valid credentials available", | 
|  | failure_message()); | 
|  | EXPECT_TRUE(response_info_); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) { | 
|  | CreateAndConnectAuthHandshake("ws://foo:bar@www.example.org/", "Zm9vOmJhcg==", | 
|  | WebSocketStandardResponse(std::string())); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | ASSERT_TRUE(response_info_); | 
|  | EXPECT_EQ(101, response_info_->headers->response_code()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) { | 
|  | CreateAndConnectAuthHandshake("ws://foo:baz@www.example.org/", | 
|  | "Zm9vOmJheg==", kUnauthorizedResponse); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_TRUE(response_info_); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessfulConnectionReuse) { | 
|  | base::test::ScopedFeatureList scoped_feature_list; | 
|  | scoped_feature_list.InitAndEnableFeature( | 
|  | WebSocketBasicHandshakeStream ::kWebSocketHandshakeReuseConnection); | 
|  |  | 
|  | std::string request1 = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | std::string response1 = kUnauthorizedResponse; | 
|  | std::string request2 = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), | 
|  | "Authorization: Basic Zm9vOmJhcg==\r\n", ""); | 
|  | std::string response2 = WebSocketStandardResponse(std::string()); | 
|  | MockWrite writes[] = { | 
|  | MockWrite(SYNCHRONOUS, 0, request1.c_str()), | 
|  | MockWrite(SYNCHRONOUS, 2, request2.c_str()), | 
|  | }; | 
|  | MockRead reads[3] = { | 
|  | MockRead(SYNCHRONOUS, 1, response1.c_str()), | 
|  | MockRead(SYNCHRONOUS, 3, response2.c_str()), | 
|  | MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), | 
|  | }; | 
|  | CreateAndConnectRawExpectations("ws://foo:bar@www.example.org/", | 
|  | NoSubProtocols(), HttpRequestHeaders(), | 
|  | BuildSocketData(reads, writes)); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | ASSERT_TRUE(response_info_); | 
|  | EXPECT_EQ(101, response_info_->headers->response_code()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredCancelAuth) { | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kUnauthorizedResponse); | 
|  |  | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | on_auth_required_rv_ = ERR_IO_PENDING; | 
|  | WaitUntilOnAuthRequired(); | 
|  |  | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  |  | 
|  | std::move(on_auth_required_callback_).Run(nullptr); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateBasicAuthTest, OnAuthRequiredSetAuth) { | 
|  | CreateAndConnectCustomResponse("ws://www.example.org/", NoSubProtocols(), {}, | 
|  | {}, kUnauthorizedResponse); | 
|  |  | 
|  | EXPECT_FALSE(request_info_); | 
|  | EXPECT_FALSE(response_info_); | 
|  | on_auth_required_rv_ = ERR_IO_PENDING; | 
|  | WaitUntilOnAuthRequired(); | 
|  |  | 
|  | EXPECT_FALSE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  |  | 
|  | AuthCredentials credentials(base::ASCIIToUTF16("foo"), | 
|  | base::ASCIIToUTF16("baz")); | 
|  | std::move(on_auth_required_callback_).Run(&credentials); | 
|  |  | 
|  | // As we are re-establishing the connection with additional credentials, | 
|  | // add new expectations. | 
|  | AddRawExpectations( | 
|  | helper_.BuildSocketData2(RequestExpectation("Zm9vOmJheg=="), | 
|  | WebSocketStandardResponse(std::string()))); | 
|  |  | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(stream_); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | } | 
|  |  | 
|  | // Digest auth has the same connection semantics as Basic auth, so we can | 
|  | // generally assume that whatever works for Basic auth will also work for | 
|  | // Digest. There's just one test here, to confirm that it works at all. | 
|  | TEST_P(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) { | 
|  | AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse)); | 
|  |  | 
|  | CreateAndConnectRawExpectations( | 
|  | "ws://FooBar:pass@www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), | 
|  | helper_.BuildSocketData2(kAuthorizedRequest, | 
|  | WebSocketStandardResponse(std::string()))); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_FALSE(has_failed()); | 
|  | EXPECT_TRUE(stream_); | 
|  | ASSERT_TRUE(response_info_); | 
|  | EXPECT_EQ(101, response_info_->headers->response_code()); | 
|  | } | 
|  |  | 
|  | // TODO: User Metrics Actions (UMA) might not be supported in Starboard. Verify | 
|  | // that this test does not need that. | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, Incomplete) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | AddSSLData(); | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | std::string request = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | MockRead reads[] = {MockRead(ASYNC, ERR_IO_PENDING, 0)}; | 
|  | MockWrite writes[] = {MockWrite(ASYNC, 1, request.c_str())}; | 
|  | CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), | 
|  | BuildSocketData(reads, writes)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ(1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::INCOMPLETE))); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ( | 
|  | 1, | 
|  | samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_INCOMPLETE))); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketMultiProtocolStreamCreateTest, Http2StreamReset) { | 
|  | AddSSLData(); | 
|  |  | 
|  | if (stream_type_ == BASIC_HANDSHAKE_STREAM) { | 
|  | // This is a dummy transaction to avoid crash in ~TestURLRequestContext(). | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | } else { | 
|  | DCHECK_EQ(stream_type_, HTTP2_HANDSHAKE_STREAM); | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | SetResetWebSocketHttp2Stream(true); | 
|  | CreateAndConnectStandard("wss://www.example.org/", NoSubProtocols(), {}, {}, | 
|  | {}); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | stream_request_.reset(); | 
|  |  | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Stream closed with error: net::ERR_SPDY_PROTOCOL_ERROR", | 
|  | failure_message()); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ( | 
|  | 1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult::HTTP2_FAILED))); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateTest, HandleErrConnectionClosed) { | 
|  | base::HistogramTester histogram_tester; | 
|  |  | 
|  | static const char kTruncatedResponse[] = | 
|  | "HTTP/1.1 101 Switching Protocols\r\n" | 
|  | "Upgrade: websocket\r\n" | 
|  | "Connection: Upgrade\r\n" | 
|  | "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" | 
|  | "Cache-Control: no-sto"; | 
|  |  | 
|  | std::string request = | 
|  | WebSocketStandardRequest("/", "www.example.org", Origin(), "", ""); | 
|  | MockRead reads[] = { | 
|  | MockRead(SYNCHRONOUS, 1, kTruncatedResponse), | 
|  | MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 2), | 
|  | }; | 
|  | MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, request.c_str())}; | 
|  | std::unique_ptr<SequencedSocketData> socket_data( | 
|  | BuildSocketData(reads, writes)); | 
|  | socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  |  | 
|  | stream_request_.reset(); | 
|  |  | 
|  | auto samples = histogram_tester.GetHistogramSamplesSinceCreation( | 
|  | "Net.WebSocket.HandshakeResult2"); | 
|  | EXPECT_EQ(1, samples->TotalCount()); | 
|  | EXPECT_EQ(1, samples->GetCount(static_cast<int>( | 
|  | WebSocketHandshakeStreamBase::HandshakeResult:: | 
|  | FAILED_SWITCHING_PROTOCOLS))); | 
|  | } | 
|  |  | 
|  | TEST_P(WebSocketStreamCreateTest, HandleErrTunnelConnectionFailed) { | 
|  | static const char kConnectRequest[] = | 
|  | "CONNECT www.example.org:80 HTTP/1.1\r\n" | 
|  | "Host: www.example.org:80\r\n" | 
|  | "Proxy-Connection: keep-alive\r\n" | 
|  | "\r\n"; | 
|  |  | 
|  | static const char kProxyResponse[] = | 
|  | "HTTP/1.1 403 Forbidden\r\n" | 
|  | "Content-Type: text/html\r\n" | 
|  | "Content-Length: 9\r\n" | 
|  | "Connection: keep-alive\r\n" | 
|  | "\r\n" | 
|  | "Forbidden"; | 
|  |  | 
|  | MockRead reads[] = {MockRead(SYNCHRONOUS, 1, kProxyResponse)}; | 
|  | MockWrite writes[] = {MockWrite(SYNCHRONOUS, 0, kConnectRequest)}; | 
|  | std::unique_ptr<SequencedSocketData> socket_data( | 
|  | BuildSocketData(reads, writes)); | 
|  | url_request_context_host_.SetProxyConfig("https=proxy:8000"); | 
|  | CreateAndConnectRawExpectations("ws://www.example.org/", NoSubProtocols(), | 
|  | HttpRequestHeaders(), std::move(socket_data)); | 
|  | WaitUntilConnectDone(); | 
|  | EXPECT_TRUE(has_failed()); | 
|  | EXPECT_EQ("Establishing a tunnel via proxy server failed.", | 
|  | failure_message()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace net |