Home | History | Annotate | Download | only in appcache
      1 // Copyright (c) 2012 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 "content/browser/appcache/appcache_update_job.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/compiler_specific.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "content/browser/appcache/appcache_group.h"
     14 #include "content/browser/appcache/appcache_histograms.h"
     15 #include "net/base/host_port_pair.h"
     16 #include "net/base/io_buffer.h"
     17 #include "net/base/load_flags.h"
     18 #include "net/base/net_errors.h"
     19 #include "net/base/request_priority.h"
     20 #include "net/http/http_request_headers.h"
     21 #include "net/http/http_response_headers.h"
     22 #include "net/url_request/url_request_context.h"
     23 
     24 namespace {
     25 bool IsDataReductionProxy(const net::HostPortPair& proxy_server) {
     26   return (
     27       proxy_server.Equals(net::HostPortPair("proxy.googlezip.net", 443)) ||
     28       proxy_server.Equals(net::HostPortPair("compress.googlezip.net", 80)) ||
     29       proxy_server.Equals(net::HostPortPair("proxy-dev.googlezip.net", 80)));
     30 }
     31 }  // namspace
     32 
     33 namespace content {
     34 
     35 static const int kBufferSize = 32768;
     36 static const size_t kMaxConcurrentUrlFetches = 2;
     37 static const int kMax503Retries = 3;
     38 
     39 static std::string FormatUrlErrorMessage(
     40       const char* format, const GURL& url,
     41       AppCacheUpdateJob::ResultType error,
     42       int response_code) {
     43     // Show the net response code if we have one.
     44     int code = response_code;
     45     if (error != AppCacheUpdateJob::SERVER_ERROR)
     46       code = static_cast<int>(error);
     47     return base::StringPrintf(format, code, url.spec().c_str());
     48 }
     49 
     50 // Helper class for collecting hosts per frontend when sending notifications
     51 // so that only one notification is sent for all hosts using the same frontend.
     52 class HostNotifier {
     53  public:
     54   typedef std::vector<int> HostIds;
     55   typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap;
     56 
     57   // Caller is responsible for ensuring there will be no duplicate hosts.
     58   void AddHost(AppCacheHost* host) {
     59     std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert(
     60         NotifyHostMap::value_type(host->frontend(), HostIds()));
     61     ret.first->second.push_back(host->host_id());
     62   }
     63 
     64   void AddHosts(const std::set<AppCacheHost*>& hosts) {
     65     for (std::set<AppCacheHost*>::const_iterator it = hosts.begin();
     66          it != hosts.end(); ++it) {
     67       AddHost(*it);
     68     }
     69   }
     70 
     71   void SendNotifications(AppCacheEventID event_id) {
     72     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     73          it != hosts_to_notify.end(); ++it) {
     74       AppCacheFrontend* frontend = it->first;
     75       frontend->OnEventRaised(it->second, event_id);
     76     }
     77   }
     78 
     79   void SendProgressNotifications(
     80       const GURL& url, int num_total, int num_complete) {
     81     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     82          it != hosts_to_notify.end(); ++it) {
     83       AppCacheFrontend* frontend = it->first;
     84       frontend->OnProgressEventRaised(it->second, url,
     85                                       num_total, num_complete);
     86     }
     87   }
     88 
     89   void SendErrorNotifications(const AppCacheErrorDetails& details) {
     90     DCHECK(!details.message.empty());
     91     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     92          it != hosts_to_notify.end(); ++it) {
     93       AppCacheFrontend* frontend = it->first;
     94       frontend->OnErrorEventRaised(it->second, details);
     95     }
     96   }
     97 
     98   void SendLogMessage(const std::string& message) {
     99     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
    100          it != hosts_to_notify.end(); ++it) {
    101       AppCacheFrontend* frontend = it->first;
    102       for (HostIds::iterator id = it->second.begin();
    103            id != it->second.end(); ++id) {
    104         frontend->OnLogMessage(*id, APPCACHE_LOG_WARNING, message);
    105       }
    106     }
    107   }
    108 
    109  private:
    110   NotifyHostMap hosts_to_notify;
    111 };
    112 
    113 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
    114                                           bool checked,
    115                                           AppCacheResponseInfo* info)
    116     : url(url),
    117       storage_checked(checked),
    118       existing_response_info(info) {
    119 }
    120 
    121 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
    122 }
    123 
    124 // Helper class to fetch resources. Depending on the fetch type,
    125 // can either fetch to an in-memory string or write the response
    126 // data out to the disk cache.
    127 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
    128                                           FetchType fetch_type,
    129                                           AppCacheUpdateJob* job)
    130     : url_(url),
    131       job_(job),
    132       fetch_type_(fetch_type),
    133       retry_503_attempts_(0),
    134       buffer_(new net::IOBuffer(kBufferSize)),
    135       request_(job->service_->request_context()
    136                    ->CreateRequest(url, net::DEFAULT_PRIORITY, this, NULL)),
    137       result_(UPDATE_OK),
    138       redirect_response_code_(-1) {}
    139 
    140 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
    141 }
    142 
    143 void AppCacheUpdateJob::URLFetcher::Start() {
    144   request_->set_first_party_for_cookies(job_->manifest_url_);
    145   request_->SetLoadFlags(request_->load_flags() |
    146                          net::LOAD_DISABLE_INTERCEPT);
    147   if (existing_response_headers_.get())
    148     AddConditionalHeaders(existing_response_headers_.get());
    149   request_->Start();
    150 }
    151 
    152 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
    153     net::URLRequest* request,
    154     const net::RedirectInfo& redirect_info,
    155     bool* defer_redirect) {
    156   DCHECK(request_ == request);
    157   // TODO(bengr): Remove this special case logic when crbug.com/429505 is
    158   // resolved. Until then, the data reduction proxy client logic uses the
    159   // redirect mechanism to resend requests over a direct connection when
    160   // the proxy instructs it to do so. The redirect is to the same location
    161   // as the original URL.
    162   if ((request->load_flags() & net::LOAD_BYPASS_PROXY) &&
    163       IsDataReductionProxy(request->proxy_server())) {
    164     DCHECK_EQ(request->original_url(), request->url());
    165     return;
    166   }
    167   // Redirect is not allowed by the update process.
    168   job_->MadeProgress();
    169   redirect_response_code_ = request->GetResponseCode();
    170   request->Cancel();
    171   result_ = REDIRECT_ERROR;
    172   OnResponseCompleted();
    173 }
    174 
    175 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
    176     net::URLRequest *request) {
    177   DCHECK(request == request_);
    178   int response_code = -1;
    179   if (request->status().is_success()) {
    180     response_code = request->GetResponseCode();
    181     job_->MadeProgress();
    182   }
    183   if ((response_code / 100) == 2) {
    184 
    185     // See http://code.google.com/p/chromium/issues/detail?id=69594
    186     // We willfully violate the HTML5 spec at this point in order
    187     // to support the appcaching of cross-origin HTTPS resources.
    188     // We've opted for a milder constraint and allow caching unless
    189     // the resource has a "no-store" header. A spec change has been
    190     // requested on the whatwg list.
    191     // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
    192     if (url_.SchemeIsSecure() &&
    193         url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
    194       if (request->response_headers()->
    195               HasHeaderValue("cache-control", "no-store")) {
    196         DCHECK_EQ(-1, redirect_response_code_);
    197         request->Cancel();
    198         result_ = SERVER_ERROR;  // Not the best match?
    199         OnResponseCompleted();
    200         return;
    201       }
    202     }
    203 
    204     // Write response info to storage for URL fetches. Wait for async write
    205     // completion before reading any response data.
    206     if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
    207       response_writer_.reset(job_->CreateResponseWriter());
    208       scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
    209           new HttpResponseInfoIOBuffer(
    210               new net::HttpResponseInfo(request->response_info())));
    211       response_writer_->WriteInfo(
    212           io_buffer.get(),
    213           base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
    214     } else {
    215       ReadResponseData();
    216     }
    217   } else {
    218     if (response_code > 0)
    219       result_ = SERVER_ERROR;
    220     else
    221       result_ = NETWORK_ERROR;
    222     OnResponseCompleted();
    223   }
    224 }
    225 
    226 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
    227     net::URLRequest* request, int bytes_read) {
    228   DCHECK(request_ == request);
    229   bool data_consumed = true;
    230   if (request->status().is_success() && bytes_read > 0) {
    231     job_->MadeProgress();
    232     data_consumed = ConsumeResponseData(bytes_read);
    233     if (data_consumed) {
    234       bytes_read = 0;
    235       while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
    236         if (bytes_read > 0) {
    237           data_consumed = ConsumeResponseData(bytes_read);
    238           if (!data_consumed)
    239             break;  // wait for async data processing, then read more
    240         } else {
    241           break;
    242         }
    243       }
    244     }
    245   }
    246   if (data_consumed && !request->status().is_io_pending()) {
    247     DCHECK_EQ(UPDATE_OK, result_);
    248     OnResponseCompleted();
    249   }
    250 }
    251 
    252 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
    253     const net::HttpResponseHeaders* headers) {
    254   DCHECK(request_.get() && headers);
    255   net::HttpRequestHeaders extra_headers;
    256 
    257   // Add If-Modified-Since header if response info has Last-Modified header.
    258   const std::string last_modified = "Last-Modified";
    259   std::string last_modified_value;
    260   headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
    261   if (!last_modified_value.empty()) {
    262     extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
    263                             last_modified_value);
    264   }
    265 
    266   // Add If-None-Match header if response info has ETag header.
    267   const std::string etag = "ETag";
    268   std::string etag_value;
    269   headers->EnumerateHeader(NULL, etag, &etag_value);
    270   if (!etag_value.empty()) {
    271     extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
    272                             etag_value);
    273   }
    274   if (!extra_headers.IsEmpty())
    275     request_->SetExtraRequestHeaders(extra_headers);
    276 }
    277 
    278 void  AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
    279   if (result < 0) {
    280     request_->Cancel();
    281     result_ = DISKCACHE_ERROR;
    282     OnResponseCompleted();
    283     return;
    284   }
    285   ReadResponseData();
    286 }
    287 
    288 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
    289   InternalUpdateState state = job_->internal_state_;
    290   if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
    291     return;
    292   int bytes_read = 0;
    293   request_->Read(buffer_.get(), kBufferSize, &bytes_read);
    294   OnReadCompleted(request_.get(), bytes_read);
    295 }
    296 
    297 // Returns false if response data is processed asynchronously, in which
    298 // case ReadResponseData will be invoked when it is safe to continue
    299 // reading more response data from the request.
    300 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
    301   DCHECK_GT(bytes_read, 0);
    302   switch (fetch_type_) {
    303     case MANIFEST_FETCH:
    304     case MANIFEST_REFETCH:
    305       manifest_data_.append(buffer_->data(), bytes_read);
    306       break;
    307     case URL_FETCH:
    308     case MASTER_ENTRY_FETCH:
    309       DCHECK(response_writer_.get());
    310       response_writer_->WriteData(
    311           buffer_.get(),
    312           bytes_read,
    313           base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
    314       return false;  // wait for async write completion to continue reading
    315     default:
    316       NOTREACHED();
    317   }
    318   return true;
    319 }
    320 
    321 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
    322   if (request_->status().is_success())
    323     job_->MadeProgress();
    324 
    325   // Retry for 503s where retry-after is 0.
    326   if (request_->status().is_success() &&
    327       request_->GetResponseCode() == 503 &&
    328       MaybeRetryRequest()) {
    329     return;
    330   }
    331 
    332   switch (fetch_type_) {
    333     case MANIFEST_FETCH:
    334       job_->HandleManifestFetchCompleted(this);
    335       break;
    336     case URL_FETCH:
    337       job_->HandleUrlFetchCompleted(this);
    338       break;
    339     case MASTER_ENTRY_FETCH:
    340       job_->HandleMasterEntryFetchCompleted(this);
    341       break;
    342     case MANIFEST_REFETCH:
    343       job_->HandleManifestRefetchCompleted(this);
    344       break;
    345     default:
    346       NOTREACHED();
    347   }
    348 
    349   delete this;
    350 }
    351 
    352 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
    353   if (retry_503_attempts_ >= kMax503Retries ||
    354       !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
    355     return false;
    356   }
    357   ++retry_503_attempts_;
    358   result_ = UPDATE_OK;
    359   request_ = job_->service_->request_context()->CreateRequest(
    360       url_, net::DEFAULT_PRIORITY, this, NULL);
    361   Start();
    362   return true;
    363 }
    364 
    365 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheServiceImpl* service,
    366                                      AppCacheGroup* group)
    367     : service_(service),
    368       manifest_url_(group->manifest_url()),
    369       group_(group),
    370       update_type_(UNKNOWN_TYPE),
    371       internal_state_(FETCH_MANIFEST),
    372       master_entries_completed_(0),
    373       url_fetches_completed_(0),
    374       manifest_fetcher_(NULL),
    375       manifest_has_valid_mime_type_(false),
    376       stored_state_(UNSTORED),
    377       storage_(service->storage()) {
    378     service_->AddObserver(this);
    379 }
    380 
    381 AppCacheUpdateJob::~AppCacheUpdateJob() {
    382   if (service_)
    383     service_->RemoveObserver(this);
    384   if (internal_state_ != COMPLETED)
    385     Cancel();
    386 
    387   DCHECK(!manifest_fetcher_);
    388   DCHECK(pending_url_fetches_.empty());
    389   DCHECK(!inprogress_cache_.get());
    390   DCHECK(pending_master_entries_.empty());
    391   DCHECK(master_entry_fetches_.empty());
    392 
    393   if (group_)
    394     group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
    395 }
    396 
    397 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
    398                                     const GURL& new_master_resource) {
    399   DCHECK(group_->update_job() == this);
    400   DCHECK(!group_->is_obsolete());
    401 
    402   bool is_new_pending_master_entry = false;
    403   if (!new_master_resource.is_empty()) {
    404     DCHECK(new_master_resource == host->pending_master_entry_url());
    405     DCHECK(!new_master_resource.has_ref());
    406     DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
    407 
    408     // Cannot add more to this update if already terminating.
    409     if (IsTerminating()) {
    410       group_->QueueUpdate(host, new_master_resource);
    411       return;
    412     }
    413 
    414     std::pair<PendingMasters::iterator, bool> ret =
    415         pending_master_entries_.insert(
    416             PendingMasters::value_type(new_master_resource, PendingHosts()));
    417     is_new_pending_master_entry = ret.second;
    418     ret.first->second.push_back(host);
    419     host->AddObserver(this);
    420   }
    421 
    422   // Notify host (if any) if already checking or downloading.
    423   AppCacheGroup::UpdateAppCacheStatus update_status = group_->update_status();
    424   if (update_status == AppCacheGroup::CHECKING ||
    425       update_status == AppCacheGroup::DOWNLOADING) {
    426     if (host) {
    427       NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
    428       if (update_status == AppCacheGroup::DOWNLOADING)
    429         NotifySingleHost(host, APPCACHE_DOWNLOADING_EVENT);
    430 
    431       // Add to fetch list or an existing entry if already fetched.
    432       if (!new_master_resource.is_empty()) {
    433         AddMasterEntryToFetchList(host, new_master_resource,
    434                                   is_new_pending_master_entry);
    435       }
    436     }
    437     return;
    438   }
    439 
    440   // Begin update process for the group.
    441   MadeProgress();
    442   group_->SetUpdateAppCacheStatus(AppCacheGroup::CHECKING);
    443   if (group_->HasCache()) {
    444     update_type_ = UPGRADE_ATTEMPT;
    445     NotifyAllAssociatedHosts(APPCACHE_CHECKING_EVENT);
    446   } else {
    447     update_type_ = CACHE_ATTEMPT;
    448     DCHECK(host);
    449     NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
    450   }
    451 
    452   if (!new_master_resource.is_empty()) {
    453     AddMasterEntryToFetchList(host, new_master_resource,
    454                               is_new_pending_master_entry);
    455   }
    456 
    457   FetchManifest(true);
    458 }
    459 
    460 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
    461   AppCacheResponseWriter* writer =
    462       storage_->CreateResponseWriter(manifest_url_,
    463                                                 group_->group_id());
    464   stored_response_ids_.push_back(writer->response_id());
    465   return writer;
    466 }
    467 
    468 void AppCacheUpdateJob::HandleCacheFailure(
    469     const AppCacheErrorDetails& error_details,
    470     ResultType result,
    471     const GURL& failed_resource_url) {
    472   // 6.9.4 cache failure steps 2-8.
    473   DCHECK(internal_state_ != CACHE_FAILURE);
    474   DCHECK(!error_details.message.empty());
    475   DCHECK(result != UPDATE_OK);
    476   internal_state_ = CACHE_FAILURE;
    477   LogHistogramStats(result, failed_resource_url);
    478   CancelAllUrlFetches();
    479   CancelAllMasterEntryFetches(error_details);
    480   NotifyAllError(error_details);
    481   DiscardInprogressCache();
    482   internal_state_ = COMPLETED;
    483   DeleteSoon();  // To unwind the stack prior to deletion.
    484 }
    485 
    486 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
    487   DCHECK(!manifest_fetcher_);
    488   manifest_fetcher_ = new URLFetcher(
    489      manifest_url_,
    490      is_first_fetch ? URLFetcher::MANIFEST_FETCH :
    491                       URLFetcher::MANIFEST_REFETCH,
    492      this);
    493 
    494   // Add any necessary Http headers before sending fetch request.
    495   if (is_first_fetch) {
    496     AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
    497         group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
    498     if (entry) {
    499       // Asynchronously load response info for manifest from newest cache.
    500       storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
    501                                  entry->response_id(), this);
    502     } else {
    503       manifest_fetcher_->Start();
    504     }
    505   } else {
    506     DCHECK(internal_state_ == REFETCH_MANIFEST);
    507     DCHECK(manifest_response_info_.get());
    508     manifest_fetcher_->set_existing_response_headers(
    509         manifest_response_info_->headers.get());
    510     manifest_fetcher_->Start();
    511   }
    512 }
    513 
    514 
    515 void AppCacheUpdateJob::HandleManifestFetchCompleted(
    516     URLFetcher* fetcher) {
    517   DCHECK_EQ(internal_state_, FETCH_MANIFEST);
    518   DCHECK_EQ(manifest_fetcher_, fetcher);
    519   manifest_fetcher_ = NULL;
    520 
    521   net::URLRequest* request = fetcher->request();
    522   int response_code = -1;
    523   bool is_valid_response_code = false;
    524   if (request->status().is_success()) {
    525     response_code = request->GetResponseCode();
    526     is_valid_response_code = (response_code / 100 == 2);
    527 
    528     std::string mime_type;
    529     request->GetMimeType(&mime_type);
    530     manifest_has_valid_mime_type_ = (mime_type == "text/cache-manifest");
    531   }
    532 
    533   if (is_valid_response_code) {
    534     manifest_data_ = fetcher->manifest_data();
    535     manifest_response_info_.reset(
    536         new net::HttpResponseInfo(request->response_info()));
    537     if (update_type_ == UPGRADE_ATTEMPT)
    538       CheckIfManifestChanged();  // continues asynchronously
    539     else
    540       ContinueHandleManifestFetchCompleted(true);
    541   } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
    542     ContinueHandleManifestFetchCompleted(false);
    543   } else if ((response_code == 404 || response_code == 410) &&
    544              update_type_ == UPGRADE_ATTEMPT) {
    545     storage_->MakeGroupObsolete(group_, this, response_code);  // async
    546   } else {
    547     const char* kFormatString = "Manifest fetch failed (%d) %s";
    548     std::string message = FormatUrlErrorMessage(
    549         kFormatString, manifest_url_, fetcher->result(), response_code);
    550     HandleCacheFailure(AppCacheErrorDetails(message,
    551                                     APPCACHE_MANIFEST_ERROR,
    552                                     manifest_url_,
    553                                     response_code,
    554                                     false /*is_cross_origin*/),
    555                        fetcher->result(),
    556                        GURL());
    557   }
    558 }
    559 
    560 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
    561                                             bool success,
    562                                             int response_code) {
    563   DCHECK(master_entry_fetches_.empty());
    564   CancelAllMasterEntryFetches(AppCacheErrorDetails(
    565       "The cache has been made obsolete, "
    566       "the manifest file returned 404 or 410",
    567       APPCACHE_MANIFEST_ERROR,
    568       GURL(),
    569       response_code,
    570       false /*is_cross_origin*/));
    571   if (success) {
    572     DCHECK(group->is_obsolete());
    573     NotifyAllAssociatedHosts(APPCACHE_OBSOLETE_EVENT);
    574     internal_state_ = COMPLETED;
    575     MaybeCompleteUpdate();
    576   } else {
    577     // Treat failure to mark group obsolete as a cache failure.
    578     HandleCacheFailure(AppCacheErrorDetails(
    579         "Failed to mark the cache as obsolete",
    580         APPCACHE_UNKNOWN_ERROR,
    581         GURL(),
    582         0,
    583         false /*is_cross_origin*/),
    584                        DB_ERROR,
    585                        GURL());
    586   }
    587 }
    588 
    589 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
    590   DCHECK(internal_state_ == FETCH_MANIFEST);
    591 
    592   if (!changed) {
    593     DCHECK(update_type_ == UPGRADE_ATTEMPT);
    594     internal_state_ = NO_UPDATE;
    595 
    596     // Wait for pending master entries to download.
    597     FetchMasterEntries();
    598     MaybeCompleteUpdate();  // if not done, run async 6.9.4 step 7 substeps
    599     return;
    600   }
    601 
    602   AppCacheManifest manifest;
    603   if (!ParseManifest(manifest_url_, manifest_data_.data(),
    604                      manifest_data_.length(),
    605                      manifest_has_valid_mime_type_ ?
    606                         PARSE_MANIFEST_ALLOWING_INTERCEPTS :
    607                         PARSE_MANIFEST_PER_STANDARD,
    608                      manifest)) {
    609     const char* kFormatString = "Failed to parse manifest %s";
    610     const std::string message = base::StringPrintf(kFormatString,
    611         manifest_url_.spec().c_str());
    612     HandleCacheFailure(
    613         AppCacheErrorDetails(
    614             message, APPCACHE_SIGNATURE_ERROR, GURL(), 0,
    615             false /*is_cross_origin*/),
    616         MANIFEST_ERROR,
    617         GURL());
    618     VLOG(1) << message;
    619     return;
    620   }
    621 
    622   // Proceed with update process. Section 6.9.4 steps 8-20.
    623   internal_state_ = DOWNLOADING;
    624   inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
    625   BuildUrlFileList(manifest);
    626   inprogress_cache_->InitializeWithManifest(&manifest);
    627 
    628   // Associate all pending master hosts with the newly created cache.
    629   for (PendingMasters::iterator it = pending_master_entries_.begin();
    630        it != pending_master_entries_.end(); ++it) {
    631     PendingHosts& hosts = it->second;
    632     for (PendingHosts::iterator host_it = hosts.begin();
    633          host_it != hosts.end(); ++host_it) {
    634       (*host_it)
    635           ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
    636     }
    637   }
    638 
    639   if (manifest.did_ignore_intercept_namespaces) {
    640     // Must be done after associating all pending master hosts.
    641     std::string message(
    642         "Ignoring the INTERCEPT section of the application cache manifest "
    643         "because the content type is not text/cache-manifest");
    644     LogConsoleMessageToAll(message);
    645   }
    646 
    647   group_->SetUpdateAppCacheStatus(AppCacheGroup::DOWNLOADING);
    648   NotifyAllAssociatedHosts(APPCACHE_DOWNLOADING_EVENT);
    649   FetchUrls();
    650   FetchMasterEntries();
    651   MaybeCompleteUpdate();  // if not done, continues when async fetches complete
    652 }
    653 
    654 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
    655   DCHECK(internal_state_ == DOWNLOADING);
    656 
    657   net::URLRequest* request = fetcher->request();
    658   const GURL& url = request->original_url();
    659   pending_url_fetches_.erase(url);
    660   NotifyAllProgress(url);
    661   ++url_fetches_completed_;
    662 
    663   int response_code = request->status().is_success()
    664                           ? request->GetResponseCode()
    665                           : fetcher->redirect_response_code();
    666 
    667   AppCacheEntry& entry = url_file_list_.find(url)->second;
    668 
    669   if (response_code / 100 == 2) {
    670     // Associate storage with the new entry.
    671     DCHECK(fetcher->response_writer());
    672     entry.set_response_id(fetcher->response_writer()->response_id());
    673     entry.set_response_size(fetcher->response_writer()->amount_written());
    674     if (!inprogress_cache_->AddOrModifyEntry(url, entry))
    675       duplicate_response_ids_.push_back(entry.response_id());
    676 
    677     // TODO(michaeln): Check for <html manifest=xxx>
    678     // See http://code.google.com/p/chromium/issues/detail?id=97930
    679     // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
    680     //   if (!manifestAttribute) skip it
    681 
    682     // Foreign entries will be detected during cache selection.
    683     // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
    684     // file whose root element is an html element with a manifest attribute
    685     // whose value doesn't match the manifest url of the application cache
    686     // being processed, mark the entry as being foreign.
    687   } else {
    688     VLOG(1) << "Request status: " << request->status().status()
    689             << " error: " << request->status().error()
    690             << " response code: " << response_code;
    691     if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
    692       if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
    693         // Keep the existing response.
    694         entry.set_response_id(fetcher->existing_entry().response_id());
    695         entry.set_response_size(fetcher->existing_entry().response_size());
    696         inprogress_cache_->AddOrModifyEntry(url, entry);
    697       } else {
    698         const char* kFormatString = "Resource fetch failed (%d) %s";
    699         std::string message = FormatUrlErrorMessage(
    700             kFormatString, url, fetcher->result(), response_code);
    701         ResultType result = fetcher->result();
    702         bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin();
    703         switch (result) {
    704           case DISKCACHE_ERROR:
    705             HandleCacheFailure(
    706                 AppCacheErrorDetails(
    707                     message, APPCACHE_UNKNOWN_ERROR, GURL(), 0,
    708                     is_cross_origin),
    709                 result,
    710                 url);
    711             break;
    712           case NETWORK_ERROR:
    713             HandleCacheFailure(
    714                 AppCacheErrorDetails(message, APPCACHE_RESOURCE_ERROR, url, 0,
    715                     is_cross_origin),
    716                 result,
    717                 url);
    718             break;
    719           default:
    720             HandleCacheFailure(AppCacheErrorDetails(message,
    721                                             APPCACHE_RESOURCE_ERROR,
    722                                             url,
    723                                             response_code,
    724                                             is_cross_origin),
    725                                result,
    726                                url);
    727             break;
    728         }
    729         return;
    730       }
    731     } else if (response_code == 404 || response_code == 410) {
    732       // Entry is skipped.  They are dropped from the cache.
    733     } else if (update_type_ == UPGRADE_ATTEMPT &&
    734                fetcher->existing_entry().has_response_id()) {
    735       // Keep the existing response.
    736       // TODO(michaeln): Not sure this is a good idea. This is spec compliant
    737       // but the old resource may or may not be compatible with the new contents
    738       // of the cache. Impossible to know one way or the other.
    739       entry.set_response_id(fetcher->existing_entry().response_id());
    740       entry.set_response_size(fetcher->existing_entry().response_size());
    741       inprogress_cache_->AddOrModifyEntry(url, entry);
    742     }
    743   }
    744 
    745   // Fetch another URL now that one request has completed.
    746   DCHECK(internal_state_ != CACHE_FAILURE);
    747   FetchUrls();
    748   MaybeCompleteUpdate();
    749 }
    750 
    751 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
    752     URLFetcher* fetcher) {
    753   DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
    754 
    755   // TODO(jennb): Handle downloads completing during cache failure when update
    756   // no longer fetches master entries directly. For now, we cancel all pending
    757   // master entry fetches when entering cache failure state so this will never
    758   // be called in CACHE_FAILURE state.
    759 
    760   net::URLRequest* request = fetcher->request();
    761   const GURL& url = request->original_url();
    762   master_entry_fetches_.erase(url);
    763   ++master_entries_completed_;
    764 
    765   int response_code = request->status().is_success()
    766       ? request->GetResponseCode() : -1;
    767 
    768   PendingMasters::iterator found = pending_master_entries_.find(url);
    769   DCHECK(found != pending_master_entries_.end());
    770   PendingHosts& hosts = found->second;
    771 
    772   // Section 6.9.4. No update case: step 7.3, else step 22.
    773   if (response_code / 100 == 2) {
    774     // Add fetched master entry to the appropriate cache.
    775     AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
    776                                               : group_->newest_complete_cache();
    777     DCHECK(fetcher->response_writer());
    778     AppCacheEntry master_entry(AppCacheEntry::MASTER,
    779                                fetcher->response_writer()->response_id(),
    780                                fetcher->response_writer()->amount_written());
    781     if (cache->AddOrModifyEntry(url, master_entry))
    782       added_master_entries_.push_back(url);
    783     else
    784       duplicate_response_ids_.push_back(master_entry.response_id());
    785 
    786     // In no-update case, associate host with the newest cache.
    787     if (!inprogress_cache_.get()) {
    788       // TODO(michaeln): defer until the updated cache has been stored
    789       DCHECK(cache == group_->newest_complete_cache());
    790       for (PendingHosts::iterator host_it = hosts.begin();
    791            host_it != hosts.end(); ++host_it) {
    792         (*host_it)->AssociateCompleteCache(cache);
    793       }
    794     }
    795   } else {
    796     HostNotifier host_notifier;
    797     for (PendingHosts::iterator host_it = hosts.begin();
    798          host_it != hosts.end(); ++host_it) {
    799       AppCacheHost* host = *host_it;
    800       host_notifier.AddHost(host);
    801 
    802       // In downloading case, disassociate host from inprogress cache.
    803       if (inprogress_cache_.get())
    804         host->AssociateNoCache(GURL());
    805 
    806       host->RemoveObserver(this);
    807     }
    808     hosts.clear();
    809 
    810     const char* kFormatString = "Manifest fetch failed (%d) %s";
    811     std::string message = FormatUrlErrorMessage(
    812         kFormatString, request->url(), fetcher->result(), response_code);
    813     host_notifier.SendErrorNotifications(
    814         AppCacheErrorDetails(message,
    815                      APPCACHE_MANIFEST_ERROR,
    816                      request->url(),
    817                      response_code,
    818                      false /*is_cross_origin*/));
    819 
    820     // In downloading case, update result is different if all master entries
    821     // failed vs. only some failing.
    822     if (inprogress_cache_.get()) {
    823       // Only count successful downloads to know if all master entries failed.
    824       pending_master_entries_.erase(found);
    825       --master_entries_completed_;
    826 
    827       // Section 6.9.4, step 22.3.
    828       if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
    829         HandleCacheFailure(AppCacheErrorDetails(message,
    830                                         APPCACHE_MANIFEST_ERROR,
    831                                         request->url(),
    832                                         response_code,
    833                                         false /*is_cross_origin*/),
    834                            fetcher->result(),
    835                            GURL());
    836         return;
    837       }
    838     }
    839   }
    840 
    841   DCHECK(internal_state_ != CACHE_FAILURE);
    842   FetchMasterEntries();
    843   MaybeCompleteUpdate();
    844 }
    845 
    846 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
    847     URLFetcher* fetcher) {
    848   DCHECK(internal_state_ == REFETCH_MANIFEST);
    849   DCHECK(manifest_fetcher_ == fetcher);
    850   manifest_fetcher_ = NULL;
    851 
    852   net::URLRequest* request = fetcher->request();
    853   int response_code = request->status().is_success()
    854       ? request->GetResponseCode() : -1;
    855   if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
    856     // Only need to store response in storage if manifest is not already
    857     // an entry in the cache.
    858     AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
    859     if (entry) {
    860       entry->add_types(AppCacheEntry::MANIFEST);
    861       StoreGroupAndCache();
    862     } else {
    863       manifest_response_writer_.reset(CreateResponseWriter());
    864       scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
    865           new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
    866       manifest_response_writer_->WriteInfo(
    867           io_buffer.get(),
    868           base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
    869                      base::Unretained(this)));
    870     }
    871   } else {
    872     VLOG(1) << "Request status: " << request->status().status()
    873             << " error: " << request->status().error()
    874             << " response code: " << response_code;
    875     ScheduleUpdateRetry(kRerunDelayMs);
    876     if (response_code == 200) {
    877       HandleCacheFailure(AppCacheErrorDetails("Manifest changed during update",
    878                                       APPCACHE_CHANGED_ERROR,
    879                                       GURL(),
    880                                       0,
    881                                       false /*is_cross_origin*/),
    882                          MANIFEST_ERROR,
    883                          GURL());
    884     } else {
    885       const char* kFormatString = "Manifest re-fetch failed (%d) %s";
    886       std::string message = FormatUrlErrorMessage(
    887           kFormatString, manifest_url_, fetcher->result(), response_code);
    888       HandleCacheFailure(AppCacheErrorDetails(message,
    889                                       APPCACHE_MANIFEST_ERROR,
    890                                       GURL(),
    891                                       response_code,
    892                                       false /*is_cross_origin*/),
    893                          fetcher->result(),
    894                          GURL());
    895     }
    896   }
    897 }
    898 
    899 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
    900   if (result > 0) {
    901     scoped_refptr<net::StringIOBuffer> io_buffer(
    902         new net::StringIOBuffer(manifest_data_));
    903     manifest_response_writer_->WriteData(
    904         io_buffer.get(),
    905         manifest_data_.length(),
    906         base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
    907                    base::Unretained(this)));
    908   } else {
    909     HandleCacheFailure(
    910         AppCacheErrorDetails("Failed to write the manifest headers to storage",
    911                      APPCACHE_UNKNOWN_ERROR,
    912                      GURL(),
    913                      0,
    914                      false /*is_cross_origin*/),
    915         DISKCACHE_ERROR,
    916         GURL());
    917   }
    918 }
    919 
    920 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
    921   if (result > 0) {
    922     AppCacheEntry entry(AppCacheEntry::MANIFEST,
    923         manifest_response_writer_->response_id(),
    924         manifest_response_writer_->amount_written());
    925     if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
    926       duplicate_response_ids_.push_back(entry.response_id());
    927     StoreGroupAndCache();
    928   } else {
    929     HandleCacheFailure(
    930         AppCacheErrorDetails("Failed to write the manifest data to storage",
    931                      APPCACHE_UNKNOWN_ERROR,
    932                      GURL(),
    933                      0,
    934                      false /*is_cross_origin*/),
    935         DISKCACHE_ERROR,
    936         GURL());
    937   }
    938 }
    939 
    940 void AppCacheUpdateJob::StoreGroupAndCache() {
    941   DCHECK(stored_state_ == UNSTORED);
    942   stored_state_ = STORING;
    943   scoped_refptr<AppCache> newest_cache;
    944   if (inprogress_cache_.get())
    945     newest_cache.swap(inprogress_cache_);
    946   else
    947     newest_cache = group_->newest_complete_cache();
    948   newest_cache->set_update_time(base::Time::Now());
    949 
    950   // TODO(michaeln): dcheck is fishing for clues to crbug/95101
    951   DCHECK_EQ(manifest_url_, group_->manifest_url());
    952   storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
    953 }
    954 
    955 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
    956                                                     AppCache* newest_cache,
    957                                                     bool success,
    958                                                     bool would_exceed_quota) {
    959   DCHECK(stored_state_ == STORING);
    960   if (success) {
    961     stored_state_ = STORED;
    962     MaybeCompleteUpdate();  // will definitely complete
    963   } else {
    964     stored_state_ = UNSTORED;
    965 
    966     // Restore inprogress_cache_ to get the proper events delivered
    967     // and the proper cleanup to occur.
    968     if (newest_cache != group->newest_complete_cache())
    969       inprogress_cache_ = newest_cache;
    970 
    971     ResultType result = DB_ERROR;
    972     AppCacheErrorReason reason = APPCACHE_UNKNOWN_ERROR;
    973     std::string message("Failed to commit new cache to storage");
    974     if (would_exceed_quota) {
    975       message.append(", would exceed quota");
    976       result = QUOTA_ERROR;
    977       reason = APPCACHE_QUOTA_ERROR;
    978     }
    979     HandleCacheFailure(
    980         AppCacheErrorDetails(message, reason, GURL(), 0,
    981             false /*is_cross_origin*/),
    982         result,
    983         GURL());
    984   }
    985 }
    986 
    987 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
    988                                          AppCacheEventID event_id) {
    989   std::vector<int> ids(1, host->host_id());
    990   host->frontend()->OnEventRaised(ids, event_id);
    991 }
    992 
    993 void AppCacheUpdateJob::NotifyAllAssociatedHosts(AppCacheEventID event_id) {
    994   HostNotifier host_notifier;
    995   AddAllAssociatedHostsToNotifier(&host_notifier);
    996   host_notifier.SendNotifications(event_id);
    997 }
    998 
    999 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
   1000   HostNotifier host_notifier;
   1001   AddAllAssociatedHostsToNotifier(&host_notifier);
   1002   host_notifier.SendProgressNotifications(
   1003       url, url_file_list_.size(), url_fetches_completed_);
   1004 }
   1005 
   1006 void AppCacheUpdateJob::NotifyAllFinalProgress() {
   1007   DCHECK(url_file_list_.size() == url_fetches_completed_);
   1008   NotifyAllProgress(GURL());
   1009 }
   1010 
   1011 void AppCacheUpdateJob::NotifyAllError(const AppCacheErrorDetails& details) {
   1012   HostNotifier host_notifier;
   1013   AddAllAssociatedHostsToNotifier(&host_notifier);
   1014   host_notifier.SendErrorNotifications(details);
   1015 }
   1016 
   1017 void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string& message) {
   1018   HostNotifier host_notifier;
   1019   AddAllAssociatedHostsToNotifier(&host_notifier);
   1020   host_notifier.SendLogMessage(message);
   1021 }
   1022 
   1023 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
   1024     HostNotifier* host_notifier) {
   1025   // Collect hosts so we only send one notification per frontend.
   1026   // A host can only be associated with a single cache so no need to worry
   1027   // about duplicate hosts being added to the notifier.
   1028   if (inprogress_cache_.get()) {
   1029     DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
   1030     host_notifier->AddHosts(inprogress_cache_->associated_hosts());
   1031   }
   1032 
   1033   AppCacheGroup::Caches old_caches = group_->old_caches();
   1034   for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
   1035        it != old_caches.end(); ++it) {
   1036     host_notifier->AddHosts((*it)->associated_hosts());
   1037   }
   1038 
   1039   AppCache* newest_cache = group_->newest_complete_cache();
   1040   if (newest_cache)
   1041     host_notifier->AddHosts(newest_cache->associated_hosts());
   1042 }
   1043 
   1044 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
   1045   // The host is about to be deleted; remove from our collection.
   1046   PendingMasters::iterator found =
   1047       pending_master_entries_.find(host->pending_master_entry_url());
   1048   DCHECK(found != pending_master_entries_.end());
   1049   PendingHosts& hosts = found->second;
   1050   PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
   1051   DCHECK(it != hosts.end());
   1052   hosts.erase(it);
   1053 }
   1054 
   1055 void AppCacheUpdateJob::OnServiceReinitialized(
   1056     AppCacheStorageReference* old_storage_ref) {
   1057   // We continue to use the disabled instance, but arrange for its
   1058   // deletion when its no longer needed.
   1059   if (old_storage_ref->storage() == storage_)
   1060     disabled_storage_reference_ = old_storage_ref;
   1061 }
   1062 
   1063 void AppCacheUpdateJob::CheckIfManifestChanged() {
   1064   DCHECK(update_type_ == UPGRADE_ATTEMPT);
   1065   AppCacheEntry* entry = NULL;
   1066   if (group_->newest_complete_cache())
   1067     entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
   1068   if (!entry) {
   1069     // TODO(michaeln): This is just a bandaid to avoid a crash.
   1070     // http://code.google.com/p/chromium/issues/detail?id=95101
   1071     if (service_->storage() == storage_) {
   1072       // Use a local variable because service_ is reset in HandleCacheFailure.
   1073       AppCacheServiceImpl* service = service_;
   1074       HandleCacheFailure(
   1075           AppCacheErrorDetails("Manifest entry not found in existing cache",
   1076                        APPCACHE_UNKNOWN_ERROR,
   1077                        GURL(),
   1078                        0,
   1079                        false /*is_cross_origin*/),
   1080           DB_ERROR,
   1081           GURL());
   1082       AppCacheHistograms::AddMissingManifestEntrySample();
   1083       service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
   1084     }
   1085     return;
   1086   }
   1087 
   1088   // Load manifest data from storage to compare against fetched manifest.
   1089   manifest_response_reader_.reset(
   1090       storage_->CreateResponseReader(manifest_url_,
   1091                                      group_->group_id(),
   1092                                      entry->response_id()));
   1093   read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
   1094   manifest_response_reader_->ReadData(
   1095       read_manifest_buffer_.get(),
   1096       kBufferSize,
   1097       base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
   1098                  base::Unretained(this)));  // async read
   1099 }
   1100 
   1101 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
   1102   if (result > 0) {
   1103     loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
   1104     manifest_response_reader_->ReadData(
   1105         read_manifest_buffer_.get(),
   1106         kBufferSize,
   1107         base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
   1108                    base::Unretained(this)));  // read more
   1109   } else {
   1110     read_manifest_buffer_ = NULL;
   1111     manifest_response_reader_.reset();
   1112     ContinueHandleManifestFetchCompleted(
   1113         result < 0 || manifest_data_ != loaded_manifest_data_);
   1114   }
   1115 }
   1116 
   1117 void AppCacheUpdateJob::BuildUrlFileList(const AppCacheManifest& manifest) {
   1118   for (base::hash_set<std::string>::const_iterator it =
   1119            manifest.explicit_urls.begin();
   1120        it != manifest.explicit_urls.end(); ++it) {
   1121     AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
   1122   }
   1123 
   1124   const std::vector<AppCacheNamespace>& intercepts =
   1125       manifest.intercept_namespaces;
   1126   for (std::vector<AppCacheNamespace>::const_iterator it = intercepts.begin();
   1127        it != intercepts.end(); ++it) {
   1128     int flags = AppCacheEntry::INTERCEPT;
   1129     if (it->is_executable)
   1130       flags |= AppCacheEntry::EXECUTABLE;
   1131     AddUrlToFileList(it->target_url, flags);
   1132   }
   1133 
   1134   const std::vector<AppCacheNamespace>& fallbacks =
   1135       manifest.fallback_namespaces;
   1136   for (std::vector<AppCacheNamespace>::const_iterator it = fallbacks.begin();
   1137        it != fallbacks.end(); ++it) {
   1138      AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
   1139   }
   1140 
   1141   // Add all master entries from newest complete cache.
   1142   if (update_type_ == UPGRADE_ATTEMPT) {
   1143     const AppCache::EntryMap& entries =
   1144         group_->newest_complete_cache()->entries();
   1145     for (AppCache::EntryMap::const_iterator it = entries.begin();
   1146          it != entries.end(); ++it) {
   1147       const AppCacheEntry& entry = it->second;
   1148       if (entry.IsMaster())
   1149         AddUrlToFileList(it->first, AppCacheEntry::MASTER);
   1150     }
   1151   }
   1152 }
   1153 
   1154 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
   1155   std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
   1156       AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
   1157 
   1158   if (ret.second)
   1159     urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
   1160   else
   1161     ret.first->second.add_types(type);  // URL already exists. Merge types.
   1162 }
   1163 
   1164 void AppCacheUpdateJob::FetchUrls() {
   1165   DCHECK(internal_state_ == DOWNLOADING);
   1166 
   1167   // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
   1168   // Fetch up to the concurrent limit. Other fetches will be triggered as each
   1169   // each fetch completes.
   1170   while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
   1171          !urls_to_fetch_.empty()) {
   1172     UrlToFetch url_to_fetch = urls_to_fetch_.front();
   1173     urls_to_fetch_.pop_front();
   1174 
   1175     AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
   1176     DCHECK(it != url_file_list_.end());
   1177     AppCacheEntry& entry = it->second;
   1178     if (ShouldSkipUrlFetch(entry)) {
   1179       NotifyAllProgress(url_to_fetch.url);
   1180       ++url_fetches_completed_;
   1181     } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
   1182       NotifyAllProgress(url_to_fetch.url);
   1183       ++url_fetches_completed_;  // saved a URL request
   1184     } else if (!url_to_fetch.storage_checked &&
   1185                MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
   1186       // Continues asynchronously after data is loaded from newest cache.
   1187     } else {
   1188       URLFetcher* fetcher = new URLFetcher(
   1189           url_to_fetch.url, URLFetcher::URL_FETCH, this);
   1190       if (url_to_fetch.existing_response_info.get()) {
   1191         DCHECK(group_->newest_complete_cache());
   1192         AppCacheEntry* existing_entry =
   1193             group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
   1194         DCHECK(existing_entry);
   1195         DCHECK(existing_entry->response_id() ==
   1196                url_to_fetch.existing_response_info->response_id());
   1197         fetcher->set_existing_response_headers(
   1198             url_to_fetch.existing_response_info->http_response_info()->headers
   1199                 .get());
   1200         fetcher->set_existing_entry(*existing_entry);
   1201       }
   1202       fetcher->Start();
   1203       pending_url_fetches_.insert(
   1204           PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
   1205     }
   1206   }
   1207 }
   1208 
   1209 void AppCacheUpdateJob::CancelAllUrlFetches() {
   1210   // Cancel any pending URL requests.
   1211   for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
   1212        it != pending_url_fetches_.end(); ++it) {
   1213     delete it->second;
   1214   }
   1215 
   1216   url_fetches_completed_ +=
   1217       pending_url_fetches_.size() + urls_to_fetch_.size();
   1218   pending_url_fetches_.clear();
   1219   urls_to_fetch_.clear();
   1220 }
   1221 
   1222 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
   1223   // 6.6.4 Step 17
   1224   // If the resource URL being processed was flagged as neither an
   1225   // "explicit entry" nor or a "fallback entry", then the user agent
   1226   // may skip this URL.
   1227   if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
   1228     return false;
   1229 
   1230   // TODO(jennb): decide if entry should be skipped to expire it from cache
   1231   return false;
   1232 }
   1233 
   1234 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
   1235                                             int entry_type) {
   1236   DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
   1237   AppCacheEntry* existing =
   1238       inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
   1239                               : group_->newest_complete_cache()->GetEntry(url);
   1240   if (existing) {
   1241     existing->add_types(entry_type);
   1242     return true;
   1243   }
   1244   return false;
   1245 }
   1246 
   1247 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
   1248                                                   const GURL& url,
   1249                                                   bool is_new) {
   1250   DCHECK(!IsTerminating());
   1251 
   1252   if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
   1253     AppCache* cache;
   1254     if (inprogress_cache_.get()) {
   1255       // always associate
   1256       host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
   1257       cache = inprogress_cache_.get();
   1258     } else {
   1259       cache = group_->newest_complete_cache();
   1260     }
   1261 
   1262     // Update existing entry if it has already been fetched.
   1263     AppCacheEntry* entry = cache->GetEntry(url);
   1264     if (entry) {
   1265       entry->add_types(AppCacheEntry::MASTER);
   1266       if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
   1267         // only associate if have entry
   1268         host->AssociateCompleteCache(cache);
   1269       }
   1270       if (is_new)
   1271         ++master_entries_completed_;  // pretend fetching completed
   1272       return;
   1273     }
   1274   }
   1275 
   1276   // Add to fetch list if not already fetching.
   1277   if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
   1278     master_entries_to_fetch_.insert(url);
   1279     if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
   1280       FetchMasterEntries();
   1281   }
   1282 }
   1283 
   1284 void AppCacheUpdateJob::FetchMasterEntries() {
   1285   DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
   1286 
   1287   // Fetch each master entry in the list, up to the concurrent limit.
   1288   // Additional fetches will be triggered as each fetch completes.
   1289   while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
   1290          !master_entries_to_fetch_.empty()) {
   1291     const GURL& url = *master_entries_to_fetch_.begin();
   1292 
   1293     if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
   1294       ++master_entries_completed_;  // saved a URL request
   1295 
   1296       // In no update case, associate hosts to newest cache in group
   1297       // now that master entry has been "successfully downloaded".
   1298       if (internal_state_ == NO_UPDATE) {
   1299         // TODO(michaeln): defer until the updated cache has been stored.
   1300         DCHECK(!inprogress_cache_.get());
   1301         AppCache* cache = group_->newest_complete_cache();
   1302         PendingMasters::iterator found = pending_master_entries_.find(url);
   1303         DCHECK(found != pending_master_entries_.end());
   1304         PendingHosts& hosts = found->second;
   1305         for (PendingHosts::iterator host_it = hosts.begin();
   1306              host_it != hosts.end(); ++host_it) {
   1307           (*host_it)->AssociateCompleteCache(cache);
   1308         }
   1309       }
   1310     } else {
   1311       URLFetcher* fetcher = new URLFetcher(
   1312           url, URLFetcher::MASTER_ENTRY_FETCH, this);
   1313       fetcher->Start();
   1314       master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
   1315     }
   1316 
   1317     master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
   1318   }
   1319 }
   1320 
   1321 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
   1322     const AppCacheErrorDetails& error_details) {
   1323   // For now, cancel all in-progress fetches for master entries and pretend
   1324   // all master entries fetches have completed.
   1325   // TODO(jennb): Delete this when update no longer fetches master entries
   1326   // directly.
   1327 
   1328   // Cancel all in-progress fetches.
   1329   for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
   1330        it != master_entry_fetches_.end(); ++it) {
   1331     delete it->second;
   1332     master_entries_to_fetch_.insert(it->first);  // back in unfetched list
   1333   }
   1334   master_entry_fetches_.clear();
   1335 
   1336   master_entries_completed_ += master_entries_to_fetch_.size();
   1337 
   1338   // Cache failure steps, step 2.
   1339   // Pretend all master entries that have not yet been fetched have completed
   1340   // downloading. Unassociate hosts from any appcache and send ERROR event.
   1341   HostNotifier host_notifier;
   1342   while (!master_entries_to_fetch_.empty()) {
   1343     const GURL& url = *master_entries_to_fetch_.begin();
   1344     PendingMasters::iterator found = pending_master_entries_.find(url);
   1345     DCHECK(found != pending_master_entries_.end());
   1346     PendingHosts& hosts = found->second;
   1347     for (PendingHosts::iterator host_it = hosts.begin();
   1348          host_it != hosts.end(); ++host_it) {
   1349       AppCacheHost* host = *host_it;
   1350       host->AssociateNoCache(GURL());
   1351       host_notifier.AddHost(host);
   1352       host->RemoveObserver(this);
   1353     }
   1354     hosts.clear();
   1355 
   1356     master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
   1357   }
   1358   host_notifier.SendErrorNotifications(error_details);
   1359 }
   1360 
   1361 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
   1362                                                  AppCacheEntry& entry) {
   1363   if (update_type_ != UPGRADE_ATTEMPT)
   1364     return false;
   1365 
   1366   AppCache* newest = group_->newest_complete_cache();
   1367   AppCacheEntry* copy_me = newest->GetEntry(url);
   1368   if (!copy_me || !copy_me->has_response_id())
   1369     return false;
   1370 
   1371   // Load HTTP headers for entry from newest cache.
   1372   loading_responses_.insert(
   1373       LoadingResponses::value_type(copy_me->response_id(), url));
   1374   storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
   1375                              copy_me->response_id(),
   1376                              this);
   1377   // Async: wait for OnResponseInfoLoaded to complete.
   1378   return true;
   1379 }
   1380 
   1381 void AppCacheUpdateJob::OnResponseInfoLoaded(
   1382     AppCacheResponseInfo* response_info, int64 response_id) {
   1383   const net::HttpResponseInfo* http_info = response_info ?
   1384       response_info->http_response_info() : NULL;
   1385 
   1386   // Needed response info for a manifest fetch request.
   1387   if (internal_state_ == FETCH_MANIFEST) {
   1388     if (http_info)
   1389       manifest_fetcher_->set_existing_response_headers(
   1390           http_info->headers.get());
   1391     manifest_fetcher_->Start();
   1392     return;
   1393   }
   1394 
   1395   LoadingResponses::iterator found = loading_responses_.find(response_id);
   1396   DCHECK(found != loading_responses_.end());
   1397   const GURL& url = found->second;
   1398 
   1399   if (!http_info) {
   1400     LoadFromNewestCacheFailed(url, NULL);  // no response found
   1401   } else {
   1402     // Check if response can be re-used according to HTTP caching semantics.
   1403     // Responses with a "vary" header get treated as expired.
   1404     const std::string name = "vary";
   1405     std::string value;
   1406     void* iter = NULL;
   1407     if (!http_info->headers.get() ||
   1408         http_info->headers->RequiresValidation(http_info->request_time,
   1409                                                http_info->response_time,
   1410                                                base::Time::Now()) ||
   1411         http_info->headers->EnumerateHeader(&iter, name, &value)) {
   1412       LoadFromNewestCacheFailed(url, response_info);
   1413     } else {
   1414       DCHECK(group_->newest_complete_cache());
   1415       AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
   1416       DCHECK(copy_me);
   1417       DCHECK(copy_me->response_id() == response_id);
   1418 
   1419       AppCache::EntryMap::iterator it = url_file_list_.find(url);
   1420       DCHECK(it != url_file_list_.end());
   1421       AppCacheEntry& entry = it->second;
   1422       entry.set_response_id(response_id);
   1423       entry.set_response_size(copy_me->response_size());
   1424       inprogress_cache_->AddOrModifyEntry(url, entry);
   1425       NotifyAllProgress(url);
   1426       ++url_fetches_completed_;
   1427     }
   1428   }
   1429   loading_responses_.erase(found);
   1430 
   1431   MaybeCompleteUpdate();
   1432 }
   1433 
   1434 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
   1435     const GURL& url, AppCacheResponseInfo* response_info) {
   1436   if (internal_state_ == CACHE_FAILURE)
   1437     return;
   1438 
   1439   // Re-insert url at front of fetch list. Indicate storage has been checked.
   1440   urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
   1441   FetchUrls();
   1442 }
   1443 
   1444 void AppCacheUpdateJob::MaybeCompleteUpdate() {
   1445   DCHECK(internal_state_ != CACHE_FAILURE);
   1446 
   1447   // Must wait for any pending master entries or url fetches to complete.
   1448   if (master_entries_completed_ != pending_master_entries_.size() ||
   1449       url_fetches_completed_ != url_file_list_.size()) {
   1450     DCHECK(internal_state_ != COMPLETED);
   1451     return;
   1452   }
   1453 
   1454   switch (internal_state_) {
   1455     case NO_UPDATE:
   1456       if (master_entries_completed_ > 0) {
   1457         switch (stored_state_) {
   1458           case UNSTORED:
   1459             StoreGroupAndCache();
   1460             return;
   1461           case STORING:
   1462             return;
   1463           case STORED:
   1464             break;
   1465         }
   1466       }
   1467       // 6.9.4 steps 7.3-7.7.
   1468       NotifyAllAssociatedHosts(APPCACHE_NO_UPDATE_EVENT);
   1469       DiscardDuplicateResponses();
   1470       internal_state_ = COMPLETED;
   1471       break;
   1472     case DOWNLOADING:
   1473       internal_state_ = REFETCH_MANIFEST;
   1474       FetchManifest(false);
   1475       break;
   1476     case REFETCH_MANIFEST:
   1477       DCHECK(stored_state_ == STORED);
   1478       NotifyAllFinalProgress();
   1479       if (update_type_ == CACHE_ATTEMPT)
   1480         NotifyAllAssociatedHosts(APPCACHE_CACHED_EVENT);
   1481       else
   1482         NotifyAllAssociatedHosts(APPCACHE_UPDATE_READY_EVENT);
   1483       DiscardDuplicateResponses();
   1484       internal_state_ = COMPLETED;
   1485       LogHistogramStats(UPDATE_OK, GURL());
   1486       break;
   1487     case CACHE_FAILURE:
   1488       NOTREACHED();  // See HandleCacheFailure
   1489       break;
   1490     default:
   1491       break;
   1492   }
   1493 
   1494   // Let the stack unwind before deletion to make it less risky as this
   1495   // method is called from multiple places in this file.
   1496   if (internal_state_ == COMPLETED)
   1497     DeleteSoon();
   1498 }
   1499 
   1500 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
   1501   // TODO(jennb): post a delayed task with the "same parameters" as this job
   1502   // to retry the update at a later time. Need group, URLs of pending master
   1503   // entries and their hosts.
   1504 }
   1505 
   1506 void AppCacheUpdateJob::Cancel() {
   1507   internal_state_ = CANCELLED;
   1508 
   1509   LogHistogramStats(CANCELLED_ERROR, GURL());
   1510 
   1511   if (manifest_fetcher_) {
   1512     delete manifest_fetcher_;
   1513     manifest_fetcher_ = NULL;
   1514   }
   1515 
   1516   for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
   1517        it != pending_url_fetches_.end(); ++it) {
   1518     delete it->second;
   1519   }
   1520   pending_url_fetches_.clear();
   1521 
   1522   for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
   1523        it != master_entry_fetches_.end(); ++it) {
   1524     delete it->second;
   1525   }
   1526   master_entry_fetches_.clear();
   1527 
   1528   ClearPendingMasterEntries();
   1529   DiscardInprogressCache();
   1530 
   1531   // Delete response writer to avoid any callbacks.
   1532   if (manifest_response_writer_)
   1533     manifest_response_writer_.reset();
   1534 
   1535   storage_->CancelDelegateCallbacks(this);
   1536 }
   1537 
   1538 void AppCacheUpdateJob::ClearPendingMasterEntries() {
   1539   for (PendingMasters::iterator it = pending_master_entries_.begin();
   1540        it != pending_master_entries_.end(); ++it) {
   1541     PendingHosts& hosts = it->second;
   1542     for (PendingHosts::iterator host_it = hosts.begin();
   1543          host_it != hosts.end(); ++host_it) {
   1544       (*host_it)->RemoveObserver(this);
   1545     }
   1546   }
   1547 
   1548   pending_master_entries_.clear();
   1549 }
   1550 
   1551 void AppCacheUpdateJob::DiscardInprogressCache() {
   1552   if (stored_state_ == STORING) {
   1553     // We can make no assumptions about whether the StoreGroupAndCacheTask
   1554     // actually completed or not. This condition should only be reachable
   1555     // during shutdown. Free things up and return to do no harm.
   1556     inprogress_cache_ = NULL;
   1557     added_master_entries_.clear();
   1558     return;
   1559   }
   1560 
   1561   storage_->DoomResponses(manifest_url_, stored_response_ids_);
   1562 
   1563   if (!inprogress_cache_.get()) {
   1564     // We have to undo the changes we made, if any, to the existing cache.
   1565     if (group_ && group_->newest_complete_cache()) {
   1566       for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
   1567            iter != added_master_entries_.end(); ++iter) {
   1568         group_->newest_complete_cache()->RemoveEntry(*iter);
   1569       }
   1570     }
   1571     added_master_entries_.clear();
   1572     return;
   1573   }
   1574 
   1575   AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
   1576   while (!hosts.empty())
   1577     (*hosts.begin())->AssociateNoCache(GURL());
   1578 
   1579   inprogress_cache_ = NULL;
   1580   added_master_entries_.clear();
   1581 }
   1582 
   1583 void AppCacheUpdateJob::DiscardDuplicateResponses() {
   1584   storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
   1585 }
   1586 
   1587 void AppCacheUpdateJob::LogHistogramStats(
   1588       ResultType result, const GURL& failed_resource_url) {
   1589   AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin());
   1590   if (result == UPDATE_OK)
   1591     return;
   1592 
   1593   int percent_complete = 0;
   1594   if (url_file_list_.size() > 0) {
   1595     size_t actual_fetches_completed = url_fetches_completed_;
   1596     if (!failed_resource_url.is_empty() && actual_fetches_completed)
   1597       --actual_fetches_completed;
   1598     percent_complete = (static_cast<double>(actual_fetches_completed) /
   1599                             static_cast<double>(url_file_list_.size())) * 100.0;
   1600     percent_complete = std::min(percent_complete, 99);
   1601   }
   1602 
   1603   bool was_making_progress =
   1604       base::Time::Now() - last_progress_time_ <
   1605           base::TimeDelta::FromMinutes(5);
   1606 
   1607   bool off_origin_resource_failure =
   1608       !failed_resource_url.is_empty() &&
   1609           (failed_resource_url.GetOrigin() != manifest_url_.GetOrigin());
   1610 
   1611   AppCacheHistograms::LogUpdateFailureStats(
   1612       manifest_url_.GetOrigin(),
   1613       percent_complete,
   1614       was_making_progress,
   1615       off_origin_resource_failure);
   1616 }
   1617 
   1618 void AppCacheUpdateJob::DeleteSoon() {
   1619   ClearPendingMasterEntries();
   1620   manifest_response_writer_.reset();
   1621   storage_->CancelDelegateCallbacks(this);
   1622   service_->RemoveObserver(this);
   1623   service_ = NULL;
   1624 
   1625   // Break the connection with the group so the group cannot call delete
   1626   // on this object after we've posted a task to delete ourselves.
   1627   group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
   1628   group_ = NULL;
   1629 
   1630   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
   1631 }
   1632 
   1633 }  // namespace content
   1634