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