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