| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "components/policy/core/common/url_blacklist_manager.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/files/file_path.h" | |
| 9 #include "base/location.h" | |
| 10 #include "base/message_loop/message_loop_proxy.h" | |
| 11 #include "base/prefs/pref_service.h" | |
| 12 #include "base/sequenced_task_runner.h" | |
| 13 #include "base/stl_util.h" | |
| 14 #include "base/strings/string_number_conversions.h" | |
| 15 #include "base/task_runner_util.h" | |
| 16 #include "base/values.h" | |
| 17 #include "components/policy/core/common/policy_pref_names.h" | |
| 18 #include "components/user_prefs/pref_registry_syncable.h" | |
| 19 #include "net/base/load_flags.h" | |
| 20 #include "net/base/net_util.h" | |
| 21 #include "net/url_request/url_request.h" | |
| 22 | |
| 23 using url_matcher::URLMatcher; | |
| 24 using url_matcher::URLMatcherCondition; | |
| 25 using url_matcher::URLMatcherConditionFactory; | |
| 26 using url_matcher::URLMatcherConditionSet; | |
| 27 using url_matcher::URLMatcherPortFilter; | |
| 28 using url_matcher::URLMatcherSchemeFilter; | |
| 29 | |
| 30 namespace policy { | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 const char kFileScheme[] = "file"; | |
| 35 | |
| 36 // Maximum filters per policy. Filters over this index are ignored. | |
| 37 const size_t kMaxFiltersPerPolicy = 1000; | |
| 38 | |
| 39 // A task that builds the blacklist on a background thread. | |
| 40 scoped_ptr<URLBlacklist> BuildBlacklist( | |
| 41 scoped_ptr<base::ListValue> block, | |
| 42 scoped_ptr<base::ListValue> allow, | |
| 43 URLBlacklist::SegmentURLCallback segment_url) { | |
| 44 scoped_ptr<URLBlacklist> blacklist(new URLBlacklist(segment_url)); | |
| 45 blacklist->Block(block.get()); | |
| 46 blacklist->Allow(allow.get()); | |
| 47 return blacklist.Pass(); | |
| 48 } | |
| 49 | |
| 50 } // namespace | |
| 51 | |
| 52 struct URLBlacklist::FilterComponents { | |
| 53 FilterComponents() : port(0), match_subdomains(true), allow(true) {} | |
| 54 ~FilterComponents() {} | |
| 55 | |
| 56 std::string scheme; | |
| 57 std::string host; | |
| 58 uint16 port; | |
| 59 std::string path; | |
| 60 bool match_subdomains; | |
| 61 bool allow; | |
| 62 }; | |
| 63 | |
| 64 URLBlacklist::URLBlacklist(SegmentURLCallback segment_url) | |
| 65 : segment_url_(segment_url), id_(0), url_matcher_(new URLMatcher) {} | |
| 66 | |
| 67 URLBlacklist::~URLBlacklist() {} | |
| 68 | |
| 69 void URLBlacklist::AddFilters(bool allow, | |
| 70 const base::ListValue* list) { | |
| 71 URLMatcherConditionSet::Vector all_conditions; | |
| 72 size_t size = std::min(kMaxFiltersPerPolicy, list->GetSize()); | |
| 73 for (size_t i = 0; i < size; ++i) { | |
| 74 std::string pattern; | |
| 75 bool success = list->GetString(i, &pattern); | |
| 76 DCHECK(success); | |
| 77 FilterComponents components; | |
| 78 components.allow = allow; | |
| 79 if (!FilterToComponents(segment_url_, pattern, &components.scheme, | |
| 80 &components.host, &components.match_subdomains, | |
| 81 &components.port, &components.path)) { | |
| 82 LOG(ERROR) << "Invalid pattern " << pattern; | |
| 83 continue; | |
| 84 } | |
| 85 | |
| 86 all_conditions.push_back( | |
| 87 CreateConditionSet(url_matcher_.get(), ++id_, components.scheme, | |
| 88 components.host, components.match_subdomains, | |
| 89 components.port, components.path)); | |
| 90 filters_[id_] = components; | |
| 91 } | |
| 92 url_matcher_->AddConditionSets(all_conditions); | |
| 93 } | |
| 94 | |
| 95 void URLBlacklist::Block(const base::ListValue* filters) { | |
| 96 AddFilters(false, filters); | |
| 97 } | |
| 98 | |
| 99 void URLBlacklist::Allow(const base::ListValue* filters) { | |
| 100 AddFilters(true, filters); | |
| 101 } | |
| 102 | |
| 103 bool URLBlacklist::IsURLBlocked(const GURL& url) const { | |
| 104 std::set<URLMatcherConditionSet::ID> matching_ids = | |
| 105 url_matcher_->MatchURL(url); | |
| 106 | |
| 107 const FilterComponents* max = NULL; | |
| 108 for (std::set<URLMatcherConditionSet::ID>::iterator id = matching_ids.begin(); | |
| 109 id != matching_ids.end(); ++id) { | |
| 110 std::map<int, FilterComponents>::const_iterator it = filters_.find(*id); | |
| 111 DCHECK(it != filters_.end()); | |
| 112 const FilterComponents& filter = it->second; | |
| 113 if (!max || FilterTakesPrecedence(filter, *max)) | |
| 114 max = &filter; | |
| 115 } | |
| 116 | |
| 117 // Default to allow. | |
| 118 if (!max) | |
| 119 return false; | |
| 120 | |
| 121 return !max->allow; | |
| 122 } | |
| 123 | |
| 124 size_t URLBlacklist::Size() const { | |
| 125 return filters_.size(); | |
| 126 } | |
| 127 | |
| 128 // static | |
| 129 bool URLBlacklist::FilterToComponents(SegmentURLCallback segment_url, | |
| 130 const std::string& filter, | |
| 131 std::string* scheme, | |
| 132 std::string* host, | |
| 133 bool* match_subdomains, | |
| 134 uint16* port, | |
| 135 std::string* path) { | |
| 136 url_parse::Parsed parsed; | |
| 137 | |
| 138 if (segment_url(filter, &parsed) == kFileScheme) { | |
| 139 base::FilePath file_path; | |
| 140 if (!net::FileURLToFilePath(GURL(filter), &file_path)) | |
| 141 return false; | |
| 142 | |
| 143 *scheme = kFileScheme; | |
| 144 host->clear(); | |
| 145 *match_subdomains = true; | |
| 146 *port = 0; | |
| 147 // Special path when the |filter| is 'file://*'. | |
| 148 *path = (filter == "file://*") ? "" : file_path.AsUTF8Unsafe(); | |
| 149 #if defined(FILE_PATH_USES_WIN_SEPARATORS) | |
| 150 // Separators have to be canonicalized on Windows. | |
| 151 std::replace(path->begin(), path->end(), '\\', '/'); | |
| 152 *path = "/" + *path; | |
| 153 #endif | |
| 154 return true; | |
| 155 } | |
| 156 | |
| 157 if (!parsed.host.is_nonempty()) | |
| 158 return false; | |
| 159 | |
| 160 if (parsed.scheme.is_nonempty()) | |
| 161 scheme->assign(filter, parsed.scheme.begin, parsed.scheme.len); | |
| 162 else | |
| 163 scheme->clear(); | |
| 164 | |
| 165 host->assign(filter, parsed.host.begin, parsed.host.len); | |
| 166 // Special '*' host, matches all hosts. | |
| 167 if (*host == "*") { | |
| 168 host->clear(); | |
| 169 *match_subdomains = true; | |
| 170 } else if ((*host)[0] == '.') { | |
| 171 // A leading dot in the pattern syntax means that we don't want to match | |
| 172 // subdomains. | |
| 173 host->erase(0, 1); | |
| 174 *match_subdomains = false; | |
| 175 } else { | |
| 176 url_canon::RawCanonOutputT<char> output; | |
| 177 url_canon::CanonHostInfo host_info; | |
| 178 url_canon::CanonicalizeHostVerbose(filter.c_str(), parsed.host, | |
| 179 &output, &host_info); | |
| 180 if (host_info.family == url_canon::CanonHostInfo::NEUTRAL) { | |
| 181 // We want to match subdomains. Add a dot in front to make sure we only | |
| 182 // match at domain component boundaries. | |
| 183 *host = "." + *host; | |
| 184 *match_subdomains = true; | |
| 185 } else { | |
| 186 *match_subdomains = false; | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 if (parsed.port.is_nonempty()) { | |
| 191 int int_port; | |
| 192 if (!base::StringToInt(filter.substr(parsed.port.begin, parsed.port.len), | |
| 193 &int_port)) { | |
| 194 return false; | |
| 195 } | |
| 196 if (int_port <= 0 || int_port > kuint16max) | |
| 197 return false; | |
| 198 *port = int_port; | |
| 199 } else { | |
| 200 // Match any port. | |
| 201 *port = 0; | |
| 202 } | |
| 203 | |
| 204 if (parsed.path.is_nonempty()) | |
| 205 path->assign(filter, parsed.path.begin, parsed.path.len); | |
| 206 else | |
| 207 path->clear(); | |
| 208 | |
| 209 return true; | |
| 210 } | |
| 211 | |
| 212 // static | |
| 213 scoped_refptr<URLMatcherConditionSet> URLBlacklist::CreateConditionSet( | |
| 214 URLMatcher* url_matcher, | |
| 215 int id, | |
| 216 const std::string& scheme, | |
| 217 const std::string& host, | |
| 218 bool match_subdomains, | |
| 219 uint16 port, | |
| 220 const std::string& path) { | |
| 221 URLMatcherConditionFactory* condition_factory = | |
| 222 url_matcher->condition_factory(); | |
| 223 std::set<URLMatcherCondition> conditions; | |
| 224 conditions.insert(match_subdomains ? | |
| 225 condition_factory->CreateHostSuffixPathPrefixCondition(host, path) : | |
| 226 condition_factory->CreateHostEqualsPathPrefixCondition(host, path)); | |
| 227 | |
| 228 scoped_ptr<URLMatcherSchemeFilter> scheme_filter; | |
| 229 if (!scheme.empty()) | |
| 230 scheme_filter.reset(new URLMatcherSchemeFilter(scheme)); | |
| 231 | |
| 232 scoped_ptr<URLMatcherPortFilter> port_filter; | |
| 233 if (port != 0) { | |
| 234 std::vector<URLMatcherPortFilter::Range> ranges; | |
| 235 ranges.push_back(URLMatcherPortFilter::CreateRange(port)); | |
| 236 port_filter.reset(new URLMatcherPortFilter(ranges)); | |
| 237 } | |
| 238 | |
| 239 return new URLMatcherConditionSet(id, conditions, | |
| 240 scheme_filter.Pass(), port_filter.Pass()); | |
| 241 } | |
| 242 | |
| 243 // static | |
| 244 bool URLBlacklist::FilterTakesPrecedence(const FilterComponents& lhs, | |
| 245 const FilterComponents& rhs) { | |
| 246 if (lhs.match_subdomains && !rhs.match_subdomains) | |
| 247 return false; | |
| 248 if (!lhs.match_subdomains && rhs.match_subdomains) | |
| 249 return true; | |
| 250 | |
| 251 size_t host_length = lhs.host.length(); | |
| 252 size_t other_host_length = rhs.host.length(); | |
| 253 if (host_length != other_host_length) | |
| 254 return host_length > other_host_length; | |
| 255 | |
| 256 size_t path_length = lhs.path.length(); | |
| 257 size_t other_path_length = rhs.path.length(); | |
| 258 if (path_length != other_path_length) | |
| 259 return path_length > other_path_length; | |
| 260 | |
| 261 if (lhs.allow && !rhs.allow) | |
| 262 return true; | |
| 263 | |
| 264 return false; | |
| 265 } | |
| 266 | |
| 267 URLBlacklistManager::URLBlacklistManager( | |
| 268 PrefService* pref_service, | |
| 269 const scoped_refptr<base::SequencedTaskRunner>& background_task_runner, | |
| 270 const scoped_refptr<base::SequencedTaskRunner>& io_task_runner, | |
| 271 URLBlacklist::SegmentURLCallback segment_url, | |
| 272 SkipBlacklistCallback skip_blacklist) | |
| 273 : ui_weak_ptr_factory_(this), | |
| 274 pref_service_(pref_service), | |
| 275 background_task_runner_(background_task_runner), | |
| 276 io_task_runner_(io_task_runner), | |
| 277 segment_url_(segment_url), | |
| 278 skip_blacklist_(skip_blacklist), | |
| 279 io_weak_ptr_factory_(this), | |
| 280 ui_task_runner_(base::MessageLoopProxy::current()), | |
| 281 blacklist_(new URLBlacklist(segment_url)) { | |
| 282 pref_change_registrar_.Init(pref_service_); | |
| 283 base::Closure callback = base::Bind(&URLBlacklistManager::ScheduleUpdate, | |
| 284 base::Unretained(this)); | |
| 285 pref_change_registrar_.Add(policy_prefs::kUrlBlacklist, callback); | |
| 286 pref_change_registrar_.Add(policy_prefs::kUrlWhitelist, callback); | |
| 287 | |
| 288 // Start enforcing the policies without a delay when they are present at | |
| 289 // startup. | |
| 290 if (pref_service_->HasPrefPath(policy_prefs::kUrlBlacklist)) | |
| 291 Update(); | |
| 292 } | |
| 293 | |
| 294 void URLBlacklistManager::ShutdownOnUIThread() { | |
| 295 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); | |
| 296 // Cancel any pending updates, and stop listening for pref change updates. | |
| 297 ui_weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 298 pref_change_registrar_.RemoveAll(); | |
| 299 } | |
| 300 | |
| 301 URLBlacklistManager::~URLBlacklistManager() { | |
| 302 } | |
| 303 | |
| 304 void URLBlacklistManager::ScheduleUpdate() { | |
| 305 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); | |
| 306 // Cancel pending updates, if any. This can happen if two preferences that | |
| 307 // change the blacklist are updated in one message loop cycle. In those cases, | |
| 308 // only rebuild the blacklist after all the preference updates are processed. | |
| 309 ui_weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 310 ui_task_runner_->PostTask( | |
| 311 FROM_HERE, | |
| 312 base::Bind(&URLBlacklistManager::Update, | |
| 313 ui_weak_ptr_factory_.GetWeakPtr())); | |
| 314 } | |
| 315 | |
| 316 void URLBlacklistManager::Update() { | |
| 317 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread()); | |
| 318 | |
| 319 // The preferences can only be read on the UI thread. | |
| 320 scoped_ptr<base::ListValue> block( | |
| 321 pref_service_->GetList(policy_prefs::kUrlBlacklist)->DeepCopy()); | |
| 322 scoped_ptr<base::ListValue> allow( | |
| 323 pref_service_->GetList(policy_prefs::kUrlWhitelist)->DeepCopy()); | |
| 324 | |
| 325 // Go through the IO thread to grab a WeakPtr to |this|. This is safe from | |
| 326 // here, since this task will always execute before a potential deletion of | |
| 327 // ProfileIOData on IO. | |
| 328 io_task_runner_->PostTask(FROM_HERE, | |
| 329 base::Bind(&URLBlacklistManager::UpdateOnIO, | |
| 330 base::Unretained(this), | |
| 331 base::Passed(&block), | |
| 332 base::Passed(&allow))); | |
| 333 } | |
| 334 | |
| 335 void URLBlacklistManager::UpdateOnIO(scoped_ptr<base::ListValue> block, | |
| 336 scoped_ptr<base::ListValue> allow) { | |
| 337 DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); | |
| 338 // The URLBlacklist is built on a worker thread. Once it's ready, it is passed | |
| 339 // to the URLBlacklistManager on IO. | |
| 340 base::PostTaskAndReplyWithResult( | |
| 341 background_task_runner_, | |
| 342 FROM_HERE, | |
| 343 base::Bind(&BuildBlacklist, | |
| 344 base::Passed(&block), | |
| 345 base::Passed(&allow), | |
| 346 segment_url_), | |
| 347 base::Bind(&URLBlacklistManager::SetBlacklist, | |
| 348 io_weak_ptr_factory_.GetWeakPtr())); | |
| 349 } | |
| 350 | |
| 351 void URLBlacklistManager::SetBlacklist(scoped_ptr<URLBlacklist> blacklist) { | |
| 352 DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); | |
| 353 blacklist_ = blacklist.Pass(); | |
| 354 } | |
| 355 | |
| 356 bool URLBlacklistManager::IsURLBlocked(const GURL& url) const { | |
| 357 DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); | |
| 358 return blacklist_->IsURLBlocked(url); | |
| 359 } | |
| 360 | |
| 361 bool URLBlacklistManager::IsRequestBlocked( | |
| 362 const net::URLRequest& request) const { | |
| 363 DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); | |
| 364 int filter_flags = net::LOAD_MAIN_FRAME | net::LOAD_SUB_FRAME; | |
| 365 if ((request.load_flags() & filter_flags) == 0) | |
| 366 return false; | |
| 367 | |
| 368 if (skip_blacklist_(request.url())) | |
| 369 return false; | |
| 370 | |
| 371 return IsURLBlocked(request.url()); | |
| 372 } | |
| 373 | |
| 374 // static | |
| 375 void URLBlacklistManager::RegisterProfilePrefs( | |
| 376 user_prefs::PrefRegistrySyncable* registry) { | |
| 377 registry->RegisterListPref(policy_prefs::kUrlBlacklist, | |
| 378 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
| 379 registry->RegisterListPref(policy_prefs::kUrlWhitelist, | |
| 380 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
| 381 } | |
| 382 | |
| 383 } // namespace policy | |
| OLD | NEW |