blob: 7fa67c6d6e81ef89f82f59fb55c1577cfccaf439 [file] [log] [blame]
// Copyright 2015 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/proxy/mojo_proxy_resolver_impl.h"
#include <string>
#include <vector>
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "net/base/net_errors.h"
#include "net/proxy/mock_proxy_resolver.h"
#include "net/proxy/mojo_type_converters.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h"
#include "third_party/mojo/src/mojo/public/cpp/bindings/error_handler.h"
namespace net {
namespace {
class TestRequestClient : public interfaces::ProxyResolverRequestClient,
public mojo::ErrorHandler {
public:
explicit TestRequestClient(
mojo::InterfaceRequest<interfaces::ProxyResolverRequestClient> request);
void WaitForResult();
void WaitForConnectionError();
Error error() { return error_; }
const mojo::Array<interfaces::ProxyServerPtr>& results() { return results_; }
private:
// interfaces::ProxyResolverRequestClient override.
void ReportResult(int32_t error,
mojo::Array<interfaces::ProxyServerPtr> results) override;
// mojo::ErrorHandler override.
void OnConnectionError() override;
bool done_ = false;
bool encountered_connection_error_ = false;
Error error_ = ERR_FAILED;
mojo::Array<interfaces::ProxyServerPtr> results_;
base::Closure run_loop_quit_closure_;
base::Closure connection_error_callback_;
mojo::Binding<interfaces::ProxyResolverRequestClient> binding_;
};
TestRequestClient::TestRequestClient(
mojo::InterfaceRequest<interfaces::ProxyResolverRequestClient> request)
: binding_(this, request.Pass()) {
binding_.set_error_handler(this);
}
void TestRequestClient::WaitForResult() {
if (done_)
return;
base::RunLoop run_loop;
run_loop_quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
ASSERT_TRUE(done_);
}
void TestRequestClient::WaitForConnectionError() {
if (encountered_connection_error_)
return;
base::RunLoop run_loop;
connection_error_callback_ = run_loop.QuitClosure();
run_loop.Run();
ASSERT_TRUE(encountered_connection_error_);
}
void TestRequestClient::ReportResult(
int32_t error,
mojo::Array<interfaces::ProxyServerPtr> results) {
if (!run_loop_quit_closure_.is_null()) {
run_loop_quit_closure_.Run();
}
ASSERT_FALSE(done_);
error_ = static_cast<Error>(error);
results_ = results.Pass();
done_ = true;
}
void TestRequestClient::OnConnectionError() {
if (!connection_error_callback_.is_null())
connection_error_callback_.Run();
encountered_connection_error_ = true;
}
class SetPacScriptClient {
public:
base::Callback<void(int32_t)> CreateCallback();
Error error() { return error_; }
private:
void ReportResult(int32_t error);
Error error_ = ERR_FAILED;
};
base::Callback<void(int32_t)> SetPacScriptClient::CreateCallback() {
return base::Bind(&SetPacScriptClient::ReportResult, base::Unretained(this));
}
void SetPacScriptClient::ReportResult(int32_t error) {
error_ = static_cast<Error>(error);
}
class CallbackMockProxyResolver : public MockAsyncProxyResolverExpectsBytes {
public:
CallbackMockProxyResolver() {}
~CallbackMockProxyResolver() override;
// MockAsyncProxyResolverExpectsBytes overrides.
int GetProxyForURL(const GURL& url,
ProxyInfo* results,
const net::CompletionCallback& callback,
RequestHandle* request_handle,
const BoundNetLog& net_log) override;
void CancelRequest(RequestHandle request_handle) override;
int SetPacScript(const scoped_refptr<ProxyResolverScriptData>& script_data,
const net::CompletionCallback& callback) override;
// Wait until the mock resolver has received a CancelRequest call.
void WaitForCancel();
// Queues a proxy result to be returned synchronously.
void ReturnProxySynchronously(const ProxyInfo& result);
// Queues a SetPacScript to be completed synchronously.
void CompleteSetPacScriptSynchronously();
private:
base::Closure cancel_callback_;
scoped_ptr<ProxyInfo> sync_result_;
bool set_pac_script_sync_ = false;
};
CallbackMockProxyResolver::~CallbackMockProxyResolver() {
EXPECT_TRUE(pending_requests().empty());
}
int CallbackMockProxyResolver::GetProxyForURL(
const GURL& url,
ProxyInfo* results,
const net::CompletionCallback& callback,
RequestHandle* request_handle,
const BoundNetLog& net_log) {
if (sync_result_) {
*results = *sync_result_;
sync_result_.reset();
return OK;
}
return MockAsyncProxyResolverExpectsBytes::GetProxyForURL(
url, results, callback, request_handle, net_log);
}
void CallbackMockProxyResolver::CancelRequest(RequestHandle request_handle) {
MockAsyncProxyResolverExpectsBytes::CancelRequest(request_handle);
if (!cancel_callback_.is_null()) {
cancel_callback_.Run();
cancel_callback_.Reset();
}
}
int CallbackMockProxyResolver::SetPacScript(
const scoped_refptr<ProxyResolverScriptData>& script_data,
const net::CompletionCallback& callback) {
if (set_pac_script_sync_) {
set_pac_script_sync_ = false;
return OK;
}
return MockAsyncProxyResolverExpectsBytes::SetPacScript(script_data,
callback);
}
void CallbackMockProxyResolver::WaitForCancel() {
while (cancelled_requests().empty()) {
base::RunLoop run_loop;
cancel_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
}
void CallbackMockProxyResolver::ReturnProxySynchronously(
const ProxyInfo& result) {
sync_result_.reset(new ProxyInfo(result));
}
void CallbackMockProxyResolver::CompleteSetPacScriptSynchronously() {
set_pac_script_sync_ = true;
}
void Fail(int32_t error) {
FAIL() << "Unexpected callback with error: " << error;
}
} // namespace
class MojoProxyResolverImplTest : public testing::Test {
protected:
void SetUp() override {
scoped_ptr<CallbackMockProxyResolver> mock_resolver(
new CallbackMockProxyResolver);
mock_proxy_resolver_ = mock_resolver.get();
resolver_.reset(new MojoProxyResolverImpl(mock_resolver.Pass()));
}
CallbackMockProxyResolver* mock_proxy_resolver_;
scoped_ptr<interfaces::ProxyResolver> resolver_;
};
TEST_F(MojoProxyResolverImplTest, GetProxyForUrl) {
interfaces::ProxyResolverRequestClientPtr client_ptr;
TestRequestClient client(mojo::GetProxy(&client_ptr));
resolver_->GetProxyForUrl("http://example.com", client_ptr.Pass());
ASSERT_EQ(1u, mock_proxy_resolver_->pending_requests().size());
scoped_refptr<MockAsyncProxyResolverBase::Request> request =
mock_proxy_resolver_->pending_requests()[0];
EXPECT_EQ(GURL("http://example.com"), request->url());
request->results()->UsePacString(
"PROXY proxy.example.com:1; "
"SOCKS4 socks4.example.com:2; "
"SOCKS5 socks5.example.com:3; "
"HTTPS https.example.com:4; "
"QUIC quic.example.com:65000; "
"DIRECT");
request->CompleteNow(OK);
client.WaitForResult();
EXPECT_EQ(net::OK, client.error());
std::vector<net::ProxyServer> servers =
client.results().To<std::vector<net::ProxyServer>>();
ASSERT_EQ(6u, servers.size());
EXPECT_EQ(ProxyServer::SCHEME_HTTP, servers[0].scheme());
EXPECT_EQ("proxy.example.com", servers[0].host_port_pair().host());
EXPECT_EQ(1, servers[0].host_port_pair().port());
EXPECT_EQ(ProxyServer::SCHEME_SOCKS4, servers[1].scheme());
EXPECT_EQ("socks4.example.com", servers[1].host_port_pair().host());
EXPECT_EQ(2, servers[1].host_port_pair().port());
EXPECT_EQ(ProxyServer::SCHEME_SOCKS5, servers[2].scheme());
EXPECT_EQ("socks5.example.com", servers[2].host_port_pair().host());
EXPECT_EQ(3, servers[2].host_port_pair().port());
EXPECT_EQ(ProxyServer::SCHEME_HTTPS, servers[3].scheme());
EXPECT_EQ("https.example.com", servers[3].host_port_pair().host());
EXPECT_EQ(4, servers[3].host_port_pair().port());
EXPECT_EQ(ProxyServer::SCHEME_QUIC, servers[4].scheme());
EXPECT_EQ("quic.example.com", servers[4].host_port_pair().host());
EXPECT_EQ(65000, servers[4].host_port_pair().port());
EXPECT_EQ(ProxyServer::SCHEME_DIRECT, servers[5].scheme());
}
TEST_F(MojoProxyResolverImplTest, GetProxyForUrlSynchronous) {
interfaces::ProxyResolverRequestClientPtr client_ptr;
TestRequestClient client(mojo::GetProxy(&client_ptr));
ProxyInfo result;
result.UsePacString("DIRECT");
mock_proxy_resolver_->ReturnProxySynchronously(result);
resolver_->GetProxyForUrl("http://example.com", client_ptr.Pass());
ASSERT_EQ(0u, mock_proxy_resolver_->pending_requests().size());
client.WaitForResult();
EXPECT_EQ(net::OK, client.error());
std::vector<net::ProxyServer> proxy_servers =
client.results().To<std::vector<net::ProxyServer>>();
ASSERT_EQ(1u, proxy_servers.size());
net::ProxyServer& server = proxy_servers[0];
EXPECT_TRUE(server.is_direct());
}
TEST_F(MojoProxyResolverImplTest, GetProxyForUrlFailure) {
interfaces::ProxyResolverRequestClientPtr client_ptr;
TestRequestClient client(mojo::GetProxy(&client_ptr));
resolver_->GetProxyForUrl("http://example.com", client_ptr.Pass());
ASSERT_EQ(1u, mock_proxy_resolver_->pending_requests().size());
scoped_refptr<MockAsyncProxyResolverBase::Request> request =
mock_proxy_resolver_->pending_requests()[0];
EXPECT_EQ(GURL("http://example.com"), request->url());
request->CompleteNow(ERR_FAILED);
client.WaitForResult();
EXPECT_EQ(ERR_FAILED, client.error());
std::vector<net::ProxyServer> proxy_servers =
client.results().To<std::vector<net::ProxyServer>>();
EXPECT_TRUE(proxy_servers.empty());
}
TEST_F(MojoProxyResolverImplTest, GetProxyForUrlMultiple) {
interfaces::ProxyResolverRequestClientPtr client_ptr1;
TestRequestClient client1(mojo::GetProxy(&client_ptr1));
interfaces::ProxyResolverRequestClientPtr client_ptr2;
TestRequestClient client2(mojo::GetProxy(&client_ptr2));
resolver_->GetProxyForUrl("http://example.com", client_ptr1.Pass());
resolver_->GetProxyForUrl("https://example.com", client_ptr2.Pass());
ASSERT_EQ(2u, mock_proxy_resolver_->pending_requests().size());
scoped_refptr<MockAsyncProxyResolverBase::Request> request1 =
mock_proxy_resolver_->pending_requests()[0];
EXPECT_EQ(GURL("http://example.com"), request1->url());
scoped_refptr<MockAsyncProxyResolverBase::Request> request2 =
mock_proxy_resolver_->pending_requests()[1];
EXPECT_EQ(GURL("https://example.com"), request2->url());
request1->results()->UsePacString("HTTPS proxy.example.com:12345");
request1->CompleteNow(OK);
request2->results()->UsePacString("SOCKS5 another-proxy.example.com:6789");
request2->CompleteNow(OK);
client1.WaitForResult();
client2.WaitForResult();
EXPECT_EQ(net::OK, client1.error());
std::vector<net::ProxyServer> proxy_servers1 =
client1.results().To<std::vector<net::ProxyServer>>();
ASSERT_EQ(1u, proxy_servers1.size());
net::ProxyServer& server1 = proxy_servers1[0];
EXPECT_EQ(ProxyServer::SCHEME_HTTPS, server1.scheme());
EXPECT_EQ("proxy.example.com", server1.host_port_pair().host());
EXPECT_EQ(12345, server1.host_port_pair().port());
EXPECT_EQ(net::OK, client2.error());
std::vector<net::ProxyServer> proxy_servers2 =
client2.results().To<std::vector<net::ProxyServer>>();
ASSERT_EQ(1u, proxy_servers1.size());
net::ProxyServer& server2 = proxy_servers2[0];
EXPECT_EQ(ProxyServer::SCHEME_SOCKS5, server2.scheme());
EXPECT_EQ("another-proxy.example.com", server2.host_port_pair().host());
EXPECT_EQ(6789, server2.host_port_pair().port());
}
TEST_F(MojoProxyResolverImplTest, SetPacScript) {
SetPacScriptClient client;
resolver_->SetPacScript("pac script", client.CreateCallback());
MockAsyncProxyResolverBase::SetPacScriptRequest* request =
mock_proxy_resolver_->pending_set_pac_script_request();
ASSERT_TRUE(request);
EXPECT_EQ("pac script", base::UTF16ToUTF8(request->script_data()->utf16()));
request->CompleteNow(OK);
EXPECT_EQ(OK, client.error());
}
TEST_F(MojoProxyResolverImplTest, SetPacScriptSynchronous) {
SetPacScriptClient client;
mock_proxy_resolver_->CompleteSetPacScriptSynchronously();
resolver_->SetPacScript("pac script", client.CreateCallback());
EXPECT_FALSE(mock_proxy_resolver_->pending_set_pac_script_request());
EXPECT_EQ(OK, client.error());
}
TEST_F(MojoProxyResolverImplTest, SetPacScriptMultiple) {
SetPacScriptClient client1;
SetPacScriptClient client2;
resolver_->SetPacScript("pac script", client1.CreateCallback());
resolver_->SetPacScript("a different pac script", client2.CreateCallback());
MockAsyncProxyResolverBase::SetPacScriptRequest* request =
mock_proxy_resolver_->pending_set_pac_script_request();
ASSERT_TRUE(request);
EXPECT_EQ("pac script", base::UTF16ToUTF8(request->script_data()->utf16()));
request->CompleteNow(OK);
EXPECT_EQ(OK, client1.error());
request = mock_proxy_resolver_->pending_set_pac_script_request();
ASSERT_TRUE(request);
EXPECT_EQ("a different pac script",
base::UTF16ToUTF8(request->script_data()->utf16()));
request->CompleteNow(ERR_PAC_SCRIPT_FAILED);
EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, client2.error());
}
TEST_F(MojoProxyResolverImplTest, DestroyClient) {
interfaces::ProxyResolverRequestClientPtr client_ptr;
scoped_ptr<TestRequestClient> client(
new TestRequestClient(mojo::GetProxy(&client_ptr)));
resolver_->GetProxyForUrl("http://example.com", client_ptr.Pass());
ASSERT_EQ(1u, mock_proxy_resolver_->pending_requests().size());
scoped_refptr<MockAsyncProxyResolverBase::Request> request =
mock_proxy_resolver_->pending_requests()[0];
EXPECT_EQ(GURL("http://example.com"), request->url());
request->results()->UsePacString("PROXY proxy.example.com:8080");
client.reset();
mock_proxy_resolver_->WaitForCancel();
}
TEST_F(MojoProxyResolverImplTest, DestroyService) {
interfaces::ProxyResolverRequestClientPtr client_ptr;
TestRequestClient client(mojo::GetProxy(&client_ptr));
resolver_->GetProxyForUrl("http://example.com", client_ptr.Pass());
resolver_->SetPacScript("pac script", base::Bind(&Fail));
ASSERT_EQ(1u, mock_proxy_resolver_->pending_requests().size());
scoped_refptr<MockAsyncProxyResolverBase::Request> request =
mock_proxy_resolver_->pending_requests()[0];
resolver_.reset();
client.WaitForConnectionError();
}
} // namespace net