blob: 9c7afb7272ccffb8f3be6b1bec2db45e161d664a [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/policy/url_blacklist_manager.h"
#include "base/bind.h"
#include "base/message_loop.h"
#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/net/url_fixer_upper.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "googleurl/src/gurl.h"
using content::BrowserThread;
using extensions::URLMatcher;
using extensions::URLMatcherCondition;
using extensions::URLMatcherConditionFactory;
using extensions::URLMatcherConditionSet;
using extensions::URLMatcherPortFilter;
using extensions::URLMatcherSchemeFilter;
namespace policy {
namespace {
// Maximum filters per policy. Filters over this index are ignored.
const size_t kMaxFiltersPerPolicy = 100;
const char* kStandardSchemes[] = {
"http",
"https",
"file",
"ftp",
"gopher",
"ws",
"wss"
};
bool IsStandardScheme(const std::string& scheme) {
for (size_t i = 0; i < arraysize(kStandardSchemes); ++i) {
if (scheme == kStandardSchemes[i])
return true;
}
return false;
}
// A task that builds the blacklist on the FILE thread.
scoped_ptr<URLBlacklist> BuildBlacklist(scoped_ptr<base::ListValue> block,
scoped_ptr<base::ListValue> allow) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
scoped_ptr<URLBlacklist> blacklist(new URLBlacklist);
blacklist->Block(block.get());
blacklist->Allow(allow.get());
return blacklist.Pass();
}
} // namespace
struct URLBlacklist::FilterComponents {
FilterComponents() : port(0), match_subdomains(true), allow(true) {}
~FilterComponents() {}
std::string scheme;
std::string host;
uint16 port;
std::string path;
bool match_subdomains;
bool allow;
};
URLBlacklist::URLBlacklist() : id_(0),
url_matcher_(new URLMatcher) {
}
URLBlacklist::~URLBlacklist() {
}
void URLBlacklist::AddFilters(bool allow,
const base::ListValue* list) {
URLMatcherConditionSet::Vector all_conditions;
size_t size = std::min(kMaxFiltersPerPolicy, list->GetSize());
for (size_t i = 0; i < size; ++i) {
std::string pattern;
bool success = list->GetString(i, &pattern);
DCHECK(success);
FilterComponents components;
components.allow = allow;
if (!FilterToComponents(pattern, &components.scheme, &components.host,
&components.match_subdomains, &components.port,
&components.path)) {
LOG(ERROR) << "Invalid pattern " << pattern;
continue;
}
all_conditions.push_back(
CreateConditionSet(url_matcher_.get(), ++id_, components.scheme,
components.host, components.match_subdomains,
components.port, components.path));
filters_[id_] = components;
}
url_matcher_->AddConditionSets(all_conditions);
}
void URLBlacklist::Block(const base::ListValue* filters) {
AddFilters(false, filters);
}
void URLBlacklist::Allow(const base::ListValue* filters) {
AddFilters(true, filters);
}
bool URLBlacklist::IsURLBlocked(const GURL& url) const {
if (!HasStandardScheme(url))
return false;
std::set<URLMatcherConditionSet::ID> matching_ids =
url_matcher_->MatchURL(url);
const FilterComponents* max = NULL;
for (std::set<URLMatcherConditionSet::ID>::iterator id = matching_ids.begin();
id != matching_ids.end(); ++id) {
std::map<int, FilterComponents>::const_iterator it = filters_.find(*id);
DCHECK(it != filters_.end());
const FilterComponents& filter = it->second;
if (!max || FilterTakesPrecedence(filter, *max))
max = &filter;
}
// Default to allow.
if (!max)
return false;
return !max->allow;
}
// static
bool URLBlacklist::HasStandardScheme(const GURL& url) {
return IsStandardScheme(url.scheme());
}
// static
bool URLBlacklist::FilterToComponents(const std::string& filter,
std::string* scheme,
std::string* host,
bool* match_subdomains,
uint16* port,
std::string* path) {
url_parse::Parsed parsed;
URLFixerUpper::SegmentURL(filter, &parsed);
if (!parsed.host.is_nonempty())
return false;
if (parsed.scheme.is_nonempty())
scheme->assign(filter, parsed.scheme.begin, parsed.scheme.len);
else
scheme->clear();
host->assign(filter, parsed.host.begin, parsed.host.len);
// Special '*' host, matches all hosts.
if (*host == "*") {
host->clear();
*match_subdomains = true;
} else if ((*host)[0] == '.') {
// A leading dot in the pattern syntax means that we don't want to match
// subdomains.
host->erase(0, 1);
*match_subdomains = false;
} else {
url_canon::RawCanonOutputT<char> output;
url_canon::CanonHostInfo host_info;
url_canon::CanonicalizeHostVerbose(filter.c_str(), parsed.host,
&output, &host_info);
if (host_info.family == url_canon::CanonHostInfo::NEUTRAL) {
// We want to match subdomains. Add a dot in front to make sure we only
// match at domain component boundaries.
*host = "." + *host;
*match_subdomains = true;
} else {
*match_subdomains = false;
}
}
if (parsed.port.is_nonempty()) {
int int_port;
if (!base::StringToInt(filter.substr(parsed.port.begin, parsed.port.len),
&int_port)) {
return false;
}
if (int_port <= 0 || int_port > kuint16max)
return false;
*port = int_port;
} else {
// Match any port.
*port = 0;
}
if (parsed.path.is_nonempty())
path->assign(filter, parsed.path.begin, parsed.path.len);
else
path->clear();
if (!scheme->empty() && !IsStandardScheme(*scheme))
return false;
return true;
}
// static
scoped_refptr<extensions::URLMatcherConditionSet>
URLBlacklist::CreateConditionSet(
extensions::URLMatcher* url_matcher,
int id,
const std::string& scheme,
const std::string& host,
bool match_subdomains,
uint16 port,
const std::string& path) {
URLMatcherConditionFactory* condition_factory =
url_matcher->condition_factory();
std::set<URLMatcherCondition> conditions;
conditions.insert(match_subdomains ?
condition_factory->CreateHostSuffixPathPrefixCondition(host, path) :
condition_factory->CreateHostEqualsPathPrefixCondition(host, path));
scoped_ptr<URLMatcherSchemeFilter> scheme_filter;
if (!scheme.empty())
scheme_filter.reset(new URLMatcherSchemeFilter(scheme));
scoped_ptr<URLMatcherPortFilter> port_filter;
if (port != 0) {
std::vector<URLMatcherPortFilter::Range> ranges;
ranges.push_back(URLMatcherPortFilter::CreateRange(port));
port_filter.reset(new URLMatcherPortFilter(ranges));
}
return new URLMatcherConditionSet(id, conditions,
scheme_filter.Pass(), port_filter.Pass());
}
// static
bool URLBlacklist::FilterTakesPrecedence(const FilterComponents& lhs,
const FilterComponents& rhs) {
if (lhs.match_subdomains && !rhs.match_subdomains)
return false;
if (!lhs.match_subdomains && rhs.match_subdomains)
return true;
size_t host_length = lhs.host.length();
size_t other_host_length = rhs.host.length();
if (host_length != other_host_length)
return host_length > other_host_length;
size_t path_length = lhs.path.length();
size_t other_path_length = rhs.path.length();
if (path_length != other_path_length)
return path_length > other_path_length;
if (lhs.allow && !rhs.allow)
return true;
return false;
}
URLBlacklistManager::URLBlacklistManager(PrefService* pref_service)
: ALLOW_THIS_IN_INITIALIZER_LIST(ui_weak_ptr_factory_(this)),
pref_service_(pref_service),
ALLOW_THIS_IN_INITIALIZER_LIST(io_weak_ptr_factory_(this)),
blacklist_(new URLBlacklist) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Add(prefs::kUrlBlacklist, this);
pref_change_registrar_.Add(prefs::kUrlWhitelist, this);
// Start enforcing the policies without a delay when they are present at
// startup.
if (pref_service_->HasPrefPath(prefs::kUrlBlacklist))
Update();
}
void URLBlacklistManager::ShutdownOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Cancel any pending updates, and stop listening for pref change updates.
ui_weak_ptr_factory_.InvalidateWeakPtrs();
pref_change_registrar_.RemoveAll();
}
URLBlacklistManager::~URLBlacklistManager() {
}
void URLBlacklistManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED);
PrefService* prefs = content::Source<PrefService>(source).ptr();
DCHECK(prefs == pref_service_);
std::string* pref_name = content::Details<std::string>(details).ptr();
DCHECK(*pref_name == prefs::kUrlBlacklist ||
*pref_name == prefs::kUrlWhitelist);
ScheduleUpdate();
}
void URLBlacklistManager::ScheduleUpdate() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Cancel pending updates, if any. This can happen if two preferences that
// change the blacklist are updated in one message loop cycle. In those cases,
// only rebuild the blacklist after all the preference updates are processed.
ui_weak_ptr_factory_.InvalidateWeakPtrs();
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&URLBlacklistManager::Update,
ui_weak_ptr_factory_.GetWeakPtr()));
}
void URLBlacklistManager::Update() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// The preferences can only be read on the UI thread.
scoped_ptr<base::ListValue> block(
pref_service_->GetList(prefs::kUrlBlacklist)->DeepCopy());
scoped_ptr<base::ListValue> allow(
pref_service_->GetList(prefs::kUrlWhitelist)->DeepCopy());
// Go through the IO thread to grab a WeakPtr to |this|. This is safe from
// here, since this task will always execute before a potential deletion of
// ProfileIOData on IO.
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&URLBlacklistManager::UpdateOnIO,
base::Unretained(this),
base::Passed(&block),
base::Passed(&allow)));
}
void URLBlacklistManager::UpdateOnIO(scoped_ptr<base::ListValue> block,
scoped_ptr<base::ListValue> allow) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// The URLBlacklist is built on the FILE thread. Once it's ready, it is passed
// to the URLBlacklistManager on IO.
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE, FROM_HERE,
base::Bind(&BuildBlacklist,
base::Passed(&block),
base::Passed(&allow)),
base::Bind(&URLBlacklistManager::SetBlacklist,
io_weak_ptr_factory_.GetWeakPtr()));
}
void URLBlacklistManager::SetBlacklist(scoped_ptr<URLBlacklist> blacklist) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
blacklist_ = blacklist.Pass();
}
bool URLBlacklistManager::IsURLBlocked(const GURL& url) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
return blacklist_->IsURLBlocked(url);
}
// static
void URLBlacklistManager::RegisterPrefs(PrefService* pref_service) {
pref_service->RegisterListPref(prefs::kUrlBlacklist,
PrefService::UNSYNCABLE_PREF);
pref_service->RegisterListPref(prefs::kUrlWhitelist,
PrefService::UNSYNCABLE_PREF);
}
} // namespace policy