blob: d5ff88704ad39fe31fd530137166c9d20e512c65 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// End-to-end tests for WebSocket.
//
// A python server is (re)started for each test, which is moderately
// inefficient. However, it makes these tests a good fit for scenarios which
// require special server configurations.
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "net/base/auth.h"
#include "net/base/connection_endpoint_metadata.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/isolation_info.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_delegate.h"
#include "net/base/request_priority.h"
#include "net/base/url_util.h"
#include "net/cookies/site_for_cookies.h"
#include "net/dns/host_resolver.h"
#include "net/dns/mock_host_resolver.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/http/http_request_headers.h"
#include "net/log/net_log.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_bypass_rules.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_config_service.h"
#include "net/proxy_resolution/proxy_config_service_fixed.h"
#include "net/proxy_resolution/proxy_config_with_annotation.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_retry_info.h"
#include "net/ssl/ssl_server_config.h"
#include "net/storage_access_api/status.h"
#include "net/test/embedded_test_server/create_websocket_handler.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
#include "net/test/embedded_test_server/register_basic_auth_handler.h"
#include "net/test/embedded_test_server/websocket_connection.h"
#include "net/test/embedded_test_server/websocket_handler.h"
#include "net/test/ssl_test_util.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_with_task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_test_util.h"
#include "net/websockets/websocket_channel.h"
#include "net/websockets/websocket_event_interface.h"
#include "net/websockets/websocket_handshake_response_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
namespace net {
class HttpResponseHeaders;
class ProxyServer;
class SSLInfo;
struct WebSocketHandshakeRequestInfo;
namespace {
using test_server::BasicHttpResponse;
using test_server::HttpRequest;
using test_server::HttpResponse;
static constexpr char kEchoServer[] = "/echo-with-no-extension";
// Simplify changing URL schemes.
GURL ReplaceUrlScheme(const GURL& in_url, std::string_view scheme) {
GURL::Replacements replacements;
replacements.SetSchemeStr(scheme);
return in_url.ReplaceComponents(replacements);
}
// An implementation of WebSocketEventInterface that waits for and records the
// results of the connect.
class ConnectTestingEventInterface : public WebSocketEventInterface {
public:
ConnectTestingEventInterface();
ConnectTestingEventInterface(const ConnectTestingEventInterface&) = delete;
ConnectTestingEventInterface& operator=(const ConnectTestingEventInterface&) =
delete;
void WaitForResponse();
bool failed() const { return failed_; }
const std::unique_ptr<WebSocketHandshakeResponseInfo>& response() const {
return response_;
}
// Only set if the handshake failed, otherwise empty.
std::string failure_message() const;
std::string selected_subprotocol() const;
std::string extensions() const;
// Implementation of WebSocketEventInterface.
void OnCreateURLRequest(URLRequest* request) override {}
void OnURLRequestConnected(net::URLRequest* request,
const net::TransportInfo& info) override {}
void OnAddChannelResponse(
std::unique_ptr<WebSocketHandshakeResponseInfo> response,
const std::string& selected_subprotocol,
const std::string& extensions) override;
void OnDataFrame(bool fin,
WebSocketMessageType type,
base::span<const char> payload) override;
bool HasPendingDataFrames() override { return false; }
void OnSendDataFrameDone() override;
void OnClosingHandshake() override;
void OnDropChannel(bool was_clean,
uint16_t code,
const std::string& reason) override;
void OnFailChannel(const std::string& message,
int net_error,
std::optional<int> response_code) override;
void OnStartOpeningHandshake(
std::unique_ptr<WebSocketHandshakeRequestInfo> request) override;
void OnSSLCertificateError(
std::unique_ptr<SSLErrorCallbacks> ssl_error_callbacks,
const GURL& url,
int net_error,
const SSLInfo& ssl_info,
bool fatal) override;
int OnAuthRequired(const AuthChallengeInfo& auth_info,
scoped_refptr<HttpResponseHeaders> response_headers,
const IPEndPoint& remote_endpoint,
base::OnceCallback<void(const AuthCredentials*)> callback,
std::optional<AuthCredentials>* credentials) override;
std::string GetDataFramePayload();
void WaitForDropChannel() { drop_channel_future_.Get(); }
private:
void QuitLoop();
void RunNewLoop();
void SetReceivedMessageFuture(std::string received_message);
// failed_ is true if the handshake failed (ie. OnFailChannel was called).
bool failed_ = false;
std::unique_ptr<WebSocketHandshakeResponseInfo> response_;
std::string selected_subprotocol_;
std::string extensions_;
std::string failure_message_;
std::optional<base::RunLoop> run_loop_;
base::test::TestFuture<std::string> received_message_future_;
base::test::TestFuture<void> drop_channel_future_;
};
ConnectTestingEventInterface::ConnectTestingEventInterface() = default;
void ConnectTestingEventInterface::WaitForResponse() {
RunNewLoop();
}
std::string ConnectTestingEventInterface::failure_message() const {
return failure_message_;
}
std::string ConnectTestingEventInterface::selected_subprotocol() const {
return selected_subprotocol_;
}
std::string ConnectTestingEventInterface::extensions() const {
return extensions_;
}
void ConnectTestingEventInterface::OnAddChannelResponse(
std::unique_ptr<WebSocketHandshakeResponseInfo> response,
const std::string& selected_subprotocol,
const std::string& extensions) {
response_ = std::move(response);
selected_subprotocol_ = selected_subprotocol;
extensions_ = extensions;
QuitLoop();
}
void ConnectTestingEventInterface::OnDataFrame(bool fin,
WebSocketMessageType type,
base::span<const char> payload) {
DVLOG(3) << "Received WebSocket data frame with message:"
<< std::string(payload.begin(), payload.end());
SetReceivedMessageFuture(std::string(base::as_string_view(payload)));
}
void ConnectTestingEventInterface::OnSendDataFrameDone() {}
void ConnectTestingEventInterface::OnClosingHandshake() {
DVLOG(3) << "OnClosingHandeshake() invoked.";
}
void ConnectTestingEventInterface::OnDropChannel(bool was_clean,
uint16_t code,
const std::string& reason) {
DVLOG(3) << "OnDropChannel() invoked, was_clean: " << was_clean
<< ", code: " << code << ", reason: " << reason;
if (was_clean) {
drop_channel_future_.SetValue();
} else {
DVLOG(1) << "OnDropChannel() did not receive a clean close.";
}
}
void ConnectTestingEventInterface::OnFailChannel(
const std::string& message,
int net_error,
std::optional<int> response_code) {
DVLOG(3) << "OnFailChannel invoked with message: " << message;
failed_ = true;
failure_message_ = message;
QuitLoop();
}
void ConnectTestingEventInterface::OnStartOpeningHandshake(
std::unique_ptr<WebSocketHandshakeRequestInfo> request) {}
void ConnectTestingEventInterface::OnSSLCertificateError(
std::unique_ptr<SSLErrorCallbacks> ssl_error_callbacks,
const GURL& url,
int net_error,
const SSLInfo& ssl_info,
bool fatal) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&SSLErrorCallbacks::CancelSSLRequest,
base::Owned(ssl_error_callbacks.release()),
ERR_SSL_PROTOCOL_ERROR, &ssl_info));
}
int ConnectTestingEventInterface::OnAuthRequired(
const AuthChallengeInfo& auth_info,
scoped_refptr<HttpResponseHeaders> response_headers,
const IPEndPoint& remote_endpoint,
base::OnceCallback<void(const AuthCredentials*)> callback,
std::optional<AuthCredentials>* credentials) {
*credentials = std::nullopt;
return OK;
}
void ConnectTestingEventInterface::QuitLoop() {
if (!run_loop_) {
DVLOG(3) << "No active run loop to quit.";
return;
}
run_loop_->Quit();
}
void ConnectTestingEventInterface::RunNewLoop() {
run_loop_.emplace();
run_loop_->Run();
}
void ConnectTestingEventInterface::SetReceivedMessageFuture(
std::string received_message) {
received_message_future_.SetValue(received_message);
}
std::string ConnectTestingEventInterface::GetDataFramePayload() {
return received_message_future_.Get();
}
// A subclass of TestNetworkDelegate that additionally implements the
// OnResolveProxy callback and records the information passed to it.
class TestProxyDelegateWithProxyInfo : public ProxyDelegate {
public:
TestProxyDelegateWithProxyInfo() = default;
TestProxyDelegateWithProxyInfo(const TestProxyDelegateWithProxyInfo&) =
delete;
TestProxyDelegateWithProxyInfo& operator=(
const TestProxyDelegateWithProxyInfo&) = delete;
struct ResolvedProxyInfo {
GURL url;
ProxyInfo proxy_info;
};
const ResolvedProxyInfo& resolved_proxy_info() const {
return resolved_proxy_info_;
}
protected:
void OnResolveProxy(const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
const std::string& method,
const ProxyRetryInfoMap& proxy_retry_info,
ProxyInfo* result) override {
resolved_proxy_info_.url = url;
resolved_proxy_info_.proxy_info = *result;
}
void OnSuccessfulRequestAfterFailures(
const ProxyRetryInfoMap& proxy_retry_info) override {}
void OnFallback(const ProxyChain& bad_chain, int net_error) override {}
base::expected<HttpRequestHeaders, Error> OnBeforeTunnelRequest(
const ProxyChain& proxy_chain,
size_t chain_index) override {
return HttpRequestHeaders();
}
Error OnTunnelHeadersReceived(
const ProxyChain& proxy_chain,
size_t chain_index,
const HttpResponseHeaders& response_headers) override {
return OK;
}
void SetProxyResolutionService(
ProxyResolutionService* proxy_resolution_service) override {}
bool AliasRequiresProxyOverride(
const std::string scheme,
const std::vector<std::string>& dns_aliases,
const net::NetworkAnonymizationKey& network_anonymization_key) override {
return false;
}
private:
ResolvedProxyInfo resolved_proxy_info_;
};
class WebSocketEndToEndTest : public TestWithTaskEnvironment {
protected:
WebSocketEndToEndTest()
: proxy_delegate_(std::make_unique<TestProxyDelegateWithProxyInfo>()),
context_builder_(CreateTestURLRequestContextBuilder()) {}
// Initialise the URLRequestContext. Normally done automatically by
// ConnectAndWait(). This method is for the use of tests that need the
// URLRequestContext initialised before calling ConnectAndWait().
void InitialiseContext() {
DCHECK(!context_);
context_ = context_builder_->Build();
context_->proxy_resolution_service()->SetProxyDelegate(
proxy_delegate_.get());
}
// Send the connect request to |socket_url| and wait for a response. Returns
// true if the handshake succeeded.
bool ConnectAndWait(const GURL& socket_url) {
if (!context_) {
InitialiseContext();
}
url::Origin origin = url::Origin::Create(GURL("http://localhost"));
net::SiteForCookies site_for_cookies =
net::SiteForCookies::FromOrigin(origin);
IsolationInfo isolation_info =
IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
origin, SiteForCookies::FromOrigin(origin));
auto event_interface = std::make_unique<ConnectTestingEventInterface>();
event_interface_ = event_interface.get();
channel_ = std::make_unique<WebSocketChannel>(std::move(event_interface),
context_.get());
channel_->SendAddChannelRequest(
GURL(socket_url), sub_protocols_, origin, site_for_cookies,
StorageAccessApiStatus::kNone, isolation_info, HttpRequestHeaders(),
TRAFFIC_ANNOTATION_FOR_TESTS);
event_interface_->WaitForResponse();
return !event_interface_->failed();
}
[[nodiscard]] WebSocketChannel::ChannelState SendMessage(
const std::string& message) {
scoped_refptr<IOBufferWithSize> buffer =
base::MakeRefCounted<IOBufferWithSize>(message.size());
buffer->span().copy_from(base::as_byte_span(message));
return channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, buffer,
message.size());
}
std::string ReceiveMessage() {
auto channel_state = channel_->ReadFrames();
if (channel_state != WebSocketChannel::ChannelState::CHANNEL_ALIVE) {
ADD_FAILURE()
<< "WebSocket channel is no longer alive after reading frames. State:"
<< channel_state;
return {};
}
return event_interface_->GetDataFramePayload();
}
void CloseWebSocket() {
const uint16_t close_code = 1000;
const std::string close_reason = "Closing connection";
DVLOG(3) << "Sending close handshake with code: " << close_code
<< " and reason: " << close_reason;
auto channel_state =
channel_->StartClosingHandshake(close_code, close_reason);
EXPECT_EQ(channel_state, WebSocketChannel::ChannelState::CHANNEL_ALIVE)
<< "WebSocket channel is no longer alive after sending the "
"Close frame. State: "
<< channel_state;
}
void CloseWebSocketSuccessfully() {
CloseWebSocket();
event_interface_->WaitForDropChannel();
}
void RunBasicSmokeTest(net::EmbeddedTestServer::Type server_type) {
test_server::EmbeddedTestServer embedded_test_server(server_type);
test_server::InstallDefaultWebSocketHandlers(&embedded_test_server);
ASSERT_TRUE(embedded_test_server.Start());
GURL echo_url = test_server::ToWebSocketUrl(
embedded_test_server.GetURL("/echo-with-no-extension"));
EXPECT_TRUE(ConnectAndWait(echo_url));
}
raw_ptr<ConnectTestingEventInterface, DanglingUntriaged>
event_interface_; // owned by channel_
std::unique_ptr<TestProxyDelegateWithProxyInfo> proxy_delegate_;
std::unique_ptr<URLRequestContextBuilder> context_builder_;
std::unique_ptr<URLRequestContext> context_;
std::unique_ptr<WebSocketChannel> channel_;
std::vector<std::string> sub_protocols_;
};
// Basic test of connectivity. If this test fails, nothing else can be expected
// to work.
TEST_F(WebSocketEndToEndTest, BasicSmokeTest) {
RunBasicSmokeTest(net::EmbeddedTestServer::TYPE_HTTP);
}
TEST_F(WebSocketEndToEndTest, BasicSmokeTestSSL) {
RunBasicSmokeTest(net::EmbeddedTestServer::TYPE_HTTPS);
}
TEST_F(WebSocketEndToEndTest, WebSocketEchoHandlerTest) {
test_server::EmbeddedTestServer embedded_test_server(
test_server::EmbeddedTestServer::TYPE_HTTP);
test_server::InstallDefaultWebSocketHandlers(&embedded_test_server);
ASSERT_TRUE(embedded_test_server.Start());
GURL echo_url = test_server::ToWebSocketUrl(
embedded_test_server.GetURL("/echo-with-no-extension"));
ASSERT_TRUE(ConnectAndWait(echo_url));
const std::string test_message = "hello echo";
auto channel_state = SendMessage(test_message);
ASSERT_EQ(channel_state, WebSocketChannel::ChannelState::CHANNEL_ALIVE);
std::string received_message = ReceiveMessage();
EXPECT_EQ(test_message, received_message);
CloseWebSocketSuccessfully();
}
// Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
// authenticated proxy times out".
TEST_F(WebSocketEndToEndTest, HttpsProxyUnauthedFails) {
// Set up WebSocket server. Should not actually be used, beyond providing a
// URL that is blocked by the proxy requesting authentication.
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::InstallDefaultWebSocketHandlers(&ws_server);
ASSERT_TRUE(ws_server.Start());
EmbeddedTestServer proxy_server(EmbeddedTestServer::Type::TYPE_HTTP);
proxy_server.EnableConnectProxy({ws_server.host_port_pair()});
RegisterProxyBasicAuthHandler(proxy_server, "user", "pass");
ASSERT_TRUE(proxy_server.Start());
ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString(
"https=" + proxy_server.host_port_pair().ToString());
// TODO(crbug.com/40600992): Don't rely on proxying localhost.
proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config,
TRAFFIC_ANNOTATION_FOR_TESTS)));
ASSERT_TRUE(proxy_resolution_service);
context_builder_->set_proxy_resolution_service(
std::move(proxy_resolution_service));
EXPECT_FALSE(
ConnectAndWait(test_server::GetWebSocketURL(ws_server, kEchoServer)));
EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
}
TEST_F(WebSocketEndToEndTest, HttpsWssProxyUnauthedFails) {
// Set up WebSocket server. Should not actually be used, beyond providing a
// URL that is blocked by the proxy requesting authentication.
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
EmbeddedTestServer proxy_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
proxy_server.EnableConnectProxy({wss_server.host_port_pair()});
RegisterProxyBasicAuthHandler(proxy_server, "user", "pass");
ASSERT_TRUE(proxy_server.Start());
ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString(
"https=" + proxy_server.host_port_pair().ToString());
// TODO(crbug.com/40600992): Don't rely on proxying localhost.
proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config,
TRAFFIC_ANNOTATION_FOR_TESTS)));
ASSERT_TRUE(proxy_resolution_service);
context_builder_->set_proxy_resolution_service(
std::move(proxy_resolution_service));
EXPECT_FALSE(
ConnectAndWait(test_server::GetWebSocketURL(wss_server, kEchoServer)));
EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
}
// Regression test for crbug.com/426736 "WebSocket connections not using
// configured system HTTPS Proxy".
TEST_F(WebSocketEndToEndTest, HttpsProxyUsed) {
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::InstallDefaultWebSocketHandlers(&ws_server);
ASSERT_TRUE(ws_server.Start());
EmbeddedTestServer proxy_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
proxy_server.EnableConnectProxy({ws_server.host_port_pair()});
ASSERT_TRUE(proxy_server.Start());
ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString(
"https=" + proxy_server.host_port_pair().ToString());
// TODO(crbug.com/40600992): Don't rely on proxying localhost.
proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config,
TRAFFIC_ANNOTATION_FOR_TESTS)));
context_builder_->set_proxy_resolution_service(
std::move(proxy_resolution_service));
InitialiseContext();
GURL ws_url = test_server::GetWebSocketURL(ws_server, kEchoServer);
EXPECT_TRUE(ConnectAndWait(ws_url));
const TestProxyDelegateWithProxyInfo::ResolvedProxyInfo& info =
proxy_delegate_->resolved_proxy_info();
EXPECT_EQ(ws_url, info.url);
EXPECT_EQ(info.proxy_info.ToDebugString(),
base::StrCat({"PROXY ", proxy_server.host_port_pair().ToString()}));
}
std::unique_ptr<HttpResponse> ProxyPacHandler(const HttpRequest& request) {
GURL url = request.GetURL();
EXPECT_EQ(url.path_piece(), "/proxy.pac");
EXPECT_TRUE(url.has_query());
std::string proxy;
EXPECT_TRUE(GetValueForKeyInQuery(url, "proxy", &proxy));
auto response = std::make_unique<BasicHttpResponse>();
response->set_content_type("application/x-ns-proxy-autoconfig");
response->set_content(
base::StringPrintf("function FindProxyForURL(url, host) {\n"
" return 'PROXY %s';\n"
"}\n",
proxy.c_str()));
return response;
}
// This tests the proxy.pac resolver that is built into the system. This is not
// the one that Chrome normally uses. Chrome's normal implementation is defined
// as a mojo service. It is outside //net and we can't use it from here. This
// tests the alternative implementations that are selected when the
// --winhttp-proxy-resolver flag is provided to Chrome. These only exist on OS X
// and Windows.
// TODO(ricea): Remove this test if --winhttp-proxy-resolver flag is removed.
// See crbug.com/644030.
TEST_F(WebSocketEndToEndTest, ProxyPacUsed) {
if constexpr (!BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE)) {
GTEST_SKIP() << "Test not supported on this platform";
}
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::InstallDefaultWebSocketHandlers(&ws_server);
ASSERT_TRUE(ws_server.Start());
EmbeddedTestServer proxy_pac_server(EmbeddedTestServer::Type::TYPE_HTTP);
proxy_pac_server.RegisterRequestHandler(base::BindRepeating(ProxyPacHandler));
ASSERT_TRUE(proxy_pac_server.Start());
// Use a name other than localhost, since localhost implicitly bypasses the
// use of proxy.pac.
GURL ws_url =
test_server::GetWebSocketURL(ws_server, "stealth-localhost", kEchoServer);
EmbeddedTestServer proxy_server(EmbeddedTestServer::Type::TYPE_HTTP);
proxy_server.EnableConnectProxy({HostPortPair::FromURL(ws_url)});
ASSERT_TRUE(proxy_server.Start());
ProxyConfig proxy_config =
ProxyConfig::CreateFromCustomPacURL(proxy_pac_server.GetURL(base::StrCat(
{"/proxy.pac?proxy=", proxy_server.host_port_pair().ToString()})));
proxy_config.set_pac_mandatory(true);
auto proxy_config_service = std::make_unique<ProxyConfigServiceFixed>(
ProxyConfigWithAnnotation(proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
std::move(proxy_config_service), NetLog::Get(),
/*quick_check_enabled=*/true));
ASSERT_EQ(ws_server.host_port_pair().host(), "127.0.0.1");
context_builder_->set_proxy_resolution_service(
std::move(proxy_resolution_service));
InitialiseContext();
EXPECT_TRUE(ConnectAndWait(ws_url));
const auto& info = proxy_delegate_->resolved_proxy_info();
EXPECT_EQ(ws_url, info.url);
EXPECT_EQ(info.proxy_info.ToDebugString(),
base::StrCat({"PROXY ", proxy_server.host_port_pair().ToString()}));
}
// This is a regression test for crbug.com/408061 Crash in
// net::WebSocketBasicHandshakeStream::Upgrade.
TEST_F(WebSocketEndToEndTest, TruncatedResponse) {
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::InstallDefaultWebSocketHandlers(&ws_server);
ASSERT_TRUE(ws_server.Start());
InitialiseContext();
GURL ws_url = test_server::GetWebSocketURL(ws_server, "/truncated-headers");
EXPECT_FALSE(ConnectAndWait(ws_url));
}
// Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
TEST_F(WebSocketEndToEndTest, HstsHttpsToWebSocket) {
base::test::ScopedFeatureList features;
// Websocket upgrades can't happen when only top-level navigations are
// upgraded, so disable the feature for this test.
features.InitAndDisableFeature(features::kHstsTopLevelNavigationsOnly);
EmbeddedTestServer https_server(EmbeddedTestServer::Type::TYPE_HTTPS);
std::string test_server_hostname = "a.test";
https_server.SetCertHostnames({test_server_hostname});
https_server.ServeFilesFromSourceDirectory("net/data/url_request_unittest");
ASSERT_TRUE(https_server.Start());
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
wss_server.SetCertHostnames({test_server_hostname});
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
InitialiseContext();
// Set HSTS via https:
TestDelegate delegate;
GURL https_page =
https_server.GetURL(test_server_hostname, "/hsts-headers.html");
std::unique_ptr<URLRequest> request(context_->CreateRequest(
https_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
delegate.RunUntilComplete();
EXPECT_EQ(OK, delegate.request_status());
// Check HSTS with ws:
// Change the scheme from wss: to ws: to verify that it is switched back.
GURL ws_url =
ReplaceUrlScheme(test_server::GetWebSocketURL(
wss_server, test_server_hostname, kEchoServer),
"ws");
EXPECT_TRUE(ConnectAndWait(ws_url));
}
// Tests that when kHstsTopLevelNavigationsOnly is enabled websocket isn't
// upgraded.
TEST_F(WebSocketEndToEndTest, HstsHttpsToWebSocketNotApplied) {
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kHstsTopLevelNavigationsOnly);
EmbeddedTestServer https_server(net::EmbeddedTestServer::Type::TYPE_HTTPS);
https_server.SetSSLConfig(
net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
https_server.ServeFilesFromSourceDirectory("net/data/url_request_unittest");
EmbeddedTestServer ws_server(net::EmbeddedTestServer::TYPE_HTTP);
net::test_server::InstallDefaultWebSocketHandlers(&ws_server);
ASSERT_TRUE(https_server.Start());
ASSERT_TRUE(ws_server.Start());
InitialiseContext();
// Set HSTS via https:
TestDelegate delegate;
GURL https_page = https_server.GetURL("/hsts-headers.html");
std::unique_ptr<URLRequest> request(context_->CreateRequest(
https_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->Start();
delegate.RunUntilComplete();
EXPECT_EQ(OK, delegate.request_status());
// Check that the ws connection was not upgraded.
GURL ws_url = net::test_server::GetWebSocketURL(ws_server, kEchoServer);
EXPECT_TRUE(ConnectAndWait(ws_url));
}
TEST_F(WebSocketEndToEndTest, HstsWebSocketToHttps) {
EmbeddedTestServer https_server(net::EmbeddedTestServer::Type::TYPE_HTTPS);
std::string test_server_hostname = "a.test";
https_server.SetCertHostnames({test_server_hostname});
https_server.ServeFilesFromSourceDirectory("net/data/url_request_unittest");
ASSERT_TRUE(https_server.Start());
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
wss_server.SetCertHostnames({test_server_hostname});
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
InitialiseContext();
// Set HSTS via wss:
GURL wss_url = test_server::GetWebSocketURL(wss_server, test_server_hostname,
"/set-hsts");
EXPECT_TRUE(ConnectAndWait(wss_url));
// Verify via http:
TestDelegate delegate;
GURL http_page = ReplaceUrlScheme(
https_server.GetURL(test_server_hostname, "/simple.html"), "http");
url::Origin http_origin = url::Origin::Create(http_page);
std::unique_ptr<URLRequest> request(context_->CreateRequest(
http_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
request->set_isolation_info(IsolationInfo::Create(
IsolationInfo::RequestType::kMainFrame, http_origin, http_origin,
SiteForCookies::FromOrigin(http_origin)));
request->Start();
delegate.RunUntilComplete();
EXPECT_EQ(OK, delegate.request_status());
EXPECT_TRUE(request->url().SchemeIs("https"));
}
TEST_F(WebSocketEndToEndTest, HstsWebSocketToWebSocket) {
base::test::ScopedFeatureList features;
// Websocket upgrades can't happen when only top-level navigations are
// upgraded, so disable the feature for this test.
features.InitAndDisableFeature(features::kHstsTopLevelNavigationsOnly);
std::string test_server_hostname = "a.test";
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
wss_server.SetCertHostnames({test_server_hostname});
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
InitialiseContext();
// Set HSTS via wss:
GURL wss_url = test_server::GetWebSocketURL(wss_server, test_server_hostname,
"/set-hsts");
EXPECT_TRUE(ConnectAndWait(wss_url));
// Verify via ws:
GURL ws_url = ReplaceUrlScheme(
wss_server.GetURL(test_server_hostname, kEchoServer), "ws");
EXPECT_TRUE(ConnectAndWait(ws_url));
}
// WebSocketHandler that sends HTTP response headers with trailing whitespace.
class WebSocketTrailingWhitespaceHandler
: public test_server::WebSocketHandler {
public:
explicit WebSocketTrailingWhitespaceHandler(
scoped_refptr<test_server::WebSocketConnection> connection)
: test_server::WebSocketHandler(std::move(connection)) {}
void OnHandshake(const test_server::HttpRequest& request) override {
CHECK(connection());
connection()->SetResponseHeader("Sec-WebSocket-Protocol", "sip ");
}
};
// Regression test for crbug.com/180504 "WebSocket handshake fails when HTTP
// headers have trailing LWS".
TEST_F(WebSocketEndToEndTest, TrailingWhitespace) {
const std::string kPath = "/trailing-whitespace";
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::RegisterWebSocketHandler<WebSocketTrailingWhitespaceHandler>(
&ws_server, kPath);
ASSERT_TRUE(ws_server.Start());
GURL ws_url = test_server::GetWebSocketURL(ws_server, kPath);
sub_protocols_.push_back("sip");
EXPECT_TRUE(ConnectAndWait(ws_url));
EXPECT_EQ("sip", event_interface_->selected_subprotocol());
}
// WebSocketHandler that sends HTTP response headers with a continuation.
class WebSocketHeaderContinuationHandler
: public test_server::WebSocketHandler {
public:
explicit WebSocketHeaderContinuationHandler(
scoped_refptr<test_server::WebSocketConnection> connection)
: test_server::WebSocketHandler(std::move(connection)) {}
void OnHandshake(const test_server::HttpRequest& request) override {
CHECK(connection());
// Response headers are added blindly, so this results in a continuation.
connection()->SetResponseHeader("Sec-WebSocket-Extensions",
"permessage-deflate;\r\n"
" server_max_window_bits=10");
}
};
// This is a regression test for crbug.com/169448 "WebSockets should support
// header continuations"
// TODO(ricea): HTTP continuation headers have been deprecated by RFC7230. If
// support for continuation headers is removed from Chrome, then this test will
// break and should be removed.
TEST_F(WebSocketEndToEndTest, HeaderContinuations) {
const std::string kPath = "/header-continuation";
EmbeddedTestServer ws_server(EmbeddedTestServer::Type::TYPE_HTTP);
test_server::RegisterWebSocketHandler<WebSocketHeaderContinuationHandler>(
&ws_server, kPath);
ASSERT_TRUE(ws_server.Start());
GURL ws_url = test_server::GetWebSocketURL(ws_server, kPath);
EXPECT_TRUE(ConnectAndWait(ws_url));
EXPECT_EQ("permessage-deflate; server_max_window_bits=10",
event_interface_->extensions());
}
// Test that ws->wss scheme upgrade is supported on receiving a DNS HTTPS
// record.
TEST_F(WebSocketEndToEndTest, DnsSchemeUpgradeSupported) {
const std::string kTestServerHostname = "a.test";
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
wss_server.SetCertHostnames({kTestServerHostname});
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
GURL wss_url = test_server::GetWebSocketURL(wss_server, kTestServerHostname,
kEchoServer);
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kWsScheme);
GURL ws_url = wss_url.ReplaceComponents(replacements);
// Note that due to socket pool behavior, HostResolver will see the ws/wss
// requests as http/https.
auto host_resolver = std::make_unique<MockHostResolver>();
MockHostResolverBase::RuleResolver::RuleKey unencrypted_resolve_key;
unencrypted_resolve_key.scheme = url::kHttpScheme;
host_resolver->rules()->AddRule(std::move(unencrypted_resolve_key),
ERR_DNS_NAME_HTTPS_ONLY);
MockHostResolverBase::RuleResolver::RuleKey encrypted_resolve_key;
encrypted_resolve_key.scheme = url::kHttpsScheme;
host_resolver->rules()->AddRule(std::move(encrypted_resolve_key),
"127.0.0.1");
context_builder_->set_host_resolver(std::move(host_resolver));
EXPECT_TRUE(ConnectAndWait(ws_url));
// Expect request to have reached the server using the upgraded URL.
EXPECT_EQ(event_interface_->response()->url, wss_url);
}
// Test that wss connections can use HostResolverEndpointResults from DNS.
TEST_F(WebSocketEndToEndTest, HostResolverEndpointResult) {
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kUseDnsHttpsSvcb);
const std::string kTestServerHostname = "a.test";
EmbeddedTestServer wss_server(EmbeddedTestServer::Type::TYPE_HTTPS);
wss_server.SetCertHostnames({kTestServerHostname});
test_server::InstallDefaultWebSocketHandlers(&wss_server);
ASSERT_TRUE(wss_server.Start());
uint16_t port = wss_server.port();
GURL wss_url = test_server::GetWebSocketURL(wss_server, kTestServerHostname,
kEchoServer);
auto host_resolver = std::make_unique<MockHostResolver>();
MockHostResolverBase::RuleResolver::RuleKey resolve_key;
// The DNS query itself is made with the https scheme rather than wss.
resolve_key.scheme = url::kHttpsScheme;
resolve_key.hostname_pattern = kTestServerHostname;
resolve_key.port = port;
HostResolverEndpointResult result;
result.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), port)};
result.metadata.supported_protocol_alpns = {"http/1.1"};
host_resolver->rules()->AddRule(
std::move(resolve_key),
MockHostResolverBase::RuleResolver::RuleResult(std::vector{result}));
context_builder_->set_host_resolver(std::move(host_resolver));
EXPECT_TRUE(ConnectAndWait(wss_url));
// Expect request to have reached the server using the upgraded URL.
EXPECT_EQ(event_interface_->response()->url, wss_url);
}
// Test that wss connections can use EncryptedClientHello.
TEST_F(WebSocketEndToEndTest, EncryptedClientHello) {
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kUseDnsHttpsSvcb);
// Configure a test server that speaks ECH.
static constexpr char kRealName[] = "secret.example";
static constexpr char kPublicName[] = "public.example";
EmbeddedTestServer::ServerCertificateConfig server_cert_config;
server_cert_config.dns_names = {kRealName};
SSLServerConfig ssl_server_config;
std::vector<uint8_t> ech_config_list;
ssl_server_config.ech_keys =
MakeTestEchKeys(kPublicName, /*max_name_len=*/128, &ech_config_list);
ASSERT_TRUE(ssl_server_config.ech_keys);
// Only complete the handshake if ECH was actually used.
ssl_server_config.client_hello_callback_for_testing =
base::BindLambdaForTesting(
[&](const SSL_CLIENT_HELLO* client_hello) -> bool {
return SSL_ech_accepted(client_hello->ssl);
});
EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTPS);
test_server.SetSSLConfig(server_cert_config, ssl_server_config);
test_server::InstallDefaultWebSocketHandlers(&test_server);
ASSERT_TRUE(test_server.Start());
GURL wss_url =
test_server::GetWebSocketURL(test_server, kRealName, kEchoServer);
auto host_resolver = std::make_unique<MockHostResolver>();
MockHostResolverBase::RuleResolver::RuleKey resolve_key;
// The DNS query itself is made with the https scheme rather than wss.
resolve_key.scheme = url::kHttpsScheme;
resolve_key.hostname_pattern = wss_url.host();
resolve_key.port = wss_url.IntPort();
HostResolverEndpointResult result;
result.ip_endpoints = {
IPEndPoint(IPAddress::IPv4Localhost(), wss_url.IntPort())};
result.metadata.supported_protocol_alpns = {"http/1.1"};
result.metadata.ech_config_list = ech_config_list;
host_resolver->rules()->AddRule(
std::move(resolve_key),
MockHostResolverBase::RuleResolver::RuleResult(std::vector{result}));
context_builder_->set_host_resolver(std::move(host_resolver));
EXPECT_TRUE(ConnectAndWait(wss_url));
// Expect request to have reached the server using the upgraded URL.
EXPECT_EQ(event_interface_->response()->url, wss_url);
}
} // namespace
} // namespace net