761 lines
30 KiB
C++
761 lines
30 KiB
C++
// 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 <string>
|
|
#include <utility>
|
|
|
|
#include "base/files/file_path.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/location.h"
|
|
#include "base/memory/ptr_util.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_piece.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "build/build_config.h"
|
|
#include "net/base/auth.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/ip_endpoint.h"
|
|
#include "net/base/isolation_info.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/proxy_delegate.h"
|
|
#include "net/base/url_util.h"
|
|
#include "net/cert/ct_policy_status.h"
|
|
#include "net/dns/mock_host_resolver.h"
|
|
#include "net/http/http_request_headers.h"
|
|
#include "net/http/transport_security_state.h"
|
|
#include "net/log/net_log.h"
|
|
#include "net/proxy_resolution/configured_proxy_resolution_service.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/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/spawned_test_server/spawned_test_server.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 "testing/gtest/include/gtest/gtest.h"
|
|
#include "url/gurl.h"
|
|
#include "url/origin.h"
|
|
#include "url/url_constants.h"
|
|
|
|
namespace net {
|
|
|
|
class URLRequest;
|
|
|
|
namespace {
|
|
|
|
using test_server::BasicHttpResponse;
|
|
using test_server::HttpRequest;
|
|
using test_server::HttpResponse;
|
|
|
|
static const char kEchoServer[] = "echo-with-no-extension";
|
|
|
|
// Simplify changing URL schemes.
|
|
GURL ReplaceUrlScheme(const GURL& in_url, base::StringPiece 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 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,
|
|
absl::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,
|
|
absl::optional<AuthCredentials>* credentials) override;
|
|
|
|
private:
|
|
void QuitNestedEventLoop();
|
|
|
|
// 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_;
|
|
base::RunLoop run_loop_;
|
|
};
|
|
|
|
ConnectTestingEventInterface::ConnectTestingEventInterface() = default;
|
|
|
|
void ConnectTestingEventInterface::WaitForResponse() {
|
|
run_loop_.Run();
|
|
}
|
|
|
|
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;
|
|
QuitNestedEventLoop();
|
|
}
|
|
|
|
void ConnectTestingEventInterface::OnDataFrame(bool fin,
|
|
WebSocketMessageType type,
|
|
base::span<const char> payload) {
|
|
}
|
|
|
|
void ConnectTestingEventInterface::OnSendDataFrameDone() {}
|
|
|
|
void ConnectTestingEventInterface::OnClosingHandshake() {}
|
|
|
|
void ConnectTestingEventInterface::OnDropChannel(bool was_clean,
|
|
uint16_t code,
|
|
const std::string& reason) {}
|
|
|
|
void ConnectTestingEventInterface::OnFailChannel(
|
|
const std::string& message,
|
|
int net_error,
|
|
absl::optional<int> response_code) {
|
|
failed_ = true;
|
|
failure_message_ = message;
|
|
QuitNestedEventLoop();
|
|
}
|
|
|
|
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,
|
|
absl::optional<AuthCredentials>* credentials) {
|
|
*credentials = absl::nullopt;
|
|
return OK;
|
|
}
|
|
|
|
void ConnectTestingEventInterface::QuitNestedEventLoop() {
|
|
run_loop_.Quit();
|
|
}
|
|
|
|
// 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 std::string& method,
|
|
const ProxyRetryInfoMap& proxy_retry_info,
|
|
ProxyInfo* result) override {
|
|
resolved_proxy_info_.url = url;
|
|
resolved_proxy_info_.proxy_info = *result;
|
|
}
|
|
|
|
void OnFallback(const ProxyServer& bad_proxy, int net_error) override {}
|
|
|
|
void OnBeforeTunnelRequest(const ProxyServer& proxy_server,
|
|
HttpRequestHeaders* extra_headers) override {}
|
|
|
|
Error OnTunnelHeadersReceived(
|
|
const ProxyServer& proxy_server,
|
|
const HttpResponseHeaders& response_headers) override {
|
|
return OK;
|
|
}
|
|
|
|
private:
|
|
ResolvedProxyInfo resolved_proxy_info_;
|
|
};
|
|
|
|
class WebSocketEndToEndTest : public TestWithTaskEnvironment {
|
|
protected:
|
|
WebSocketEndToEndTest()
|
|
: event_interface_(),
|
|
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,
|
|
isolation_info, HttpRequestHeaders(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
event_interface_->WaitForResponse();
|
|
return !event_interface_->failed();
|
|
}
|
|
|
|
raw_ptr<ConnectTestingEventInterface> 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) {
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(ws_server.Start());
|
|
EXPECT_TRUE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
|
|
}
|
|
|
|
// Test for issue crbug.com/433695 "Unencrypted WebSocket connection via
|
|
// authenticated proxy times out"
|
|
// TODO(ricea): Enable this when the issue is fixed.
|
|
TEST_F(WebSocketEndToEndTest, DISABLED_HttpsProxyUnauthedFails) {
|
|
SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
|
|
base::FilePath());
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(proxy_server.StartInBackground());
|
|
ASSERT_TRUE(ws_server.StartInBackground());
|
|
ASSERT_TRUE(proxy_server.BlockUntilStarted());
|
|
ASSERT_TRUE(ws_server.BlockUntilStarted());
|
|
std::string proxy_config =
|
|
"https=" + proxy_server.host_port_pair().ToString();
|
|
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
|
|
ConfiguredProxyResolutionService::CreateFixedForTest(
|
|
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(ws_server.GetURL(kEchoServer)));
|
|
EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
|
|
}
|
|
|
|
// These test are not compatible with RemoteTestServer because RemoteTestServer
|
|
// doesn't support TYPE_BASIC_AUTH_PROXY.
|
|
// TODO(ricea): Make these tests work. See crbug.com/441711.
|
|
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
|
|
#define MAYBE_HttpsWssProxyUnauthedFails DISABLED_HttpsWssProxyUnauthedFails
|
|
#define MAYBE_HttpsProxyUsed DISABLED_HttpsProxyUsed
|
|
#else
|
|
#define MAYBE_HttpsWssProxyUnauthedFails HttpsWssProxyUnauthedFails
|
|
#define MAYBE_HttpsProxyUsed HttpsProxyUsed
|
|
#endif
|
|
|
|
TEST_F(WebSocketEndToEndTest, MAYBE_HttpsWssProxyUnauthedFails) {
|
|
SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
|
|
base::FilePath());
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(proxy_server.StartInBackground());
|
|
ASSERT_TRUE(wss_server.StartInBackground());
|
|
ASSERT_TRUE(proxy_server.BlockUntilStarted());
|
|
ASSERT_TRUE(wss_server.BlockUntilStarted());
|
|
ProxyConfig proxy_config;
|
|
proxy_config.proxy_rules().ParseFromString(
|
|
"https=" + proxy_server.host_port_pair().ToString());
|
|
// TODO(https://crbug.com/901896): 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(wss_server.GetURL(kEchoServer)));
|
|
EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
|
|
}
|
|
|
|
// Regression test for crbug/426736 "WebSocket connections not using configured
|
|
// system HTTPS Proxy".
|
|
TEST_F(WebSocketEndToEndTest, MAYBE_HttpsProxyUsed) {
|
|
SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
|
|
base::FilePath());
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(proxy_server.StartInBackground());
|
|
ASSERT_TRUE(ws_server.StartInBackground());
|
|
ASSERT_TRUE(proxy_server.BlockUntilStarted());
|
|
ASSERT_TRUE(ws_server.BlockUntilStarted());
|
|
ProxyConfig proxy_config;
|
|
proxy_config.proxy_rules().ParseFromString(
|
|
"https=" + proxy_server.host_port_pair().ToString() + ";" +
|
|
"http=" + proxy_server.host_port_pair().ToString());
|
|
// TODO(https://crbug.com/901896): 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 = ws_server.GetURL(kEchoServer);
|
|
EXPECT_TRUE(ConnectAndWait(ws_url));
|
|
const TestProxyDelegateWithProxyInfo::ResolvedProxyInfo& info =
|
|
proxy_delegate_->resolved_proxy_info();
|
|
EXPECT_EQ(ws_url, info.url);
|
|
EXPECT_TRUE(info.proxy_info.is_http());
|
|
}
|
|
|
|
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.
|
|
|
|
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE)
|
|
#define MAYBE_ProxyPacUsed ProxyPacUsed
|
|
#else
|
|
#define MAYBE_ProxyPacUsed DISABLED_ProxyPacUsed
|
|
#endif
|
|
|
|
TEST_F(WebSocketEndToEndTest, MAYBE_ProxyPacUsed) {
|
|
EmbeddedTestServer proxy_pac_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
|
|
SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
|
|
base::FilePath());
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
proxy_pac_server.RegisterRequestHandler(base::BindRepeating(ProxyPacHandler));
|
|
proxy_server.set_redirect_connect_to_localhost(true);
|
|
|
|
ASSERT_TRUE(proxy_pac_server.Start());
|
|
ASSERT_TRUE(proxy_server.StartInBackground());
|
|
ASSERT_TRUE(ws_server.StartInBackground());
|
|
ASSERT_TRUE(proxy_server.BlockUntilStarted());
|
|
ASSERT_TRUE(ws_server.BlockUntilStarted());
|
|
|
|
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();
|
|
|
|
// Use a name other than localhost, since localhost implicitly bypasses the
|
|
// use of proxy.pac.
|
|
HostPortPair fake_ws_host_port_pair("stealth-localhost",
|
|
ws_server.host_port_pair().port());
|
|
|
|
GURL ws_url(base::StrCat(
|
|
{"ws://", fake_ws_host_port_pair.ToString(), "/", kEchoServer}));
|
|
EXPECT_TRUE(ConnectAndWait(ws_url));
|
|
const auto& info = proxy_delegate_->resolved_proxy_info();
|
|
EXPECT_EQ(ws_url, info.url);
|
|
EXPECT_TRUE(info.proxy_info.is_http());
|
|
EXPECT_EQ(info.proxy_info.ToPacString(),
|
|
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) {
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(ws_server.Start());
|
|
InitialiseContext();
|
|
|
|
GURL ws_url = ws_server.GetURL("truncated-headers");
|
|
EXPECT_FALSE(ConnectAndWait(ws_url));
|
|
}
|
|
|
|
// Regression test for crbug.com/455215 "HSTS not applied to WebSocket"
|
|
TEST_F(WebSocketEndToEndTest, HstsHttpsToWebSocket) {
|
|
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");
|
|
|
|
SpawnedTestServer::SSLOptions ssl_options(
|
|
SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN);
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
|
|
GetWebSocketTestDataDirectory());
|
|
|
|
ASSERT_TRUE(https_server.Start());
|
|
ASSERT_TRUE(wss_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 HSTS with ws:
|
|
// Change the scheme from wss: to ws: to verify that it is switched back.
|
|
GURL ws_url = ReplaceUrlScheme(wss_server.GetURL(kEchoServer), "ws");
|
|
EXPECT_TRUE(ConnectAndWait(ws_url));
|
|
}
|
|
|
|
TEST_F(WebSocketEndToEndTest, HstsWebSocketToHttps) {
|
|
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");
|
|
|
|
SpawnedTestServer::SSLOptions ssl_options(
|
|
SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN);
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(https_server.Start());
|
|
ASSERT_TRUE(wss_server.Start());
|
|
InitialiseContext();
|
|
// Set HSTS via wss:
|
|
GURL wss_url = wss_server.GetURL("set-hsts");
|
|
EXPECT_TRUE(ConnectAndWait(wss_url));
|
|
|
|
// Verify via http:
|
|
TestDelegate delegate;
|
|
GURL http_page =
|
|
ReplaceUrlScheme(https_server.GetURL("/simple.html"), "http");
|
|
std::unique_ptr<URLRequest> request(context_->CreateRequest(
|
|
http_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
|
request->Start();
|
|
delegate.RunUntilComplete();
|
|
EXPECT_EQ(OK, delegate.request_status());
|
|
EXPECT_TRUE(request->url().SchemeIs("https"));
|
|
}
|
|
|
|
TEST_F(WebSocketEndToEndTest, HstsWebSocketToWebSocket) {
|
|
SpawnedTestServer::SSLOptions ssl_options(
|
|
SpawnedTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN);
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, ssl_options,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(wss_server.Start());
|
|
InitialiseContext();
|
|
// Set HSTS via wss:
|
|
GURL wss_url = wss_server.GetURL("set-hsts");
|
|
EXPECT_TRUE(ConnectAndWait(wss_url));
|
|
|
|
// Verify via wss:
|
|
GURL ws_url = ReplaceUrlScheme(wss_server.GetURL(kEchoServer), "ws");
|
|
EXPECT_TRUE(ConnectAndWait(ws_url));
|
|
}
|
|
|
|
// Regression test for crbug.com/180504 "WebSocket handshake fails when HTTP
|
|
// headers have trailing LWS".
|
|
TEST_F(WebSocketEndToEndTest, TrailingWhitespace) {
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(ws_server.Start());
|
|
|
|
GURL ws_url = ws_server.GetURL("trailing-whitespace");
|
|
sub_protocols_.push_back("sip");
|
|
EXPECT_TRUE(ConnectAndWait(ws_url));
|
|
EXPECT_EQ("sip", event_interface_->selected_subprotocol());
|
|
}
|
|
|
|
// 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) {
|
|
SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(ws_server.Start());
|
|
|
|
GURL ws_url = ws_server.GetURL("header-continuation");
|
|
|
|
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) {
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
|
|
SpawnedTestServer::SSLOptions(base::FilePath(
|
|
FILE_PATH_LITERAL("test_names.pem"))),
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(wss_server.Start());
|
|
|
|
GURL wss_url("wss://a.test:" +
|
|
base::NumberToString(wss_server.host_port_pair().port()) + "/" +
|
|
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);
|
|
|
|
SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
|
|
SpawnedTestServer::SSLOptions(base::FilePath(
|
|
FILE_PATH_LITERAL("test_names.pem"))),
|
|
GetWebSocketTestDataDirectory());
|
|
ASSERT_TRUE(wss_server.Start());
|
|
|
|
uint16_t port = wss_server.host_port_pair().port();
|
|
GURL wss_url("wss://a.test:" + base::NumberToString(port) + "/" +
|
|
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 = "a.test";
|
|
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.InitWithFeatures(
|
|
{features::kUseDnsHttpsSvcb, features::kEncryptedClientHello}, {});
|
|
|
|
// SpawnedTestServer does not support ECH, while EmbeddedTestServer does not
|
|
// support WebSockets (https://crbug.com/1281277). Until that is fixed, test
|
|
// ECH by configuring a non-WebSockets HTTPS server. The WebSockets handshake
|
|
// will fail, but getting that far tests that ECH worked.
|
|
|
|
// 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);
|
|
|
|
EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTPS);
|
|
test_server.SetSSLConfig(server_cert_config, ssl_server_config);
|
|
ASSERT_TRUE(test_server.Start());
|
|
|
|
GURL https_url = test_server.GetURL(kRealName, "/");
|
|
GURL::Replacements replacements;
|
|
replacements.SetSchemeStr(url::kWssScheme);
|
|
GURL wss_url = https_url.ReplaceComponents(replacements);
|
|
|
|
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_FALSE(ConnectAndWait(wss_url));
|
|
EXPECT_EQ("Error during WebSocket handshake: Unexpected response code: 404",
|
|
event_interface_->failure_message());
|
|
}
|
|
} // namespace
|
|
|
|
} // namespace net
|