// 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/memory/ptr_util.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/stl_util.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/isolation_info.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 net::SiteForCookies SiteForCookies() {
  return net::SiteForCookies::FromOrigin(Origin());
}

static IsolationInfo CreateIsolationInfo() {
  url::Origin origin = Origin();
  return IsolationInfo::Create(IsolationInfo::RedirectMode::kUpdateNothing,
                               origin, origin,
                               SiteForCookies::FromOrigin(origin));
}

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(), CreateIsolationInfo(),
                             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;
    write_settings[spdy::SETTINGS_MAX_HEADER_LIST_SIZE] =
        kSpdyMaxHeaderListSize;
    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, base::size(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(), CreateIsolationInfo(),
                           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(), CreateIsolationInfo(),
                           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(), CreateIsolationInfo(),
                           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(),
                           CreateIsolationInfo(), 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_SUITE_P(All,
                         WebSocketStreamCreateTest,
                         Values(BASIC_HANDSHAKE_STREAM));

using WebSocketMultiProtocolStreamCreateTest = WebSocketStreamCreateTest;

INSTANTIATE_TEST_SUITE_P(All,
                         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_SUITE_P(All,
                         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() : reads_(), writes_() {}

  std::unique_ptr<SequencedSocketData> BuildAuthSocketData(
      std::string response1,
      std::string request2,
      std::string response2) {
    request1_ =
        WebSocketStandardRequest("/", "www.example.org", Origin(), "", "");
    response1_ = std::move(response1);
    request2_ = std::move(request2);
    response2_ = std::move(response2);
    writes_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str());
    reads_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str());
    writes_[1] = MockWrite(SYNCHRONOUS, 2, request2_.c_str());
    reads_[1] = MockRead(SYNCHRONOUS, 3, response2_.c_str());
    reads_[2] = MockRead(SYNCHRONOUS, OK, 4);  // Close connection

    return BuildSocketData(reads_, writes_);
  }

 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 reads_[3];
  MockWrite writes_[2];

  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) {
    CreateAndConnectRawExpectations(
        url, NoSubProtocols(), HttpRequestHeaders(),
        helper_.BuildAuthSocketData(kUnauthorizedResponse,
                                    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_SUITE_P(All,
                         WebSocketStreamCreateBasicAuthTest,
                         Values(BASIC_HANDSHAKE_STREAM));

class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest {
 protected:
  static const char kUnauthorizedResponse[];
  static const char kAuthorizedRequest[];

  CommonAuthTestHelper helper_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         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(std::string("Hello"),
            std::string(frames[0]->payload, frames[0]->header.payload_length));
}

// 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_FALSE(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_FALSE(response_info_);
}

TEST_P(WebSocketStreamCreateBasicAuthTest, SuccessfulConnectionReuse) {
  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) {
  CreateAndConnectRawExpectations(
      "ws://www.example.org/", NoSubProtocols(), HttpRequestHeaders(),
      helper_.BuildAuthSocketData(kUnauthorizedResponse,
                                  RequestExpectation("Zm9vOmJheg=="),
                                  WebSocketStandardResponse(std::string())));

  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);

  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) {
  CreateAndConnectRawExpectations(
      "ws://FooBar:pass@www.example.org/", NoSubProtocols(),
      HttpRequestHeaders(),
      helper_.BuildAuthSocketData(kUnauthorizedResponse, kAuthorizedRequest,
                                  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(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_HTTP2_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());
}

TEST_P(WebSocketStreamCreateTest, CancelSSLRequestAfterDelete) {
  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));

  MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
  MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
  std::unique_ptr<SequencedSocketData> raw_socket_data(
      BuildSocketData(reads, writes));
  CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
                                  HttpRequestHeaders(),
                                  std::move(raw_socket_data));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(has_failed());
  ASSERT_TRUE(ssl_error_callbacks_);
  stream_request_.reset();
  ssl_error_callbacks_->CancelSSLRequest(ERR_CERT_AUTHORITY_INVALID,
                                         &ssl_info_);
}

TEST_P(WebSocketStreamCreateTest, ContinueSSLRequestAfterDelete) {
  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));

  MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 0)};
  MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1)};
  std::unique_ptr<SequencedSocketData> raw_socket_data(
      BuildSocketData(reads, writes));
  CreateAndConnectRawExpectations("wss://www.example.org/", NoSubProtocols(),
                                  HttpRequestHeaders(),
                                  std::move(raw_socket_data));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(has_failed());
  ASSERT_TRUE(ssl_error_callbacks_);
  stream_request_.reset();
  ssl_error_callbacks_->ContinueSSLRequest();
}

}  // namespace
}  // namespace net
