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