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.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "base/stl_util.h"
11#include "webkit/browser/appcache/appcache_executable_handler.h"
12#include "webkit/browser/appcache/appcache_group.h"
13#include "webkit/browser/appcache/appcache_host.h"
14#include "webkit/browser/appcache/appcache_storage.h"
15#include "webkit/common/appcache/appcache_interfaces.h"
16
17namespace appcache {
18
19AppCache::AppCache(AppCacheStorage* storage, int64 cache_id)
20    : cache_id_(cache_id),
21      owning_group_(NULL),
22      online_whitelist_all_(false),
23      is_complete_(false),
24      cache_size_(0),
25      storage_(storage) {
26  storage_->working_set()->AddCache(this);
27}
28
29AppCache::~AppCache() {
30  DCHECK(associated_hosts_.empty());
31  if (owning_group_.get()) {
32    DCHECK(is_complete_);
33    owning_group_->RemoveCache(this);
34  }
35  DCHECK(!owning_group_.get());
36  storage_->working_set()->RemoveCache(this);
37  STLDeleteContainerPairSecondPointers(
38      executable_handlers_.begin(), executable_handlers_.end());
39}
40
41void AppCache::UnassociateHost(AppCacheHost* host) {
42  associated_hosts_.erase(host);
43}
44
45void AppCache::AddEntry(const GURL& url, const AppCacheEntry& entry) {
46  DCHECK(entries_.find(url) == entries_.end());
47  entries_.insert(EntryMap::value_type(url, entry));
48  cache_size_ += entry.response_size();
49}
50
51bool AppCache::AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry) {
52  std::pair<EntryMap::iterator, bool> ret =
53      entries_.insert(EntryMap::value_type(url, entry));
54
55  // Entry already exists.  Merge the types of the new and existing entries.
56  if (!ret.second)
57    ret.first->second.add_types(entry.types());
58  else
59    cache_size_ += entry.response_size();  // New entry. Add to cache size.
60  return ret.second;
61}
62
63void AppCache::RemoveEntry(const GURL& url) {
64  EntryMap::iterator found = entries_.find(url);
65  DCHECK(found != entries_.end());
66  cache_size_ -= found->second.response_size();
67  entries_.erase(found);
68}
69
70AppCacheEntry* AppCache::GetEntry(const GURL& url) {
71  EntryMap::iterator it = entries_.find(url);
72  return (it != entries_.end()) ? &(it->second) : NULL;
73}
74
75const AppCacheEntry* AppCache::GetEntryAndUrlWithResponseId(
76    int64 response_id, GURL* optional_url_out) {
77  for (EntryMap::const_iterator iter = entries_.begin();
78       iter !=  entries_.end(); ++iter) {
79    if (iter->second.response_id() == response_id) {
80      if (optional_url_out)
81        *optional_url_out = iter->first;
82      return &iter->second;
83    }
84  }
85  return NULL;
86}
87
88AppCacheExecutableHandler* AppCache::GetExecutableHandler(int64 response_id) {
89  HandlerMap::const_iterator found = executable_handlers_.find(response_id);
90  if (found != executable_handlers_.end())
91    return found->second;
92  return NULL;
93}
94
95AppCacheExecutableHandler* AppCache::GetOrCreateExecutableHandler(
96    int64 response_id, net::IOBuffer* handler_source) {
97  AppCacheExecutableHandler* handler = GetExecutableHandler(response_id);
98  if (handler)
99    return handler;
100
101  GURL handler_url;
102  const AppCacheEntry* entry = GetEntryAndUrlWithResponseId(
103      response_id, &handler_url);
104  if (!entry || !entry->IsExecutable())
105    return NULL;
106
107  DCHECK(storage_->service()->handler_factory());
108  scoped_ptr<AppCacheExecutableHandler> own_ptr =
109      storage_->service()->handler_factory()->
110          CreateHandler(handler_url, handler_source);
111  handler = own_ptr.release();
112  if (!handler)
113    return NULL;
114  executable_handlers_[response_id] = handler;
115  return handler;
116}
117
118GURL AppCache::GetNamespaceEntryUrl(const NamespaceVector& namespaces,
119                                    const GURL& namespace_url) const {
120  size_t count = namespaces.size();
121  for (size_t i = 0; i < count; ++i) {
122    if (namespaces[i].namespace_url == namespace_url)
123      return namespaces[i].target_url;
124  }
125  NOTREACHED();
126  return GURL();
127}
128
129namespace {
130bool SortNamespacesByLength(
131    const Namespace& lhs, const Namespace& rhs) {
132  return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length();
133}
134}
135
136void AppCache::InitializeWithManifest(Manifest* manifest) {
137  DCHECK(manifest);
138  intercept_namespaces_.swap(manifest->intercept_namespaces);
139  fallback_namespaces_.swap(manifest->fallback_namespaces);
140  online_whitelist_namespaces_.swap(manifest->online_whitelist_namespaces);
141  online_whitelist_all_ = manifest->online_whitelist_all;
142
143  // Sort the namespaces by url string length, longest to shortest,
144  // since longer matches trump when matching a url to a namespace.
145  std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
146            SortNamespacesByLength);
147  std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
148            SortNamespacesByLength);
149}
150
151void AppCache::InitializeWithDatabaseRecords(
152    const AppCacheDatabase::CacheRecord& cache_record,
153    const std::vector<AppCacheDatabase::EntryRecord>& entries,
154    const std::vector<AppCacheDatabase::NamespaceRecord>& intercepts,
155    const std::vector<AppCacheDatabase::NamespaceRecord>& fallbacks,
156    const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists) {
157  DCHECK(cache_id_ == cache_record.cache_id);
158  online_whitelist_all_ = cache_record.online_wildcard;
159  update_time_ = cache_record.update_time;
160
161  for (size_t i = 0; i < entries.size(); ++i) {
162    const AppCacheDatabase::EntryRecord& entry = entries.at(i);
163    AddEntry(entry.url, AppCacheEntry(entry.flags, entry.response_id,
164                                      entry.response_size));
165  }
166  DCHECK(cache_size_ == cache_record.cache_size);
167
168  for (size_t i = 0; i < intercepts.size(); ++i)
169    intercept_namespaces_.push_back(intercepts.at(i).namespace_);
170
171  for (size_t i = 0; i < fallbacks.size(); ++i)
172    fallback_namespaces_.push_back(fallbacks.at(i).namespace_);
173
174  // Sort the fallback namespaces by url string length, longest to shortest,
175  // since longer matches trump when matching a url to a namespace.
176  std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
177            SortNamespacesByLength);
178  std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
179            SortNamespacesByLength);
180
181  for (size_t i = 0; i < whitelists.size(); ++i) {
182    const AppCacheDatabase::OnlineWhiteListRecord& record = whitelists.at(i);
183    online_whitelist_namespaces_.push_back(
184        Namespace(APPCACHE_NETWORK_NAMESPACE,
185                  record.namespace_url,
186                  GURL(),
187                  record.is_pattern));
188  }
189}
190
191void AppCache::ToDatabaseRecords(
192    const AppCacheGroup* group,
193    AppCacheDatabase::CacheRecord* cache_record,
194    std::vector<AppCacheDatabase::EntryRecord>* entries,
195    std::vector<AppCacheDatabase::NamespaceRecord>* intercepts,
196    std::vector<AppCacheDatabase::NamespaceRecord>* fallbacks,
197    std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists) {
198  DCHECK(group && cache_record && entries && fallbacks && whitelists);
199  DCHECK(entries->empty() && fallbacks->empty() && whitelists->empty());
200
201  cache_record->cache_id = cache_id_;
202  cache_record->group_id = group->group_id();
203  cache_record->online_wildcard = online_whitelist_all_;
204  cache_record->update_time = update_time_;
205  cache_record->cache_size = 0;
206
207  for (EntryMap::const_iterator iter = entries_.begin();
208       iter != entries_.end(); ++iter) {
209    entries->push_back(AppCacheDatabase::EntryRecord());
210    AppCacheDatabase::EntryRecord& record = entries->back();
211    record.url = iter->first;
212    record.cache_id = cache_id_;
213    record.flags = iter->second.types();
214    record.response_id = iter->second.response_id();
215    record.response_size = iter->second.response_size();
216    cache_record->cache_size += record.response_size;
217  }
218
219  GURL origin = group->manifest_url().GetOrigin();
220
221  for (size_t i = 0; i < intercept_namespaces_.size(); ++i) {
222    intercepts->push_back(AppCacheDatabase::NamespaceRecord());
223    AppCacheDatabase::NamespaceRecord& record = intercepts->back();
224    record.cache_id = cache_id_;
225    record.origin = origin;
226    record.namespace_ = intercept_namespaces_[i];
227  }
228
229  for (size_t i = 0; i < fallback_namespaces_.size(); ++i) {
230    fallbacks->push_back(AppCacheDatabase::NamespaceRecord());
231    AppCacheDatabase::NamespaceRecord& record = fallbacks->back();
232    record.cache_id = cache_id_;
233    record.origin = origin;
234    record.namespace_ = fallback_namespaces_[i];
235  }
236
237  for (size_t i = 0; i < online_whitelist_namespaces_.size(); ++i) {
238    whitelists->push_back(AppCacheDatabase::OnlineWhiteListRecord());
239    AppCacheDatabase::OnlineWhiteListRecord& record = whitelists->back();
240    record.cache_id = cache_id_;
241    record.namespace_url = online_whitelist_namespaces_[i].namespace_url;
242    record.is_pattern = online_whitelist_namespaces_[i].is_pattern;
243  }
244}
245
246bool AppCache::FindResponseForRequest(const GURL& url,
247    AppCacheEntry* found_entry, GURL* found_intercept_namespace,
248    AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace,
249    bool* found_network_namespace) {
250  // Ignore fragments when looking up URL in the cache.
251  GURL url_no_ref;
252  if (url.has_ref()) {
253    GURL::Replacements replacements;
254    replacements.ClearRef();
255    url_no_ref = url.ReplaceComponents(replacements);
256  } else {
257    url_no_ref = url;
258  }
259
260  // 6.6.6 Changes to the networking model
261
262  AppCacheEntry* entry = GetEntry(url_no_ref);
263  if (entry) {
264    *found_entry = *entry;
265    return true;
266  }
267
268  if ((*found_network_namespace = IsInNetworkNamespace(url_no_ref)))
269    return true;
270
271  const Namespace* intercept_namespace = FindInterceptNamespace(url_no_ref);
272  if (intercept_namespace) {
273    entry = GetEntry(intercept_namespace->target_url);
274    DCHECK(entry);
275    *found_entry = *entry;
276    *found_intercept_namespace = intercept_namespace->namespace_url;
277    return true;
278  }
279
280  const Namespace* fallback_namespace = FindFallbackNamespace(url_no_ref);
281  if (fallback_namespace) {
282    entry = GetEntry(fallback_namespace->target_url);
283    DCHECK(entry);
284    *found_fallback_entry = *entry;
285    *found_fallback_namespace = fallback_namespace->namespace_url;
286    return true;
287  }
288
289  *found_network_namespace = online_whitelist_all_;
290  return *found_network_namespace;
291}
292
293
294void AppCache::ToResourceInfoVector(AppCacheResourceInfoVector* infos) const {
295  DCHECK(infos && infos->empty());
296  for (EntryMap::const_iterator iter = entries_.begin();
297       iter !=  entries_.end(); ++iter) {
298    infos->push_back(AppCacheResourceInfo());
299    AppCacheResourceInfo& info = infos->back();
300    info.url = iter->first;
301    info.is_master = iter->second.IsMaster();
302    info.is_manifest = iter->second.IsManifest();
303    info.is_intercept = iter->second.IsIntercept();
304    info.is_fallback = iter->second.IsFallback();
305    info.is_foreign = iter->second.IsForeign();
306    info.is_explicit = iter->second.IsExplicit();
307    info.size = iter->second.response_size();
308    info.response_id = iter->second.response_id();
309  }
310}
311
312// static
313const Namespace* AppCache::FindNamespace(
314    const NamespaceVector& namespaces,
315    const GURL& url) {
316  size_t count = namespaces.size();
317  for (size_t i = 0; i < count; ++i) {
318    if (namespaces[i].IsMatch(url))
319      return &namespaces[i];
320  }
321  return NULL;
322}
323
324}  // namespace appcache
325