#include "extensions/browser/api/content_settings/content_settings_store.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/debug/alias.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "components/content_settings/core/browser/content_settings_info.h"
#include "components/content_settings/core/browser/content_settings_origin_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_uma_util.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.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/permissions/features.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/content_settings/content_settings_helpers.h"
#include "extensions/common/api/types.h"
#include "extensions/common/extension_id.h"
#include "url/gurl.h"
using content::BrowserThread;
using content_settings::ConcatenationIterator;
using content_settings::OriginValueMap;
using content_settings::Rule;
using content_settings::RuleIterator;
namespace extensions {
struct ContentSettingsStore::ExtensionEntry {
std::string id;
base::Time install_time;
bool enabled;
OriginValueMap settings;
OriginValueMap incognito_persistent_settings;
OriginValueMap incognito_session_only_settings;
};
ContentSettingsStore::ContentSettingsStore() {
DCHECK(OnCorrectThread());
}
ContentSettingsStore::~ContentSettingsStore() = default;
constexpr char ContentSettingsStore::kContentSettingKey[];
constexpr char ContentSettingsStore::kContentSettingsTypeKey[];
constexpr char ContentSettingsStore::kPrimaryPatternKey[];
constexpr char ContentSettingsStore::kSecondaryPatternKey[];
std::unique_ptr<RuleIterator> ContentSettingsStore::GetRuleIterator(
ContentSettingsType type,
bool incognito) const {
std::vector<std::unique_ptr<RuleIterator>> iterators;
base::AutoLock auto_lock(lock_);
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);
if (rule_it)
iterators.push_back(std::move(rule_it));
rule_it = entry->incognito_persistent_settings.GetRuleIterator(type);
if (rule_it)
iterators.push_back(std::move(rule_it));
} else {
rule_it = entry->settings.GetRuleIterator(type);
if (rule_it)
iterators.push_back(std::move(rule_it));
}
}
if (iterators.empty())
return nullptr;
return std::make_unique<ConcatenationIterator>(std::move(iterators));
}
std::unique_ptr<content_settings::Rule> ContentSettingsStore::GetRule(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType content_type,
bool off_the_record) const {
base::AutoLock entries_lock(lock_);
std::unique_ptr<content_settings::Rule> result;
for (const auto& entry : entries_) {
if (off_the_record) {
{
base::AutoLock lock(entry->incognito_session_only_settings.GetLock());
result = entry->incognito_session_only_settings.GetRule(
primary_url, secondary_url, content_type);
if (result) {
return result;
}
}
{
base::AutoLock lock(entry->incognito_persistent_settings.GetLock());
result = entry->incognito_persistent_settings.GetRule(
primary_url, secondary_url, content_type);
if (result) {
return result;
}
}
} else {
base::AutoLock lock(entry->settings.GetLock());
result =
entry->settings.GetRule(primary_url, secondary_url, content_type);
if (result) {
return result;
}
}
}
return nullptr;
}
void ContentSettingsStore::SetExtensionContentSetting(
const std::string& ext_id,
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType type,
ContentSetting setting,
ChromeSettingScope scope) {
if (primary_pattern.Matches(GURL("chrome-extension://" + ext_id))
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
|| primary_pattern.Matches(GURL("arkweb-extension://" + ext_id))
#endif
) {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings.PrimaryPatternMatchesExtensionOrigin",
type);
}
if (secondary_pattern.Matches(GURL("chrome-extension://" + ext_id))
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
|| secondary_pattern.Matches(GURL("arkweb-extension://" + ext_id))
#endif
) {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings.SecondaryPatternMatchesExtensionOrigin",
type);
}
if (primary_pattern == ContentSettingsPattern::Wildcard()) {
if (secondary_pattern == ContentSettingsPattern::Wildcard()) {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings."
"PrimaryPatternWildcardSecondaryPatternWildcard",
type);
} else {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings."
"PrimaryPatternWildcardSecondaryPatternUnique",
type);
}
} else {
if (secondary_pattern == ContentSettingsPattern::Wildcard()) {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings."
"PrimaryPatternUniqueSecondaryPatternWildcard",
type);
} else if (secondary_pattern == primary_pattern) {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings."
"PrimaryPatternUniqueSecondaryPatternIdentical",
type);
} else {
content_settings_uma_util::RecordContentSettingsHistogram(
"Extensions.ContentSettings."
"PrimaryPatternUniqueSecondaryPatternDifferent",
type);
}
}
{
base::AutoLock lock(lock_);
OriginValueMap* map = GetValueMap(ext_id, scope);
base::AutoLock map_lock(map->GetLock());
if (setting == CONTENT_SETTING_DEFAULT) {
map->DeleteValue(primary_pattern, secondary_pattern, type);
} else {
map->SetValue(primary_pattern, secondary_pattern, type,
base::Value(setting), {});
}
}
NotifyOfContentSettingChanged(ext_id, scope != ChromeSettingScope::kRegular);
}
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;
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;
ShouldNotifyForEntry(**i, ¬ify, ¬ify_incognito);
entries_.erase(i);
}
if (notify)
NotifyOfContentSettingChanged(ext_id, false);
if (notify_incognito)
NotifyOfContentSettingChanged(ext_id, true);
}
void ContentSettingsStore::ShouldNotifyForEntry(const ExtensionEntry& entry,
bool* notify,
bool* notify_incognito) {
{
base::AutoLock map_lock(entry.settings.GetLock());
*notify = !entry.settings.empty();
}
{
base::AutoLock map_lock(entry.incognito_persistent_settings.GetLock());
*notify_incognito = !entry.incognito_persistent_settings.empty();
}
if (!*notify_incognito) {
base::AutoLock map_lock(entry.incognito_session_only_settings.GetLock());
*notify_incognito = !entry.incognito_session_only_settings.empty();
}
}
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;
ShouldNotifyForEntry(*entry, ¬ify, ¬ify_incognito);
entry->enabled = is_enabled;
}
if (notify)
NotifyOfContentSettingChanged(ext_id, false);
if (notify_incognito)
NotifyOfContentSettingChanged(ext_id, true);
}
OriginValueMap* ContentSettingsStore::GetValueMap(const std::string& ext_id,
ChromeSettingScope scope) {
const OriginValueMap* result =
static_cast<const ContentSettingsStore*>(this)->GetValueMap(ext_id,
scope);
return const_cast<OriginValueMap*>(result);
}
const OriginValueMap* ContentSettingsStore::GetValueMap(
const std::string& ext_id,
ChromeSettingScope scope) const {
ExtensionEntry* entry = FindEntry(ext_id);
if (!entry)
return nullptr;
switch (scope) {
case ChromeSettingScope::kRegular:
return &entry->settings;
case ChromeSettingScope::kRegularOnly:
NOTREACHED();
case ChromeSettingScope::kIncognitoPersistent:
return &entry->incognito_persistent_settings;
case ChromeSettingScope::kIncognitoSessionOnly:
return &entry->incognito_session_only_settings;
case ChromeSettingScope::kNone:
break;
}
NOTREACHED();
}
void ContentSettingsStore::ClearContentSettingsForExtension(
const std::string& ext_id,
ChromeSettingScope scope) {
bool notify = false;
{
base::AutoLock lock(lock_);
OriginValueMap* map = GetValueMap(ext_id, scope);
DCHECK(map);
base::AutoLock map_lock(map->GetLock());
notify = !map->empty();
map->clear();
}
if (notify) {
NotifyOfContentSettingChanged(ext_id,
scope != ChromeSettingScope::kRegular);
}
}
void ContentSettingsStore::ClearContentSettingsForExtensionAndContentType(
const std::string& ext_id,
ChromeSettingScope scope,
ContentSettingsType content_type) {
{
base::AutoLock lock(lock_);
OriginValueMap* map = GetValueMap(ext_id, scope);
DCHECK(map);
base::AutoLock map_lock(map->GetLock());
map->DeleteValues(content_type);
}
NotifyOfContentSettingChanged(ext_id, scope != ChromeSettingScope::kRegular);
}
base::Value::List ContentSettingsStore::GetSettingsForExtension(
const ExtensionId& extension_id,
ChromeSettingScope scope) const {
base::AutoLock lock(lock_);
const OriginValueMap* map = GetValueMap(extension_id, scope);
if (!map)
return {};
std::vector<ContentSettingsType> keys;
{
base::AutoLock map_lock(map->GetLock());
keys = map->types();
}
base::Value::List settings;
for (ContentSettingsType key : keys) {
std::unique_ptr<RuleIterator> rule_iterator(map->GetRuleIterator(key));
if (!rule_iterator)
continue;
while (rule_iterator->HasNext()) {
std::unique_ptr<Rule> rule = rule_iterator->Next();
base::Value::Dict setting_dict;
setting_dict.Set(kPrimaryPatternKey, rule->primary_pattern.ToString());
setting_dict.Set(kSecondaryPatternKey,
rule->secondary_pattern.ToString());
setting_dict.Set(
kContentSettingsTypeKey,
content_settings_helpers::ContentSettingsTypeToString(key));
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.Set(kContentSettingKey, setting_string);
settings.Append(std::move(setting_dict));
}
}
return settings;
}
#define LOG_INVALID_EXTENSION_PREFERENCE_DETAILS \
LOG(ERROR) << "Found invalid extension pref: " << value \
<< " extension id: " << extension_id
void ContentSettingsStore::SetExtensionContentSettingFromList(
const ExtensionId& extension_id,
const base::Value::List& list,
ChromeSettingScope scope) {
for (const base::Value& value : list) {
if (!value.is_dict()) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
const base::Value::Dict& dict = value.GetDict();
const std::string* primary_pattern_str =
dict.FindString(kPrimaryPatternKey);
if (!primary_pattern_str) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
ContentSettingsPattern primary_pattern =
ContentSettingsPattern::FromString(*primary_pattern_str);
if (!primary_pattern.IsValid()) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
const std::string* secondary_pattern_str =
dict.FindString(kSecondaryPatternKey);
if (!secondary_pattern_str) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
ContentSettingsPattern secondary_pattern =
ContentSettingsPattern::FromString(*secondary_pattern_str);
if (!secondary_pattern.IsValid()) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
auto* content_settings_type_str = dict.FindString(kContentSettingsTypeKey);
if (!content_settings_type_str) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
ContentSettingsType content_settings_type =
content_settings_helpers::StringToContentSettingsType(
*content_settings_type_str);
if (content_settings_type == ContentSettingsType::DEFAULT) {
DCHECK(*content_settings_type_str == "fullscreen" ||
*content_settings_type_str == "mouselock" ||
*content_settings_type_str == "plugins");
continue;
}
const content_settings::ContentSettingsInfo* info =
content_settings::ContentSettingsRegistry::GetInstance()->Get(
content_settings_type);
if (secondary_pattern == primary_pattern &&
info->website_settings_info()->SupportsSecondaryPattern()) {
secondary_pattern = ContentSettingsPattern::Wildcard();
}
if (primary_pattern != secondary_pattern &&
secondary_pattern != ContentSettingsPattern::Wildcard() &&
!info->website_settings_info()->SupportsSecondaryPattern()) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
ContentSetting setting;
const std::string* content_setting_str =
dict.FindString(kContentSettingKey);
if (!content_setting_str) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
bool result = content_settings::ContentSettingFromString(
*content_setting_str, &setting);
if (!result != CONTENT_SETTING_DEFAULT) {
LOG_INVALID_EXTENSION_PREFERENCE_DETAILS;
continue;
}
SetExtensionContentSetting(extension_id, primary_pattern, secondary_pattern,
content_settings_type, setting, scope);
}
}
#undef LOG_INVALID_EXTENSION_PREFERENCE_DETAILS
void ContentSettingsStore::AddObserver(Observer* observer) {
DCHECK(OnCorrectThread());
observers_.AddObserver(observer);
}
void ContentSettingsStore::RemoveObserver(Observer* observer) {
DCHECK(OnCorrectThread());
observers_.RemoveObserver(observer);
}
void ContentSettingsStore::NotifyOfContentSettingChanged(
const ExtensionId& extension_id,
bool incognito) {
for (auto& observer : observers_)
observer.OnContentSettingChanged(extension_id, incognito);
}
bool ContentSettingsStore::OnCorrectThread() {
return !BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::UI);
}
ContentSettingsStore::ExtensionEntry* ContentSettingsStore::FindEntry(
const std::string& ext_id) const {
auto iter = std::ranges::find(entries_, ext_id, &ExtensionEntry::id);
return iter == entries_.end() ? nullptr : iter->get();
}
ContentSettingsStore::ExtensionEntries::iterator
ContentSettingsStore::FindIterator(const std::string& ext_id) {
return std::ranges::find(entries_, ext_id, &ExtensionEntry::id);
}
}