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