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