OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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 "chrome/browser/policy/url_blacklist_manager.h" |
| 6 |
| 7 #include "base/string_number_conversions.h" |
| 8 #include "base/string_util.h" |
| 9 #include "base/values.h" |
| 10 #include "chrome/browser/net/url_fixer_upper.h" |
| 11 #include "chrome/browser/prefs/pref_service.h" |
| 12 #include "chrome/browser/profiles/profile.h" |
| 13 #include "chrome/common/chrome_notification_types.h" |
| 14 #include "chrome/common/pref_names.h" |
| 15 #include "content/browser/browser_thread.h" |
| 16 #include "content/common/notification_details.h" |
| 17 #include "content/common/notification_source.h" |
| 18 #include "googleurl/src/gurl.h" |
| 19 |
| 20 namespace policy { |
| 21 |
| 22 namespace { |
| 23 |
| 24 // Time to wait before starting an update of the blacklist. Scheduling another |
| 25 // update during this period will reset the timer. |
| 26 const int64 kUpdateDelayMs = 1000; |
| 27 |
| 28 // Maximum filters per policy. Filters over this index are ignored. |
| 29 const size_t kMaxFiltersPerPolicy = 100; |
| 30 |
| 31 typedef std::vector<std::string> StringVector; |
| 32 |
| 33 StringVector* ListValueToStringVector(const base::ListValue* list) { |
| 34 StringVector* vector = new StringVector; |
| 35 |
| 36 if (!list) |
| 37 return vector; |
| 38 |
| 39 vector->reserve(list->GetSize()); |
| 40 std::string s; |
| 41 for (base::ListValue::const_iterator it = list->begin(); |
| 42 it != list->end() && vector->size() < kMaxFiltersPerPolicy; ++it) { |
| 43 if ((*it)->GetAsString(&s)) |
| 44 vector->push_back(s); |
| 45 } |
| 46 |
| 47 return vector; |
| 48 } |
| 49 |
| 50 // A task that owns the Blacklist, and passes it to the URLBlacklistManager |
| 51 // on the IO thread, if the URLBlacklistManager still exists. |
| 52 class SetBlacklistTask : public Task { |
| 53 public: |
| 54 SetBlacklistTask( |
| 55 base::WeakPtr<URLBlacklistManager> url_blacklist_manager, |
| 56 Blacklist* blacklist) |
| 57 : url_blacklist_manager_(url_blacklist_manager), |
| 58 blacklist_(blacklist) { |
| 59 } |
| 60 |
| 61 virtual void Run() OVERRIDE { |
| 62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 63 if (url_blacklist_manager_) |
| 64 url_blacklist_manager_->SetBlacklist(blacklist_.release()); |
| 65 } |
| 66 |
| 67 private: |
| 68 base::WeakPtr<URLBlacklistManager> url_blacklist_manager_; |
| 69 scoped_ptr<Blacklist> blacklist_; |
| 70 DISALLOW_COPY_AND_ASSIGN(SetBlacklistTask); |
| 71 }; |
| 72 |
| 73 // A task that builds the blacklist on the FILE thread, and then posts another |
| 74 // task to pass it to the URLBlacklistManager on the IO thread. |
| 75 class BuildBlacklistTask : public Task { |
| 76 public: |
| 77 BuildBlacklistTask( |
| 78 base::WeakPtr<URLBlacklistManager> url_blacklist_manager, |
| 79 StringVector* blacklist, |
| 80 StringVector* whitelist) |
| 81 : url_blacklist_manager_(url_blacklist_manager), |
| 82 blacklist_(blacklist), |
| 83 whitelist_(whitelist) { |
| 84 } |
| 85 |
| 86 virtual void Run() OVERRIDE { |
| 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 88 |
| 89 Blacklist* blacklist = NULL; |
| 90 |
| 91 if (!blacklist_->empty()) { |
| 92 blacklist = new Blacklist; |
| 93 for (StringVector::iterator it = blacklist_->begin(); |
| 94 it != blacklist_->end(); ++it) { |
| 95 blacklist->Block(*it); |
| 96 } |
| 97 for (StringVector::iterator it = whitelist_->begin(); |
| 98 it != whitelist_->end(); ++it) { |
| 99 blacklist->Allow(*it); |
| 100 } |
| 101 } |
| 102 |
| 103 BrowserThread::PostTask( |
| 104 BrowserThread::IO, FROM_HERE, |
| 105 new SetBlacklistTask(url_blacklist_manager_, blacklist)); |
| 106 } |
| 107 |
| 108 private: |
| 109 base::WeakPtr<URLBlacklistManager> url_blacklist_manager_; |
| 110 scoped_ptr<StringVector> blacklist_; |
| 111 scoped_ptr<StringVector> whitelist_; |
| 112 DISALLOW_COPY_AND_ASSIGN(BuildBlacklistTask); |
| 113 }; |
| 114 |
| 115 } // namespace |
| 116 |
| 117 Blacklist::Blacklist() { |
| 118 } |
| 119 |
| 120 Blacklist::~Blacklist() { |
| 121 } |
| 122 |
| 123 void Blacklist::AddFilter(const std::string& filter, bool block) { |
| 124 std::string scheme; |
| 125 std::string host; |
| 126 uint16 port; |
| 127 std::string path; |
| 128 SchemeFlag flag; |
| 129 bool match_subdomains = true; |
| 130 |
| 131 if (!FilterToComponents(filter, &scheme, &host, &port, &path)) { |
| 132 LOG(WARNING) << "Invalid filter, ignoring: " << filter; |
| 133 return; |
| 134 } |
| 135 |
| 136 if (!SchemeToFlag(scheme, &flag)) { |
| 137 LOG(WARNING) << "Unsupported scheme in filter, ignoring filter: " << filter; |
| 138 return; |
| 139 } |
| 140 |
| 141 // Special syntax to disable subdomain matching. |
| 142 if (!host.empty() && host[0] == '.') { |
| 143 host.erase(0, 1); |
| 144 match_subdomains = false; |
| 145 } |
| 146 |
| 147 // Try to find an existing PathFilter with the same path prefix, port and |
| 148 // match_subdomains value. |
| 149 PathFilterList& list = host_filters_[host]; |
| 150 PathFilterList::iterator it; |
| 151 for (it = list.begin(); it != list.end(); ++it) { |
| 152 if (it->port == port && it->match_subdomains == match_subdomains && |
| 153 it->path_prefix == path) |
| 154 break; |
| 155 } |
| 156 PathFilter* path_filter; |
| 157 if (it == list.end()) { |
| 158 list.push_back(PathFilter(path, port, match_subdomains)); |
| 159 path_filter = &list.back(); |
| 160 } else { |
| 161 path_filter = &(*it); |
| 162 } |
| 163 |
| 164 if (block) |
| 165 path_filter->blocked_schemes |= flag; |
| 166 else |
| 167 path_filter->allowed_schemes |= flag; |
| 168 } |
| 169 |
| 170 void Blacklist::Block(const std::string& filter) { |
| 171 AddFilter(filter, true); |
| 172 } |
| 173 |
| 174 void Blacklist::Allow(const std::string& filter) { |
| 175 AddFilter(filter, false); |
| 176 } |
| 177 |
| 178 bool Blacklist::IsURLBlocked(const GURL& url) const { |
| 179 SchemeFlag flag; |
| 180 if (!SchemeToFlag(url.scheme(), &flag)) { |
| 181 // Not a scheme that can be filtered. |
| 182 return false; |
| 183 } |
| 184 |
| 185 std::string host(url.host()); |
| 186 int int_port = url.EffectiveIntPort(); |
| 187 const uint16 port = int_port > 0 ? int_port : 0; |
| 188 const std::string& path = url.path(); |
| 189 |
| 190 // The first iteration through the loop will be an exact host match. |
| 191 // Subsequent iterations are subdomain matches, and some filters don't apply |
| 192 // to those. |
| 193 bool is_matching_subdomains = false; |
| 194 const bool host_is_ip = url.HostIsIPAddress(); |
| 195 for (;;) { |
| 196 HostFilterTable::const_iterator host_filter = host_filters_.find(host); |
| 197 if (host_filter != host_filters_.end()) { |
| 198 const PathFilterList& v = host_filter->second; |
| 199 size_t longest_length = 0; |
| 200 bool is_blocked = false; |
| 201 bool has_match = false; |
| 202 bool has_exact_host_match = false; |
| 203 for (PathFilterList::const_iterator it = v.begin(); it != v.end(); ++it) { |
| 204 // Filters that apply to an exact hostname only take precedence over |
| 205 // filters that can apply to subdomains too. |
| 206 // E.g. ".google.com" filters take priority over "google.com". |
| 207 if (has_exact_host_match && it->match_subdomains) |
| 208 continue; |
| 209 |
| 210 // Skip if filter doesn't apply to subdomains, and this is a subdomain. |
| 211 if (is_matching_subdomains && !it->match_subdomains) |
| 212 continue; |
| 213 |
| 214 if (it->port != 0 && it->port != port) |
| 215 continue; |
| 216 |
| 217 // If this match can't be longer than the current match, skip it. |
| 218 // For same size matches, the first rule to match takes precedence. |
| 219 // If this is an exact host match, it can be actually shorter than |
| 220 // a previous, non-exact match. |
| 221 if ((has_match && it->path_prefix.length() <= longest_length) && |
| 222 (has_exact_host_match || it->match_subdomains)) { |
| 223 continue; |
| 224 } |
| 225 |
| 226 // Skip if the filter's |path_prefix| is not a prefix of |path|. |
| 227 if (path.compare(0, it->path_prefix.length(), it->path_prefix) != 0) |
| 228 continue; |
| 229 |
| 230 // Check if there is a blocked or allowed bit set for the scheme. |
| 231 if ((it->allowed_schemes & flag) || (it->blocked_schemes & flag)) { |
| 232 // This is the best match so far. |
| 233 has_match = true; |
| 234 has_exact_host_match = !it->match_subdomains; |
| 235 longest_length = it->path_prefix.length(); |
| 236 // If both blocked and allowed bits are set, allowed takes precedence. |
| 237 is_blocked = !(it->allowed_schemes & flag); |
| 238 } |
| 239 } |
| 240 // If a match was found, return its decision. |
| 241 if (has_match) |
| 242 return is_blocked; |
| 243 } |
| 244 |
| 245 // Quit after trying the empty string (corresponding to host '*'). |
| 246 // Also skip subdomain matching for IP addresses. |
| 247 if (host.empty() || host_is_ip) |
| 248 break; |
| 249 |
| 250 // No match found for this host. Try a subdomain match, by removing the |
| 251 // leftmost subdomain from the hostname. |
| 252 is_matching_subdomains = true; |
| 253 size_t i = host.find('.'); |
| 254 if (i != std::string::npos) |
| 255 ++i; |
| 256 host.erase(0, i); |
| 257 } |
| 258 |
| 259 // Default is to allow. |
| 260 return false; |
| 261 } |
| 262 |
| 263 // static |
| 264 bool Blacklist::SchemeToFlag(const std::string& scheme, SchemeFlag* flag) { |
| 265 if (scheme.empty()) |
| 266 *flag = SCHEME_ALL; |
| 267 else if (scheme == "http") |
| 268 *flag = SCHEME_HTTP; |
| 269 else if (scheme == "https") |
| 270 *flag = SCHEME_HTTPS; |
| 271 else if (scheme == "ftp") |
| 272 *flag = SCHEME_FTP; |
| 273 else |
| 274 return false; |
| 275 return true; |
| 276 } |
| 277 |
| 278 // static |
| 279 bool Blacklist::FilterToComponents(const std::string& filter, |
| 280 std::string* scheme, |
| 281 std::string* host, |
| 282 uint16* port, |
| 283 std::string* path) { |
| 284 url_parse::Parsed parsed; |
| 285 URLFixerUpper::SegmentURL(filter, &parsed); |
| 286 |
| 287 if (!parsed.host.is_nonempty()) |
| 288 return false; |
| 289 |
| 290 if (parsed.scheme.is_nonempty()) |
| 291 scheme->assign(filter, parsed.scheme.begin, parsed.scheme.len); |
| 292 else |
| 293 scheme->clear(); |
| 294 |
| 295 host->assign(filter, parsed.host.begin, parsed.host.len); |
| 296 // Special '*' host, matches all hosts. |
| 297 if (*host == "*") |
| 298 host->clear(); |
| 299 |
| 300 if (parsed.port.is_nonempty()) { |
| 301 int int_port; |
| 302 if (!base::StringToInt(filter.substr(parsed.port.begin, parsed.port.len), |
| 303 &int_port)) { |
| 304 return false; |
| 305 } |
| 306 if (int_port <= 0 || int_port > kuint16max) |
| 307 return false; |
| 308 *port = int_port; |
| 309 } else { |
| 310 // Match any port. |
| 311 *port = 0; |
| 312 } |
| 313 |
| 314 if (parsed.path.is_nonempty()) |
| 315 path->assign(filter, parsed.path.begin, parsed.path.len); |
| 316 else |
| 317 path->clear(); |
| 318 |
| 319 return true; |
| 320 } |
| 321 |
| 322 URLBlacklistManager::URLBlacklistManager(Profile* profile) |
| 323 : ALLOW_THIS_IN_INITIALIZER_LIST(ui_method_factory_(this)), |
| 324 ALLOW_THIS_IN_INITIALIZER_LIST(io_weak_ptr_factory_(this)), |
| 325 pref_service_(profile->GetPrefs()) { |
| 326 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 327 // This has to be created on the UI thread, but is only posted later, after |
| 328 // the initialization on the IO thread is complete. |
| 329 // Posting a task to invoke InitializeOnIOThread from here isn't safe, since |
| 330 // we can't get weak ptrs for the IO thread yet. |
| 331 initialize_on_ui_task_ = ui_method_factory_.NewRunnableMethod( |
| 332 &URLBlacklistManager::InitializeOnUIThread); |
| 333 } |
| 334 |
| 335 void URLBlacklistManager::InitializeOnIOThread() { |
| 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 337 DCHECK(initialize_on_ui_task_ != NULL); |
| 338 io_weak_ptr_factory_.DetachFromThread(); |
| 339 weak_ptr_ = io_weak_ptr_factory_.GetWeakPtr(); |
| 340 |
| 341 // It is now safe to resume initialization on the UI thread, since it is now |
| 342 // possible to get weak refs to self to use on the IO thread. |
| 343 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, initialize_on_ui_task_); |
| 344 } |
| 345 |
| 346 void URLBlacklistManager::InitializeOnUIThread() { |
| 347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 348 DCHECK(initialize_on_ui_task_ != NULL); |
| 349 initialize_on_ui_task_ = NULL; |
| 350 |
| 351 pref_change_registrar_.Init(pref_service_); |
| 352 pref_change_registrar_.Add(prefs::kUrlBlacklist, this); |
| 353 pref_change_registrar_.Add(prefs::kUrlWhitelist, this); |
| 354 |
| 355 // Start enforcing the policies without a delay when they are present at |
| 356 // startup. |
| 357 if (pref_service_->HasPrefPath(prefs::kUrlBlacklist)) |
| 358 Update(); |
| 359 } |
| 360 |
| 361 void URLBlacklistManager::ShutdownOnUIThread() { |
| 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 363 // Cancel any pending updates, and stop listening for pref change updates. |
| 364 // This also cancels a pending InitializeOnUIThread, if it hasn't executed |
| 365 // yet. |
| 366 ui_method_factory_.RevokeAll(); |
| 367 pref_change_registrar_.RemoveAll(); |
| 368 } |
| 369 |
| 370 URLBlacklistManager::~URLBlacklistManager() { |
| 371 // Cancel any weak ptrs on the IO thread. Pending tasks won't execute their |
| 372 // updates anymore. |
| 373 io_weak_ptr_factory_.InvalidateWeakPtrs(); |
| 374 } |
| 375 |
| 376 void URLBlacklistManager::Observe(int type, |
| 377 const NotificationSource& source, |
| 378 const NotificationDetails& details) { |
| 379 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 380 DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED); |
| 381 PrefService* prefs = Source<PrefService>(source).ptr(); |
| 382 DCHECK(prefs == pref_service_); |
| 383 std::string* pref_name = Details<std::string>(details).ptr(); |
| 384 DCHECK(*pref_name == prefs::kUrlBlacklist || |
| 385 *pref_name == prefs::kUrlWhitelist); |
| 386 ScheduleUpdate(); |
| 387 } |
| 388 |
| 389 void URLBlacklistManager::ScheduleUpdate() { |
| 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 391 // Cancel pending updates, if any. |
| 392 ui_method_factory_.RevokeAll(); |
| 393 PostUpdateTask( |
| 394 ui_method_factory_.NewRunnableMethod(&URLBlacklistManager::Update)); |
| 395 } |
| 396 |
| 397 void URLBlacklistManager::PostUpdateTask(Task* task) { |
| 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 399 // This is overriden in tests to post the task without the delay. |
| 400 MessageLoop::current()->PostDelayedTask(FROM_HERE, task, kUpdateDelayMs); |
| 401 } |
| 402 |
| 403 void URLBlacklistManager::Update() { |
| 404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 405 |
| 406 // The preferences can only be read on the UI thread. |
| 407 StringVector* blacklist = ListValueToStringVector( |
| 408 pref_service_->GetList(prefs::kUrlBlacklist)); |
| 409 StringVector* whitelist = ListValueToStringVector( |
| 410 pref_service_->GetList(prefs::kUrlWhitelist)); |
| 411 |
| 412 BrowserThread::PostTask( |
| 413 BrowserThread::FILE, FROM_HERE, |
| 414 new BuildBlacklistTask(weak_ptr_, blacklist, whitelist)); |
| 415 } |
| 416 |
| 417 void URLBlacklistManager::SetBlacklist(Blacklist* blacklist) { |
| 418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 419 blacklist_.reset(blacklist); |
| 420 } |
| 421 |
| 422 bool URLBlacklistManager::IsURLBlocked(const GURL& url) const { |
| 423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 424 |
| 425 if (!blacklist_.get()) |
| 426 return false; |
| 427 |
| 428 return blacklist_->IsURLBlocked(url); |
| 429 } |
| 430 |
| 431 // static |
| 432 void URLBlacklistManager::RegisterPrefs(PrefService* pref_service) { |
| 433 pref_service->RegisterListPref(prefs::kUrlBlacklist, |
| 434 PrefService::UNSYNCABLE_PREF); |
| 435 pref_service->RegisterListPref(prefs::kUrlWhitelist, |
| 436 PrefService::UNSYNCABLE_PREF); |
| 437 } |
| 438 |
| 439 } // namespace policy |
OLD | NEW |