blob: 4f9a1570b1f16b30e583c755e3baf9373eda84d9 [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 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.
initial.commit09911bf2008-07-26 23:55:294//
5
6#include "chrome/browser/safe_browsing/safe_browsing_service.h"
7
8#include "base/histogram.h"
9#include "base/logging.h"
10#include "base/message_loop.h"
11#include "base/path_service.h"
12#include "base/string_util.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/profile_manager.h"
15#include "chrome/browser/safe_browsing/protocol_manager.h"
16#include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
17#include "chrome/browser/safe_browsing/safe_browsing_database.h"
18#include "chrome/common/chrome_constants.h"
19#include "chrome/common/chrome_paths.h"
20#include "chrome/common/pref_names.h"
21#include "chrome/common/pref_service.h"
22#include "net/base/registry_controlled_domain.h"
23
24SafeBrowsingService::SafeBrowsingService()
25 : io_loop_(NULL),
26 database_(NULL),
27 protocol_manager_(NULL),
28 enabled_(false),
29 resetting_(false) {
30}
31
32SafeBrowsingService::~SafeBrowsingService() {
33}
34
35// Only called on the UI thread.
36void SafeBrowsingService::Initialize(MessageLoop* io_loop) {
37 io_loop_ = io_loop;
38
39 // Get the profile's preference for SafeBrowsing.
40 std::wstring user_data_dir;
41 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
42 ProfileManager* profile_manager = g_browser_process->profile_manager();
43 Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
44 PrefService* pref_service = profile->GetPrefs();
45 if (pref_service->GetBoolean(prefs::kSafeBrowsingEnabled))
46 Start();
47}
48
49// Start up SafeBrowsing objects. This can be called at browser start, or when
50// the user checks the "Enable SafeBrowsing" option in the Advanced options UI.
51void SafeBrowsingService::Start() {
52 DCHECK(!db_thread_.get());
[email protected]ab820df2008-08-26 05:55:1053 db_thread_.reset(new base::Thread("Chrome_SafeBrowsingThread"));
initial.commit09911bf2008-07-26 23:55:2954 if (!db_thread_->Start())
55 return;
56
57 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
58 this, &SafeBrowsingService::OnDBInitialize));
59
60 // Retrieve client MAC keys.
61 PrefService* local_state = g_browser_process->local_state();
62 std::string client_key, wrapped_key;
63 if (local_state) {
64 client_key =
65 WideToASCII(local_state->GetString(prefs::kSafeBrowsingClientKey));
66 wrapped_key =
67 WideToASCII(local_state->GetString(prefs::kSafeBrowsingWrappedKey));
68 }
69
70 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
71 this, &SafeBrowsingService::OnIOInitialize, MessageLoop::current(),
72 client_key, wrapped_key));
73}
74
75void SafeBrowsingService::ShutDown() {
76 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
77 this, &SafeBrowsingService::OnIOShutdown));
78}
79
80void SafeBrowsingService::OnIOInitialize(MessageLoop* notify_loop,
81 const std::string& client_key,
82 const std::string& wrapped_key) {
83 DCHECK(MessageLoop::current() == io_loop_);
84 enabled_ = true;
85 protocol_manager_ = new SafeBrowsingProtocolManager(this,
86 notify_loop,
87 client_key,
88 wrapped_key);
89 protocol_manager_->Initialize();
90}
91
92void SafeBrowsingService::OnDBInitialize() {
93 DCHECK(MessageLoop::current() == db_thread_->message_loop());
94 GetDatabase();
95}
96
97void SafeBrowsingService::OnIOShutdown() {
98 DCHECK(MessageLoop::current() == io_loop_);
99 if (!enabled_)
100 return;
101
102 enabled_ = false;
103
104 // This cancels all in-flight GetHash requests.
105 delete protocol_manager_;
106
107 if (db_thread_.get())
108 db_thread_->message_loop()->DeleteSoon(FROM_HERE, database_);
109
110 // Flush the database thread. Any in-progress database check results will be
111 // ignored and cleaned up below.
112 db_thread_.reset(NULL);
113
114 database_ = NULL;
115
116 // Delete checks once the database thread is done, calling back any clients
117 // with 'URL_SAFE'.
118 for (CurrentChecks::iterator it = checks_.begin();
119 it != checks_.end(); ++it) {
120 if ((*it)->client)
121 (*it)->client->OnUrlCheckResult((*it)->url, URL_SAFE);
122 delete *it;
123 }
124 checks_.clear();
125
126 gethash_requests_.clear();
127}
128
129// Runs on the UI thread.
130void SafeBrowsingService::OnEnable(bool enabled) {
131 if (enabled)
132 Start();
133 else
134 ShutDown();
135}
136
137bool SafeBrowsingService::CanCheckUrl(const GURL& url) const {
138 return url.SchemeIs("http") || url.SchemeIs("https");
139}
140
141bool SafeBrowsingService::CheckUrl(const GURL& url, Client* client) {
142 DCHECK(MessageLoop::current() == io_loop_);
143
144 if (!enabled_ || !database_)
145 return true;
146
147 if (!resetting_) {
148 Time start_time = Time::Now();
149 bool need_check = database_->NeedToCheckUrl(url);
150 UMA_HISTOGRAM_TIMES(L"SB.BloomFilter", Time::Now() - start_time);
151 if (!need_check)
152 return true; // The url is definitely safe.
153 }
154
155 // The url may or may not be safe, need to go to the database to be sure.
156 SafeBrowsingCheck* check = new SafeBrowsingCheck();
157 check->url = url;
158 check->client = client;
159 check->result = URL_SAFE;
160 check->need_get_hash = false;
161 check->start = Time::Now();
162 checks_.insert(check);
163
164 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
165 this, &SafeBrowsingService::CheckDatabase,
166 check, protocol_manager_->last_update()));
167 return false;
168}
169
170void SafeBrowsingService::DisplayBlockingPage(const GURL& url,
171 ResourceType::Type resource_type,
172 UrlCheckResult result,
173 Client* client,
174 MessageLoop* ui_loop,
175 int render_process_host_id,
176 int render_view_id) {
177 // Check if the user has already ignored our warning for this render_view
178 // and domain.
179 for (size_t i = 0; i < white_listed_entries_.size(); ++i) {
180 const WhiteListedEntry& entry = white_listed_entries_[i];
181 if (entry.render_process_host_id == render_process_host_id &&
182 entry.render_view_id == render_view_id &&
183 entry.result == result &&
184 entry.domain ==
[email protected]8ac1a752008-07-31 19:40:37185 net::RegistryControlledDomainService::GetDomainAndRegistry(url)) {
initial.commit09911bf2008-07-26 23:55:29186 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
187 this, &SafeBrowsingService::NotifyClientBlockingComplete,
188 client, true));
189 return;
190 }
191 }
192
[email protected]cbab76d2008-10-13 22:42:47193 BlockingPageParam param;
194 param.url = url;
195 param.resource_type = resource_type;
196 param.result = result;
197 param.client = client;
198 param.render_process_host_id = render_process_host_id;
199 param.render_view_id = render_view_id;
200 // The blocking page must be created from the UI thread.
201 ui_loop->PostTask(FROM_HERE, NewRunnableMethod(this,
202 &SafeBrowsingService::DoDisplayBlockingPage,
203 param));
204}
205
206// Invoked on the UI thread.
207void SafeBrowsingService::DoDisplayBlockingPage(
208 const BlockingPageParam& param) {
209 SafeBrowsingBlockingPage* blocking_page = new SafeBrowsingBlockingPage(this,
210 param);
211 blocking_page->Show();
initial.commit09911bf2008-07-26 23:55:29212}
213
214void SafeBrowsingService::CancelCheck(Client* client) {
215 DCHECK(MessageLoop::current() == io_loop_);
216
217 for (CurrentChecks::iterator i = checks_.begin(); i != checks_.end(); ++i) {
218 if ((*i)->client == client)
219 (*i)->client = NULL;
220 }
221}
222
223void SafeBrowsingService::CheckDatabase(SafeBrowsingCheck* info,
224 Time last_update) {
225 DCHECK(MessageLoop::current() == db_thread_->message_loop());
226 // If client == NULL it means it was cancelled, no need for db lookup.
227 if (info->client && GetDatabase()) {
228 Time now = Time::Now();
229 std::string list;
230 if (GetDatabase()->ContainsUrl(info->url,
231 &list,
232 &info->prefix_hits,
233 &info->full_hits,
234 last_update)) {
235 if (info->prefix_hits.empty()) {
236 info->result = GetResultFromListname(list);
237 } else {
238 if (info->full_hits.empty())
239 info->need_get_hash = true;
240 }
241 }
242 info->db_time = Time::Now() - now;
243 }
244
245 if (io_loop_)
246 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
247 this, &SafeBrowsingService::OnCheckDone, info));
248}
249
250void SafeBrowsingService::OnCheckDone(SafeBrowsingCheck* info) {
251 DCHECK(MessageLoop::current() == io_loop_);
252
253 // If we've been shutdown during the database lookup, this check will already
254 // have been deleted (in OnIOShutdown).
255 if (!enabled_ || checks_.find(info) == checks_.end())
256 return;
257
258 UMA_HISTOGRAM_TIMES(L"SB.Database", Time::Now() - info->start);
259 if (info->client && info->need_get_hash) {
260 // We have a partial match so we need to query Google for the full hash.
261 // Clean up will happen in HandleGetHashResults.
262
263 // See if we have a GetHash request already in progress for this particular
264 // prefix. If so, we just append ourselves to the list of interested parties
265 // when the results arrive. We only do this for checks involving one prefix,
266 // since that is the common case (multiple prefixes will issue the request
267 // as normal).
268 if (info->prefix_hits.size() == 1) {
269 SBPrefix prefix = info->prefix_hits[0];
270 GetHashRequests::iterator it = gethash_requests_.find(prefix);
271 if (it != gethash_requests_.end()) {
272 // There's already a request in progress.
273 it->second.push_back(info);
274 return;
275 }
276
277 // No request in progress, so we're the first for this prefix.
278 GetHashRequestors requestors;
279 requestors.push_back(info);
280 gethash_requests_[prefix] = requestors;
281 }
282
283 // Reset the start time so that we can measure the network time without the
284 // database time.
285 info->start = Time::Now();
286 protocol_manager_->GetFullHash(info, info->prefix_hits);
287 } else {
288 // We may have cached results for previous GetHash queries.
289 HandleOneCheck(info, info->full_hits);
290 }
291}
292
293SafeBrowsingDatabase* SafeBrowsingService::GetDatabase() {
294 DCHECK(MessageLoop::current() == db_thread_->message_loop());
295 if (database_)
296 return database_;
297
298 std::wstring path;
299 bool result = PathService::Get(chrome::DIR_USER_DATA, &path);
300 DCHECK(result);
301
302 path.append(L"\\");
303 path.append(chrome::kSafeBrowsingFilename);
304
305 Time before = Time::Now();
[email protected]54d80bb02008-09-20 02:03:08306 SafeBrowsingDatabase* database = SafeBrowsingDatabase::Create();
initial.commit09911bf2008-07-26 23:55:29307 Callback0::Type* callback =
308 NewCallback(this, &SafeBrowsingService::ChunkInserted);
309 result = database->Init(path, callback);
310 if (!result) {
311 NOTREACHED();
312 return NULL;
313 }
314
315 database_ = database;
316
317 TimeDelta open_time = Time::Now() - before;
318 SB_DLOG(INFO) << "SafeBrowsing database open took " <<
319 open_time.InMilliseconds() << " ms.";
320
321 return database_;
322}
323
324// Public API called only on the IO thread.
325// The SafeBrowsingProtocolManager has received the full hash results for
326// prefix hits detected in the database.
327void SafeBrowsingService::HandleGetHashResults(
328 SafeBrowsingCheck* check,
[email protected]200abc32008-09-05 01:44:33329 const std::vector<SBFullHashResult>& full_hashes,
330 bool can_cache) {
initial.commit09911bf2008-07-26 23:55:29331 if (checks_.find(check) == checks_.end())
332 return;
333
334 DCHECK(enabled_);
335
[email protected]200abc32008-09-05 01:44:33336 std::vector<SBPrefix> prefixes = check->prefix_hits;
initial.commit09911bf2008-07-26 23:55:29337 UMA_HISTOGRAM_LONG_TIMES(L"SB.Network", Time::Now() - check->start);
338 OnHandleGetHashResults(check, full_hashes); // 'check' is deleted here.
339
[email protected]200abc32008-09-05 01:44:33340 if (can_cache)
341 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
342 this, &SafeBrowsingService::CacheHashResults, prefixes, full_hashes));
initial.commit09911bf2008-07-26 23:55:29343}
344
345void SafeBrowsingService::OnHandleGetHashResults(
346 SafeBrowsingCheck* check,
347 const std::vector<SBFullHashResult>& full_hashes) {
348 SBPrefix prefix = check->prefix_hits[0];
349 GetHashRequests::iterator it = gethash_requests_.find(prefix);
350 if (check->prefix_hits.size() > 1 || it == gethash_requests_.end()) {
351 HandleOneCheck(check, full_hashes);
352 return;
353 }
354
355 // Call back all interested parties.
356 GetHashRequestors& requestors = it->second;
357 for (GetHashRequestors::iterator r = requestors.begin();
358 r != requestors.end(); ++r) {
359 HandleOneCheck(*r, full_hashes);
360 }
361
362 gethash_requests_.erase(it);
363}
364
365void SafeBrowsingService::HandleOneCheck(
366 SafeBrowsingCheck* check,
367 const std::vector<SBFullHashResult>& full_hashes) {
368 if (check->client) {
369 UrlCheckResult result = URL_SAFE;
370 int index = safe_browsing_util::CompareFullHashes(check->url, full_hashes);
371 if (index != -1)
372 result = GetResultFromListname(full_hashes[index].list_name);
373
374 // Let the client continue handling the original request.
375 check->client->OnUrlCheckResult(check->url, result);
376 }
377
378 checks_.erase(check);
379 delete check;
380}
381
382void SafeBrowsingService::GetAllChunks() {
383 DCHECK(MessageLoop::current() == io_loop_);
384 DCHECK(enabled_);
385 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
386 this, &SafeBrowsingService::GetAllChunksFromDatabase));
387}
388
[email protected]aad08752008-10-02 22:13:41389void SafeBrowsingService::UpdateFinished() {
390 DCHECK(MessageLoop::current() == io_loop_);
391 DCHECK(enabled_);
392 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
393 this, &SafeBrowsingService::DatabaseUpdateFinished));
394}
395
396void SafeBrowsingService::DatabaseUpdateFinished() {
397 DCHECK(MessageLoop::current() == db_thread_->message_loop());
398 if (GetDatabase())
399 GetDatabase()->UpdateFinished();
400}
401
[email protected]cbab76d2008-10-13 22:42:47402void SafeBrowsingService::OnBlockingPageDone(const BlockingPageParam& param) {
403 NotifyClientBlockingComplete(param.client, param.proceed);
initial.commit09911bf2008-07-26 23:55:29404
[email protected]cbab76d2008-10-13 22:42:47405 if (param.proceed) {
initial.commit09911bf2008-07-26 23:55:29406 // Whitelist this domain and warning type for the given tab.
407 WhiteListedEntry entry;
[email protected]cbab76d2008-10-13 22:42:47408 entry.render_process_host_id = param.render_process_host_id;
409 entry.render_view_id = param.render_view_id;
410 entry.domain =
411 net::RegistryControlledDomainService::GetDomainAndRegistry(param.url);
412 entry.result = param.result;
initial.commit09911bf2008-07-26 23:55:29413 white_listed_entries_.push_back(entry);
414 }
initial.commit09911bf2008-07-26 23:55:29415}
416
417void SafeBrowsingService::NotifyClientBlockingComplete(Client* client,
418 bool proceed) {
419 client->OnBlockingPageComplete(proceed);
420}
421
422// This method runs on the UI loop to access the prefs.
423void SafeBrowsingService::OnNewMacKeys(const std::string& client_key,
424 const std::string& wrapped_key) {
425 PrefService* prefs = g_browser_process->local_state();
426 if (prefs) {
427 prefs->SetString(prefs::kSafeBrowsingClientKey, ASCIIToWide(client_key));
428 prefs->SetString(prefs::kSafeBrowsingWrappedKey, ASCIIToWide(wrapped_key));
429 }
430}
431
432void SafeBrowsingService::ChunkInserted() {
433 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
434 this, &SafeBrowsingService::OnChunkInserted));
435}
436
437void SafeBrowsingService::OnChunkInserted() {
438 DCHECK(MessageLoop::current() == io_loop_);
439 protocol_manager_->OnChunkInserted();
440}
441
442// static
443void SafeBrowsingService::RegisterUserPrefs(PrefService* prefs) {
444 prefs->RegisterStringPref(prefs::kSafeBrowsingClientKey, L"");
445 prefs->RegisterStringPref(prefs::kSafeBrowsingWrappedKey, L"");
446}
447
448void SafeBrowsingService::ResetDatabase() {
449 DCHECK(MessageLoop::current() == io_loop_);
450 resetting_ = true;
451 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
452 this, &SafeBrowsingService::OnResetDatabase));
453}
454
455void SafeBrowsingService::OnResetDatabase() {
456 DCHECK(MessageLoop::current() == db_thread_->message_loop());
457 GetDatabase()->ResetDatabase();
458 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
459 this, &SafeBrowsingService::OnResetComplete));
460}
461
462void SafeBrowsingService::OnResetComplete() {
463 DCHECK(MessageLoop::current() == io_loop_);
464 resetting_ = false;
465}
466
467void SafeBrowsingService::HandleChunk(const std::string& list,
468 std::deque<SBChunk>* chunks) {
469 DCHECK(MessageLoop::current() == io_loop_);
470 DCHECK(enabled_);
471 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
472 this, &SafeBrowsingService::HandleChunkForDatabase, list, chunks));
473}
474
475void SafeBrowsingService::HandleChunkForDatabase(
476 const std::string& list_name,
477 std::deque<SBChunk>* chunks) {
478 DCHECK(MessageLoop::current() == db_thread_->message_loop());
479
480 GetDatabase()->InsertChunks(list_name, chunks);
481}
482
483void SafeBrowsingService::HandleChunkDelete(
484 std::vector<SBChunkDelete>* chunk_deletes) {
485 DCHECK(MessageLoop::current() == io_loop_);
486 DCHECK(enabled_);
487 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
488 this, &SafeBrowsingService::DeleteChunks, chunk_deletes));
489}
490
491void SafeBrowsingService::DeleteChunks(
492 std::vector<SBChunkDelete>* chunk_deletes) {
493 DCHECK(MessageLoop::current() == db_thread_->message_loop());
494
495 GetDatabase()->DeleteChunks(chunk_deletes);
496}
497
498// Database worker function.
499void SafeBrowsingService::GetAllChunksFromDatabase() {
500 DCHECK(MessageLoop::current() == db_thread_->message_loop());
501 bool database_error = false;
502 std::vector<SBListChunkRanges> lists;
503 if (GetDatabase()) {
504 GetDatabase()->GetListsInfo(&lists);
505 } else {
506 database_error = true;
507 }
508
509 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
510 this, &SafeBrowsingService::OnGetAllChunksFromDatabase, lists,
511 database_error));
512}
513
514// Called on the io thread with the results of all chunks.
515void SafeBrowsingService::OnGetAllChunksFromDatabase(
516 const std::vector<SBListChunkRanges>& lists, bool database_error) {
517 DCHECK(MessageLoop::current() == io_loop_);
518 if (!enabled_)
519 return;
520
521 protocol_manager_->OnGetChunksComplete(lists, database_error);
522}
523
524SafeBrowsingService::UrlCheckResult SafeBrowsingService::GetResultFromListname(
525 const std::string& list_name) {
526 if (safe_browsing_util::IsPhishingList(list_name)) {
527 return URL_PHISHING;
528 }
529
530 if (safe_browsing_util::IsMalwareList(list_name)) {
531 return URL_MALWARE;
532 }
533
534 SB_DLOG(INFO) << "Unknown safe browsing list " << list_name;
535 return URL_SAFE;
536}
537
538// static
539void SafeBrowsingService::LogPauseDelay(TimeDelta time) {
540 UMA_HISTOGRAM_LONG_TIMES(L"SB.Delay", time);
541}
542
543void SafeBrowsingService::CacheHashResults(
[email protected]200abc32008-09-05 01:44:33544 const std::vector<SBPrefix>& prefixes,
initial.commit09911bf2008-07-26 23:55:29545 const std::vector<SBFullHashResult>& full_hashes) {
546 DCHECK(MessageLoop::current() == db_thread_->message_loop());
[email protected]200abc32008-09-05 01:44:33547 GetDatabase()->CacheHashResults(prefixes, full_hashes);
initial.commit09911bf2008-07-26 23:55:29548}
549
550void SafeBrowsingService::OnSuspend() {
551}
552
553// Tell the SafeBrowsing database not to do expensive disk operations for a few
554// minutes after waking up. It's quite likely that the act of resuming from a
555// low power state will involve much disk activity, which we don't want to
556// exacerbate.
557void SafeBrowsingService::OnResume() {
558 DCHECK(MessageLoop::current() == io_loop_);
559 if (enabled_) {
560 db_thread_->message_loop()->PostTask(FROM_HERE,
561 NewRunnableMethod(this, &SafeBrowsingService::HandleResume));
562 }
563}
564
565void SafeBrowsingService::HandleResume() {
566 DCHECK(MessageLoop::current() == db_thread_->message_loop());
567 GetDatabase()->HandleResume();
license.botbf09a502008-08-24 00:55:55568}