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