| // 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/run_loop.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<scoped_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 scoped_ptr<net::URLRequestJobFactory::ProtocolHandler> Create() { |
| return make_scoped_ptr(new 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) { |
| scoped_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), make_scoped_ptr(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_; |
| scoped_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(); |
| |
| ResourceController* driver_as_resource_controller = driver_.get(); |
| driver_as_resource_controller->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(), 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, |
| scoped_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 |