Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Side by Side Diff: chrome/browser/policy/url_blacklist_manager.cc

Issue 7716003: WIP: URL blacklisting by policy. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Always filter including paths Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698