// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "chrome/browser/safe_browsing/safe_browsing_service.h"

#include "base/command_line.h"
#include "base/histogram.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/profile_manager.h"
#include "chrome/browser/safe_browsing/protocol_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
#include "chrome/browser/safe_browsing/safe_browsing_database.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/tab_contents/web_contents.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/url_constants.h"
#include "net/base/registry_controlled_domain.h"

using base::Time;
using base::TimeDelta;

SafeBrowsingService::SafeBrowsingService()
    : io_loop_(NULL),
      database_(NULL),
      protocol_manager_(NULL),
      enabled_(false),
      resetting_(false),
      database_loaded_(false),
      update_in_progress_(false) {
  new_safe_browsing_ = !CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kUseOldSafeBrowsing);
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  DCHECK(monitor);
  if (monitor)
    monitor->AddObserver(this);
}

SafeBrowsingService::~SafeBrowsingService() {
  base::SystemMonitor* monitor = base::SystemMonitor::Get();
  if (monitor)
    monitor->RemoveObserver(this);
}

// Only called on the UI thread.
void SafeBrowsingService::Initialize(MessageLoop* io_loop) {
  io_loop_ = io_loop;

  // Get the profile's preference for SafeBrowsing.
  FilePath user_data_dir;
  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
  PrefService* pref_service = profile->GetPrefs();
  if (pref_service->GetBoolean(prefs::kSafeBrowsingEnabled))
    Start();
}

// Start up SafeBrowsing objects. This can be called at browser start, or when
// the user checks the "Enable SafeBrowsing" option in the Advanced options UI.
void SafeBrowsingService::Start() {
  DCHECK(!db_thread_.get());
  db_thread_.reset(new base::Thread("Chrome_SafeBrowsingThread"));
  if (!db_thread_->Start())
    return;

  // Retrieve client MAC keys.
  PrefService* local_state = g_browser_process->local_state();
  std::string client_key, wrapped_key;
  if (local_state) {
    client_key =
      WideToASCII(local_state->GetString(prefs::kSafeBrowsingClientKey));
    wrapped_key =
      WideToASCII(local_state->GetString(prefs::kSafeBrowsingWrappedKey));
  }

  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnIOInitialize, MessageLoop::current(),
      client_key, wrapped_key));

  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnDBInitialize));
}

void SafeBrowsingService::ShutDown() {
  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnIOShutdown));
}

void SafeBrowsingService::OnIOInitialize(MessageLoop* notify_loop,
                                         const std::string& client_key,
                                         const std::string& wrapped_key) {
  DCHECK(MessageLoop::current() == io_loop_);
  enabled_ = true;
  protocol_manager_ = new SafeBrowsingProtocolManager(this,
                                                      notify_loop,
                                                      client_key,
                                                      wrapped_key);
  // We want to initialize the protocol manager only after the database has
  // loaded, which we'll receive asynchronously (DatabaseLoadComplete). If
  // database_loaded_ isn't true, we'll wait for that notification to do the
  // init.
  if (database_loaded_)
    protocol_manager_->Initialize();
}

void SafeBrowsingService::OnDBInitialize() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  GetDatabase();
}

void SafeBrowsingService::OnIOShutdown() {
  DCHECK(MessageLoop::current() == io_loop_);
  if (!enabled_)
    return;

  enabled_ = false;
  resetting_ = false;

  // This cancels all in-flight GetHash requests.
  delete protocol_manager_;
  protocol_manager_ = NULL;

  if (db_thread_.get())
    db_thread_->message_loop()->DeleteSoon(FROM_HERE, database_);

  // Flush the database thread. Any in-progress database check results will be
  // ignored and cleaned up below.
  db_thread_.reset(NULL);

  database_ = NULL;
  database_loaded_ = false;

  // Delete queued and pending checks once the database thread is done, calling
  // back any clients with 'URL_SAFE'.
  while (!queued_checks_.empty()) {
    QueuedCheck check = queued_checks_.front();
    if (check.client)
      check.client->OnUrlCheckResult(check.url, URL_SAFE);
    queued_checks_.pop_front();
  }

  for (CurrentChecks::iterator it = checks_.begin();
       it != checks_.end(); ++it) {
    if ((*it)->client)
      (*it)->client->OnUrlCheckResult((*it)->url, URL_SAFE);
    delete *it;
  }
  checks_.clear();

  gethash_requests_.clear();
}

// Runs on the UI thread.
void SafeBrowsingService::OnEnable(bool enabled) {
  if (enabled)
    Start();
  else
    ShutDown();
}

bool SafeBrowsingService::CanCheckUrl(const GURL& url) const {
  return url.SchemeIs(chrome::kHttpScheme) ||
         url.SchemeIs(chrome::kHttpsScheme);
}

bool SafeBrowsingService::CheckUrl(const GURL& url, Client* client) {
  DCHECK(MessageLoop::current() == io_loop_);
  if (!enabled_ || !database_)
    return true;

  if (new_safe_browsing_)
    return CheckUrlNew(url, client);

  if (!resetting_) {
    Time start_time = Time::Now();
    bool need_check = database_->NeedToCheckUrl(url);
    UMA_HISTOGRAM_TIMES("SB.BloomFilter", Time::Now() - start_time);
    if (!need_check)
      return true;  // The url is definitely safe.
  }

  // The url may or may not be safe, need to go to the database to be sure.
  SafeBrowsingCheck* check = new SafeBrowsingCheck();
  check->url = url;
  check->client = client;
  check->result = URL_SAFE;
  check->need_get_hash = false;
  check->start = Time::Now();
  checks_.insert(check);

  // Old school SafeBrowsing does an asynchronous database check.
  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::CheckDatabase,
      check, protocol_manager_->last_update()));

  return false;
}

bool SafeBrowsingService::CheckUrlNew(const GURL& url, Client* client) {
  if (resetting_ || !database_loaded_) {
    QueuedCheck check;
    check.client = client;
    check.url = url;
    queued_checks_.push_back(check);
    return false;
  }

  std::string list;
  std::vector<SBPrefix> prefix_hits;
  std::vector<SBFullHashResult> full_hits;
  base::Time check_start = base::Time::Now();
  bool prefix_match = database_->ContainsUrl(url, &list, &prefix_hits,
                                             &full_hits,
                                             protocol_manager_->last_update());
  
  UMA_HISTOGRAM_TIMES("SB2.FilterCheck", base::Time::Now() - check_start);

  if (!prefix_match)
    return true;  // URL is okay.

  // Needs to be asynchronous, since we could be in the constructor of a
  // ResourceDispatcherHost event handler which can't pause there.
  SafeBrowsingCheck* check = new SafeBrowsingCheck();
  check->url = url;
  check->client = client;
  check->result = URL_SAFE;
  check->start = Time::Now();
  check->need_get_hash = full_hits.empty();
  check->prefix_hits.swap(prefix_hits);
  check->full_hits.swap(full_hits);
  checks_.insert(check);

  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnCheckDone, check));

  return false;
}

void SafeBrowsingService::DisplayBlockingPage(const GURL& url,
                                              ResourceType::Type resource_type,
                                              UrlCheckResult result,
                                              Client* client,
                                              MessageLoop* ui_loop,
                                              int render_process_host_id,
                                              int render_view_id) {
  // Check if the user has already ignored our warning for this render_view
  // and domain.
  for (size_t i = 0; i < white_listed_entries_.size(); ++i) {
    const WhiteListedEntry& entry = white_listed_entries_[i];
    if (entry.render_process_host_id == render_process_host_id &&
        entry.render_view_id == render_view_id &&
        entry.result == result &&
        entry.domain ==
        net::RegistryControlledDomainService::GetDomainAndRegistry(url)) {
      MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
          this, &SafeBrowsingService::NotifyClientBlockingComplete,
          client, true));
      return;
    }
  }

  UnsafeResource resource;
  resource.url = url;
  resource.resource_type = resource_type;
  resource.threat_type= result;
  resource.client = client;
  resource.render_process_host_id = render_process_host_id;
  resource.render_view_id = render_view_id;
  // The blocking page must be created from the UI thread.
  ui_loop->PostTask(FROM_HERE, NewRunnableMethod(this,
      &SafeBrowsingService::DoDisplayBlockingPage,
      resource));
}

// Invoked on the UI thread.
void SafeBrowsingService::DoDisplayBlockingPage(
    const UnsafeResource& resource) {
  // The tab might have been closed.
  WebContents* wc =
      tab_util::GetWebContentsByID(resource.render_process_host_id,
                                   resource.render_view_id);

  if (!wc) {
    // The tab is gone and we did not have a chance at showing the interstitial.
    // Just act as "Don't Proceed" was chosen.
    base::Thread* io_thread = g_browser_process->io_thread();
    if (!io_thread)
      return;
    std::vector<UnsafeResource> resources;
    resources.push_back(resource);
    io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
        this, &SafeBrowsingService::OnBlockingPageDone, resources, false));
    return;
  }

  // Report the malware sub-resource to the SafeBrowsing servers if we have a
  // malware sub-resource on a safe page and only if the user has opted in to
  // reporting statistics.
  PrefService* prefs = g_browser_process->local_state();
  DCHECK(prefs);
  if (prefs && prefs->GetBoolean(prefs::kMetricsReportingEnabled) &&
      resource.resource_type != ResourceType::MAIN_FRAME &&
      resource.threat_type == SafeBrowsingService::URL_MALWARE) {
    GURL page_url = wc->GetURL();
    GURL referrer_url;
    if (wc->controller()) {
      NavigationEntry* entry = wc->controller()->GetActiveEntry();
      if (entry)
        referrer_url = entry->referrer();
    }
    io_loop_->PostTask(FROM_HERE,
        NewRunnableMethod(this,
                          &SafeBrowsingService::ReportMalware,
                          resource.url,
                          page_url,
                          referrer_url));
  }

  SafeBrowsingBlockingPage::ShowBlockingPage(this, resource);
}

void SafeBrowsingService::CancelCheck(Client* client) {
  DCHECK(MessageLoop::current() == io_loop_);

  for (CurrentChecks::iterator i = checks_.begin(); i != checks_.end(); ++i) {
    if ((*i)->client == client)
      (*i)->client = NULL;
  }

  // Scan the queued clients store. Clients may be here if they requested a URL
  // check before the database has finished loading or resetting.
  if (!database_loaded_ || resetting_) {
    std::deque<QueuedCheck>::iterator it = queued_checks_.begin();
    for (; it != queued_checks_.end(); ++it) {
      if (it->client == client)
        it->client = NULL;
    }
  }
}

void SafeBrowsingService::CheckDatabase(SafeBrowsingCheck* info,
                                        Time last_update) {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  // If client == NULL it means it was cancelled, no need for db lookup.
  if (info->client && GetDatabase()) {
    Time now = Time::Now();
    std::string list;
    if (GetDatabase()->ContainsUrl(info->url,
                                   &list,
                                   &info->prefix_hits,
                                   &info->full_hits,
                                   last_update)) {
      if (info->prefix_hits.empty()) {
        info->result = GetResultFromListname(list);
      } else {
        if (info->full_hits.empty())
          info->need_get_hash = true;
      }
    }
    info->db_time = Time::Now() - now;
  }

  if (io_loop_)
    io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
        this, &SafeBrowsingService::OnCheckDone, info));
}

void SafeBrowsingService::OnCheckDone(SafeBrowsingCheck* check) {
  DCHECK(MessageLoop::current() == io_loop_);

  // If we've been shutdown during the database lookup, this check will already
  // have been deleted (in OnIOShutdown).
  if (!enabled_ || checks_.find(check) == checks_.end())
    return;

  UMA_HISTOGRAM_TIMES("SB.Database", Time::Now() - check->start);
  if (check->client && check->need_get_hash) {
    // We have a partial match so we need to query Google for the full hash.
    // Clean up will happen in HandleGetHashResults.

    // See if we have a GetHash request already in progress for this particular
    // prefix. If so, we just append ourselves to the list of interested parties
    // when the results arrive. We only do this for checks involving one prefix,
    // since that is the common case (multiple prefixes will issue the request
    // as normal).
    if (check->prefix_hits.size() == 1) {
      SBPrefix prefix = check->prefix_hits[0];
      GetHashRequests::iterator it = gethash_requests_.find(prefix);
      if (it != gethash_requests_.end()) {
        // There's already a request in progress.
        it->second.push_back(check);
        return;
      }

      // No request in progress, so we're the first for this prefix.
      GetHashRequestors requestors;
      requestors.push_back(check);
      gethash_requests_[prefix] = requestors;
    }

    // Reset the start time so that we can measure the network time without the
    // database time.
    check->start = Time::Now();
    protocol_manager_->GetFullHash(check, check->prefix_hits);
  } else {
    // We may have cached results for previous GetHash queries.
    HandleOneCheck(check, check->full_hits);
  }
}

SafeBrowsingDatabase* SafeBrowsingService::GetDatabase() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  if (database_)
    return database_;

  FilePath path;
  bool result = PathService::Get(chrome::DIR_USER_DATA, &path);
  DCHECK(result);
  path = path.Append(chrome::kSafeBrowsingFilename);

  Time before = Time::Now();
  SafeBrowsingDatabase* database = SafeBrowsingDatabase::Create();
  Callback0::Type* chunk_callback =
      NewCallback(this, &SafeBrowsingService::ChunkInserted);
  bool init_success = database->Init(path, chunk_callback);

  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::DatabaseLoadComplete, !init_success));

  if (!init_success) {
    NOTREACHED();
    return NULL;
  }

  database_ = database;

  TimeDelta open_time = Time::Now() - before;
  SB_DLOG(INFO) << "SafeBrowsing database open took " <<
      open_time.InMilliseconds() << " ms.";

  return database_;
}

// Public API called only on the IO thread.
// The SafeBrowsingProtocolManager has received the full hash results for
// prefix hits detected in the database.
void SafeBrowsingService::HandleGetHashResults(
    SafeBrowsingCheck* check,
    const std::vector<SBFullHashResult>& full_hashes,
    bool can_cache) {
  if (checks_.find(check) == checks_.end())
    return;

  DCHECK(enabled_);

  if (new_safe_browsing_)
    UMA_HISTOGRAM_LONG_TIMES("SB2.Network", Time::Now() - check->start);
  else
    UMA_HISTOGRAM_LONG_TIMES("SB.Network", Time::Now() - check->start);

  std::vector<SBPrefix> prefixes = check->prefix_hits;
  OnHandleGetHashResults(check, full_hashes);  // 'check' is deleted here.

  if (can_cache) {
    if (!new_safe_browsing_) {
      db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
          this, &SafeBrowsingService::CacheHashResults, prefixes, full_hashes));
    } else if (database_) {
      // Cache the GetHash results in memory:
      database_->CacheHashResults(prefixes, full_hashes);
    }
  }
}

void SafeBrowsingService::OnHandleGetHashResults(
    SafeBrowsingCheck* check,
    const std::vector<SBFullHashResult>& full_hashes) {
  SBPrefix prefix = check->prefix_hits[0];
  GetHashRequests::iterator it = gethash_requests_.find(prefix);
  if (check->prefix_hits.size() > 1 || it == gethash_requests_.end()) {
    HandleOneCheck(check, full_hashes);
    return;
  }

  // Call back all interested parties.
  GetHashRequestors& requestors = it->second;
  for (GetHashRequestors::iterator r = requestors.begin();
       r != requestors.end(); ++r) {
    HandleOneCheck(*r, full_hashes);
  }

  gethash_requests_.erase(it);
}

void SafeBrowsingService::HandleOneCheck(
    SafeBrowsingCheck* check,
    const std::vector<SBFullHashResult>& full_hashes) {
  if (check->client) {
    UrlCheckResult result = URL_SAFE;
    int index = safe_browsing_util::CompareFullHashes(check->url, full_hashes);
    if (index != -1)
      result = GetResultFromListname(full_hashes[index].list_name);

    // Let the client continue handling the original request.
    check->client->OnUrlCheckResult(check->url, result);
  }

  checks_.erase(check);
  delete check;
}

void SafeBrowsingService::UpdateStarted() {
  DCHECK(MessageLoop::current() == io_loop_);
  DCHECK(enabled_);
  DCHECK(!update_in_progress_);
  update_in_progress_ = true;
  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::GetAllChunksFromDatabase));
}

void SafeBrowsingService::UpdateFinished(bool update_succeeded) {
  DCHECK(MessageLoop::current() == io_loop_);
  DCHECK(enabled_);
  if (update_in_progress_) {
    update_in_progress_ = false;
    db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
        this, &SafeBrowsingService::DatabaseUpdateFinished, update_succeeded));
  }
}

void SafeBrowsingService::DatabaseUpdateFinished(bool update_succeeded) {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  if (GetDatabase())
    GetDatabase()->UpdateFinished(update_succeeded);
}

void SafeBrowsingService::OnBlockingPageDone(
    const std::vector<UnsafeResource>& resources,
    bool proceed) {
  for (std::vector<UnsafeResource>::const_iterator iter = resources.begin();
       iter != resources.end(); ++iter) {
    const UnsafeResource& resource = *iter;
    NotifyClientBlockingComplete(resource.client, proceed);

    if (proceed) {
      // Whitelist this domain and warning type for the given tab.
      WhiteListedEntry entry;
      entry.render_process_host_id = resource.render_process_host_id;
      entry.render_view_id = resource.render_view_id;
      entry.domain = net::RegistryControlledDomainService::GetDomainAndRegistry(
            resource.url);
      entry.result = resource.threat_type;
      white_listed_entries_.push_back(entry);
    }
  }
}

void SafeBrowsingService::NotifyClientBlockingComplete(Client* client,
                                                       bool proceed) {
  client->OnBlockingPageComplete(proceed);
}

// This method runs on the UI loop to access the prefs.
void SafeBrowsingService::OnNewMacKeys(const std::string& client_key,
                                       const std::string& wrapped_key) {
  PrefService* prefs = g_browser_process->local_state();
  if (prefs) {
    prefs->SetString(prefs::kSafeBrowsingClientKey, ASCIIToWide(client_key));
    prefs->SetString(prefs::kSafeBrowsingWrappedKey, ASCIIToWide(wrapped_key));
  }
}

void SafeBrowsingService::ChunkInserted() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnChunkInserted));
}

void SafeBrowsingService::OnChunkInserted() {
  DCHECK(MessageLoop::current() == io_loop_);
  if (enabled_)
    protocol_manager_->OnChunkInserted();
}

void SafeBrowsingService::DatabaseLoadComplete(bool database_error) {
  DCHECK(MessageLoop::current() == io_loop_);
  if (!enabled_)
    return;

  database_loaded_ = true;

  // TODO(paulg): More robust database initialization error handling.
  if (protocol_manager_ && !database_error)
    protocol_manager_->Initialize();

  // If we have any queued requests, we can now check them.
  if (!resetting_)
    RunQueuedClients();
}

// static
void SafeBrowsingService::RegisterPrefs(PrefService* prefs) {
  prefs->RegisterStringPref(prefs::kSafeBrowsingClientKey, L"");
  prefs->RegisterStringPref(prefs::kSafeBrowsingWrappedKey, L"");
}

void SafeBrowsingService::ResetDatabase() {
  DCHECK(MessageLoop::current() == io_loop_);
  resetting_ = true;
  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnResetDatabase));
}

void SafeBrowsingService::OnResetDatabase() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  GetDatabase()->ResetDatabase();
  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnResetComplete));
}

void SafeBrowsingService::OnResetComplete() {
  DCHECK(MessageLoop::current() == io_loop_);
  if (enabled_) {
    resetting_ = false;
    database_loaded_ = true;
    RunQueuedClients();
  }
}

void SafeBrowsingService::HandleChunk(const std::string& list,
                                      std::deque<SBChunk>* chunks) {
  DCHECK(MessageLoop::current() == io_loop_);
  DCHECK(enabled_);
  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::HandleChunkForDatabase, list, chunks));
}

void SafeBrowsingService::HandleChunkForDatabase(
    const std::string& list_name,
    std::deque<SBChunk>* chunks) {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());

  GetDatabase()->InsertChunks(list_name, chunks);
}

void SafeBrowsingService::HandleChunkDelete(
    std::vector<SBChunkDelete>* chunk_deletes) {
  DCHECK(MessageLoop::current() == io_loop_);
  DCHECK(enabled_);
  db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::DeleteChunks, chunk_deletes));
}

void SafeBrowsingService::DeleteChunks(
    std::vector<SBChunkDelete>* chunk_deletes) {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());

  GetDatabase()->DeleteChunks(chunk_deletes);
}

// Database worker function.
void SafeBrowsingService::GetAllChunksFromDatabase() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  bool database_error = true;
  std::vector<SBListChunkRanges> lists;
  if (GetDatabase()) {
    if (GetDatabase()->UpdateStarted()) {
      GetDatabase()->GetListsInfo(&lists);
      database_error = false;
    } else {
      GetDatabase()->UpdateFinished(false);
    }
  }

  io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
      this, &SafeBrowsingService::OnGetAllChunksFromDatabase, lists,
      database_error));
}

// Called on the io thread with the results of all chunks.
void SafeBrowsingService::OnGetAllChunksFromDatabase(
    const std::vector<SBListChunkRanges>& lists, bool database_error) {
  DCHECK(MessageLoop::current() == io_loop_);
  if (enabled_)
    protocol_manager_->OnGetChunksComplete(lists, database_error);
}

SafeBrowsingService::UrlCheckResult SafeBrowsingService::GetResultFromListname(
    const std::string& list_name) {
  if (safe_browsing_util::IsPhishingList(list_name)) {
    return URL_PHISHING;
  }

  if (safe_browsing_util::IsMalwareList(list_name)) {
    return URL_MALWARE;
  }

  SB_DLOG(INFO) << "Unknown safe browsing list " << list_name;
  return URL_SAFE;
}

void SafeBrowsingService::LogPauseDelay(TimeDelta time) {
  if (new_safe_browsing_)
    UMA_HISTOGRAM_LONG_TIMES("SB2.Delay", time);
  else
    UMA_HISTOGRAM_LONG_TIMES("SB.Delay", time);
}

void SafeBrowsingService::CacheHashResults(
  const std::vector<SBPrefix>& prefixes,
  const std::vector<SBFullHashResult>& full_hashes) {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  GetDatabase()->CacheHashResults(prefixes, full_hashes);
}

void SafeBrowsingService::OnSuspend(base::SystemMonitor*) {
}

// Tell the SafeBrowsing database not to do expensive disk operations for a few
// minutes after waking up. It's quite likely that the act of resuming from a
// low power state will involve much disk activity, which we don't want to
// exacerbate.
void SafeBrowsingService::OnResume(base::SystemMonitor*) {
  if (enabled_) {
    ChromeThread::GetMessageLoop(ChromeThread::DB)->PostTask(FROM_HERE,
      NewRunnableMethod(this, &SafeBrowsingService::HandleResume));
  }
}

void SafeBrowsingService::HandleResume() {
  DCHECK(MessageLoop::current() == db_thread_->message_loop());
  // We don't call GetDatabase() here, since we want to avoid unnecessary calls
  // to Open, Reset, etc, or reload the bloom filter while we're coming out of
  // a suspended state.
  if (database_)
    database_->HandleResume();
}

void SafeBrowsingService::RunQueuedClients() {
  DCHECK(MessageLoop::current() == io_loop_);
  HISTOGRAM_COUNTS("SB.QueueDepth", queued_checks_.size());
  while (!queued_checks_.empty()) {
    QueuedCheck check = queued_checks_.front();
    HISTOGRAM_TIMES("SB.QueueDelay", Time::Now() - check.start);
    CheckUrl(check.url, check.client);
    queued_checks_.pop_front();
  }
}

void SafeBrowsingService::ReportMalware(const GURL& malware_url,
                                        const GURL& page_url,
                                        const GURL& referrer_url) {
  DCHECK(MessageLoop::current() == io_loop_);

  // We need to access the database cache on the io_loop_ which is only allowed
  // in the new SafeBrowsing database system.
  if (!new_safe_browsing_ || !enabled_ || !database_)
    return;

  // Check if 'page_url' is already blacklisted (exists in our cache). Only
  // report if it's not there.
  std::string list;
  std::vector<SBPrefix> prefix_hits;
  std::vector<SBFullHashResult> full_hits;
  database_->ContainsUrl(page_url, &list, &prefix_hits, &full_hits,
                         protocol_manager_->last_update());

  if (full_hits.empty())
    protocol_manager_->ReportMalware(malware_url, page_url, referrer_url);
}