diff options
author | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 23:58:54 +0000 |
---|---|---|
committer | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 23:58:54 +0000 |
commit | d0820564192ab9ffa781592271d3dec073dbe0cd (patch) | |
tree | 2fd804368ec972310a0ad579634d23fc95b7411e /components | |
parent | 1165bdcc43b1504b28dc7d343c8511c7ce1246a5 (diff) | |
download | chromium_src-d0820564192ab9ffa781592271d3dec073dbe0cd.zip chromium_src-d0820564192ab9ffa781592271d3dec073dbe0cd.tar.gz chromium_src-d0820564192ab9ffa781592271d3dec073dbe0cd.tar.bz2 |
Move URLBlacklistManager to components/policy.
This enables building that class on the iOS build.
BUG=271392
TBR=joaodasilva@chromium.org
Review URL: https://codereview.chromium.org/102973005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241421 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r-- | components/policy.gypi | 6 | ||||
-rw-r--r-- | components/policy/core/DEPS | 2 | ||||
-rw-r--r-- | components/policy/core/common/url_blacklist_manager.cc | 383 | ||||
-rw-r--r-- | components/policy/core/common/url_blacklist_manager.h | 227 |
4 files changed, 618 insertions, 0 deletions
diff --git a/components/policy.gypi b/components/policy.gypi index 5d31487..4d89984 100644 --- a/components/policy.gypi +++ b/components/policy.gypi @@ -55,6 +55,8 @@ 'cloud_policy_proto', 'json_schema', 'policy', + 'url_matcher', + 'user_prefs', ], 'sources': [ 'policy/core/browser/cloud/message_util.cc', @@ -171,6 +173,8 @@ 'policy/core/common/schema_map.h', 'policy/core/common/schema_registry.cc', 'policy/core/common/schema_registry.h', + 'policy/core/common/url_blacklist_manager.cc', + 'policy/core/common/url_blacklist_manager.h', 'policy/policy_export.h', ], 'conditions': [ @@ -227,6 +231,8 @@ 'policy/core/common/policy_service.h', 'policy/core/common/policy_service_stub.cc', 'policy/core/common/policy_service_stub.h', + 'policy/core/common/url_blacklist_manager.cc', + 'policy/core/common/url_blacklist_manager.h', ], }], ], diff --git a/components/policy/core/DEPS b/components/policy/core/DEPS index 7b46c50..db1e843 100644 --- a/components/policy/core/DEPS +++ b/components/policy/core/DEPS @@ -1,5 +1,7 @@ include_rules = [ "+components/json_schema", + "+components/url_matcher", + "+components/user_prefs", "+crypto", "+google_apis", "+net", diff --git a/components/policy/core/common/url_blacklist_manager.cc b/components/policy/core/common/url_blacklist_manager.cc new file mode 100644 index 0000000..6381f96 --- /dev/null +++ b/components/policy/core/common/url_blacklist_manager.cc @@ -0,0 +1,383 @@ +// Copyright 2013 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 "components/policy/core/common/url_blacklist_manager.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/prefs/pref_service.h" +#include "base/sequenced_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner_util.h" +#include "base/values.h" +#include "components/policy/core/common/policy_pref_names.h" +#include "components/user_prefs/pref_registry_syncable.h" +#include "net/base/load_flags.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" + +using url_matcher::URLMatcher; +using url_matcher::URLMatcherCondition; +using url_matcher::URLMatcherConditionFactory; +using url_matcher::URLMatcherConditionSet; +using url_matcher::URLMatcherPortFilter; +using url_matcher::URLMatcherSchemeFilter; + +namespace policy { + +namespace { + +const char kFileScheme[] = "file"; + +// Maximum filters per policy. Filters over this index are ignored. +const size_t kMaxFiltersPerPolicy = 1000; + +// A task that builds the blacklist on a background thread. +scoped_ptr<URLBlacklist> BuildBlacklist( + scoped_ptr<base::ListValue> block, + scoped_ptr<base::ListValue> allow, + URLBlacklist::SegmentURLCallback segment_url) { + scoped_ptr<URLBlacklist> blacklist(new URLBlacklist(segment_url)); + 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(SegmentURLCallback segment_url) + : segment_url_(segment_url), 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(segment_url_, 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 { + 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; +} + +size_t URLBlacklist::Size() const { + return filters_.size(); +} + +// static +bool URLBlacklist::FilterToComponents(SegmentURLCallback segment_url, + const std::string& filter, + std::string* scheme, + std::string* host, + bool* match_subdomains, + uint16* port, + std::string* path) { + url_parse::Parsed parsed; + + if (segment_url(filter, &parsed) == kFileScheme) { + base::FilePath file_path; + if (!net::FileURLToFilePath(GURL(filter), &file_path)) + return false; + + *scheme = kFileScheme; + host->clear(); + *match_subdomains = true; + *port = 0; + // Special path when the |filter| is 'file://*'. + *path = (filter == "file://*") ? "" : file_path.AsUTF8Unsafe(); +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + // Separators have to be canonicalized on Windows. + std::replace(path->begin(), path->end(), '\\', '/'); + *path = "/" + *path; +#endif + return true; + } + + 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(); + + return true; +} + +// static +scoped_refptr<URLMatcherConditionSet> URLBlacklist::CreateConditionSet( + 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, + const scoped_refptr<base::SequencedTaskRunner>& background_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner, + URLBlacklist::SegmentURLCallback segment_url, + SkipBlacklistCallback skip_blacklist) + : ui_weak_ptr_factory_(this), + pref_service_(pref_service), + background_task_runner_(background_task_runner), + io_task_runner_(io_task_runner), + segment_url_(segment_url), + skip_blacklist_(skip_blacklist), + io_weak_ptr_factory_(this), + ui_task_runner_(base::MessageLoopProxy::current()), + blacklist_(new URLBlacklist(segment_url)) { + pref_change_registrar_.Init(pref_service_); + base::Closure callback = base::Bind(&URLBlacklistManager::ScheduleUpdate, + base::Unretained(this)); + pref_change_registrar_.Add(policy_prefs::kUrlBlacklist, callback); + pref_change_registrar_.Add(policy_prefs::kUrlWhitelist, callback); + + // Start enforcing the policies without a delay when they are present at + // startup. + if (pref_service_->HasPrefPath(policy_prefs::kUrlBlacklist)) + Update(); +} + +void URLBlacklistManager::ShutdownOnUIThread() { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + // Cancel any pending updates, and stop listening for pref change updates. + ui_weak_ptr_factory_.InvalidateWeakPtrs(); + pref_change_registrar_.RemoveAll(); +} + +URLBlacklistManager::~URLBlacklistManager() { +} + +void URLBlacklistManager::ScheduleUpdate() { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + // 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(); + ui_task_runner_->PostTask( + FROM_HERE, + base::Bind(&URLBlacklistManager::Update, + ui_weak_ptr_factory_.GetWeakPtr())); +} + +void URLBlacklistManager::Update() { + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + // The preferences can only be read on the UI thread. + scoped_ptr<base::ListValue> block( + pref_service_->GetList(policy_prefs::kUrlBlacklist)->DeepCopy()); + scoped_ptr<base::ListValue> allow( + pref_service_->GetList(policy_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. + io_task_runner_->PostTask(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(io_task_runner_->RunsTasksOnCurrentThread()); + // The URLBlacklist is built on a worker thread. Once it's ready, it is passed + // to the URLBlacklistManager on IO. + base::PostTaskAndReplyWithResult( + background_task_runner_, + FROM_HERE, + base::Bind(&BuildBlacklist, + base::Passed(&block), + base::Passed(&allow), + segment_url_), + base::Bind(&URLBlacklistManager::SetBlacklist, + io_weak_ptr_factory_.GetWeakPtr())); +} + +void URLBlacklistManager::SetBlacklist(scoped_ptr<URLBlacklist> blacklist) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + blacklist_ = blacklist.Pass(); +} + +bool URLBlacklistManager::IsURLBlocked(const GURL& url) const { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + return blacklist_->IsURLBlocked(url); +} + +bool URLBlacklistManager::IsRequestBlocked( + const net::URLRequest& request) const { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + int filter_flags = net::LOAD_MAIN_FRAME | net::LOAD_SUB_FRAME; + if ((request.load_flags() & filter_flags) == 0) + return false; + + if (skip_blacklist_(request.url())) + return false; + + return IsURLBlocked(request.url()); +} + +// static +void URLBlacklistManager::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterListPref(policy_prefs::kUrlBlacklist, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); + registry->RegisterListPref(policy_prefs::kUrlWhitelist, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); +} + +} // namespace policy diff --git a/components/policy/core/common/url_blacklist_manager.h b/components/policy/core/common/url_blacklist_manager.h new file mode 100644 index 0000000..dea27d2 --- /dev/null +++ b/components/policy/core/common/url_blacklist_manager.h @@ -0,0 +1,227 @@ +// Copyright 2013 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. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_URL_BLACKLIST_MANAGER_H_ +#define COMPONENTS_POLICY_CORE_COMMON_URL_BLACKLIST_MANAGER_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/prefs/pref_change_registrar.h" +#include "components/policy/policy_export.h" +#include "components/url_matcher/url_matcher.h" +#include "url/gurl.h" + +class PrefService; + +namespace base { +class ListValue; +class SequencedTaskRunner; +} + +namespace net { +class URLRequest; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +namespace policy { + +// Contains a set of filters to block and allow certain URLs, and matches GURLs +// against this set. The filters are currently kept in memory. +class POLICY_EXPORT URLBlacklist { + public: + // This is meant to be bound to URLFixerUpper::SegmentURL. See that function + // for documentation on the parameters and return value. + typedef std::string (*SegmentURLCallback)(const std::string&, + url_parse::Parsed*); + + explicit URLBlacklist(SegmentURLCallback segment_url); + virtual ~URLBlacklist(); + + // Allows or blocks URLs matching one of the filters, depending on |allow|. + void AddFilters(bool allow, const base::ListValue* filters); + + // URLs matching one of the |filters| will be blocked. The filter format is + // documented at + // http://www.chromium.org/administrators/url-blacklist-filter-format. + void Block(const base::ListValue* filters); + + // URLs matching one of the |filters| will be allowed. If a URL is both + // Blocked and Allowed, Allow takes precedence. + void Allow(const base::ListValue* filters); + + // Returns true if the URL is blocked. + bool IsURLBlocked(const GURL& url) const; + + // Returns the number of items in the list. + size_t Size() const; + + // Splits a URL filter into its components. A GURL isn't used because these + // can be invalid URLs e.g. "google.com". + // Returns false if the URL couldn't be parsed. + // The |host| is preprocessed so it can be passed to URLMatcher for the + // appropriate condition. + // The optional username and password are ignored. + // |match_subdomains| specifies whether the filter should include subdomains + // of the hostname (if it is one.) + // |port| is 0 if none is explicitly defined. + // |path| does not include query parameters. + static bool FilterToComponents(SegmentURLCallback segment_url, + const std::string& filter, + std::string* scheme, + std::string* host, + bool* match_subdomains, + uint16* port, + std::string* path); + + // Creates a condition set that can be used with the |url_matcher|. |id| needs + // to be a unique number that will be returned by the |url_matcher| if the URL + // matches that condition set. + static scoped_refptr<url_matcher::URLMatcherConditionSet> CreateConditionSet( + url_matcher::URLMatcher* url_matcher, + url_matcher::URLMatcherConditionSet::ID id, + const std::string& scheme, + const std::string& host, + bool match_subdomains, + uint16 port, + const std::string& path); + + private: + struct FilterComponents; + + // Returns true if |lhs| takes precedence over |rhs|. + static bool FilterTakesPrecedence(const FilterComponents& lhs, + const FilterComponents& rhs); + + SegmentURLCallback segment_url_; + url_matcher::URLMatcherConditionSet::ID id_; + std::map<url_matcher::URLMatcherConditionSet::ID, FilterComponents> filters_; + scoped_ptr<url_matcher::URLMatcher> url_matcher_; + + DISALLOW_COPY_AND_ASSIGN(URLBlacklist); +}; + +// Tracks the blacklist policies for a given profile, and updates it on changes. +// +// This class interacts with both the UI thread, where notifications of pref +// changes are received from, and the IO thread, which owns it (in the +// ProfileIOData) and checks for blacklisted URLs (from ChromeNetworkDelegate). +// +// It must be constructed on the UI thread, to set up |ui_weak_ptr_factory_| and +// the prefs listeners. +// +// ShutdownOnUIThread must be called from UI before destruction, to release +// the prefs listeners on the UI thread. This is done from ProfileIOData. +// +// Update tasks from the UI thread can post safely to the IO thread, since the +// destruction order of Profile and ProfileIOData guarantees that if this +// exists in UI, then a potential destruction on IO will come after any task +// posted to IO from that method on UI. This is used to go through IO before +// the actual update starts, and grab a WeakPtr. +class POLICY_EXPORT URLBlacklistManager { + public: + // Returns true if the blacklist should be skipped for |url|. + typedef bool (*SkipBlacklistCallback)(const GURL& url); + + // Must be constructed on the UI thread. + // |background_task_runner| is used to build the blacklist in a background + // thread. + // |io_task_runner| must be backed by the IO thread. + // |segment_url| is used to break a URL spec into its components. + URLBlacklistManager( + PrefService* pref_service, + const scoped_refptr<base::SequencedTaskRunner>& background_task_runner, + const scoped_refptr<base::SequencedTaskRunner>& io_task_runner, + URLBlacklist::SegmentURLCallback segment_url, + SkipBlacklistCallback skip_blacklist); + virtual ~URLBlacklistManager(); + + // Must be called on the UI thread, before destruction. + void ShutdownOnUIThread(); + + // Returns true if |url| is blocked by the current blacklist. Must be called + // from the IO thread. + bool IsURLBlocked(const GURL& url) const; + + // Returns true if |request| is blocked by the current blacklist. + // Only main frame and sub frame requests may be blocked; other sub resources + // or background downloads (e.g. extensions updates, sync, etc) are not + // filtered. The sync signin page is also not filtered. + // Must be called from the IO thread. + bool IsRequestBlocked(const net::URLRequest& request) const; + + // Replaces the current blacklist. Must be called on the IO thread. + // Virtual for testing. + virtual void SetBlacklist(scoped_ptr<URLBlacklist> blacklist); + + // Registers the preferences related to blacklisting in the given PrefService. + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + protected: + // Used to delay updating the blacklist while the preferences are + // changing, and execute only one update per simultaneous prefs changes. + void ScheduleUpdate(); + + // Updates the blacklist using the current preference values. + // Virtual for testing. + virtual void Update(); + + // Starts the blacklist update on the IO thread, using the filters in + // |block| and |allow|. Protected for testing. + void UpdateOnIO(scoped_ptr<base::ListValue> block, + scoped_ptr<base::ListValue> allow); + + private: + // --------- + // UI thread + // --------- + + // Used to post update tasks to the UI thread. + base::WeakPtrFactory<URLBlacklistManager> ui_weak_ptr_factory_; + + // Used to track the policies and update the blacklist on changes. + PrefChangeRegistrar pref_change_registrar_; + PrefService* pref_service_; // Weak. + + // Used to post tasks to a background thread. + scoped_refptr<base::SequencedTaskRunner> background_task_runner_; + + // Used to post tasks to the IO thread. + scoped_refptr<base::SequencedTaskRunner> io_task_runner_; + + // Used to break a URL into its components. + URLBlacklist::SegmentURLCallback segment_url_; + + // Used to optionally skip blacklisting for some URLs. + SkipBlacklistCallback skip_blacklist_; + + // --------- + // IO thread + // --------- + + // Used to get |weak_ptr_| to self on the IO thread. + base::WeakPtrFactory<URLBlacklistManager> io_weak_ptr_factory_; + + // Used to post tasks to the UI thread. + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_; + + // The current blacklist. + scoped_ptr<URLBlacklist> blacklist_; + + DISALLOW_COPY_AND_ASSIGN(URLBlacklistManager); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_URL_BLACKLIST_MANAGER_H_ |