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