// 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