| Index: chrome/browser/policy/url_blacklist_manager.cc
|
| diff --git a/chrome/browser/policy/url_blacklist_manager.cc b/chrome/browser/policy/url_blacklist_manager.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9dfaa384e3c68dd85ae02d9b2296bb910fd9e8cb
|
| --- /dev/null
|
| +++ b/chrome/browser/policy/url_blacklist_manager.cc
|
| @@ -0,0 +1,439 @@
|
| +// Copyright (c) 2011 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/string_number_conversions.h"
|
| +#include "base/string_util.h"
|
| +#include "base/values.h"
|
| +#include "chrome/browser/net/url_fixer_upper.h"
|
| +#include "chrome/browser/prefs/pref_service.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "chrome/common/chrome_notification_types.h"
|
| +#include "chrome/common/pref_names.h"
|
| +#include "content/browser/browser_thread.h"
|
| +#include "content/common/notification_details.h"
|
| +#include "content/common/notification_source.h"
|
| +#include "googleurl/src/gurl.h"
|
| +
|
| +namespace policy {
|
| +
|
| +namespace {
|
| +
|
| +// Time to wait before starting an update of the blacklist. Scheduling another
|
| +// update during this period will reset the timer.
|
| +const int64 kUpdateDelayMs = 1000;
|
| +
|
| +// Maximum filters per policy. Filters over this index are ignored.
|
| +const size_t kMaxFiltersPerPolicy = 100;
|
| +
|
| +typedef std::vector<std::string> StringVector;
|
| +
|
| +StringVector* ListValueToStringVector(const base::ListValue* list) {
|
| + StringVector* vector = new StringVector;
|
| +
|
| + if (!list)
|
| + return vector;
|
| +
|
| + vector->reserve(list->GetSize());
|
| + std::string s;
|
| + for (base::ListValue::const_iterator it = list->begin();
|
| + it != list->end() && vector->size() < kMaxFiltersPerPolicy; ++it) {
|
| + if ((*it)->GetAsString(&s))
|
| + vector->push_back(s);
|
| + }
|
| +
|
| + return vector;
|
| +}
|
| +
|
| +// A task that owns the Blacklist, and passes it to the URLBlacklistManager
|
| +// on the IO thread, if the URLBlacklistManager still exists.
|
| +class SetBlacklistTask : public Task {
|
| + public:
|
| + SetBlacklistTask(
|
| + base::WeakPtr<URLBlacklistManager> url_blacklist_manager,
|
| + Blacklist* blacklist)
|
| + : url_blacklist_manager_(url_blacklist_manager),
|
| + blacklist_(blacklist) {
|
| + }
|
| +
|
| + virtual void Run() OVERRIDE {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + if (url_blacklist_manager_)
|
| + url_blacklist_manager_->SetBlacklist(blacklist_.release());
|
| + }
|
| +
|
| + private:
|
| + base::WeakPtr<URLBlacklistManager> url_blacklist_manager_;
|
| + scoped_ptr<Blacklist> blacklist_;
|
| + DISALLOW_COPY_AND_ASSIGN(SetBlacklistTask);
|
| +};
|
| +
|
| +// A task that builds the blacklist on the FILE thread, and then posts another
|
| +// task to pass it to the URLBlacklistManager on the IO thread.
|
| +class BuildBlacklistTask : public Task {
|
| + public:
|
| + BuildBlacklistTask(
|
| + base::WeakPtr<URLBlacklistManager> url_blacklist_manager,
|
| + StringVector* blacklist,
|
| + StringVector* whitelist)
|
| + : url_blacklist_manager_(url_blacklist_manager),
|
| + blacklist_(blacklist),
|
| + whitelist_(whitelist) {
|
| + }
|
| +
|
| + virtual void Run() OVERRIDE {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +
|
| + Blacklist* blacklist = NULL;
|
| +
|
| + if (!blacklist_->empty()) {
|
| + blacklist = new Blacklist;
|
| + for (StringVector::iterator it = blacklist_->begin();
|
| + it != blacklist_->end(); ++it) {
|
| + blacklist->Block(*it);
|
| + }
|
| + for (StringVector::iterator it = whitelist_->begin();
|
| + it != whitelist_->end(); ++it) {
|
| + blacklist->Allow(*it);
|
| + }
|
| + }
|
| +
|
| + BrowserThread::PostTask(
|
| + BrowserThread::IO, FROM_HERE,
|
| + new SetBlacklistTask(url_blacklist_manager_, blacklist));
|
| + }
|
| +
|
| + private:
|
| + base::WeakPtr<URLBlacklistManager> url_blacklist_manager_;
|
| + scoped_ptr<StringVector> blacklist_;
|
| + scoped_ptr<StringVector> whitelist_;
|
| + DISALLOW_COPY_AND_ASSIGN(BuildBlacklistTask);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +Blacklist::Blacklist() {
|
| +}
|
| +
|
| +Blacklist::~Blacklist() {
|
| +}
|
| +
|
| +void Blacklist::AddFilter(const std::string& filter, bool block) {
|
| + std::string scheme;
|
| + std::string host;
|
| + uint16 port;
|
| + std::string path;
|
| + SchemeFlag flag;
|
| + bool match_subdomains = true;
|
| +
|
| + if (!FilterToComponents(filter, &scheme, &host, &port, &path)) {
|
| + LOG(WARNING) << "Invalid filter, ignoring: " << filter;
|
| + return;
|
| + }
|
| +
|
| + if (!SchemeToFlag(scheme, &flag)) {
|
| + LOG(WARNING) << "Unsupported scheme in filter, ignoring filter: " << filter;
|
| + return;
|
| + }
|
| +
|
| + // Special syntax to disable subdomain matching.
|
| + if (!host.empty() && host[0] == '.') {
|
| + host.erase(0, 1);
|
| + match_subdomains = false;
|
| + }
|
| +
|
| + // Try to find an existing PathFilter with the same path prefix, port and
|
| + // match_subdomains value.
|
| + PathFilterList& list = host_filters_[host];
|
| + PathFilterList::iterator it;
|
| + for (it = list.begin(); it != list.end(); ++it) {
|
| + if (it->port == port && it->match_subdomains == match_subdomains &&
|
| + it->path_prefix == path)
|
| + break;
|
| + }
|
| + PathFilter* path_filter;
|
| + if (it == list.end()) {
|
| + list.push_back(PathFilter(path, port, match_subdomains));
|
| + path_filter = &list.back();
|
| + } else {
|
| + path_filter = &(*it);
|
| + }
|
| +
|
| + if (block)
|
| + path_filter->blocked_schemes |= flag;
|
| + else
|
| + path_filter->allowed_schemes |= flag;
|
| +}
|
| +
|
| +void Blacklist::Block(const std::string& filter) {
|
| + AddFilter(filter, true);
|
| +}
|
| +
|
| +void Blacklist::Allow(const std::string& filter) {
|
| + AddFilter(filter, false);
|
| +}
|
| +
|
| +bool Blacklist::IsURLBlocked(const GURL& url) const {
|
| + SchemeFlag flag;
|
| + if (!SchemeToFlag(url.scheme(), &flag)) {
|
| + // Not a scheme that can be filtered.
|
| + return false;
|
| + }
|
| +
|
| + std::string host(url.host());
|
| + int int_port = url.EffectiveIntPort();
|
| + const uint16 port = int_port > 0 ? int_port : 0;
|
| + const std::string& path = url.path();
|
| +
|
| + // The first iteration through the loop will be an exact host match.
|
| + // Subsequent iterations are subdomain matches, and some filters don't apply
|
| + // to those.
|
| + bool is_matching_subdomains = false;
|
| + const bool host_is_ip = url.HostIsIPAddress();
|
| + for (;;) {
|
| + HostFilterTable::const_iterator host_filter = host_filters_.find(host);
|
| + if (host_filter != host_filters_.end()) {
|
| + const PathFilterList& v = host_filter->second;
|
| + size_t longest_length = 0;
|
| + bool is_blocked = false;
|
| + bool has_match = false;
|
| + bool has_exact_host_match = false;
|
| + for (PathFilterList::const_iterator it = v.begin(); it != v.end(); ++it) {
|
| + // Filters that apply to an exact hostname only take precedence over
|
| + // filters that can apply to subdomains too.
|
| + // E.g. ".google.com" filters take priority over "google.com".
|
| + if (has_exact_host_match && it->match_subdomains)
|
| + continue;
|
| +
|
| + // Skip if filter doesn't apply to subdomains, and this is a subdomain.
|
| + if (is_matching_subdomains && !it->match_subdomains)
|
| + continue;
|
| +
|
| + if (it->port != 0 && it->port != port)
|
| + continue;
|
| +
|
| + // If this match can't be longer than the current match, skip it.
|
| + // For same size matches, the first rule to match takes precedence.
|
| + // If this is an exact host match, it can be actually shorter than
|
| + // a previous, non-exact match.
|
| + if ((has_match && it->path_prefix.length() <= longest_length) &&
|
| + (has_exact_host_match || it->match_subdomains)) {
|
| + continue;
|
| + }
|
| +
|
| + // Skip if the filter's |path_prefix| is not a prefix of |path|.
|
| + if (path.compare(0, it->path_prefix.length(), it->path_prefix) != 0)
|
| + continue;
|
| +
|
| + // Check if there is a blocked or allowed bit set for the scheme.
|
| + if ((it->allowed_schemes & flag) || (it->blocked_schemes & flag)) {
|
| + // This is the best match so far.
|
| + has_match = true;
|
| + has_exact_host_match = !it->match_subdomains;
|
| + longest_length = it->path_prefix.length();
|
| + // If both blocked and allowed bits are set, allowed takes precedence.
|
| + is_blocked = !(it->allowed_schemes & flag);
|
| + }
|
| + }
|
| + // If a match was found, return its decision.
|
| + if (has_match)
|
| + return is_blocked;
|
| + }
|
| +
|
| + // Quit after trying the empty string (corresponding to host '*').
|
| + // Also skip subdomain matching for IP addresses.
|
| + if (host.empty() || host_is_ip)
|
| + break;
|
| +
|
| + // No match found for this host. Try a subdomain match, by removing the
|
| + // leftmost subdomain from the hostname.
|
| + is_matching_subdomains = true;
|
| + size_t i = host.find('.');
|
| + if (i != std::string::npos)
|
| + ++i;
|
| + host.erase(0, i);
|
| + }
|
| +
|
| + // Default is to allow.
|
| + return false;
|
| +}
|
| +
|
| +// static
|
| +bool Blacklist::SchemeToFlag(const std::string& scheme, SchemeFlag* flag) {
|
| + if (scheme.empty())
|
| + *flag = SCHEME_ALL;
|
| + else if (scheme == "http")
|
| + *flag = SCHEME_HTTP;
|
| + else if (scheme == "https")
|
| + *flag = SCHEME_HTTPS;
|
| + else if (scheme == "ftp")
|
| + *flag = SCHEME_FTP;
|
| + else
|
| + return false;
|
| + return true;
|
| +}
|
| +
|
| +// static
|
| +bool Blacklist::FilterToComponents(const std::string& filter,
|
| + std::string* scheme,
|
| + std::string* host,
|
| + 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();
|
| +
|
| + 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();
|
| +
|
| + return true;
|
| +}
|
| +
|
| +URLBlacklistManager::URLBlacklistManager(Profile* profile)
|
| + : ALLOW_THIS_IN_INITIALIZER_LIST(ui_method_factory_(this)),
|
| + ALLOW_THIS_IN_INITIALIZER_LIST(io_weak_ptr_factory_(this)),
|
| + pref_service_(profile->GetPrefs()) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + // This has to be created on the UI thread, but is only posted later, after
|
| + // the initialization on the IO thread is complete.
|
| + // Posting a task to invoke InitializeOnIOThread from here isn't safe, since
|
| + // we can't get weak ptrs for the IO thread yet.
|
| + initialize_on_ui_task_ = ui_method_factory_.NewRunnableMethod(
|
| + &URLBlacklistManager::InitializeOnUIThread);
|
| +}
|
| +
|
| +void URLBlacklistManager::InitializeOnIOThread() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + DCHECK(initialize_on_ui_task_ != NULL);
|
| + io_weak_ptr_factory_.DetachFromThread();
|
| + weak_ptr_ = io_weak_ptr_factory_.GetWeakPtr();
|
| +
|
| + // It is now safe to resume initialization on the UI thread, since it is now
|
| + // possible to get weak refs to self to use on the IO thread.
|
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, initialize_on_ui_task_);
|
| +}
|
| +
|
| +void URLBlacklistManager::InitializeOnUIThread() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DCHECK(initialize_on_ui_task_ != NULL);
|
| + initialize_on_ui_task_ = NULL;
|
| +
|
| + 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.
|
| + // This also cancels a pending InitializeOnUIThread, if it hasn't executed
|
| + // yet.
|
| + ui_method_factory_.RevokeAll();
|
| + pref_change_registrar_.RemoveAll();
|
| +}
|
| +
|
| +URLBlacklistManager::~URLBlacklistManager() {
|
| + // Cancel any weak ptrs on the IO thread. Pending tasks won't execute their
|
| + // updates anymore.
|
| + io_weak_ptr_factory_.InvalidateWeakPtrs();
|
| +}
|
| +
|
| +void URLBlacklistManager::Observe(int type,
|
| + const NotificationSource& source,
|
| + const NotificationDetails& details) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED);
|
| + PrefService* prefs = Source<PrefService>(source).ptr();
|
| + DCHECK(prefs == pref_service_);
|
| + std::string* pref_name = 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.
|
| + ui_method_factory_.RevokeAll();
|
| + PostUpdateTask(
|
| + ui_method_factory_.NewRunnableMethod(&URLBlacklistManager::Update));
|
| +}
|
| +
|
| +void URLBlacklistManager::PostUpdateTask(Task* task) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + // This is overriden in tests to post the task without the delay.
|
| + MessageLoop::current()->PostDelayedTask(FROM_HERE, task, kUpdateDelayMs);
|
| +}
|
| +
|
| +void URLBlacklistManager::Update() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + // The preferences can only be read on the UI thread.
|
| + StringVector* blacklist = ListValueToStringVector(
|
| + pref_service_->GetList(prefs::kUrlBlacklist));
|
| + StringVector* whitelist = ListValueToStringVector(
|
| + pref_service_->GetList(prefs::kUrlWhitelist));
|
| +
|
| + BrowserThread::PostTask(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + new BuildBlacklistTask(weak_ptr_, blacklist, whitelist));
|
| +}
|
| +
|
| +void URLBlacklistManager::SetBlacklist(Blacklist* blacklist) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + blacklist_.reset(blacklist);
|
| +}
|
| +
|
| +bool URLBlacklistManager::IsURLBlocked(const GURL& url) const {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| +
|
| + if (!blacklist_.get())
|
| + return false;
|
| +
|
| + 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
|
|
|