Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / content / browser / appcache / appcache_update_job.cc
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