blob: d6050cd35af94364bf31814c41658d9fccc92322 [file] [log] [blame]
// Copyright 2020 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 "ui/webui/webui_allowlist.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/supports_user_data.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/url_constants.h"
#include "ui/webui/webui_allowlist_provider.h"
#include "url/gurl.h"
const char kWebUIAllowlistKeyName[] = "WebUIAllowlist";
namespace {
class AllowlistRuleIterator : public content_settings::RuleIterator {
using MapType = std::map<url::Origin, ContentSetting>;
public:
// Hold a reference to `allowlist` to keep it alive during iteration.
explicit AllowlistRuleIterator(scoped_refptr<const WebUIAllowlist> allowlist,
const MapType& map,
std::unique_ptr<base::AutoLock> auto_lock)
: auto_lock_(std::move(auto_lock)),
allowlist_(std::move(allowlist)),
it_(map.cbegin()),
end_(map.cend()) {}
AllowlistRuleIterator(const AllowlistRuleIterator&) = delete;
void operator=(const AllowlistRuleIterator&) = delete;
~AllowlistRuleIterator() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool HasNext() const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return it_ != end_;
}
content_settings::Rule Next() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto& origin = it_->first;
const auto& setting = it_->second;
it_++;
return content_settings::Rule(
ContentSettingsPattern::FromURLNoWildcard(origin.GetURL()),
ContentSettingsPattern::Wildcard(), base::Value(setting), base::Time(),
content_settings::SessionModel::Durable);
}
private:
// Satisfies GUARDED_BY(lock_) on WebUIAllowlist::permissions_.
//
// `it_` is an iterator on `allowlist_`'s `permissions_` map. So, accesses to
// `it_` are implicit accesses on the map, and must only be performed while
// holding the associated lock.
//
// This detailed explanation is here because Clang's static analysis does not
// catch accesses through iterators, and does not account for locks held
// through std::unique_ptr<AutoLock>. So, it's on code reviewers to ensure
// this implementation remains thread-safe.
const std::unique_ptr<base::AutoLock> auto_lock_;
// Keeps the map backing `it_` and `end_` alive.
const scoped_refptr<const WebUIAllowlist> allowlist_;
SEQUENCE_CHECKER(sequence_checker_);
MapType::const_iterator it_ GUARDED_BY_CONTEXT(sequence_checker_);
MapType::const_iterator end_ GUARDED_BY_CONTEXT(sequence_checker_);
};
struct WebUIAllowlistHolder : base::SupportsUserData::Data {
explicit WebUIAllowlistHolder(scoped_refptr<WebUIAllowlist> list)
: allow_list(std::move(list)) {}
const scoped_refptr<WebUIAllowlist> allow_list;
};
} // namespace
// static
WebUIAllowlist* WebUIAllowlist::GetOrCreate(
content::BrowserContext* browser_context) {
if (!browser_context->GetUserData(kWebUIAllowlistKeyName)) {
auto list = base::MakeRefCounted<WebUIAllowlist>();
browser_context->SetUserData(
kWebUIAllowlistKeyName,
std::make_unique<WebUIAllowlistHolder>(std::move(list)));
}
return static_cast<WebUIAllowlistHolder*>(
browser_context->GetUserData(kWebUIAllowlistKeyName))
->allow_list.get();
}
WebUIAllowlist::WebUIAllowlist() = default;
WebUIAllowlist::~WebUIAllowlist() = default;
void WebUIAllowlist::RegisterAutoGrantedPermission(const url::Origin& origin,
ContentSettingsType type,
ContentSetting setting) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// It doesn't make sense to grant a default content setting.
DCHECK_NE(CONTENT_SETTING_DEFAULT, setting);
// We only support auto-granting permissions to chrome://,
// chrome-untrusted://, and devtools:// schemes.
DCHECK(origin.scheme() == content::kChromeUIScheme ||
origin.scheme() == content::kChromeUIUntrustedScheme ||
origin.scheme() == content::kChromeDevToolsScheme);
{
base::AutoLock auto_lock(lock_);
// If the same permission is already registered, do nothing. We don't want
// to notify the provider of ContentSettingChange when it is unnecessary.
ContentSetting& map_setting = permissions_[type][origin];
if (map_setting == setting)
return;
map_setting = setting;
}
// Notify the provider. |provider_| can be nullptr if
// HostContentSettingsRegistry is shutting down i.e. when Chrome shuts down.
if (provider_) {
auto primary_pattern =
ContentSettingsPattern::FromURLNoWildcard(origin.GetURL());
auto secondary_pattern = ContentSettingsPattern::Wildcard();
provider_->NotifyContentSettingChange(primary_pattern, secondary_pattern,
type);
}
}
void WebUIAllowlist::RegisterAutoGrantedPermissions(
const url::Origin& origin,
std::initializer_list<ContentSettingsType> types) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (const ContentSettingsType& type : types)
RegisterAutoGrantedPermission(origin, type);
}
void WebUIAllowlist::SetWebUIAllowlistProvider(
WebUIAllowlistProvider* provider) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
provider_ = provider;
}
void WebUIAllowlist::ResetWebUIAllowlistProvider() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
provider_ = nullptr;
}
std::unique_ptr<content_settings::RuleIterator> WebUIAllowlist::GetRuleIterator(
ContentSettingsType content_type) const NO_THREAD_SAFETY_ANALYSIS {
// This method acquires `lock_` and transfers it to the returned iterator.
// NO_THREAD_SAFETY_ANALYSIS because the analyzer doesn't recognize acquiring
// the lock in a unique_ptr.
auto auto_lock_ = std::make_unique<base::AutoLock>(lock_);
auto permissions_it = permissions_.find(content_type);
if (permissions_it != permissions_.end()) {
return std::make_unique<AllowlistRuleIterator>(this, permissions_it->second,
std::move(auto_lock_));
}
return nullptr;
}