| Index: net/websockets/websocket_end_to_end_test.cc
|
| diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0b5944f33f2cd413e39545a89d8172cb15626177
|
| --- /dev/null
|
| +++ b/net/websockets/websocket_end_to_end_test.cc
|
| @@ -0,0 +1,370 @@
|
| +// Copyright 2014 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.
|
| +
|
| +// 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 <string>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/callback.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/run_loop.h"
|
| +#include "net/base/auth.h"
|
| +#include "net/base/network_delegate.h"
|
| +#include "net/base/test_data_directory.h"
|
| +#include "net/proxy/proxy_service.h"
|
| +#include "net/test/spawned_test_server/spawned_test_server.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/origin.h"
|
| +
|
| +namespace net {
|
| +
|
| +namespace {
|
| +
|
| +static const char kEchoServer[] = "echo-with-no-extension";
|
| +
|
| +// An implementation of WebSocketEventInterface that waits for and records the
|
| +// results of the connect.
|
| +class ConnectTestingEventInterface : public WebSocketEventInterface {
|
| + public:
|
| + ConnectTestingEventInterface();
|
| +
|
| + void WaitForResponse();
|
| +
|
| + bool failed() const { return failed_; }
|
| +
|
| + // 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.
|
| + ChannelState OnAddChannelResponse(bool fail,
|
| + const std::string& selected_subprotocol,
|
| + const std::string& extensions) override;
|
| +
|
| + ChannelState OnDataFrame(bool fin,
|
| + WebSocketMessageType type,
|
| + const std::vector<char>& data) override;
|
| +
|
| + ChannelState OnFlowControl(int64 quota) override;
|
| +
|
| + ChannelState OnClosingHandshake() override;
|
| +
|
| + ChannelState OnDropChannel(bool was_clean,
|
| + uint16 code,
|
| + const std::string& reason) override;
|
| +
|
| + ChannelState OnFailChannel(const std::string& message) override;
|
| +
|
| + ChannelState OnStartOpeningHandshake(
|
| + scoped_ptr<WebSocketHandshakeRequestInfo> request) override;
|
| +
|
| + ChannelState OnFinishOpeningHandshake(
|
| + scoped_ptr<WebSocketHandshakeResponseInfo> response) override;
|
| +
|
| + ChannelState OnSSLCertificateError(
|
| + scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
|
| + const GURL& url,
|
| + const SSLInfo& ssl_info,
|
| + bool fatal) override;
|
| +
|
| + private:
|
| + void QuitNestedEventLoop();
|
| +
|
| + // failed_ is true if the handshake failed (ie. OnFailChannel was called).
|
| + bool failed_;
|
| + std::string selected_subprotocol_;
|
| + std::string extensions_;
|
| + std::string failure_message_;
|
| + base::RunLoop run_loop_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface);
|
| +};
|
| +
|
| +ConnectTestingEventInterface::ConnectTestingEventInterface() : failed_(true) {
|
| +}
|
| +
|
| +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_;
|
| +}
|
| +
|
| +// Make the function definitions below less verbose.
|
| +typedef ConnectTestingEventInterface::ChannelState ChannelState;
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnAddChannelResponse(
|
| + bool fail,
|
| + const std::string& selected_subprotocol,
|
| + const std::string& extensions) {
|
| + failed_ = fail;
|
| + selected_subprotocol_ = selected_subprotocol;
|
| + extensions_ = extensions;
|
| + QuitNestedEventLoop();
|
| + return fail ? CHANNEL_DELETED : CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnDataFrame(
|
| + bool fin,
|
| + WebSocketMessageType type,
|
| + const std::vector<char>& data) {
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnFlowControl(int64 quota) {
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnClosingHandshake() {
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnDropChannel(
|
| + bool was_clean,
|
| + uint16 code,
|
| + const std::string& reason) {
|
| + return CHANNEL_DELETED;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnFailChannel(
|
| + const std::string& message) {
|
| + failed_ = true;
|
| + failure_message_ = message;
|
| + QuitNestedEventLoop();
|
| + return CHANNEL_DELETED;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnStartOpeningHandshake(
|
| + scoped_ptr<WebSocketHandshakeRequestInfo> request) {
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnFinishOpeningHandshake(
|
| + scoped_ptr<WebSocketHandshakeResponseInfo> response) {
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +ChannelState ConnectTestingEventInterface::OnSSLCertificateError(
|
| + scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks,
|
| + const GURL& url,
|
| + const SSLInfo& ssl_info,
|
| + bool fatal) {
|
| + base::MessageLoop::current()->PostTask(
|
| + FROM_HERE, base::Bind(&SSLErrorCallbacks::CancelSSLRequest,
|
| + base::Owned(ssl_error_callbacks.release()),
|
| + ERR_SSL_PROTOCOL_ERROR, &ssl_info));
|
| + return CHANNEL_ALIVE;
|
| +}
|
| +
|
| +void ConnectTestingEventInterface::QuitNestedEventLoop() {
|
| + run_loop_.Quit();
|
| +}
|
| +
|
| +// A subclass of TestNetworkDelegate that additionally implements the
|
| +// OnResolveProxy callback and records the information passed to it.
|
| +class TestNetworkDelegateWithProxyInfo : public TestNetworkDelegate {
|
| + public:
|
| + TestNetworkDelegateWithProxyInfo() {}
|
| +
|
| + struct ResolvedProxyInfo {
|
| + GURL url;
|
| + ProxyInfo proxy_info;
|
| + };
|
| +
|
| + const ResolvedProxyInfo& resolved_proxy_info() const {
|
| + return resolved_proxy_info_;
|
| + }
|
| +
|
| + protected:
|
| + void OnResolveProxy(const GURL& url,
|
| + int load_flags,
|
| + const ProxyService& proxy_service,
|
| + ProxyInfo* result) override {
|
| + resolved_proxy_info_.url = url;
|
| + resolved_proxy_info_.proxy_info = *result;
|
| + }
|
| +
|
| + private:
|
| + ResolvedProxyInfo resolved_proxy_info_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo);
|
| +};
|
| +
|
| +class WebSocketEndToEndTest : public ::testing::Test {
|
| + protected:
|
| + WebSocketEndToEndTest()
|
| + : event_interface_(new ConnectTestingEventInterface),
|
| + network_delegate_(new TestNetworkDelegateWithProxyInfo),
|
| + context_(true),
|
| + channel_(make_scoped_ptr(event_interface_), &context_),
|
| + initialised_context_(false) {}
|
| +
|
| + // 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() {
|
| + context_.set_network_delegate(network_delegate_.get());
|
| + context_.Init();
|
| + initialised_context_ = true;
|
| + }
|
| +
|
| + // 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 (!initialised_context_) {
|
| + InitialiseContext();
|
| + }
|
| + std::vector<std::string> sub_protocols;
|
| + url::Origin origin("http://localhost");
|
| + channel_.SendAddChannelRequest(GURL(socket_url), sub_protocols, origin);
|
| + event_interface_->WaitForResponse();
|
| + return !event_interface_->failed();
|
| + }
|
| +
|
| + ConnectTestingEventInterface* event_interface_; // owned by channel_
|
| + scoped_ptr<TestNetworkDelegateWithProxyInfo> network_delegate_;
|
| + TestURLRequestContext context_;
|
| + WebSocketChannel channel_;
|
| + bool initialised_context_;
|
| +};
|
| +
|
| +// None of these tests work on Android.
|
| +// TODO(ricea): Make these tests work on Android. See crbug.com/441711.
|
| +#if defined(OS_ANDROID)
|
| +#define DISABLED_ON_ANDROID(test) DISABLED_##test
|
| +#else
|
| +#define DISABLED_ON_ANDROID(test) test
|
| +#endif
|
| +
|
| +// Basic test of connectivity. If this test fails, nothing else can be expected
|
| +// to work.
|
| +TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(BasicSmokeTest)) {
|
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
| + SpawnedTestServer::kLocalhost,
|
| + 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,
|
| + SpawnedTestServer::kLocalhost,
|
| + base::FilePath());
|
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
| + SpawnedTestServer::kLocalhost,
|
| + 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();
|
| + scoped_ptr<ProxyService> proxy_service(
|
| + ProxyService::CreateFixed(proxy_config));
|
| + ASSERT_TRUE(proxy_service);
|
| + context_.set_proxy_service(proxy_service.get());
|
| + EXPECT_FALSE(ConnectAndWait(ws_server.GetURL(kEchoServer)));
|
| + EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message());
|
| +}
|
| +
|
| +TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails)) {
|
| + SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
|
| + SpawnedTestServer::kLocalhost,
|
| + base::FilePath());
|
| + SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS,
|
| + SpawnedTestServer::kLocalhost,
|
| + GetWebSocketTestDataDirectory());
|
| + ASSERT_TRUE(proxy_server.StartInBackground());
|
| + ASSERT_TRUE(wss_server.StartInBackground());
|
| + ASSERT_TRUE(proxy_server.BlockUntilStarted());
|
| + ASSERT_TRUE(wss_server.BlockUntilStarted());
|
| + std::string proxy_config =
|
| + "https=" + proxy_server.host_port_pair().ToString();
|
| + scoped_ptr<ProxyService> proxy_service(
|
| + ProxyService::CreateFixed(proxy_config));
|
| + ASSERT_TRUE(proxy_service);
|
| + context_.set_proxy_service(proxy_service.get());
|
| + 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, DISABLED_ON_ANDROID(HttpsProxyUsed)) {
|
| + SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
|
| + SpawnedTestServer::kLocalhost,
|
| + base::FilePath());
|
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
|
| + SpawnedTestServer::kLocalhost,
|
| + 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() + ";" +
|
| + "http=" + proxy_server.host_port_pair().ToString();
|
| + scoped_ptr<ProxyService> proxy_service(
|
| + ProxyService::CreateFixed(proxy_config));
|
| + context_.set_proxy_service(proxy_service.get());
|
| + InitialiseContext();
|
| +
|
| + // The test server doesn't have an unauthenticated proxy mode. WebSockets
|
| + // cannot provide auth information that isn't already cached, so it's
|
| + // necessary to preflight an HTTP request to authenticate against the proxy.
|
| + std::string scheme("http");
|
| + GURL::Replacements replacements;
|
| + replacements.SetSchemeStr(scheme);
|
| + // It doesn't matter what the URL is, as long as it is an HTTP navigation.
|
| + GURL http_page =
|
| + ws_server.GetURL("connect_check.html").ReplaceComponents(replacements);
|
| + TestDelegate delegate;
|
| + delegate.set_credentials(
|
| + AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
|
| + {
|
| + scoped_ptr<URLRequest> request(
|
| + context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL));
|
| + request->Start();
|
| + // TestDelegate exits the message loop when the request completes by
|
| + // default.
|
| + base::RunLoop().Run();
|
| + EXPECT_TRUE(delegate.auth_required_called());
|
| + }
|
| +
|
| + GURL ws_url = ws_server.GetURL(kEchoServer);
|
| + EXPECT_TRUE(ConnectAndWait(ws_url));
|
| + const TestNetworkDelegateWithProxyInfo::ResolvedProxyInfo& info =
|
| + network_delegate_->resolved_proxy_info();
|
| + EXPECT_EQ(ws_url, info.url);
|
| + EXPECT_TRUE(info.proxy_info.is_http());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +} // namespace net
|
|
|