1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/appcache/appcache_host.h"
6
7#include "base/logging.h"
8#include "base/strings/string_util.h"
9#include "base/strings/stringprintf.h"
10#include "content/browser/appcache/appcache.h"
11#include "content/browser/appcache/appcache_backend_impl.h"
12#include "content/browser/appcache/appcache_policy.h"
13#include "content/browser/appcache/appcache_request_handler.h"
14#include "net/url_request/url_request.h"
15#include "storage/browser/quota/quota_manager_proxy.h"
16
17namespace content {
18
19namespace {
20
21void FillCacheInfo(const AppCache* cache,
22                   const GURL& manifest_url,
23                   AppCacheStatus status, AppCacheInfo* info) {
24  info->manifest_url = manifest_url;
25  info->status = status;
26
27  if (!cache)
28    return;
29
30  info->cache_id = cache->cache_id();
31
32  if (!cache->is_complete())
33    return;
34
35  DCHECK(cache->owning_group());
36  info->is_complete = true;
37  info->group_id = cache->owning_group()->group_id();
38  info->last_update_time = cache->update_time();
39  info->creation_time = cache->owning_group()->creation_time();
40  info->size = cache->cache_size();
41}
42
43}  // Anonymous namespace
44
45AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend,
46                           AppCacheServiceImpl* service)
47    : host_id_(host_id),
48      spawning_host_id_(kAppCacheNoHostId), spawning_process_id_(0),
49      parent_host_id_(kAppCacheNoHostId), parent_process_id_(0),
50      pending_main_resource_cache_id_(kAppCacheNoCacheId),
51      pending_selected_cache_id_(kAppCacheNoCacheId),
52      frontend_(frontend), service_(service),
53      storage_(service->storage()),
54      pending_callback_param_(NULL),
55      main_resource_was_namespace_entry_(false),
56      main_resource_blocked_(false),
57      associated_cache_info_pending_(false) {
58  service_->AddObserver(this);
59}
60
61AppCacheHost::~AppCacheHost() {
62  service_->RemoveObserver(this);
63  FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this));
64  if (associated_cache_.get())
65    associated_cache_->UnassociateHost(this);
66  if (group_being_updated_.get())
67    group_being_updated_->RemoveUpdateObserver(this);
68  storage()->CancelDelegateCallbacks(this);
69  if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
70    service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_);
71}
72
73void AppCacheHost::AddObserver(Observer* observer) {
74  observers_.AddObserver(observer);
75}
76
77void AppCacheHost::RemoveObserver(Observer* observer) {
78  observers_.RemoveObserver(observer);
79}
80
81void AppCacheHost::SelectCache(const GURL& document_url,
82                               const int64 cache_document_was_loaded_from,
83                               const GURL& manifest_url) {
84  DCHECK(pending_start_update_callback_.is_null() &&
85         pending_swap_cache_callback_.is_null() &&
86         pending_get_status_callback_.is_null() &&
87         !is_selection_pending());
88
89  origin_in_use_ = document_url.GetOrigin();
90  if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
91    service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_);
92
93  if (main_resource_blocked_)
94    frontend_->OnContentBlocked(host_id_,
95                                blocked_manifest_url_);
96
97  // 6.9.6 The application cache selection algorithm.
98  // The algorithm is started here and continues in FinishCacheSelection,
99  // after cache or group loading is complete.
100  // Note: Foreign entries are detected on the client side and
101  // MarkAsForeignEntry is called in that case, so that detection
102  // step is skipped here. See WebApplicationCacheHostImpl.cc
103
104  if (cache_document_was_loaded_from != kAppCacheNoCacheId) {
105    LoadSelectedCache(cache_document_was_loaded_from);
106    return;
107  }
108
109  if (!manifest_url.is_empty() &&
110      (manifest_url.GetOrigin() == document_url.GetOrigin())) {
111    DCHECK(!first_party_url_.is_empty());
112    AppCachePolicy* policy = service()->appcache_policy();
113    if (policy &&
114        !policy->CanCreateAppCache(manifest_url, first_party_url_)) {
115      FinishCacheSelection(NULL, NULL);
116      std::vector<int> host_ids(1, host_id_);
117      frontend_->OnEventRaised(host_ids, APPCACHE_CHECKING_EVENT);
118      frontend_->OnErrorEventRaised(
119          host_ids,
120          AppCacheErrorDetails(
121              "Cache creation was blocked by the content policy",
122              APPCACHE_POLICY_ERROR,
123              GURL(),
124              0,
125              false /*is_cross_origin*/));
126      frontend_->OnContentBlocked(host_id_, manifest_url);
127      return;
128    }
129
130    // Note: The client detects if the document was not loaded using HTTP GET
131    // and invokes SelectCache without a manifest url, so that detection step
132    // is also skipped here. See WebApplicationCacheHostImpl.cc
133    set_preferred_manifest_url(manifest_url);
134    new_master_entry_url_ = document_url;
135    LoadOrCreateGroup(manifest_url);
136    return;
137  }
138
139  // TODO(michaeln): If there was a manifest URL, the user agent may report
140  // to the user that it was ignored, to aid in application development.
141  FinishCacheSelection(NULL, NULL);
142}
143
144void AppCacheHost::SelectCacheForWorker(int parent_process_id,
145                                        int parent_host_id) {
146  DCHECK(pending_start_update_callback_.is_null() &&
147         pending_swap_cache_callback_.is_null() &&
148         pending_get_status_callback_.is_null() &&
149         !is_selection_pending());
150
151  parent_process_id_ = parent_process_id;
152  parent_host_id_ = parent_host_id;
153  FinishCacheSelection(NULL, NULL);
154}
155
156void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) {
157  DCHECK(pending_start_update_callback_.is_null() &&
158         pending_swap_cache_callback_.is_null() &&
159         pending_get_status_callback_.is_null() &&
160         !is_selection_pending());
161
162  if (appcache_id != kAppCacheNoCacheId) {
163    LoadSelectedCache(appcache_id);
164    return;
165  }
166  FinishCacheSelection(NULL, NULL);
167}
168
169// TODO(michaeln): change method name to MarkEntryAsForeign for consistency
170void AppCacheHost::MarkAsForeignEntry(const GURL& document_url,
171                                      int64 cache_document_was_loaded_from) {
172  // The document url is not the resource url in the fallback case.
173  storage()->MarkEntryAsForeign(
174      main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url,
175      cache_document_was_loaded_from);
176  SelectCache(document_url, kAppCacheNoCacheId, GURL());
177}
178
179void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback,
180                                         void* callback_param) {
181  DCHECK(pending_start_update_callback_.is_null() &&
182         pending_swap_cache_callback_.is_null() &&
183         pending_get_status_callback_.is_null());
184
185  pending_get_status_callback_ = callback;
186  pending_callback_param_ = callback_param;
187  if (is_selection_pending())
188    return;
189
190  DoPendingGetStatus();
191}
192
193void AppCacheHost::DoPendingGetStatus() {
194  DCHECK_EQ(false, pending_get_status_callback_.is_null());
195
196  pending_get_status_callback_.Run(GetStatus(), pending_callback_param_);
197  pending_get_status_callback_.Reset();
198  pending_callback_param_ = NULL;
199}
200
201void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback,
202                                           void* callback_param) {
203  DCHECK(pending_start_update_callback_.is_null() &&
204         pending_swap_cache_callback_.is_null() &&
205         pending_get_status_callback_.is_null());
206
207  pending_start_update_callback_ = callback;
208  pending_callback_param_ = callback_param;
209  if (is_selection_pending())
210    return;
211
212  DoPendingStartUpdate();
213}
214
215void AppCacheHost::DoPendingStartUpdate() {
216  DCHECK_EQ(false, pending_start_update_callback_.is_null());
217
218  // 6.9.8 Application cache API
219  bool success = false;
220  if (associated_cache_.get() && associated_cache_->owning_group()) {
221    AppCacheGroup* group = associated_cache_->owning_group();
222    if (!group->is_obsolete() && !group->is_being_deleted()) {
223      success = true;
224      group->StartUpdate();
225    }
226  }
227
228  pending_start_update_callback_.Run(success, pending_callback_param_);
229  pending_start_update_callback_.Reset();
230  pending_callback_param_ = NULL;
231}
232
233void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback,
234                                         void* callback_param) {
235  DCHECK(pending_start_update_callback_.is_null() &&
236         pending_swap_cache_callback_.is_null() &&
237         pending_get_status_callback_.is_null());
238
239  pending_swap_cache_callback_ = callback;
240  pending_callback_param_ = callback_param;
241  if (is_selection_pending())
242    return;
243
244  DoPendingSwapCache();
245}
246
247void AppCacheHost::DoPendingSwapCache() {
248  DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
249
250  // 6.9.8 Application cache API
251  bool success = false;
252  if (associated_cache_.get() && associated_cache_->owning_group()) {
253    if (associated_cache_->owning_group()->is_obsolete()) {
254      success = true;
255      AssociateNoCache(GURL());
256    } else if (swappable_cache_.get()) {
257      DCHECK(swappable_cache_.get() ==
258             swappable_cache_->owning_group()->newest_complete_cache());
259      success = true;
260      AssociateCompleteCache(swappable_cache_.get());
261    }
262  }
263
264  pending_swap_cache_callback_.Run(success, pending_callback_param_);
265  pending_swap_cache_callback_.Reset();
266  pending_callback_param_ = NULL;
267}
268
269void AppCacheHost::SetSpawningHostId(
270    int spawning_process_id, int spawning_host_id) {
271  spawning_process_id_ = spawning_process_id;
272  spawning_host_id_ = spawning_host_id;
273}
274
275const AppCacheHost* AppCacheHost::GetSpawningHost() const {
276  AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_);
277  return backend ? backend->GetHost(spawning_host_id_) : NULL;
278}
279
280AppCacheHost* AppCacheHost::GetParentAppCacheHost() const {
281  DCHECK(is_for_dedicated_worker());
282  AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_);
283  return backend ? backend->GetHost(parent_host_id_) : NULL;
284}
285
286AppCacheRequestHandler* AppCacheHost::CreateRequestHandler(
287    net::URLRequest* request,
288    ResourceType resource_type) {
289  if (is_for_dedicated_worker()) {
290    AppCacheHost* parent_host = GetParentAppCacheHost();
291    if (parent_host)
292      return parent_host->CreateRequestHandler(request, resource_type);
293    return NULL;
294  }
295
296  if (AppCacheRequestHandler::IsMainResourceType(resource_type)) {
297    // Store the first party origin so that it can be used later in SelectCache
298    // for checking whether the creation of the appcache is allowed.
299    first_party_url_ = request->first_party_for_cookies();
300    return new AppCacheRequestHandler(this, resource_type);
301  }
302
303  if ((associated_cache() && associated_cache()->is_complete()) ||
304      is_selection_pending()) {
305    return new AppCacheRequestHandler(this, resource_type);
306  }
307  return NULL;
308}
309
310void AppCacheHost::GetResourceList(
311    AppCacheResourceInfoVector* resource_infos) {
312  if (associated_cache_.get() && associated_cache_->is_complete())
313    associated_cache_->ToResourceInfoVector(resource_infos);
314}
315
316AppCacheStatus AppCacheHost::GetStatus() {
317  // 6.9.8 Application cache API
318  AppCache* cache = associated_cache();
319  if (!cache)
320    return APPCACHE_STATUS_UNCACHED;
321
322  // A cache without an owning group represents the cache being constructed
323  // during the application cache update process.
324  if (!cache->owning_group())
325    return APPCACHE_STATUS_DOWNLOADING;
326
327  if (cache->owning_group()->is_obsolete())
328    return APPCACHE_STATUS_OBSOLETE;
329  if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING)
330    return APPCACHE_STATUS_CHECKING;
331  if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING)
332    return APPCACHE_STATUS_DOWNLOADING;
333  if (swappable_cache_.get())
334    return APPCACHE_STATUS_UPDATE_READY;
335  return APPCACHE_STATUS_IDLE;
336}
337
338void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) {
339  DCHECK(manifest_url.is_valid());
340  pending_selected_manifest_url_ = manifest_url;
341  storage()->LoadOrCreateGroup(manifest_url, this);
342}
343
344void AppCacheHost::OnGroupLoaded(AppCacheGroup* group,
345                                 const GURL& manifest_url) {
346  DCHECK(manifest_url == pending_selected_manifest_url_);
347  pending_selected_manifest_url_ = GURL();
348  FinishCacheSelection(NULL, group);
349}
350
351void AppCacheHost::LoadSelectedCache(int64 cache_id) {
352  DCHECK(cache_id != kAppCacheNoCacheId);
353  pending_selected_cache_id_ = cache_id;
354  storage()->LoadCache(cache_id, this);
355}
356
357void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) {
358  if (cache_id == pending_main_resource_cache_id_) {
359    pending_main_resource_cache_id_ = kAppCacheNoCacheId;
360    main_resource_cache_ = cache;
361  } else if (cache_id == pending_selected_cache_id_) {
362    pending_selected_cache_id_ = kAppCacheNoCacheId;
363    FinishCacheSelection(cache, NULL);
364  }
365}
366
367void AppCacheHost::FinishCacheSelection(
368    AppCache *cache, AppCacheGroup* group) {
369  DCHECK(!associated_cache());
370
371  // 6.9.6 The application cache selection algorithm
372  if (cache) {
373    // If document was loaded from an application cache, Associate document
374    // with the application cache from which it was loaded. Invoke the
375    // application cache update process for that cache and with the browsing
376    // context being navigated.
377    DCHECK(cache->owning_group());
378    DCHECK(new_master_entry_url_.is_empty());
379    DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_);
380    AppCacheGroup* owing_group = cache->owning_group();
381    const char* kFormatString =
382        "Document was loaded from Application Cache with manifest %s";
383    frontend_->OnLogMessage(
384        host_id_, APPCACHE_LOG_INFO,
385        base::StringPrintf(
386            kFormatString, owing_group->manifest_url().spec().c_str()));
387    AssociateCompleteCache(cache);
388    if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) {
389      owing_group->StartUpdateWithHost(this);
390      ObserveGroupBeingUpdated(owing_group);
391    }
392  } else if (group && !group->is_being_deleted()) {
393    // If document was loaded using HTTP GET or equivalent, and, there is a
394    // manifest URL, and manifest URL has the same origin as document.
395    // Invoke the application cache update process for manifest URL, with
396    // the browsing context being navigated, and with document and the
397    // resource from which document was loaded as the new master resourse.
398    DCHECK(!group->is_obsolete());
399    DCHECK(new_master_entry_url_.is_valid());
400    DCHECK_EQ(group->manifest_url(), preferred_manifest_url_);
401    const char* kFormatString = group->HasCache() ?
402        "Adding master entry to Application Cache with manifest %s" :
403        "Creating Application Cache with manifest %s";
404    frontend_->OnLogMessage(
405        host_id_, APPCACHE_LOG_INFO,
406        base::StringPrintf(kFormatString,
407                           group->manifest_url().spec().c_str()));
408    // The UpdateJob may produce one for us later.
409    AssociateNoCache(preferred_manifest_url_);
410    group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_);
411    ObserveGroupBeingUpdated(group);
412  } else {
413    // Otherwise, the Document is not associated with any application cache.
414    new_master_entry_url_ = GURL();
415    AssociateNoCache(GURL());
416  }
417
418  // Respond to pending callbacks now that we have a selection.
419  if (!pending_get_status_callback_.is_null())
420    DoPendingGetStatus();
421  else if (!pending_start_update_callback_.is_null())
422    DoPendingStartUpdate();
423  else if (!pending_swap_cache_callback_.is_null())
424    DoPendingSwapCache();
425
426  FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this));
427}
428
429void AppCacheHost::OnServiceReinitialized(
430    AppCacheStorageReference* old_storage_ref) {
431  // We continue to use the disabled instance, but arrange for its
432  // deletion when its no longer needed.
433  if (old_storage_ref->storage() == storage())
434    disabled_storage_reference_ = old_storage_ref;
435}
436
437void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
438  DCHECK(!group_being_updated_.get());
439  group_being_updated_ = group;
440  newest_cache_of_group_being_updated_ = group->newest_complete_cache();
441  group->AddUpdateObserver(this);
442}
443
444void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
445  DCHECK_EQ(group, group_being_updated_.get());
446  group->RemoveUpdateObserver(this);
447
448  // Add a reference to the newest complete cache.
449  SetSwappableCache(group);
450
451  group_being_updated_ = NULL;
452  newest_cache_of_group_being_updated_ = NULL;
453
454  if (associated_cache_info_pending_ && associated_cache_.get() &&
455      associated_cache_->is_complete()) {
456    AppCacheInfo info;
457    FillCacheInfo(
458        associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info);
459    associated_cache_info_pending_ = false;
460    frontend_->OnCacheSelected(host_id_, info);
461  }
462}
463
464void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
465  if (!group) {
466    swappable_cache_ = NULL;
467  } else {
468    AppCache* new_cache = group->newest_complete_cache();
469    if (new_cache != associated_cache_.get())
470      swappable_cache_ = new_cache;
471    else
472      swappable_cache_ = NULL;
473  }
474}
475
476void AppCacheHost::LoadMainResourceCache(int64 cache_id) {
477  DCHECK(cache_id != kAppCacheNoCacheId);
478  if (pending_main_resource_cache_id_ == cache_id ||
479      (main_resource_cache_.get() &&
480       main_resource_cache_->cache_id() == cache_id)) {
481    return;
482  }
483  pending_main_resource_cache_id_ = cache_id;
484  storage()->LoadCache(cache_id, this);
485}
486
487void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
488    const GURL& namespace_entry_url) {
489  main_resource_was_namespace_entry_ = true;
490  namespace_entry_url_ = namespace_entry_url;
491}
492
493void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
494  main_resource_blocked_ = true;
495  blocked_manifest_url_ = manifest_url;
496}
497
498void AppCacheHost::PrepareForTransfer() {
499  // This can only happen prior to the document having been loaded.
500  DCHECK(!associated_cache());
501  DCHECK(!is_selection_pending());
502  DCHECK(!group_being_updated_.get());
503  host_id_ = kAppCacheNoHostId;
504  frontend_ = NULL;
505}
506
507void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) {
508  host_id_ = host_id;
509  frontend_ = frontend;
510}
511
512void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
513  // manifest url can be empty.
514  AssociateCacheHelper(NULL, manifest_url);
515}
516
517void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
518                                            const GURL& manifest_url) {
519  DCHECK(cache && !cache->is_complete());
520  DCHECK(!manifest_url.is_empty());
521  AssociateCacheHelper(cache, manifest_url);
522}
523
524void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
525  DCHECK(cache && cache->is_complete());
526  AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
527}
528
529void AppCacheHost::AssociateCacheHelper(AppCache* cache,
530                                        const GURL& manifest_url) {
531  if (associated_cache_.get()) {
532    associated_cache_->UnassociateHost(this);
533  }
534
535  associated_cache_ = cache;
536  SetSwappableCache(cache ? cache->owning_group() : NULL);
537  associated_cache_info_pending_ = cache && !cache->is_complete();
538  AppCacheInfo info;
539  if (cache)
540    cache->AssociateHost(this);
541
542  FillCacheInfo(cache, manifest_url, GetStatus(), &info);
543  frontend_->OnCacheSelected(host_id_, info);
544}
545
546}  // namespace content
547