Home | History | Annotate | Download | only in appcache
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "webkit/browser/appcache/appcache_service_impl.h"
      6 
      7 #include <functional>
      8 
      9 #include "base/bind.h"
     10 #include "base/bind_helpers.h"
     11 #include "base/logging.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/stl_util.h"
     14 #include "net/base/completion_callback.h"
     15 #include "net/base/io_buffer.h"
     16 #include "webkit/browser/appcache/appcache.h"
     17 #include "webkit/browser/appcache/appcache_backend_impl.h"
     18 #include "webkit/browser/appcache/appcache_entry.h"
     19 #include "webkit/browser/appcache/appcache_executable_handler.h"
     20 #include "webkit/browser/appcache/appcache_histograms.h"
     21 #include "webkit/browser/appcache/appcache_policy.h"
     22 #include "webkit/browser/appcache/appcache_quota_client.h"
     23 #include "webkit/browser/appcache/appcache_response.h"
     24 #include "webkit/browser/appcache/appcache_service_impl.h"
     25 #include "webkit/browser/appcache/appcache_storage_impl.h"
     26 #include "webkit/browser/quota/special_storage_policy.h"
     27 
     28 namespace appcache {
     29 
     30 namespace {
     31 
     32 void DeferredCallback(const net::CompletionCallback& callback, int rv) {
     33   callback.Run(rv);
     34 }
     35 
     36 }  // namespace
     37 
     38 AppCacheInfoCollection::AppCacheInfoCollection() {}
     39 
     40 AppCacheInfoCollection::~AppCacheInfoCollection() {}
     41 
     42 // AsyncHelper -------
     43 
     44 class AppCacheServiceImpl::AsyncHelper
     45     : public AppCacheStorage::Delegate {
     46  public:
     47   AsyncHelper(AppCacheServiceImpl* service,
     48               const net::CompletionCallback& callback)
     49       : service_(service), callback_(callback) {
     50     service_->pending_helpers_.insert(this);
     51   }
     52 
     53   virtual ~AsyncHelper() {
     54     if (service_)
     55       service_->pending_helpers_.erase(this);
     56   }
     57 
     58   virtual void Start() = 0;
     59   virtual void Cancel();
     60 
     61  protected:
     62   void CallCallback(int rv) {
     63     if (!callback_.is_null()) {
     64       // Defer to guarantee async completion.
     65       base::MessageLoop::current()->PostTask(
     66           FROM_HERE, base::Bind(&DeferredCallback, callback_, rv));
     67     }
     68     callback_.Reset();
     69   }
     70 
     71   AppCacheServiceImpl* service_;
     72   net::CompletionCallback callback_;
     73 };
     74 
     75 void AppCacheServiceImpl::AsyncHelper::Cancel() {
     76   if (!callback_.is_null()) {
     77     callback_.Run(net::ERR_ABORTED);
     78     callback_.Reset();
     79   }
     80   service_->storage()->CancelDelegateCallbacks(this);
     81   service_ = NULL;
     82 }
     83 
     84 // CanHandleOfflineHelper -------
     85 
     86 class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper {
     87  public:
     88   CanHandleOfflineHelper(
     89       AppCacheServiceImpl* service, const GURL& url,
     90       const GURL& first_party, const net::CompletionCallback& callback)
     91       : AsyncHelper(service, callback),
     92         url_(url),
     93         first_party_(first_party) {
     94   }
     95 
     96   virtual void Start() OVERRIDE {
     97     AppCachePolicy* policy = service_->appcache_policy();
     98     if (policy && !policy->CanLoadAppCache(url_, first_party_)) {
     99       CallCallback(net::ERR_FAILED);
    100       delete this;
    101       return;
    102     }
    103 
    104     service_->storage()->FindResponseForMainRequest(url_, GURL(), this);
    105   }
    106 
    107  private:
    108   // AppCacheStorage::Delegate implementation.
    109   virtual void OnMainResponseFound(
    110       const GURL& url, const AppCacheEntry& entry,
    111       const GURL& fallback_url, const AppCacheEntry& fallback_entry,
    112       int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE;
    113 
    114   GURL url_;
    115   GURL first_party_;
    116 
    117   DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper);
    118 };
    119 
    120 void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound(
    121       const GURL& url, const AppCacheEntry& entry,
    122       const GURL& fallback_url, const AppCacheEntry& fallback_entry,
    123       int64 cache_id, int64 group_id, const GURL& manifest_url) {
    124   bool can = (entry.has_response_id() || fallback_entry.has_response_id());
    125   CallCallback(can ? net::OK : net::ERR_FAILED);
    126   delete this;
    127 }
    128 
    129 // DeleteHelper -------
    130 
    131 class AppCacheServiceImpl::DeleteHelper : public AsyncHelper {
    132  public:
    133   DeleteHelper(
    134       AppCacheServiceImpl* service, const GURL& manifest_url,
    135       const net::CompletionCallback& callback)
    136       : AsyncHelper(service, callback), manifest_url_(manifest_url) {
    137   }
    138 
    139   virtual void Start() OVERRIDE {
    140     service_->storage()->LoadOrCreateGroup(manifest_url_, this);
    141   }
    142 
    143  private:
    144   // AppCacheStorage::Delegate implementation.
    145   virtual void OnGroupLoaded(
    146       appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
    147   virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group,
    148                                    bool success,
    149                                    int response_code) OVERRIDE;
    150 
    151   GURL manifest_url_;
    152   DISALLOW_COPY_AND_ASSIGN(DeleteHelper);
    153 };
    154 
    155 void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded(
    156       appcache::AppCacheGroup* group, const GURL& manifest_url) {
    157   if (group) {
    158     group->set_being_deleted(true);
    159     group->CancelUpdate();
    160     service_->storage()->MakeGroupObsolete(group, this, 0);
    161   } else {
    162     CallCallback(net::ERR_FAILED);
    163     delete this;
    164   }
    165 }
    166 
    167 void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete(
    168     appcache::AppCacheGroup* group,
    169     bool success,
    170     int response_code) {
    171   CallCallback(success ? net::OK : net::ERR_FAILED);
    172   delete this;
    173 }
    174 
    175 // DeleteOriginHelper -------
    176 
    177 class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper {
    178  public:
    179   DeleteOriginHelper(
    180       AppCacheServiceImpl* service, const GURL& origin,
    181       const net::CompletionCallback& callback)
    182       : AsyncHelper(service, callback), origin_(origin),
    183         num_caches_to_delete_(0), successes_(0), failures_(0) {
    184   }
    185 
    186   virtual void Start() OVERRIDE {
    187     // We start by listing all caches, continues in OnAllInfo().
    188     service_->storage()->GetAllInfo(this);
    189   }
    190 
    191  private:
    192   // AppCacheStorage::Delegate implementation.
    193   virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
    194   virtual void OnGroupLoaded(
    195       appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
    196   virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group,
    197                                    bool success,
    198                                    int response_code) OVERRIDE;
    199 
    200   void CacheCompleted(bool success);
    201 
    202   GURL origin_;
    203   int num_caches_to_delete_;
    204   int successes_;
    205   int failures_;
    206 
    207   DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper);
    208 };
    209 
    210 void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo(
    211     AppCacheInfoCollection* collection) {
    212   if (!collection) {
    213     // Failed to get a listing.
    214     CallCallback(net::ERR_FAILED);
    215     delete this;
    216     return;
    217   }
    218 
    219   std::map<GURL, AppCacheInfoVector>::iterator found =
    220       collection->infos_by_origin.find(origin_);
    221   if (found == collection->infos_by_origin.end() || found->second.empty()) {
    222     // No caches for this origin.
    223     CallCallback(net::OK);
    224     delete this;
    225     return;
    226   }
    227 
    228   // We have some caches to delete.
    229   const AppCacheInfoVector& caches_to_delete = found->second;
    230   successes_ = 0;
    231   failures_ = 0;
    232   num_caches_to_delete_ = static_cast<int>(caches_to_delete.size());
    233   for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin();
    234        iter != caches_to_delete.end(); ++iter) {
    235     service_->storage()->LoadOrCreateGroup(iter->manifest_url, this);
    236   }
    237 }
    238 
    239 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded(
    240       appcache::AppCacheGroup* group, const GURL& manifest_url) {
    241   if (group) {
    242     group->set_being_deleted(true);
    243     group->CancelUpdate();
    244     service_->storage()->MakeGroupObsolete(group, this, 0);
    245   } else {
    246     CacheCompleted(false);
    247   }
    248 }
    249 
    250 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete(
    251     appcache::AppCacheGroup* group,
    252     bool success,
    253     int response_code) {
    254   CacheCompleted(success);
    255 }
    256 
    257 void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) {
    258   if (success)
    259     ++successes_;
    260   else
    261     ++failures_;
    262   if ((successes_ + failures_) < num_caches_to_delete_)
    263     return;
    264 
    265   CallCallback(!failures_ ? net::OK : net::ERR_FAILED);
    266   delete this;
    267 }
    268 
    269 
    270 // GetInfoHelper -------
    271 
    272 class AppCacheServiceImpl::GetInfoHelper : AsyncHelper {
    273  public:
    274   GetInfoHelper(
    275       AppCacheServiceImpl* service, AppCacheInfoCollection* collection,
    276       const net::CompletionCallback& callback)
    277       : AsyncHelper(service, callback), collection_(collection) {
    278   }
    279 
    280   virtual void Start() OVERRIDE {
    281     service_->storage()->GetAllInfo(this);
    282   }
    283 
    284  private:
    285   // AppCacheStorage::Delegate implementation.
    286   virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
    287 
    288   scoped_refptr<AppCacheInfoCollection> collection_;
    289 
    290   DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
    291 };
    292 
    293 void AppCacheServiceImpl::GetInfoHelper::OnAllInfo(
    294       AppCacheInfoCollection* collection) {
    295   if (collection)
    296     collection->infos_by_origin.swap(collection_->infos_by_origin);
    297   CallCallback(collection ? net::OK : net::ERR_FAILED);
    298   delete this;
    299 }
    300 
    301 // CheckResponseHelper -------
    302 
    303 class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper {
    304  public:
    305   CheckResponseHelper(
    306       AppCacheServiceImpl* service, const GURL& manifest_url, int64 cache_id,
    307       int64 response_id)
    308       : AsyncHelper(service, net::CompletionCallback()),
    309         manifest_url_(manifest_url),
    310         cache_id_(cache_id),
    311         response_id_(response_id),
    312         kIOBufferSize(32 * 1024),
    313         expected_total_size_(0),
    314         amount_headers_read_(0),
    315         amount_data_read_(0) {
    316   }
    317 
    318   virtual void Start() OVERRIDE {
    319     service_->storage()->LoadOrCreateGroup(manifest_url_, this);
    320   }
    321 
    322   virtual void Cancel() OVERRIDE {
    323     AppCacheHistograms::CountCheckResponseResult(
    324         AppCacheHistograms::CHECK_CANCELED);
    325     response_reader_.reset();
    326     AsyncHelper::Cancel();
    327   }
    328 
    329  private:
    330   virtual void OnGroupLoaded(AppCacheGroup* group,
    331                              const GURL& manifest_url) OVERRIDE;
    332   void OnReadInfoComplete(int result);
    333   void OnReadDataComplete(int result);
    334 
    335   // Inputs describing what to check.
    336   GURL manifest_url_;
    337   int64 cache_id_;
    338   int64 response_id_;
    339 
    340   // Internals used to perform the checks.
    341   const int kIOBufferSize;
    342   scoped_refptr<AppCache> cache_;
    343   scoped_ptr<AppCacheResponseReader> response_reader_;
    344   scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_;
    345   scoped_refptr<net::IOBuffer> data_buffer_;
    346   int64 expected_total_size_;
    347   int amount_headers_read_;
    348   int amount_data_read_;
    349   DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper);
    350 };
    351 
    352 void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded(
    353     AppCacheGroup* group, const GURL& manifest_url) {
    354   DCHECK_EQ(manifest_url_, manifest_url);
    355   if (!group || !group->newest_complete_cache() || group->is_being_deleted() ||
    356       group->is_obsolete()) {
    357     AppCacheHistograms::CountCheckResponseResult(
    358         AppCacheHistograms::MANIFEST_OUT_OF_DATE);
    359     delete this;
    360     return;
    361   }
    362 
    363   cache_ = group->newest_complete_cache();
    364   const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_);
    365   if (!entry) {
    366     if (cache_->cache_id() == cache_id_) {
    367       AppCacheHistograms::CountCheckResponseResult(
    368           AppCacheHistograms::ENTRY_NOT_FOUND);
    369       service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
    370     } else {
    371       AppCacheHistograms::CountCheckResponseResult(
    372           AppCacheHistograms::RESPONSE_OUT_OF_DATE);
    373     }
    374     delete this;
    375     return;
    376   }
    377 
    378   // Verify that we can read the response info and data.
    379   expected_total_size_ = entry->response_size();
    380   response_reader_.reset(service_->storage()->CreateResponseReader(
    381       manifest_url_, group->group_id(), response_id_));
    382   info_buffer_ = new HttpResponseInfoIOBuffer();
    383   response_reader_->ReadInfo(
    384       info_buffer_.get(),
    385       base::Bind(&CheckResponseHelper::OnReadInfoComplete,
    386                  base::Unretained(this)));
    387 }
    388 
    389 void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) {
    390   if (result < 0) {
    391     AppCacheHistograms::CountCheckResponseResult(
    392         AppCacheHistograms::READ_HEADERS_ERROR);
    393     service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
    394     delete this;
    395     return;
    396   }
    397   amount_headers_read_ = result;
    398 
    399   // Start reading the data.
    400   data_buffer_ = new net::IOBuffer(kIOBufferSize);
    401   response_reader_->ReadData(
    402       data_buffer_.get(),
    403       kIOBufferSize,
    404       base::Bind(&CheckResponseHelper::OnReadDataComplete,
    405                  base::Unretained(this)));
    406 }
    407 
    408 void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) {
    409   if (result > 0) {
    410     // Keep reading until we've read thru everything or failed to read.
    411     amount_data_read_ += result;
    412     response_reader_->ReadData(
    413         data_buffer_.get(),
    414         kIOBufferSize,
    415         base::Bind(&CheckResponseHelper::OnReadDataComplete,
    416                    base::Unretained(this)));
    417     return;
    418   }
    419 
    420   AppCacheHistograms::CheckResponseResultType check_result;
    421   if (result < 0)
    422     check_result = AppCacheHistograms::READ_DATA_ERROR;
    423   else if (info_buffer_->response_data_size != amount_data_read_ ||
    424            expected_total_size_ != amount_data_read_ + amount_headers_read_)
    425     check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE;
    426   else
    427     check_result = AppCacheHistograms::RESPONSE_OK;
    428   AppCacheHistograms::CountCheckResponseResult(check_result);
    429 
    430   if (check_result != AppCacheHistograms::RESPONSE_OK)
    431     service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
    432   delete this;
    433 }
    434 
    435 // AppCacheStorageReference ------
    436 
    437 AppCacheStorageReference::AppCacheStorageReference(
    438     scoped_ptr<AppCacheStorage> storage)
    439     : storage_(storage.Pass()) {}
    440 AppCacheStorageReference::~AppCacheStorageReference() {}
    441 
    442 // AppCacheServiceImpl -------
    443 
    444 AppCacheServiceImpl::AppCacheServiceImpl(quota::QuotaManagerProxy*
    445     quota_manager_proxy)
    446     : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL),
    447       quota_manager_proxy_(quota_manager_proxy),
    448       request_context_(NULL),
    449       force_keep_session_state_(false) {
    450   if (quota_manager_proxy_.get()) {
    451     quota_client_ = new AppCacheQuotaClient(this);
    452     quota_manager_proxy_->RegisterClient(quota_client_);
    453   }
    454 }
    455 
    456 AppCacheServiceImpl::~AppCacheServiceImpl() {
    457   DCHECK(backends_.empty());
    458   std::for_each(pending_helpers_.begin(),
    459                 pending_helpers_.end(),
    460                 std::mem_fun(&AsyncHelper::Cancel));
    461   STLDeleteElements(&pending_helpers_);
    462   if (quota_client_)
    463     quota_client_->NotifyAppCacheDestroyed();
    464 
    465   // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
    466   // (special_storage_policy_).
    467   storage_.reset();
    468 }
    469 
    470 void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory,
    471                                  base::MessageLoopProxy* db_thread,
    472                                  base::MessageLoopProxy* cache_thread) {
    473   DCHECK(!storage_.get());
    474   cache_directory_ = cache_directory;
    475   db_thread_ = db_thread;
    476   cache_thread_ = cache_thread;
    477   AppCacheStorageImpl* storage = new AppCacheStorageImpl(this);
    478   storage->Initialize(cache_directory, db_thread, cache_thread);
    479   storage_.reset(storage);
    480 }
    481 
    482 void AppCacheServiceImpl::ScheduleReinitialize() {
    483   if (reinit_timer_.IsRunning())
    484     return;
    485 
    486   // Reinitialization only happens when corruption has been noticed.
    487   // We don't want to thrash the disk but we also don't want to
    488   // leave the appcache disabled for an indefinite period of time. Some
    489   // users never shutdown the browser.
    490 
    491   const base::TimeDelta kZeroDelta;
    492   const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1));
    493   const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30));
    494 
    495   // If the system managed to stay up for long enough, reset the
    496   // delay so a new failure won't incur a long wait to get going again.
    497   base::TimeDelta up_time = base::Time::Now() - last_reinit_time_;
    498   if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour)
    499     next_reinit_delay_ = kZeroDelta;
    500 
    501   reinit_timer_.Start(FROM_HERE, next_reinit_delay_,
    502                       this, &AppCacheServiceImpl::Reinitialize);
    503 
    504   // Adjust the delay for next time.
    505   base::TimeDelta increment = std::max(k30Seconds, next_reinit_delay_);
    506   next_reinit_delay_ = std::min(next_reinit_delay_ + increment,  kOneHour);
    507 }
    508 
    509 void AppCacheServiceImpl::Reinitialize() {
    510   AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null());
    511   last_reinit_time_ = base::Time::Now();
    512 
    513   // Inform observers of about this and give them a chance to
    514   // defer deletion of the old storage object.
    515   scoped_refptr<AppCacheStorageReference>
    516       old_storage_ref(new AppCacheStorageReference(storage_.Pass()));
    517   FOR_EACH_OBSERVER(Observer, observers_,
    518                     OnServiceReinitialized(old_storage_ref.get()));
    519 
    520   Initialize(cache_directory_, db_thread_, cache_thread_);
    521 }
    522 
    523 void AppCacheServiceImpl::CanHandleMainResourceOffline(
    524     const GURL& url,
    525     const GURL& first_party,
    526     const net::CompletionCallback& callback) {
    527   CanHandleOfflineHelper* helper =
    528       new CanHandleOfflineHelper(this, url, first_party, callback);
    529   helper->Start();
    530 }
    531 
    532 void AppCacheServiceImpl::GetAllAppCacheInfo(
    533     AppCacheInfoCollection* collection,
    534     const net::CompletionCallback& callback) {
    535   DCHECK(collection);
    536   GetInfoHelper* helper = new GetInfoHelper(this, collection, callback);
    537   helper->Start();
    538 }
    539 
    540 void AppCacheServiceImpl::DeleteAppCacheGroup(
    541     const GURL& manifest_url,
    542     const net::CompletionCallback& callback) {
    543   DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback);
    544   helper->Start();
    545 }
    546 
    547 void AppCacheServiceImpl::DeleteAppCachesForOrigin(
    548     const GURL& origin,  const net::CompletionCallback& callback) {
    549   DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
    550   helper->Start();
    551 }
    552 
    553 void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url,
    554                                             int64 cache_id,
    555                                             int64 response_id) {
    556   CheckResponseHelper* helper = new CheckResponseHelper(
    557       this, manifest_url, cache_id, response_id);
    558   helper->Start();
    559 }
    560 
    561 void AppCacheServiceImpl::set_special_storage_policy(
    562     quota::SpecialStoragePolicy* policy) {
    563   special_storage_policy_ = policy;
    564 }
    565 
    566 void AppCacheServiceImpl::RegisterBackend(
    567     AppCacheBackendImpl* backend_impl) {
    568   DCHECK(backends_.find(backend_impl->process_id()) == backends_.end());
    569   backends_.insert(
    570       BackendMap::value_type(backend_impl->process_id(), backend_impl));
    571 }
    572 
    573 void AppCacheServiceImpl::UnregisterBackend(
    574     AppCacheBackendImpl* backend_impl) {
    575   backends_.erase(backend_impl->process_id());
    576 }
    577 
    578 }  // namespace appcache
    579