blob: f823298b52346df7eff7917166f51842f67d91a8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/appcache/appcache.h"
#include <stddef.h>
#include <algorithm>
#include <vector>
#include "base/logging.h"
#include "base/stl_util.h"
#include "content/browser/appcache/appcache_database.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_host.h"
#include "content/browser/appcache/appcache_storage.h"
#include "content/common/appcache_interfaces.h"
#include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
#include "url/origin.h"
namespace content {
// static
bool AppCache::CheckValidManifestScope(const GURL& manifest_url,
const std::string& manifest_scope) {
if (manifest_scope.empty())
return false;
const GURL url = manifest_url.Resolve(manifest_scope);
return url.is_valid() && !url.has_ref() && !url.has_query() &&
url.spec().back() == '/';
}
// static
std::string AppCache::GetManifestScope(const GURL& manifest_url,
std::string optional_scope) {
DCHECK(manifest_url.is_valid());
if (!optional_scope.empty()) {
std::string scope = manifest_url.Resolve(optional_scope).path();
if (CheckValidManifestScope(manifest_url, scope)) {
return optional_scope;
}
}
// The default manifest scope is the path to the manifest URL's containing
// directory.
const GURL manifest_scope_url = manifest_url.GetWithoutFilename();
DCHECK(manifest_scope_url.is_valid());
DCHECK(CheckValidManifestScope(manifest_url, manifest_scope_url.path()));
return manifest_scope_url.path();
}
AppCache::AppCache(AppCacheStorage* storage, int64_t cache_id)
: cache_id_(cache_id),
owning_group_(nullptr),
online_whitelist_all_(false),
is_complete_(false),
cache_size_(0),
padding_size_(0),
manifest_parser_version_(-1),
manifest_scope_(""),
storage_(storage) {
storage_->working_set()->AddCache(this);
}
AppCache::~AppCache() {
DCHECK(associated_hosts_.empty());
if (owning_group_.get()) {
DCHECK(is_complete_);
owning_group_->RemoveCache(this);
}
DCHECK(!owning_group_.get());
storage_->working_set()->RemoveCache(this);
}
void AppCache::UnassociateHost(AppCacheHost* host) {
associated_hosts_.erase(host);
}
void AppCache::AddEntry(const GURL& url, const AppCacheEntry& entry) {
DCHECK(entries_.find(url) == entries_.end());
entries_.insert(EntryMap::value_type(url, entry));
cache_size_ += entry.response_size();
padding_size_ += entry.padding_size();
}
bool AppCache::AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry) {
std::pair<EntryMap::iterator, bool> ret =
entries_.insert(EntryMap::value_type(url, entry));
// Entry already exists. Merge the types and token expiration of the new and
// existing entries.
if (!ret.second) {
ret.first->second.add_types(entry.types());
} else {
cache_size_ += entry.response_size(); // New entry. Add to cache size.
padding_size_ += entry.padding_size();
}
// TODO(pwnall): Figure out if we want to overwrite or max the two entries.
ret.first->second.set_token_expires(
std::max(entry.token_expires(), ret.first->second.token_expires()));
return ret.second;
}
void AppCache::RemoveEntry(const GURL& url) {
auto found = entries_.find(url);
DCHECK(found != entries_.end());
DCHECK_GE(cache_size_, found->second.response_size());
DCHECK_GE(padding_size_, found->second.padding_size());
cache_size_ -= found->second.response_size();
padding_size_ -= found->second.padding_size();
entries_.erase(found);
}
AppCacheEntry* AppCache::GetEntry(const GURL& url) {
auto it = entries_.find(url);
return (it != entries_.end()) ? &(it->second) : nullptr;
}
const AppCacheEntry* AppCache::GetEntryAndUrlWithResponseId(
int64_t response_id,
GURL* optional_url_out) {
for (const auto& pair : entries_) {
if (pair.second.response_id() == response_id) {
if (optional_url_out)
*optional_url_out = pair.first;
return &pair.second;
}
}
return nullptr;
}
GURL AppCache::GetNamespaceEntryUrl(
const std::vector<AppCacheNamespace>& namespaces,
const GURL& namespace_url) const {
size_t count = namespaces.size();
for (size_t i = 0; i < count; ++i) {
if (namespaces[i].namespace_url == namespace_url)
return namespaces[i].target_url;
}
NOTREACHED();
return GURL();
}
namespace {
bool SortNamespacesByLength(
const AppCacheNamespace& lhs, const AppCacheNamespace& rhs) {
return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length();
}
}
void AppCache::InitializeWithManifest(AppCacheManifest* manifest,
base::Time token_expires) {
DCHECK(manifest);
manifest_parser_version_ = manifest->parser_version;
manifest_scope_ = manifest->scope;
intercept_namespaces_.swap(manifest->intercept_namespaces);
fallback_namespaces_.swap(manifest->fallback_namespaces);
online_whitelist_namespaces_.swap(manifest->online_whitelist_namespaces);
online_whitelist_all_ = manifest->online_whitelist_all;
token_expires_ = token_expires;
// Sort the namespaces by url string length, longest to shortest,
// since longer matches trump when matching a url to a namespace.
std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
SortNamespacesByLength);
std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
SortNamespacesByLength);
for (auto& intercept : intercept_namespaces_)
intercept.token_expires = token_expires;
for (auto& fallback : fallback_namespaces_)
fallback.token_expires = token_expires;
}
void AppCache::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) {
DCHECK_EQ(cache_id_, cache_record.cache_id);
manifest_parser_version_ = cache_record.manifest_parser_version;
manifest_scope_ = cache_record.manifest_scope;
online_whitelist_all_ = cache_record.online_wildcard;
update_time_ = cache_record.update_time;
token_expires_ = cache_record.token_expires;
for (const AppCacheDatabase::EntryRecord& entry : entries) {
AddEntry(entry.url,
AppCacheEntry(entry.flags, entry.response_id, entry.response_size,
entry.padding_size, entry.token_expires));
}
DCHECK_EQ(cache_size_, cache_record.cache_size);
DCHECK_EQ(padding_size_, cache_record.padding_size);
for (const auto& intercept : intercepts)
intercept_namespaces_.push_back(intercept.namespace_);
for (const auto& fallback : fallbacks)
fallback_namespaces_.push_back(fallback.namespace_);
// Sort the fallback namespaces by url string length, longest to shortest,
// since longer matches trump when matching a url to a namespace.
std::sort(intercept_namespaces_.begin(), intercept_namespaces_.end(),
SortNamespacesByLength);
std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
SortNamespacesByLength);
for (const auto& record : whitelists) {
online_whitelist_namespaces_.emplace_back(APPCACHE_NETWORK_NAMESPACE,
record.namespace_url, GURL());
}
}
void AppCache::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) {
DCHECK(group && cache_record && entries && fallbacks && whitelists);
DCHECK(entries->empty() && fallbacks->empty() && whitelists->empty());
cache_record->cache_id = cache_id_;
cache_record->group_id = group->group_id();
cache_record->online_wildcard = online_whitelist_all_;
cache_record->update_time = update_time_;
cache_record->cache_size = cache_size_;
cache_record->padding_size = padding_size_;
cache_record->manifest_parser_version = manifest_parser_version_;
cache_record->manifest_scope = manifest_scope_;
cache_record->token_expires = token_expires_;
for (const auto& pair : entries_) {
entries->push_back(AppCacheDatabase::EntryRecord());
AppCacheDatabase::EntryRecord& record = entries->back();
record.url = pair.first;
record.cache_id = cache_id_;
record.flags = pair.second.types();
record.response_id = pair.second.response_id();
record.response_size = pair.second.response_size();
record.padding_size = pair.second.padding_size();
record.token_expires = pair.second.token_expires();
}
const url::Origin origin = url::Origin::Create(group->manifest_url());
for (const AppCacheNamespace& intercept_namespace : intercept_namespaces_) {
intercepts->push_back(AppCacheDatabase::NamespaceRecord());
AppCacheDatabase::NamespaceRecord& record = intercepts->back();
record.cache_id = cache_id_;
record.origin = origin;
record.namespace_ = intercept_namespace;
record.token_expires = intercept_namespace.token_expires;
}
for (const AppCacheNamespace& fallback_namespace : fallback_namespaces_) {
fallbacks->push_back(AppCacheDatabase::NamespaceRecord());
AppCacheDatabase::NamespaceRecord& record = fallbacks->back();
record.cache_id = cache_id_;
record.origin = origin;
record.namespace_ = fallback_namespace;
record.token_expires = fallback_namespace.token_expires;
}
for (const AppCacheNamespace& online_namespace :
online_whitelist_namespaces_) {
whitelists->push_back(AppCacheDatabase::OnlineWhiteListRecord());
AppCacheDatabase::OnlineWhiteListRecord& record = whitelists->back();
record.cache_id = cache_id_;
record.namespace_url = online_namespace.namespace_url;
}
}
bool AppCache::FindResponseForRequest(const GURL& url,
AppCacheEntry* found_entry, GURL* found_intercept_namespace,
AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace,
bool* found_network_namespace) {
// Ignore fragments when looking up URL in the cache.
GURL url_no_ref;
if (url.has_ref()) {
GURL::Replacements replacements;
replacements.ClearRef();
url_no_ref = url.ReplaceComponents(replacements);
} else {
url_no_ref = url;
}
// 6.6.6 Changes to the networking model
AppCacheEntry* entry = GetEntry(url_no_ref);
if (entry) {
*found_entry = *entry;
return true;
}
*found_network_namespace = IsInNetworkNamespace(url_no_ref);
if (*found_network_namespace)
return true;
const AppCacheNamespace* intercept_namespace =
FindInterceptNamespace(url_no_ref);
if (intercept_namespace) {
entry = GetEntry(intercept_namespace->target_url);
DCHECK(entry);
*found_entry = *entry;
*found_intercept_namespace = intercept_namespace->namespace_url;
return true;
}
const AppCacheNamespace* fallback_namespace =
FindFallbackNamespace(url_no_ref);
if (fallback_namespace) {
entry = GetEntry(fallback_namespace->target_url);
DCHECK(entry);
*found_fallback_entry = *entry;
*found_fallback_namespace = fallback_namespace->namespace_url;
return true;
}
*found_network_namespace = online_whitelist_all_;
return *found_network_namespace;
}
void AppCache::ToResourceInfoVector(
std::vector<blink::mojom::AppCacheResourceInfo>* infos) const {
DCHECK(infos && infos->empty());
for (const auto& pair : entries_) {
infos->push_back(blink::mojom::AppCacheResourceInfo());
blink::mojom::AppCacheResourceInfo& info = infos->back();
info.url = pair.first;
info.is_master = pair.second.IsMaster();
info.is_manifest = pair.second.IsManifest();
info.is_intercept = pair.second.IsIntercept();
info.is_fallback = pair.second.IsFallback();
info.is_foreign = pair.second.IsForeign();
info.is_explicit = pair.second.IsExplicit();
info.response_size = pair.second.response_size();
info.padding_size = pair.second.padding_size();
info.response_id = pair.second.response_id();
}
}
// static
const AppCacheNamespace* AppCache::FindNamespace(
const std::vector<AppCacheNamespace>& namespaces,
const GURL& url) {
size_t count = namespaces.size();
for (size_t i = 0; i < count; ++i) {
if (namespaces[i].IsMatch(url))
return &namespaces[i];
}
return nullptr;
}
} // namespace content