blob: 83bda4a2a8b008700272406ccb291cb953baadbf [file] [log] [blame]
// 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.
#include "content/browser/appcache/appcache_service_impl.h"
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/check_op.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/thread_annotations.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/services/storage/public/cpp/quota_client_callback_wrapper.h"
#include "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_disk_cache_ops.h"
#include "content/browser/appcache/appcache_entry.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_host.h"
#include "content/browser/appcache/appcache_navigation_handle.h"
#include "content/browser/appcache/appcache_policy.h"
#include "content/browser/appcache/appcache_quota_client.h"
#include "content/browser/appcache/appcache_response_info.h"
#include "content/browser/appcache/appcache_storage_impl.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/completion_once_callback.h"
#include "net/base/io_buffer.h"
#include "storage/browser/quota/quota_client_type.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
namespace content {
// AsyncHelper -------
class AppCacheServiceImpl::AsyncHelper : public AppCacheStorage::Delegate {
public:
AsyncHelper(AppCacheServiceImpl* service,
net::CompletionOnceCallback callback)
: service_(service), callback_(std::move(callback)) {
service_->pending_helpers_[this] = base::WrapUnique(this);
}
~AsyncHelper() override {
if (service_) {
service_->pending_helpers_[this].release();
service_->pending_helpers_.erase(this);
}
}
virtual void Start() = 0;
virtual void Cancel();
protected:
void CallCallback(int rv) {
if (callback_) {
// Defer to guarantee async completion.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), rv));
}
DCHECK(!callback_);
}
AppCacheServiceImpl* service_;
net::CompletionOnceCallback callback_;
};
void AppCacheServiceImpl::AsyncHelper::Cancel() {
if (!callback_.is_null()) {
std::move(callback_).Run(net::ERR_ABORTED);
}
service_->storage()->CancelDelegateCallbacks(this);
service_ = nullptr;
}
// DeleteHelper -------
class AppCacheServiceImpl::DeleteHelper : public AsyncHelper {
public:
DeleteHelper(AppCacheServiceImpl* service,
const GURL& manifest_url,
net::CompletionOnceCallback callback)
: AsyncHelper(service, std::move(callback)),
manifest_url_(manifest_url) {}
void Start() override {
service_->storage()->LoadOrCreateGroup(manifest_url_, this);
}
private:
// AppCacheStorage::Delegate implementation.
void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) override;
void OnGroupMadeObsolete(AppCacheGroup* group,
bool success,
int response_code) override;
GURL manifest_url_;
DISALLOW_COPY_AND_ASSIGN(DeleteHelper);
};
void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded(
AppCacheGroup* group, const GURL& manifest_url) {
if (group) {
group->set_being_deleted(true);
group->CancelUpdate();
service_->storage()->MakeGroupObsolete(group, this, 0);
} else {
CallCallback(net::ERR_FAILED);
delete this;
}
}
void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete(
AppCacheGroup* group,
bool success,
int response_code) {
CallCallback(success ? net::OK : net::ERR_FAILED);
delete this;
}
// DeleteOriginHelper -------
class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper {
public:
DeleteOriginHelper(AppCacheServiceImpl* service,
const url::Origin& origin,
net::CompletionOnceCallback callback)
: AsyncHelper(service, std::move(callback)),
origin_(origin),
num_caches_to_delete_(0),
successes_(0),
failures_(0) {}
void Start() override {
// We start by listing all caches, continues in OnAllInfo().
service_->storage()->GetAllInfo(this);
}
private:
// AppCacheStorage::Delegate implementation.
void OnAllInfo(AppCacheInfoCollection* collection) override;
void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) override;
void OnGroupMadeObsolete(AppCacheGroup* group,
bool success,
int response_code) override;
void CacheCompleted(bool success);
url::Origin origin_;
int num_caches_to_delete_;
int successes_;
int failures_;
DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper);
};
void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo(
AppCacheInfoCollection* collection) {
if (!collection) {
// Failed to get a listing.
CallCallback(net::ERR_FAILED);
delete this;
return;
}
auto found = collection->infos_by_origin.find(origin_);
if (found == collection->infos_by_origin.end() || found->second.empty()) {
// No caches for this origin.
CallCallback(net::OK);
delete this;
return;
}
// We have some caches to delete.
const std::vector<blink::mojom::AppCacheInfo>& caches_to_delete =
found->second;
successes_ = 0;
failures_ = 0;
num_caches_to_delete_ = static_cast<int>(caches_to_delete.size());
for (const auto& cache : caches_to_delete) {
service_->storage()->LoadOrCreateGroup(cache.manifest_url, this);
}
}
void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded(
AppCacheGroup* group, const GURL& manifest_url) {
if (group) {
group->set_being_deleted(true);
group->CancelUpdate();
service_->storage()->MakeGroupObsolete(group, this, 0);
} else {
CacheCompleted(false);
}
}
void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete(
AppCacheGroup* group,
bool success,
int response_code) {
CacheCompleted(success);
}
void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) {
if (success)
++successes_;
else
++failures_;
if ((successes_ + failures_) < num_caches_to_delete_)
return;
CallCallback(!failures_ ? net::OK : net::ERR_FAILED);
delete this;
}
// GetInfoHelper -------
class AppCacheServiceImpl::GetInfoHelper : AsyncHelper {
public:
GetInfoHelper(AppCacheServiceImpl* service,
AppCacheInfoCollection* collection,
net::CompletionOnceCallback callback)
: AsyncHelper(service, std::move(callback)), collection_(collection) {}
void Start() override { service_->storage()->GetAllInfo(this); }
private:
// AppCacheStorage::Delegate implementation.
void OnAllInfo(AppCacheInfoCollection* collection) override;
scoped_refptr<AppCacheInfoCollection> collection_;
DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
};
void AppCacheServiceImpl::GetInfoHelper::OnAllInfo(
AppCacheInfoCollection* collection) {
if (collection)
collection->infos_by_origin.swap(collection_->infos_by_origin);
CallCallback(collection ? net::OK : net::ERR_FAILED);
delete this;
}
// CheckResponseHelper -------
class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper {
public:
CheckResponseHelper(AppCacheServiceImpl* service,
const GURL& manifest_url,
int64_t cache_id,
int64_t response_id)
: AsyncHelper(service, net::CompletionOnceCallback()),
manifest_url_(manifest_url),
cache_id_(cache_id),
response_id_(response_id),
kIOBufferSize(32 * 1024),
expected_total_size_(0),
amount_headers_read_(0),
amount_data_read_(0) {}
void Start() override {
service_->storage()->LoadOrCreateGroup(manifest_url_, this);
}
void Cancel() override {
response_reader_.reset();
AsyncHelper::Cancel();
}
private:
void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) override;
void OnReadInfoComplete(int result);
void OnReadDataComplete(int result);
// Inputs describing what to check.
GURL manifest_url_;
int64_t cache_id_;
int64_t response_id_;
// Internals used to perform the checks.
const int kIOBufferSize;
scoped_refptr<AppCache> cache_;
std::unique_ptr<AppCacheResponseReader> response_reader_;
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_;
scoped_refptr<net::IOBuffer> data_buffer_;
int64_t expected_total_size_;
int amount_headers_read_;
int amount_data_read_;
DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper);
};
void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded(
AppCacheGroup* group, const GURL& manifest_url) {
DCHECK_EQ(manifest_url_, manifest_url);
if (!group || !group->newest_complete_cache() || group->is_being_deleted() ||
group->is_obsolete()) {
delete this;
return;
}
cache_ = group->newest_complete_cache();
const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_);
if (!entry) {
if (cache_->cache_id() == cache_id_) {
service_->DeleteAppCacheGroup(manifest_url_,
net::CompletionOnceCallback());
}
delete this;
return;
}
// Verify that we can read the response info and data.
expected_total_size_ = entry->response_size();
response_reader_ =
service_->storage()->CreateResponseReader(manifest_url_, response_id_);
info_buffer_ = base::MakeRefCounted<HttpResponseInfoIOBuffer>();
response_reader_->ReadInfo(
info_buffer_.get(),
base::BindOnce(&CheckResponseHelper::OnReadInfoComplete,
base::Unretained(this)));
}
void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) {
if (result < 0) {
service_->DeleteAppCacheGroup(manifest_url_, net::CompletionOnceCallback());
delete this;
return;
}
amount_headers_read_ = result;
// Start reading the data.
data_buffer_ = base::MakeRefCounted<net::IOBuffer>(kIOBufferSize);
response_reader_->ReadData(
data_buffer_.get(), kIOBufferSize,
base::BindOnce(&CheckResponseHelper::OnReadDataComplete,
base::Unretained(this)));
}
void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) {
if (result > 0) {
// Keep reading until we've read thru everything or failed to read.
amount_data_read_ += result;
response_reader_->ReadData(
data_buffer_.get(), kIOBufferSize,
base::BindOnce(&CheckResponseHelper::OnReadDataComplete,
base::Unretained(this)));
return;
}
// TODO(pwnall): Deleted histograms show that some of the checks below
// (incomplete headers and incomplete responses) are never hit
// in production. They are covered by unit tests. Figure out if
// the predicates should be converted into DCHECKs.
if (result != 0 || amount_data_read_ != info_buffer_->response_data_size ||
expected_total_size_ != amount_data_read_ + amount_headers_read_) {
service_->DeleteAppCacheGroup(manifest_url_, net::CompletionOnceCallback());
}
delete this;
}
// AppCacheStorageReference ------
AppCacheStorageReference::AppCacheStorageReference(
std::unique_ptr<AppCacheStorage> storage)
: storage_(std::move(storage)) {}
AppCacheStorageReference::~AppCacheStorageReference() = default;
// QuotaClientHolder -------
// Lives on the UI thread, manages an AppCacheQuotaClient on the IO thread.
class AppCacheServiceImpl::QuotaClientHolder
: public base::RefCountedDeleteOnSequence<QuotaClientHolder> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
explicit QuotaClientHolder();
QuotaClientHolder(QuotaClientHolder&) = delete;
QuotaClientHolder& operator=(QuotaClientHolder&) = delete;
void Initialize(scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
base::WeakPtr<AppCacheServiceImpl> appcache_service);
void NotifyStorageReady();
private:
friend class base::RefCountedDeleteOnSequence<QuotaClientHolder>;
friend class base::DeleteHelper<QuotaClientHolder>;
~QuotaClientHolder();
void InitializeOnIOThread(
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
base::WeakPtr<AppCacheServiceImpl> appcache_service);
void NotifyStorageReadyOnIOThread();
// The client must only be accessed on the IO thread.
//
// Can be null in tests that don't set up a QuotaManager. Always non-null in
// shipping code.
std::unique_ptr<AppCacheQuotaClient> quota_client_;
// The client callback wrapper must only be accessed on the IO thread.
std::unique_ptr<storage::QuotaClientCallbackWrapper> quota_client_wrapper_;
// The client receiver must only be accessed on the IO thread.
std::unique_ptr<mojo::Receiver<storage::mojom::QuotaClient>>
quota_client_receiver_;
};
AppCacheServiceImpl::QuotaClientHolder::QuotaClientHolder()
: base::RefCountedDeleteOnSequence<QuotaClientHolder>(
GetIOThreadTaskRunner({})) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
AppCacheServiceImpl::QuotaClientHolder::~QuotaClientHolder() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
quota_client_receiver_.reset();
quota_client_wrapper_.reset();
if (quota_client_) {
quota_client_->NotifyServiceDestroyed();
quota_client_.reset();
}
}
void AppCacheServiceImpl::QuotaClientHolder::Initialize(
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
base::WeakPtr<AppCacheServiceImpl> appcache_service) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&QuotaClientHolder::InitializeOnIOThread,
base::RetainedRef(this), std::move(quota_manager_proxy),
std::move(appcache_service)));
}
void AppCacheServiceImpl::QuotaClientHolder::NotifyStorageReady() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&QuotaClientHolder::NotifyStorageReadyOnIOThread,
base::RetainedRef(this)));
}
void AppCacheServiceImpl::QuotaClientHolder::InitializeOnIOThread(
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
base::WeakPtr<AppCacheServiceImpl> appcache_service) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Some tests don't set up a QuotaManager.
if (!quota_manager_proxy.get())
return;
quota_client_ =
std::make_unique<AppCacheQuotaClient>(std::move(appcache_service));
quota_client_wrapper_ = std::make_unique<storage::QuotaClientCallbackWrapper>(
quota_client_.get());
mojo::PendingRemote<storage::mojom::QuotaClient> quota_client_remote;
quota_client_receiver_ =
std::make_unique<mojo::Receiver<storage::mojom::QuotaClient>>(
quota_client_wrapper_.get(),
quota_client_remote.InitWithNewPipeAndPassReceiver());
quota_client_receiver_->set_disconnect_handler(
base::BindOnce(&AppCacheQuotaClient::OnMojoDisconnect,
base::Unretained(quota_client_.get())));
quota_manager_proxy->RegisterClient(std::move(quota_client_remote),
storage::QuotaClientType::kAppcache,
{blink::mojom::StorageType::kTemporary});
}
void AppCacheServiceImpl::QuotaClientHolder::NotifyStorageReadyOnIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (quota_client_)
quota_client_->NotifyStorageReady();
}
// AppCacheServiceImpl -------
AppCacheServiceImpl::AppCacheServiceImpl(
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
base::WeakPtr<StoragePartitionImpl> partition)
: db_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
appcache_policy_(nullptr),
quota_manager_proxy_(std::move(quota_manager_proxy)),
force_keep_session_state_(false),
partition_(std::move(partition)),
quota_client_holder_(base::MakeRefCounted<QuotaClientHolder>()) {
quota_client_holder_->Initialize(quota_manager_proxy_, AsWeakPtr());
}
AppCacheServiceImpl::~AppCacheServiceImpl() {
hosts_.clear();
for (auto& observer : observers_)
observer.OnServiceDestructionImminent(this);
for (auto& helper : pending_helpers_)
helper.first->Cancel();
pending_helpers_.clear();
// Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
// (special_storage_policy_).
storage_.reset();
}
void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!storage_.get());
cache_directory_ = cache_directory;
auto storage = std::make_unique<AppCacheStorageImpl>(this);
storage->Initialize(cache_directory, db_task_runner_);
storage_ = std::move(storage);
}
void AppCacheServiceImpl::ScheduleReinitialize() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (reinit_timer_.IsRunning())
return;
// Reinitialization only happens when corruption has been noticed.
// We don't want to thrash the disk but we also don't want to
// leave the appcache disabled for an indefinite period of time. Some
// users never shutdown the browser.
constexpr base::TimeDelta kZeroDelta;
constexpr base::TimeDelta kOneHour = base::Hours(1);
constexpr base::TimeDelta kThirtySeconds = base::Seconds(30);
// If the system managed to stay up for long enough, reset the
// delay so a new failure won't incur a long wait to get going again.
base::TimeDelta up_time = base::Time::Now() - last_reinit_time_;
if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour)
next_reinit_delay_ = kZeroDelta;
reinit_timer_.Start(FROM_HERE, next_reinit_delay_,
this, &AppCacheServiceImpl::Reinitialize);
// Adjust the delay for next time.
base::TimeDelta increment = std::max(kThirtySeconds, next_reinit_delay_);
next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour);
}
void AppCacheServiceImpl::Reinitialize() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null());
last_reinit_time_ = base::Time::Now();
// Inform observers of about this and give them a chance to
// defer deletion of the old storage object.
auto old_storage_ref =
base::MakeRefCounted<AppCacheStorageReference>(std::move(storage_));
for (auto& observer : observers_)
observer.OnServiceReinitialized(old_storage_ref.get());
Initialize(cache_directory_);
}
void AppCacheServiceImpl::NotifyStorageReady() {
quota_client_holder_->NotifyStorageReady();
}
void AppCacheServiceImpl::GetAllAppCacheInfo(
AppCacheInfoCollection* collection,
net::CompletionOnceCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(collection);
GetInfoHelper* helper =
new GetInfoHelper(this, collection, std::move(callback));
helper->Start();
}
void AppCacheServiceImpl::DeleteAppCacheGroup(
const GURL& manifest_url,
net::CompletionOnceCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DeleteHelper* helper =
new DeleteHelper(this, manifest_url, std::move(callback));
helper->Start();
}
void AppCacheServiceImpl::DeleteAppCachesForOrigin(
const url::Origin& origin,
net::CompletionOnceCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DeleteOriginHelper* helper =
new DeleteOriginHelper(this, origin, std::move(callback));
helper->Start();
}
void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url,
int64_t cache_id,
int64_t response_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CheckResponseHelper* helper = new CheckResponseHelper(
this, manifest_url, cache_id, response_id);
helper->Start();
}
void AppCacheServiceImpl::set_special_storage_policy(
storage::SpecialStoragePolicy* policy) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
special_storage_policy_ = policy;
}
AppCacheHost* AppCacheServiceImpl::GetHost(
const base::UnguessableToken& host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = hosts_.find(host_id);
return (it != hosts_.end()) ? (it->second.get()) : nullptr;
}
bool AppCacheServiceImpl::EraseHost(const base::UnguessableToken& host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return (hosts_.erase(host_id) != 0);
}
void AppCacheServiceImpl::RegisterHost(
mojo::PendingReceiver<blink::mojom::AppCacheHost> host_receiver,
mojo::PendingRemote<blink::mojom::AppCacheFrontend> frontend_remote,
const base::UnguessableToken& host_id,
int32_t render_frame_id,
int process_id,
ChildProcessSecurityPolicyImpl::Handle security_policy_handle,
mojo::ReportBadMessageCallback bad_message_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (GetHost(host_id)) {
std::move(bad_message_callback).Run("ACSI_REGISTER");
return;
}
// The AppCacheHost could have been precreated in which case we want to
// register it with the backend here.
std::unique_ptr<AppCacheHost> host =
AppCacheNavigationHandle::TakePrecreatedHost(host_id);
if (host) {
// Switch the frontend proxy so that the host can make IPC calls from
// here on.
host->set_frontend(std::move(frontend_remote), render_frame_id);
} else {
host = std::make_unique<AppCacheHost>(host_id, process_id, render_frame_id,
std::move(security_policy_handle),
std::move(frontend_remote), this);
}
host->BindReceiver(std::move(host_receiver));
hosts_.emplace(std::piecewise_construct, std::forward_as_tuple(host_id),
std::forward_as_tuple(std::move(host)));
}
} // namespace content