blob: ebd361559d1633f7b6ad7ddf20c6f77a1a69b687 [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 "chrome/browser/extensions/api/content_settings/content_settings_store.h"
#include <algorithm>
#include <memory>
#include <set>
#include <utility>
#include "base/debug/alias.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/content_settings/content_settings_api_constants.h"
#include "chrome/browser/extensions/api/content_settings/content_settings_helpers.h"
#include "chrome/common/chrome_features.h"
#include "components/content_settings/core/browser/content_settings_info.h"
#include "components/content_settings/core/browser/content_settings_origin_identifier_value_map.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/browser/website_settings_info.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
using content_settings::ConcatenationIterator;
using content_settings::Rule;
using content_settings::RuleIterator;
using content_settings::OriginIdentifierValueMap;
using content_settings::ResourceIdentifier;
namespace extensions {
struct ContentSettingsStore::ExtensionEntry {
// Extension id.
std::string id;
// Installation time.
base::Time install_time;
// Whether extension is enabled in the profile.
bool enabled;
// Content settings.
OriginIdentifierValueMap settings;
// Persistent incognito content settings.
OriginIdentifierValueMap incognito_persistent_settings;
// Session-only incognito content settings.
OriginIdentifierValueMap incognito_session_only_settings;
};
ContentSettingsStore::ContentSettingsStore() {
DCHECK(OnCorrectThread());
}
ContentSettingsStore::~ContentSettingsStore() {
}
std::unique_ptr<RuleIterator> ContentSettingsStore::GetRuleIterator(
ContentSettingsType type,
const content_settings::ResourceIdentifier& identifier,
bool incognito) const {
std::vector<std::unique_ptr<RuleIterator>> iterators;
// The individual |RuleIterators| shouldn't lock; pass |lock_| to the
// |ConcatenationIterator| in a locked state.
std::unique_ptr<base::AutoLock> auto_lock(new base::AutoLock(lock_));
// Iterate the extensions based on install time (most-recently installed
// items first).
for (const auto& entry : entries_) {
if (!entry->enabled)
continue;
std::unique_ptr<RuleIterator> rule_it;
if (incognito) {
rule_it = entry->incognito_session_only_settings.GetRuleIterator(
type, identifier, nullptr);
if (rule_it)
iterators.push_back(std::move(rule_it));
rule_it = entry->incognito_persistent_settings.GetRuleIterator(
type, identifier, nullptr);
if (rule_it)
iterators.push_back(std::move(rule_it));
} else {
rule_it = entry->settings.GetRuleIterator(type, identifier, nullptr);
if (rule_it)
iterators.push_back(std::move(rule_it));
}
}
if (iterators.empty())
return nullptr;
return std::make_unique<ConcatenationIterator>(std::move(iterators),
auto_lock.release());
}
void ContentSettingsStore::SetExtensionContentSetting(
const std::string& ext_id,
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType type,
const content_settings::ResourceIdentifier& identifier,
ContentSetting setting,
ExtensionPrefsScope scope) {
{
base::AutoLock lock(lock_);
OriginIdentifierValueMap* map = GetValueMap(ext_id, scope);
if (setting == CONTENT_SETTING_DEFAULT) {
map->DeleteValue(primary_pattern, secondary_pattern, type, identifier);
} else {
// Do not set a timestamp for extension settings.
map->SetValue(primary_pattern, secondary_pattern, type, identifier,
base::Time(), base::Value(setting));
}
}
// Send notification that content settings changed. (Note: This is responsible
// for updating the pref store, so cannot be skipped even if the setting would
// be masked by another extension.)
NotifyOfContentSettingChanged(ext_id,
scope != kExtensionPrefsScopeRegular);
}
void ContentSettingsStore::RegisterExtension(
const std::string& ext_id,
const base::Time& install_time,
bool is_enabled) {
base::AutoLock lock(lock_);
auto i = FindIterator(ext_id);
ExtensionEntry* entry = nullptr;
if (i != entries_.end()) {
entry = i->get();
} else {
entry = new ExtensionEntry;
entry->install_time = install_time;
// Insert in reverse-chronological order to maintain the invariant.
auto unique_entry = base::WrapUnique(entry);
auto location =
std::upper_bound(entries_.begin(), entries_.end(), unique_entry,
[](const std::unique_ptr<ExtensionEntry>& a,
const std::unique_ptr<ExtensionEntry>& b) {
return a->install_time > b->install_time;
});
entries_.insert(location, std::move(unique_entry));
}
entry->id = ext_id;
entry->enabled = is_enabled;
}
void ContentSettingsStore::UnregisterExtension(
const std::string& ext_id) {
bool notify = false;
bool notify_incognito = false;
{
base::AutoLock lock(lock_);
auto i = FindIterator(ext_id);
if (i == entries_.end())
return;
notify = !(*i)->settings.empty();
notify_incognito = !(*i)->incognito_persistent_settings.empty() ||
!(*i)->incognito_session_only_settings.empty();
entries_.erase(i);
}
if (notify)
NotifyOfContentSettingChanged(ext_id, false);
if (notify_incognito)
NotifyOfContentSettingChanged(ext_id, true);
}
void ContentSettingsStore::SetExtensionState(
const std::string& ext_id, bool is_enabled) {
bool notify = false;
bool notify_incognito = false;
{
base::AutoLock lock(lock_);
ExtensionEntry* entry = FindEntry(ext_id);
if (!entry)
return;
notify = !entry->settings.empty();
notify_incognito = !entry->incognito_persistent_settings.empty() ||
!entry->incognito_session_only_settings.empty();
entry->enabled = is_enabled;
}
if (notify)
NotifyOfContentSettingChanged(ext_id, false);
if (notify_incognito)
NotifyOfContentSettingChanged(ext_id, true);
}
OriginIdentifierValueMap* ContentSettingsStore::GetValueMap(
const std::string& ext_id,
ExtensionPrefsScope scope) {
const OriginIdentifierValueMap* result =
static_cast<const ContentSettingsStore*>(this)->GetValueMap(ext_id,
scope);
return const_cast<OriginIdentifierValueMap*>(result);
}
const OriginIdentifierValueMap* ContentSettingsStore::GetValueMap(
const std::string& ext_id,
ExtensionPrefsScope scope) const {
ExtensionEntry* entry = FindEntry(ext_id);
if (!entry)
return nullptr;
switch (scope) {
case kExtensionPrefsScopeRegular:
return &(entry->settings);
case kExtensionPrefsScopeRegularOnly:
// TODO(bauerb): Implement regular-only content settings.
NOTREACHED();
return nullptr;
case kExtensionPrefsScopeIncognitoPersistent:
return &(entry->incognito_persistent_settings);
case kExtensionPrefsScopeIncognitoSessionOnly:
return &(entry->incognito_session_only_settings);
}
NOTREACHED();
return nullptr;
}
void ContentSettingsStore::ClearContentSettingsForExtension(
const std::string& ext_id,
ExtensionPrefsScope scope) {
bool notify = false;
{
base::AutoLock lock(lock_);
OriginIdentifierValueMap* map = GetValueMap(ext_id, scope);
DCHECK(map);
notify = !map->empty();
map->clear();
}
if (notify) {
NotifyOfContentSettingChanged(ext_id, scope != kExtensionPrefsScopeRegular);
}
}
void ContentSettingsStore::ClearContentSettingsForExtensionAndContentType(
const std::string& ext_id,
ExtensionPrefsScope scope,
ContentSettingsType content_type) {
bool notify = false;
{
base::AutoLock lock(lock_);
OriginIdentifierValueMap* map = GetValueMap(ext_id, scope);
DCHECK(map);
// Get all of the resource identifiers for this |content_type|.
std::set<ResourceIdentifier> resource_identifiers;
for (const auto& entry : *map) {
if (entry.first.content_type == content_type)
resource_identifiers.insert(entry.first.resource_identifier);
}
notify = !resource_identifiers.empty();
for (const ResourceIdentifier& resource_identifier : resource_identifiers)
map->DeleteValues(content_type, resource_identifier);
}
if (notify) {
NotifyOfContentSettingChanged(ext_id, scope != kExtensionPrefsScopeRegular);
}
}
std::unique_ptr<base::ListValue> ContentSettingsStore::GetSettingsForExtension(
const std::string& extension_id,
ExtensionPrefsScope scope) const {
base::AutoLock lock(lock_);
const OriginIdentifierValueMap* map = GetValueMap(extension_id, scope);
if (!map)
return nullptr;
auto settings = std::make_unique<base::ListValue>();
for (const auto& it : *map) {
const auto& key = it.first;
std::unique_ptr<RuleIterator> rule_iterator(
map->GetRuleIterator(key.content_type, key.resource_identifier,
nullptr)); // We already hold the lock.
if (!rule_iterator)
continue;
while (rule_iterator->HasNext()) {
const Rule& rule = rule_iterator->Next();
std::unique_ptr<base::DictionaryValue> setting_dict(
new base::DictionaryValue());
setting_dict->SetString(
content_settings_api_constants::kPrimaryPatternKey,
rule.primary_pattern.ToString());
setting_dict->SetString(
content_settings_api_constants::kSecondaryPatternKey,
rule.secondary_pattern.ToString());
setting_dict->SetString(
content_settings_api_constants::kContentSettingsTypeKey,
content_settings_helpers::ContentSettingsTypeToString(
key.content_type));
setting_dict->SetString(
content_settings_api_constants::kResourceIdentifierKey,
key.resource_identifier);
ContentSetting content_setting =
content_settings::ValueToContentSetting(&rule.value);
DCHECK_NE(CONTENT_SETTING_DEFAULT, content_setting);
std::string setting_string =
content_settings::ContentSettingToString(content_setting);
DCHECK(!setting_string.empty());
setting_dict->SetString(
content_settings_api_constants::kContentSettingKey, setting_string);
settings->Append(std::move(setting_dict));
}
}
return settings;
}
void ContentSettingsStore::SetExtensionContentSettingFromList(
const std::string& extension_id,
const base::ListValue* list,
ExtensionPrefsScope scope) {
for (const auto& value : *list) {
const base::DictionaryValue* dict = nullptr;
if (!value.GetAsDictionary(&dict)) {
NOTREACHED();
continue;
}
std::string primary_pattern_str;
dict->GetString(content_settings_api_constants::kPrimaryPatternKey,
&primary_pattern_str);
ContentSettingsPattern primary_pattern =
ContentSettingsPattern::FromString(primary_pattern_str);
DCHECK(primary_pattern.IsValid());
std::string secondary_pattern_str;
dict->GetString(content_settings_api_constants::kSecondaryPatternKey,
&secondary_pattern_str);
ContentSettingsPattern secondary_pattern =
ContentSettingsPattern::FromString(secondary_pattern_str);
DCHECK(secondary_pattern.IsValid());
std::string content_settings_type_str;
dict->GetString(content_settings_api_constants::kContentSettingsTypeKey,
&content_settings_type_str);
ContentSettingsType content_settings_type =
content_settings_helpers::StringToContentSettingsType(
content_settings_type_str);
if (content_settings_type == CONTENT_SETTINGS_TYPE_DEFAULT) {
// We'll end up with DEFAULT here if the type string isn't recognised.
// This could be if it's a string from an old settings type that has been
// deleted. DCHECK to make sure this is the case (not some random string).
DCHECK(content_settings_type_str == "fullscreen" ||
content_settings_type_str == "mouselock");
// In this case, we just skip over that setting, effectively deleting it
// from the in-memory model. This will implicitly delete these old
// settings from the pref store when it is written back.
continue;
}
const content_settings::ContentSettingsInfo* info =
content_settings::ContentSettingsRegistry::GetInstance()->Get(
content_settings_type);
if (primary_pattern != secondary_pattern &&
secondary_pattern != ContentSettingsPattern::Wildcard() &&
!info->website_settings_info()->SupportsEmbeddedExceptions() &&
base::FeatureList::IsEnabled(::features::kPermissionDelegation)) {
// Some types may have had embedded exceptions written even though they
// aren't supported. This will implicitly delete these old settings from
// the pref store when it is written back.
continue;
}
std::string resource_identifier;
dict->GetString(content_settings_api_constants::kResourceIdentifierKey,
&resource_identifier);
std::string content_setting_string;
dict->GetString(content_settings_api_constants::kContentSettingKey,
&content_setting_string);
ContentSetting setting;
bool result = content_settings::ContentSettingFromString(
content_setting_string, &setting);
DCHECK(result);
// The content settings extensions API does not support setting any content
// settings to |CONTENT_SETTING_DEFAULT|.
DCHECK_NE(CONTENT_SETTING_DEFAULT, setting);
SetExtensionContentSetting(extension_id,
primary_pattern,
secondary_pattern,
content_settings_type,
resource_identifier,
setting,
scope);
}
}
void ContentSettingsStore::AddObserver(Observer* observer) {
DCHECK(OnCorrectThread());
observers_.AddObserver(observer);
}
void ContentSettingsStore::RemoveObserver(Observer* observer) {
DCHECK(OnCorrectThread());
observers_.RemoveObserver(observer);
}
void ContentSettingsStore::NotifyOfContentSettingChanged(
const std::string& extension_id,
bool incognito) {
for (auto& observer : observers_)
observer.OnContentSettingChanged(extension_id, incognito);
}
bool ContentSettingsStore::OnCorrectThread() {
// If there is no UI thread, we're most likely in a unit test.
return !BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::UI);
}
ContentSettingsStore::ExtensionEntry* ContentSettingsStore::FindEntry(
const std::string& ext_id) const {
auto iter =
std::find_if(entries_.begin(), entries_.end(),
[ext_id](const std::unique_ptr<ExtensionEntry>& entry) {
return entry->id == ext_id;
});
return iter == entries_.end() ? nullptr : iter->get();
}
ContentSettingsStore::ExtensionEntries::iterator
ContentSettingsStore::FindIterator(const std::string& ext_id) {
return std::find_if(entries_.begin(), entries_.end(),
[ext_id](const std::unique_ptr<ExtensionEntry>& entry) {
return entry->id == ext_id;
});
}
} // namespace extensions