// 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 <functional>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_backend_impl.h"
#include "content/browser/appcache/appcache_entry.h"
#include "content/browser/appcache/appcache_executable_handler.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_policy.h"
#include "content/browser/appcache/appcache_quota_client.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/appcache_service_impl.h"
#include "content/browser/appcache/appcache_storage_impl.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "storage/browser/quota/special_storage_policy.h"
namespace content {
namespace {
void DeferredCallback(const net::CompletionCallback& callback, int rv) {
callback.Run(rv);
}
} // namespace
AppCacheInfoCollection::AppCacheInfoCollection() {}
AppCacheInfoCollection::~AppCacheInfoCollection() {}
// AsyncHelper -------
class AppCacheServiceImpl::AsyncHelper
: public AppCacheStorage::Delegate {
public:
AsyncHelper(AppCacheServiceImpl* service,
const net::CompletionCallback& callback)
: service_(service), callback_(callback) {
service_->pending_helpers_.insert(this);
}
virtual ~AsyncHelper() {
if (service_)
service_->pending_helpers_.erase(this);
}
virtual void Start() = 0;
virtual void Cancel();
protected:
void CallCallback(int rv) {
if (!callback_.is_null()) {
// Defer to guarantee async completion.
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&DeferredCallback, callback_, rv));
}
callback_.Reset();
}
AppCacheServiceImpl* service_;
net::CompletionCallback callback_;
};
void AppCacheServiceImpl::AsyncHelper::Cancel() {
if (!callback_.is_null()) {
callback_.Run(net::ERR_ABORTED);
callback_.Reset();
}
service_->storage()->CancelDelegateCallbacks(this);
service_ = NULL;
}
// CanHandleOfflineHelper -------
class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper {
public:
CanHandleOfflineHelper(
AppCacheServiceImpl* service, const GURL& url,
const GURL& first_party, const net::CompletionCallback& callback)
: AsyncHelper(service, callback),
url_(url),
first_party_(first_party) {
}
virtual void Start() OVERRIDE {
AppCachePolicy* policy = service_->appcache_policy();
if (policy && !policy->CanLoadAppCache(url_, first_party_)) {
CallCallback(net::ERR_FAILED);
delete this;
return;
}
service_->storage()->FindResponseForMainRequest(url_, GURL(), this);
}
private:
// AppCacheStorage::Delegate implementation.
virtual void OnMainResponseFound(
const GURL& url, const AppCacheEntry& entry,
const GURL& fallback_url, const AppCacheEntry& fallback_entry,
int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE;
GURL url_;
GURL first_party_;
DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper);
};
void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound(
const GURL& url, const AppCacheEntry& entry,
const GURL& fallback_url, const AppCacheEntry& fallback_entry,
int64 cache_id, int64 group_id, const GURL& manifest_url) {
bool can = (entry.has_response_id() || fallback_entry.has_response_id());
CallCallback(can ? net::OK : net::ERR_FAILED);
delete this;
}
// DeleteHelper -------
class AppCacheServiceImpl::DeleteHelper : public AsyncHelper {
public:
DeleteHelper(
AppCacheServiceImpl* service, const GURL& manifest_url,
const net::CompletionCallback& callback)
: AsyncHelper(service, callback), manifest_url_(manifest_url) {
}
virtual void Start() OVERRIDE {
service_->storage()->LoadOrCreateGroup(manifest_url_, this);
}
private:
// AppCacheStorage::Delegate implementation.
virtual void OnGroupLoaded(
AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
virtual 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 GURL& origin,
const net::CompletionCallback& callback)
: AsyncHelper(service, callback), origin_(origin),
num_caches_to_delete_(0), successes_(0), failures_(0) {
}
virtual void Start() OVERRIDE {
// We start by listing all caches, continues in OnAllInfo().
service_->storage()->GetAllInfo(this);
}
private:
// AppCacheStorage::Delegate implementation.
virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
virtual void OnGroupLoaded(
AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
virtual void OnGroupMadeObsolete(AppCacheGroup* group,
bool success,
int response_code) OVERRIDE;
void CacheCompleted(bool success);
GURL 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;
}
std::map<GURL, AppCacheInfoVector>::iterator 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 AppCacheInfoVector& caches_to_delete = found->second;
successes_ = 0;
failures_ = 0;
num_caches_to_delete_ = static_cast<int>(caches_to_delete.size());
for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin();
iter != caches_to_delete.end(); ++iter) {
service_->storage()->LoadOrCreateGroup(iter->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,
const net::CompletionCallback& callback)
: AsyncHelper(service, callback), collection_(collection) {
}
virtual void Start() OVERRIDE {
service_->storage()->GetAllInfo(this);
}
private:
// AppCacheStorage::Delegate implementation.
virtual 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 cache_id,
int64 response_id)
: AsyncHelper(service, net::CompletionCallback()),
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) {
}
virtual void Start() OVERRIDE {
service_->storage()->LoadOrCreateGroup(manifest_url_, this);
}
virtual void Cancel() OVERRIDE {
AppCacheHistograms::CountCheckResponseResult(
AppCacheHistograms::CHECK_CANCELED);
response_reader_.reset();
AsyncHelper::Cancel();
}
private:
virtual 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 cache_id_;
int64 response_id_;
// Internals used to perform the checks.
const int kIOBufferSize;
scoped_refptr<AppCache> cache_;
scoped_ptr<AppCacheResponseReader> response_reader_;
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_;
scoped_refptr<net::IOBuffer> data_buffer_;
int64 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()) {
AppCacheHistograms::CountCheckResponseResult(
AppCacheHistograms::MANIFEST_OUT_OF_DATE);
delete this;
return;
}
cache_ = group->newest_complete_cache();
const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_);
if (!entry) {
if (cache_->cache_id() == cache_id_) {
AppCacheHistograms::CountCheckResponseResult(
AppCacheHistograms::ENTRY_NOT_FOUND);
service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
} else {
AppCacheHistograms::CountCheckResponseResult(
AppCacheHistograms::RESPONSE_OUT_OF_DATE);
}
delete this;
return;
}
// Verify that we can read the response info and data.
expected_total_size_ = entry->response_size();
response_reader_.reset(service_->storage()->CreateResponseReader(
manifest_url_, group->group_id(), response_id_));
info_buffer_ = new HttpResponseInfoIOBuffer();
response_reader_->ReadInfo(
info_buffer_.get(),
base::Bind(&CheckResponseHelper::OnReadInfoComplete,
base::Unretained(this)));
}
void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) {
if (result < 0) {
AppCacheHistograms::CountCheckResponseResult(
AppCacheHistograms::READ_HEADERS_ERROR);
service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
delete this;
return;
}
amount_headers_read_ = result;
// Start reading the data.
data_buffer_ = new net::IOBuffer(kIOBufferSize);
response_reader_->ReadData(
data_buffer_.get(),
kIOBufferSize,
base::Bind(&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::Bind(&CheckResponseHelper::OnReadDataComplete,
base::Unretained(this)));
return;
}
AppCacheHistograms::CheckResponseResultType check_result;
if (result < 0)
check_result = AppCacheHistograms::READ_DATA_ERROR;
else if (info_buffer_->response_data_size != amount_data_read_ ||
expected_total_size_ != amount_data_read_ + amount_headers_read_)
check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE;
else
check_result = AppCacheHistograms::RESPONSE_OK;
AppCacheHistograms::CountCheckResponseResult(check_result);
if (check_result != AppCacheHistograms::RESPONSE_OK)
service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
delete this;
}
// AppCacheStorageReference ------
AppCacheStorageReference::AppCacheStorageReference(
scoped_ptr<AppCacheStorage> storage)
: storage_(storage.Pass()) {}
AppCacheStorageReference::~AppCacheStorageReference() {}
// AppCacheServiceImpl -------
AppCacheServiceImpl::AppCacheServiceImpl(
storage::QuotaManagerProxy* quota_manager_proxy)
: appcache_policy_(NULL),
quota_client_(NULL),
handler_factory_(NULL),
quota_manager_proxy_(quota_manager_proxy),
request_context_(NULL),
force_keep_session_state_(false) {
if (quota_manager_proxy_.get()) {
quota_client_ = new AppCacheQuotaClient(this);
quota_manager_proxy_->RegisterClient(quota_client_);
}
}
AppCacheServiceImpl::~AppCacheServiceImpl() {
DCHECK(backends_.empty());
std::for_each(pending_helpers_.begin(),
pending_helpers_.end(),
std::mem_fun(&AsyncHelper::Cancel));
STLDeleteElements(&pending_helpers_);
if (quota_client_)
quota_client_->NotifyAppCacheDestroyed();
// Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
// (special_storage_policy_).
storage_.reset();
}
void AppCacheServiceImpl::Initialize(
const base::FilePath& cache_directory,
const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread) {
DCHECK(!storage_.get());
cache_directory_ = cache_directory;
db_thread_ = db_thread;
cache_thread_ = cache_thread;
AppCacheStorageImpl* storage = new AppCacheStorageImpl(this);
storage->Initialize(cache_directory, db_thread, cache_thread);
storage_.reset(storage);
}
void AppCacheServiceImpl::ScheduleReinitialize() {
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.
const base::TimeDelta kZeroDelta;
const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1));
const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(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(k30Seconds, next_reinit_delay_);
next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour);
}
void AppCacheServiceImpl::Reinitialize() {
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.
scoped_refptr<AppCacheStorageReference>
old_storage_ref(new AppCacheStorageReference(storage_.Pass()));
FOR_EACH_OBSERVER(Observer, observers_,
OnServiceReinitialized(old_storage_ref.get()));
Initialize(cache_directory_, db_thread_, cache_thread_);
}
void AppCacheServiceImpl::CanHandleMainResourceOffline(
const GURL& url,
const GURL& first_party,
const net::CompletionCallback& callback) {
CanHandleOfflineHelper* helper =
new CanHandleOfflineHelper(this, url, first_party, callback);
helper->Start();
}
void AppCacheServiceImpl::GetAllAppCacheInfo(
AppCacheInfoCollection* collection,
const net::CompletionCallback& callback) {
DCHECK(collection);
GetInfoHelper* helper = new GetInfoHelper(this, collection, callback);
helper->Start();
}
void AppCacheServiceImpl::DeleteAppCacheGroup(
const GURL& manifest_url,
const net::CompletionCallback& callback) {
DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback);
helper->Start();
}
void AppCacheServiceImpl::DeleteAppCachesForOrigin(
const GURL& origin, const net::CompletionCallback& callback) {
DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
helper->Start();
}
void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url,
int64 cache_id,
int64 response_id) {
CheckResponseHelper* helper = new CheckResponseHelper(
this, manifest_url, cache_id, response_id);
helper->Start();
}
void AppCacheServiceImpl::set_special_storage_policy(
storage::SpecialStoragePolicy* policy) {
special_storage_policy_ = policy;
}
void AppCacheServiceImpl::RegisterBackend(
AppCacheBackendImpl* backend_impl) {
DCHECK(backends_.find(backend_impl->process_id()) == backends_.end());
backends_.insert(
BackendMap::value_type(backend_impl->process_id(), backend_impl));
}
void AppCacheServiceImpl::UnregisterBackend(
AppCacheBackendImpl* backend_impl) {
backends_.erase(backend_impl->process_id());
}
} // namespace content