blob: f7e47f7c4526fd9f58441ab3812f3fe4b1c452f9 [file] [log] [blame]
initial.commit09911bf2008-07-26 23:55:291// Copyright 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29//
30
31#include "chrome/browser/safe_browsing/safe_browsing_service.h"
32
33#include "base/histogram.h"
34#include "base/logging.h"
35#include "base/message_loop.h"
36#include "base/path_service.h"
37#include "base/string_util.h"
38#include "chrome/browser/browser_process.h"
39#include "chrome/browser/profile_manager.h"
40#include "chrome/browser/safe_browsing/protocol_manager.h"
41#include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
42#include "chrome/browser/safe_browsing/safe_browsing_database.h"
43#include "chrome/common/chrome_constants.h"
44#include "chrome/common/chrome_paths.h"
45#include "chrome/common/pref_names.h"
46#include "chrome/common/pref_service.h"
47#include "net/base/registry_controlled_domain.h"
48
49SafeBrowsingService::SafeBrowsingService()
50 : io_loop_(NULL),
51 database_(NULL),
52 protocol_manager_(NULL),
53 enabled_(false),
54 resetting_(false) {
55}
56
57SafeBrowsingService::~SafeBrowsingService() {
58}
59
60// Only called on the UI thread.
61void SafeBrowsingService::Initialize(MessageLoop* io_loop) {
62 io_loop_ = io_loop;
63
64 // Get the profile's preference for SafeBrowsing.
65 std::wstring user_data_dir;
66 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
67 ProfileManager* profile_manager = g_browser_process->profile_manager();
68 Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
69 PrefService* pref_service = profile->GetPrefs();
70 if (pref_service->GetBoolean(prefs::kSafeBrowsingEnabled))
71 Start();
72}
73
74// Start up SafeBrowsing objects. This can be called at browser start, or when
75// the user checks the "Enable SafeBrowsing" option in the Advanced options UI.
76void SafeBrowsingService::Start() {
77 DCHECK(!db_thread_.get());
78 db_thread_.reset(new Thread("Chrome_SafeBrowsingThread"));
79 if (!db_thread_->Start())
80 return;
81
82 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
83 this, &SafeBrowsingService::OnDBInitialize));
84
85 // Retrieve client MAC keys.
86 PrefService* local_state = g_browser_process->local_state();
87 std::string client_key, wrapped_key;
88 if (local_state) {
89 client_key =
90 WideToASCII(local_state->GetString(prefs::kSafeBrowsingClientKey));
91 wrapped_key =
92 WideToASCII(local_state->GetString(prefs::kSafeBrowsingWrappedKey));
93 }
94
95 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
96 this, &SafeBrowsingService::OnIOInitialize, MessageLoop::current(),
97 client_key, wrapped_key));
98}
99
100void SafeBrowsingService::ShutDown() {
101 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
102 this, &SafeBrowsingService::OnIOShutdown));
103}
104
105void SafeBrowsingService::OnIOInitialize(MessageLoop* notify_loop,
106 const std::string& client_key,
107 const std::string& wrapped_key) {
108 DCHECK(MessageLoop::current() == io_loop_);
109 enabled_ = true;
110 protocol_manager_ = new SafeBrowsingProtocolManager(this,
111 notify_loop,
112 client_key,
113 wrapped_key);
114 protocol_manager_->Initialize();
115}
116
117void SafeBrowsingService::OnDBInitialize() {
118 DCHECK(MessageLoop::current() == db_thread_->message_loop());
119 GetDatabase();
120}
121
122void SafeBrowsingService::OnIOShutdown() {
123 DCHECK(MessageLoop::current() == io_loop_);
124 if (!enabled_)
125 return;
126
127 enabled_ = false;
128
129 // This cancels all in-flight GetHash requests.
130 delete protocol_manager_;
131
132 if (db_thread_.get())
133 db_thread_->message_loop()->DeleteSoon(FROM_HERE, database_);
134
135 // Flush the database thread. Any in-progress database check results will be
136 // ignored and cleaned up below.
137 db_thread_.reset(NULL);
138
139 database_ = NULL;
140
141 // Delete checks once the database thread is done, calling back any clients
142 // with 'URL_SAFE'.
143 for (CurrentChecks::iterator it = checks_.begin();
144 it != checks_.end(); ++it) {
145 if ((*it)->client)
146 (*it)->client->OnUrlCheckResult((*it)->url, URL_SAFE);
147 delete *it;
148 }
149 checks_.clear();
150
151 gethash_requests_.clear();
152}
153
154// Runs on the UI thread.
155void SafeBrowsingService::OnEnable(bool enabled) {
156 if (enabled)
157 Start();
158 else
159 ShutDown();
160}
161
162bool SafeBrowsingService::CanCheckUrl(const GURL& url) const {
163 return url.SchemeIs("http") || url.SchemeIs("https");
164}
165
166bool SafeBrowsingService::CheckUrl(const GURL& url, Client* client) {
167 DCHECK(MessageLoop::current() == io_loop_);
168
169 if (!enabled_ || !database_)
170 return true;
171
172 if (!resetting_) {
173 Time start_time = Time::Now();
174 bool need_check = database_->NeedToCheckUrl(url);
175 UMA_HISTOGRAM_TIMES(L"SB.BloomFilter", Time::Now() - start_time);
176 if (!need_check)
177 return true; // The url is definitely safe.
178 }
179
180 // The url may or may not be safe, need to go to the database to be sure.
181 SafeBrowsingCheck* check = new SafeBrowsingCheck();
182 check->url = url;
183 check->client = client;
184 check->result = URL_SAFE;
185 check->need_get_hash = false;
186 check->start = Time::Now();
187 checks_.insert(check);
188
189 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
190 this, &SafeBrowsingService::CheckDatabase,
191 check, protocol_manager_->last_update()));
192 return false;
193}
194
195void SafeBrowsingService::DisplayBlockingPage(const GURL& url,
196 ResourceType::Type resource_type,
197 UrlCheckResult result,
198 Client* client,
199 MessageLoop* ui_loop,
200 int render_process_host_id,
201 int render_view_id) {
202 // Check if the user has already ignored our warning for this render_view
203 // and domain.
204 for (size_t i = 0; i < white_listed_entries_.size(); ++i) {
205 const WhiteListedEntry& entry = white_listed_entries_[i];
206 if (entry.render_process_host_id == render_process_host_id &&
207 entry.render_view_id == render_view_id &&
208 entry.result == result &&
209 entry.domain ==
210 RegistryControlledDomainService::GetDomainAndRegistry(url)) {
211 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
212 this, &SafeBrowsingService::NotifyClientBlockingComplete,
213 client, true));
214 return;
215 }
216 }
217
218 SafeBrowsingBlockingPage* blocking_page = new SafeBrowsingBlockingPage(
219 this, client, render_process_host_id, render_view_id, url, resource_type,
220 result);
221 blocking_page->AddRef();
222 ui_loop->PostTask(FROM_HERE, NewRunnableMethod(
223 blocking_page, &SafeBrowsingBlockingPage::DisplayBlockingPage));
224}
225
226void SafeBrowsingService::CancelCheck(Client* client) {
227 DCHECK(MessageLoop::current() == io_loop_);
228
229 for (CurrentChecks::iterator i = checks_.begin(); i != checks_.end(); ++i) {
230 if ((*i)->client == client)
231 (*i)->client = NULL;
232 }
233}
234
235void SafeBrowsingService::CheckDatabase(SafeBrowsingCheck* info,
236 Time last_update) {
237 DCHECK(MessageLoop::current() == db_thread_->message_loop());
238 // If client == NULL it means it was cancelled, no need for db lookup.
239 if (info->client && GetDatabase()) {
240 Time now = Time::Now();
241 std::string list;
242 if (GetDatabase()->ContainsUrl(info->url,
243 &list,
244 &info->prefix_hits,
245 &info->full_hits,
246 last_update)) {
247 if (info->prefix_hits.empty()) {
248 info->result = GetResultFromListname(list);
249 } else {
250 if (info->full_hits.empty())
251 info->need_get_hash = true;
252 }
253 }
254 info->db_time = Time::Now() - now;
255 }
256
257 if (io_loop_)
258 io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
259 this, &SafeBrowsingService::OnCheckDone, info));
260}
261
262void SafeBrowsingService::OnCheckDone(SafeBrowsingCheck* info) {
263 DCHECK(MessageLoop::current() == io_loop_);
264
265 // If we've been shutdown during the database lookup, this check will already
266 // have been deleted (in OnIOShutdown).
267 if (!enabled_ || checks_.find(info) == checks_.end())
268 return;
269
270 UMA_HISTOGRAM_TIMES(L"SB.Database", Time::Now() - info->start);
271 if (info->client && info->need_get_hash) {
272 // We have a partial match so we need to query Google for the full hash.
273 // Clean up will happen in HandleGetHashResults.
274
275 // See if we have a GetHash request already in progress for this particular
276 // prefix. If so, we just append ourselves to the list of interested parties
277 // when the results arrive. We only do this for checks involving one prefix,
278 // since that is the common case (multiple prefixes will issue the request
279 // as normal).
280 if (info->prefix_hits.size() == 1) {
281 SBPrefix prefix = info->prefix_hits[0];
282 GetHashRequests::iterator it = gethash_requests_.find(prefix);
283 if (it != gethash_requests_.end()) {
284 // There's already a request in progress.
285 it->second.push_back(info);
286 return;
287 }
288
289 // No request in progress, so we're the first for this prefix.
290 GetHashRequestors requestors;
291 requestors.push_back(info);
292 gethash_requests_[prefix] = requestors;
293 }
294
295 // Reset the start time so that we can measure the network time without the
296 // database time.
297 info->start = Time::Now();
298 protocol_manager_->GetFullHash(info, info->prefix_hits);
299 } else {
300 // We may have cached results for previous GetHash queries.
301 HandleOneCheck(info, info->full_hits);
302 }
303}
304
305SafeBrowsingDatabase* SafeBrowsingService::GetDatabase() {
306 DCHECK(MessageLoop::current() == db_thread_->message_loop());
307 if (database_)
308 return database_;
309
310 std::wstring path;
311 bool result = PathService::Get(chrome::DIR_USER_DATA, &path);
312 DCHECK(result);
313
314 path.append(L"\\");
315 path.append(chrome::kSafeBrowsingFilename);
316
317 Time before = Time::Now();
318 SafeBrowsingDatabase* database = new SafeBrowsingDatabase();
319 Callback0::Type* callback =
320 NewCallback(this, &SafeBrowsingService::ChunkInserted);
321 result = database->Init(path, callback);
322 if (!result) {
323 NOTREACHED();
324 return NULL;
325 }
326
327 database_ = database;
328
329 TimeDelta open_time = Time::Now() - before;
330 SB_DLOG(INFO) << "SafeBrowsing database open took " <<
331 open_time.InMilliseconds() << " ms.";
332
333 return database_;
334}
335
336// Public API called only on the IO thread.
337// The SafeBrowsingProtocolManager has received the full hash results for
338// prefix hits detected in the database.
339void SafeBrowsingService::HandleGetHashResults(
340 SafeBrowsingCheck* check,
341 const std::vector<SBFullHashResult>& full_hashes) {
342 if (checks_.find(check) == checks_.end())
343 return;
344
345 DCHECK(enabled_);
346
347 UMA_HISTOGRAM_LONG_TIMES(L"SB.Network", Time::Now() - check->start);
348 OnHandleGetHashResults(check, full_hashes); // 'check' is deleted here.
349
350 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
351 this, &SafeBrowsingService::CacheHashResults, full_hashes));
352}
353
354void SafeBrowsingService::OnHandleGetHashResults(
355 SafeBrowsingCheck* check,
356 const std::vector<SBFullHashResult>& full_hashes) {
357 SBPrefix prefix = check->prefix_hits[0];
358 GetHashRequests::iterator it = gethash_requests_.find(prefix);
359 if (check->prefix_hits.size() > 1 || it == gethash_requests_.end()) {
360 HandleOneCheck(check, full_hashes);
361 return;
362 }
363
364 // Call back all interested parties.
365 GetHashRequestors& requestors = it->second;
366 for (GetHashRequestors::iterator r = requestors.begin();
367 r != requestors.end(); ++r) {
368 HandleOneCheck(*r, full_hashes);
369 }
370
371 gethash_requests_.erase(it);
372}
373
374void SafeBrowsingService::HandleOneCheck(
375 SafeBrowsingCheck* check,
376 const std::vector<SBFullHashResult>& full_hashes) {
377 if (check->client) {
378 UrlCheckResult result = URL_SAFE;
379 int index = safe_browsing_util::CompareFullHashes(check->url, full_hashes);
380 if (index != -1)
381 result = GetResultFromListname(full_hashes[index].list_name);
382
383 // Let the client continue handling the original request.
384 check->client->OnUrlCheckResult(check->url, result);
385 }
386
387 checks_.erase(check);
388 delete check;
389}
390
391void SafeBrowsingService::GetAllChunks() {
392 DCHECK(MessageLoop::current() == io_loop_);
393 DCHECK(enabled_);
394 db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
395 this, &SafeBrowsingService::GetAllChunksFromDatabase));
396}
397
398void SafeBrowsingService::OnBlockingPageDone(SafeBrowsingBlockingPage* page,
399 Client* client,
400 bool proceed) {
401 NotifyClientBlockingComplete(client, proceed);
402
403 if (proceed) {
404 // Whitelist this domain and warning type for the given tab.
405 WhiteListedEntry entry;
406 entry.render_process_host_id = page->render_process_host_id();
407 entry.render_view_id = page->render_view_id();
408 entry.domain =
409 RegistryControlledDomainService::GetDomainAndRegistry(page->url());
410 entry.result = page->result();
411 white_listed_entries_.push_back(entry);
412 }
413
414 page->Release();
415}
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(
544 const std::vector<SBFullHashResult>& full_hashes) {
545 DCHECK(MessageLoop::current() == db_thread_->message_loop());
546 GetDatabase()->CacheHashResults(full_hashes);
547}
548
549void SafeBrowsingService::OnSuspend() {
550}
551
552// Tell the SafeBrowsing database not to do expensive disk operations for a few
553// minutes after waking up. It's quite likely that the act of resuming from a
554// low power state will involve much disk activity, which we don't want to
555// exacerbate.
556void SafeBrowsingService::OnResume() {
557 DCHECK(MessageLoop::current() == io_loop_);
558 if (enabled_) {
559 db_thread_->message_loop()->PostTask(FROM_HERE,
560 NewRunnableMethod(this, &SafeBrowsingService::HandleResume));
561 }
562}
563
564void SafeBrowsingService::HandleResume() {
565 DCHECK(MessageLoop::current() == db_thread_->message_loop());
566 GetDatabase()->HandleResume();
567}