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 |