blob: 7543d13393b0207ee82bec94f5a5a8f4918ddf2c [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 "content/browser/loader/async_revalidation_driver.h"
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "ipc/ipc_message.h"
#include "net/base/net_errors.h"
#include "net/base/network_delegate_impl.h"
#include "net/base/request_priority.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/url_request/url_request_job_factory.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
// Dummy implementation of ResourceThrottle, an instance of which is needed to
// initialize AsyncRevalidationDriver.
class ResourceThrottleStub : public ResourceThrottle {
public:
ResourceThrottleStub() {}
// If true, defers the request in WillStartRequest.
void set_defer_request_on_will_start_request(
bool defer_request_on_will_start_request) {
defer_request_on_will_start_request_ = defer_request_on_will_start_request;
}
// ResourceThrottler implementation:
void WillStartRequest(bool* defer) override {
*defer = defer_request_on_will_start_request_;
}
const char* GetNameForLogging() const override {
return "ResourceThrottleStub";
}
private:
bool defer_request_on_will_start_request_ = false;
DISALLOW_COPY_AND_ASSIGN(ResourceThrottleStub);
};
// There are multiple layers of boilerplate needed to use a URLRequestTestJob
// subclass. Subclasses of AsyncRevalidationDriverTest can use
// BindCreateProtocolHandlerCallback() to bypass most of that boilerplate.
using CreateProtocolHandlerCallback = base::Callback<
std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler>()>;
template <typename T>
CreateProtocolHandlerCallback BindCreateProtocolHandlerCallback() {
static_assert(std::is_base_of<net::URLRequestJob, T>::value,
"Template argument to BindCreateProtocolHandlerCallback() must "
"be a subclass of URLRequestJob.");
class TemplatedProtocolHandler
: public net::URLRequestJobFactory::ProtocolHandler {
public:
static std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler>
Create() {
return base::MakeUnique<TemplatedProtocolHandler>();
}
// URLRequestJobFactory::ProtocolHandler implementation:
net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
return new T(request, network_delegate);
}
};
return base::Bind(&TemplatedProtocolHandler::Create);
}
// An implementation of NetworkDelegate that captures the status of the last
// URLRequest to be destroyed.
class StatusCapturingNetworkDelegate : public net::NetworkDelegateImpl {
public:
const net::URLRequestStatus& last_status() { return last_status_; }
private:
// net::NetworkDelegate implementation.
void OnURLRequestDestroyed(net::URLRequest* request) override {
last_status_ = request->status();
}
net::URLRequestStatus last_status_;
};
class AsyncRevalidationDriverTest : public testing::Test {
protected:
// Constructor for test fixtures that subclass this one.
AsyncRevalidationDriverTest(
const CreateProtocolHandlerCallback& create_protocol_handler_callback)
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
create_protocol_handler_callback_(create_protocol_handler_callback),
raw_ptr_resource_throttle_(nullptr),
raw_ptr_request_(nullptr) {
test_url_request_context_.set_job_factory(&job_factory_);
test_url_request_context_.set_network_delegate(&network_delegate_);
}
// Constructor for tests that use this fixture directly.
AsyncRevalidationDriverTest()
: AsyncRevalidationDriverTest(
base::Bind(net::URLRequestTestJob::CreateProtocolHandler)) {}
bool async_revalidation_complete_called() const {
return async_revalidation_complete_called_;
}
const net::URLRequestStatus& last_status() {
return network_delegate_.last_status();
}
void SetUpAsyncRevalidationDriverWithRequestToUrl(const GURL& url) {
std::unique_ptr<net::URLRequest> request(
test_url_request_context_.CreateRequest(url, net::DEFAULT_PRIORITY,
nullptr /* delegate */));
raw_ptr_request_ = request.get();
raw_ptr_resource_throttle_ = new ResourceThrottleStub();
// This use of base::Unretained() is safe because |driver_|, and the closure
// passed to it, will be destroyed before this object is.
driver_.reset(new AsyncRevalidationDriver(
std::move(request), base::WrapUnique(raw_ptr_resource_throttle_),
base::Bind(&AsyncRevalidationDriverTest::OnAsyncRevalidationComplete,
base::Unretained(this))));
}
void SetUpAsyncRevalidationDriverWithDefaultRequest() {
SetUpAsyncRevalidationDriverWithRequestToUrl(
net::URLRequestTestJob::test_url_1());
}
void SetUp() override {
job_factory_.SetProtocolHandler("test",
create_protocol_handler_callback_.Run());
SetUpAsyncRevalidationDriverWithDefaultRequest();
}
void OnAsyncRevalidationComplete() {
EXPECT_FALSE(async_revalidation_complete_called_);
async_revalidation_complete_called_ = true;
driver_.reset();
}
TestBrowserThreadBundle thread_bundle_;
net::URLRequestJobFactoryImpl job_factory_;
net::TestURLRequestContext test_url_request_context_;
StatusCapturingNetworkDelegate network_delegate_;
CreateProtocolHandlerCallback create_protocol_handler_callback_;
// The AsyncRevalidationDriver owns the URLRequest and the ResourceThrottle.
ResourceThrottleStub* raw_ptr_resource_throttle_;
net::URLRequest* raw_ptr_request_;
std::unique_ptr<AsyncRevalidationDriver> driver_;
bool async_revalidation_complete_called_ = false;
};
TEST_F(AsyncRevalidationDriverTest, NormalRequestCompletes) {
driver_->StartRequest();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(async_revalidation_complete_called());
}
// Verifies that request that should be deferred at start is deferred.
TEST_F(AsyncRevalidationDriverTest, DeferOnStart) {
raw_ptr_resource_throttle_->set_defer_request_on_will_start_request(true);
driver_->StartRequest();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(raw_ptr_request_->is_pending());
EXPECT_FALSE(async_revalidation_complete_called());
}
// Verifies that resuming a deferred request works. Assumes that DeferOnStart
// passes.
TEST_F(AsyncRevalidationDriverTest, ResumeDeferredRequestWorks) {
raw_ptr_resource_throttle_->set_defer_request_on_will_start_request(true);
driver_->StartRequest();
base::RunLoop().RunUntilIdle();
ResourceThrottle::Delegate* driver_as_resource_throttle_delegate =
driver_.get();
driver_as_resource_throttle_delegate->Resume();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(async_revalidation_complete_called());
}
// Verifies that redirects are not followed.
TEST_F(AsyncRevalidationDriverTest, RedirectsAreNotFollowed) {
SetUpAsyncRevalidationDriverWithRequestToUrl(
net::URLRequestTestJob::test_url_redirect_to_url_2());
driver_->StartRequest();
while (net::URLRequestTestJob::ProcessOnePendingMessage())
base::RunLoop().RunUntilIdle();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(last_status().is_success());
EXPECT_EQ(net::ERR_ABORTED, last_status().error());
EXPECT_TRUE(async_revalidation_complete_called());
}
// A mock URLRequestJob which simulates an SSL client auth request.
class MockClientCertURLRequestJob : public net::URLRequestTestJob {
public:
MockClientCertURLRequestJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: net::URLRequestTestJob(request, network_delegate, true),
weak_factory_(this) {}
// net::URLRequestTestJob implementation:
void Start() override {
scoped_refptr<net::SSLCertRequestInfo> cert_request_info(
new net::SSLCertRequestInfo);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&MockClientCertURLRequestJob::NotifyCertificateRequested,
weak_factory_.GetWeakPtr(),
base::RetainedRef(cert_request_info)));
}
void ContinueWithCertificate(
net::X509Certificate* cert,
net::SSLPrivateKey* client_private_key) override {
ADD_FAILURE() << "Certificate supplied.";
}
void Kill() override {
weak_factory_.InvalidateWeakPtrs();
URLRequestJob::Kill();
}
private:
base::WeakPtrFactory<MockClientCertURLRequestJob> weak_factory_;
};
class AsyncRevalidationDriverClientCertTest
: public AsyncRevalidationDriverTest {
protected:
AsyncRevalidationDriverClientCertTest()
: AsyncRevalidationDriverTest(
BindCreateProtocolHandlerCallback<MockClientCertURLRequestJob>()) {}
};
// Test browser client that causes the test to fail if SelectClientCertificate()
// is called. Automatically sets itself as the browser client when constructed
// and restores the old browser client in the destructor.
class ScopedDontSelectCertificateBrowserClient
: public TestContentBrowserClient {
public:
ScopedDontSelectCertificateBrowserClient() {
old_client_ = SetBrowserClientForTesting(this);
}
~ScopedDontSelectCertificateBrowserClient() override {
SetBrowserClientForTesting(old_client_);
}
void SelectClientCertificate(
WebContents* web_contents,
net::SSLCertRequestInfo* cert_request_info,
std::unique_ptr<ClientCertificateDelegate> delegate) override {
ADD_FAILURE() << "SelectClientCertificate was called.";
}
private:
ContentBrowserClient* old_client_;
DISALLOW_COPY_AND_ASSIGN(ScopedDontSelectCertificateBrowserClient);
};
// Verifies that async revalidation requests do not attempt to provide client
// certificates.
TEST_F(AsyncRevalidationDriverClientCertTest, RequestRejected) {
// Ensure that SelectClientCertificate is not called during this test.
ScopedDontSelectCertificateBrowserClient test_client;
// Start the request and wait for it to pause.
driver_->StartRequest();
// Because TestBrowserThreadBundle only uses one real thread, this is
// sufficient to ensure that tasks posted to the "UI thread" have run.
base::RunLoop().RunUntilIdle();
// Check that the request aborted.
EXPECT_FALSE(last_status().is_success());
EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, last_status().error());
EXPECT_TRUE(async_revalidation_complete_called());
}
// A mock URLRequestJob which simulates an SSL certificate error.
class MockSSLErrorURLRequestJob : public net::URLRequestTestJob {
public:
MockSSLErrorURLRequestJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: net::URLRequestTestJob(request, network_delegate, true),
weak_factory_(this) {}
// net::URLRequestTestJob implementation:
void Start() override {
// This SSLInfo isn't really valid, but it is good enough for testing.
net::SSLInfo ssl_info;
ssl_info.SetCertError(net::ERR_CERT_DATE_INVALID);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&MockSSLErrorURLRequestJob::NotifySSLCertificateError,
weak_factory_.GetWeakPtr(), ssl_info, false));
}
void ContinueDespiteLastError() override {
ADD_FAILURE() << "ContinueDespiteLastError called.";
}
private:
base::WeakPtrFactory<MockSSLErrorURLRequestJob> weak_factory_;
};
class AsyncRevalidationDriverSSLErrorTest : public AsyncRevalidationDriverTest {
protected:
AsyncRevalidationDriverSSLErrorTest()
: AsyncRevalidationDriverTest(
BindCreateProtocolHandlerCallback<MockSSLErrorURLRequestJob>()) {}
};
// Verifies that async revalidation requests do not attempt to recover from SSL
// certificate errors.
TEST_F(AsyncRevalidationDriverSSLErrorTest, RequestWithSSLErrorRejected) {
// Start the request and wait for it to pause.
driver_->StartRequest();
base::RunLoop().RunUntilIdle();
// Check that the request has been aborted.
EXPECT_FALSE(last_status().is_success());
EXPECT_EQ(net::ERR_ABORTED, last_status().error());
EXPECT_TRUE(async_revalidation_complete_called());
}
// A URLRequestTestJob that sets |request_time| and |was_cached| on their
// response_info, and causes the test to fail if Read() is called.
class FromCacheURLRequestJob : public net::URLRequestTestJob {
public:
FromCacheURLRequestJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: net::URLRequestTestJob(request, network_delegate, true) {}
void GetResponseInfo(net::HttpResponseInfo* info) override {
URLRequestTestJob::GetResponseInfo(info);
info->request_time = base::Time::Now();
info->was_cached = true;
}
int ReadRawData(net::IOBuffer* buf, int buf_size) override {
ADD_FAILURE() << "ReadRawData() was called.";
return URLRequestTestJob::ReadRawData(buf, buf_size);
}
private:
~FromCacheURLRequestJob() override {}
DISALLOW_COPY_AND_ASSIGN(FromCacheURLRequestJob);
};
class AsyncRevalidationDriverFromCacheTest
: public AsyncRevalidationDriverTest {
protected:
AsyncRevalidationDriverFromCacheTest()
: AsyncRevalidationDriverTest(
BindCreateProtocolHandlerCallback<FromCacheURLRequestJob>()) {}
};
TEST_F(AsyncRevalidationDriverFromCacheTest,
CacheNotReadOnSuccessfulRevalidation) {
driver_->StartRequest();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(async_revalidation_complete_called());
}
} // namespace
} // namespace content