普通文本  |  353行  |  12.12 KB

// Copyright 2012 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/extensions/blacklist.h"

#include <algorithm>
#include <iterator>

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/ref_counted.h"
#include "base/prefs/pref_service.h"
#include "base/stl_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/blacklist_state_fetcher.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/safe_browsing_util.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "extensions/browser/extension_prefs.h"

using content::BrowserThread;

namespace extensions {

namespace {

// The safe browsing database manager to use. Make this a global/static variable
// rather than a member of Blacklist because Blacklist accesses the real
// database manager before it has a chance to get a fake one.
class LazySafeBrowsingDatabaseManager {
 public:
  LazySafeBrowsingDatabaseManager() {
#if defined(FULL_SAFE_BROWSING) || defined(MOBILE_SAFE_BROWSING)
    if (g_browser_process && g_browser_process->safe_browsing_service()) {
      instance_ =
          g_browser_process->safe_browsing_service()->database_manager();
    }
#endif
  }

  scoped_refptr<SafeBrowsingDatabaseManager> get() {
    return instance_;
  }

  void set(scoped_refptr<SafeBrowsingDatabaseManager> instance) {
    instance_ = instance;
  }

 private:
  scoped_refptr<SafeBrowsingDatabaseManager> instance_;
};

static base::LazyInstance<LazySafeBrowsingDatabaseManager> g_database_manager =
    LAZY_INSTANCE_INITIALIZER;

// Implementation of SafeBrowsingDatabaseManager::Client, the class which is
// called back from safebrowsing queries.
//
// Constructed on any thread but lives on the IO from then on.
class SafeBrowsingClientImpl
    : public SafeBrowsingDatabaseManager::Client,
      public base::RefCountedThreadSafe<SafeBrowsingClientImpl> {
 public:
  typedef base::Callback<void(const std::set<std::string>&)> OnResultCallback;

  // Constructs a client to query the database manager for |extension_ids| and
  // run |callback| with the IDs of those which have been blacklisted.
  SafeBrowsingClientImpl(
      const std::set<std::string>& extension_ids,
      const OnResultCallback& callback)
      : callback_message_loop_(base::MessageLoopProxy::current()),
        callback_(callback) {
    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(&SafeBrowsingClientImpl::StartCheck, this,
                   g_database_manager.Get().get(),
                   extension_ids));
  }

 private:
  friend class base::RefCountedThreadSafe<SafeBrowsingClientImpl>;
  virtual ~SafeBrowsingClientImpl() {}

  // Pass |database_manager| as a parameter to avoid touching
  // SafeBrowsingService on the IO thread.
  void StartCheck(scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
                  const std::set<std::string>& extension_ids) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    if (database_manager->CheckExtensionIDs(extension_ids, this)) {
      // Definitely not blacklisted. Callback immediately.
      callback_message_loop_->PostTask(
          FROM_HERE,
          base::Bind(callback_, std::set<std::string>()));
      return;
    }
    // Something might be blacklisted, response will come in
    // OnCheckExtensionsResult.
    AddRef();  // Balanced in OnCheckExtensionsResult
  }

  virtual void OnCheckExtensionsResult(
      const std::set<std::string>& hits) OVERRIDE {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    callback_message_loop_->PostTask(FROM_HERE, base::Bind(callback_, hits));
    Release();  // Balanced in StartCheck.
  }

  scoped_refptr<base::MessageLoopProxy> callback_message_loop_;
  OnResultCallback callback_;

  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingClientImpl);
};

void CheckOneExtensionState(
    const Blacklist::IsBlacklistedCallback& callback,
    const Blacklist::BlacklistStateMap& state_map) {
  callback.Run(state_map.empty() ? NOT_BLACKLISTED : state_map.begin()->second);
}

void GetMalwareFromBlacklistStateMap(
    const Blacklist::GetMalwareIDsCallback& callback,
    const Blacklist::BlacklistStateMap& state_map) {
  std::set<std::string> malware;
  for (Blacklist::BlacklistStateMap::const_iterator it = state_map.begin();
       it != state_map.end(); ++it) {
    // TODO(oleg): UNKNOWN is treated as MALWARE for backwards compatibility.
    // In future GetMalwareIDs will be removed and the caller will have to
    // deal with BLACKLISTED_UNKNOWN state returned from GetBlacklistedIDs.
    if (it->second == BLACKLISTED_MALWARE || it->second == BLACKLISTED_UNKNOWN)
      malware.insert(it->first);
  }
  callback.Run(malware);
}

}  // namespace

Blacklist::Observer::Observer(Blacklist* blacklist) : blacklist_(blacklist) {
  blacklist_->AddObserver(this);
}

Blacklist::Observer::~Observer() {
  blacklist_->RemoveObserver(this);
}

Blacklist::ScopedDatabaseManagerForTest::ScopedDatabaseManagerForTest(
    scoped_refptr<SafeBrowsingDatabaseManager> database_manager)
    : original_(GetDatabaseManager()) {
  SetDatabaseManager(database_manager);
}

Blacklist::ScopedDatabaseManagerForTest::~ScopedDatabaseManagerForTest() {
  SetDatabaseManager(original_);
}

Blacklist::Blacklist(ExtensionPrefs* prefs) {
  scoped_refptr<SafeBrowsingDatabaseManager> database_manager =
      g_database_manager.Get().get();
  if (database_manager) {
    registrar_.Add(
        this,
        chrome::NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE,
        content::Source<SafeBrowsingDatabaseManager>(database_manager.get()));
  }

  // Clear out the old prefs-backed blacklist, stored as empty extension entries
  // with just a "blacklisted" property.
  //
  // TODO(kalman): Delete this block of code, see http://crbug.com/295882.
  std::set<std::string> blacklisted = prefs->GetBlacklistedExtensions();
  for (std::set<std::string>::iterator it = blacklisted.begin();
       it != blacklisted.end(); ++it) {
    if (!prefs->GetInstalledExtensionInfo(*it))
      prefs->DeleteExtensionPrefs(*it);
  }
}

Blacklist::~Blacklist() {
}

void Blacklist::GetBlacklistedIDs(const std::set<std::string>& ids,
                                  const GetBlacklistedIDsCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (ids.empty() || !g_database_manager.Get().get().get()) {
    base::MessageLoopProxy::current()->PostTask(
        FROM_HERE, base::Bind(callback, BlacklistStateMap()));
    return;
  }

  // Constructing the SafeBrowsingClientImpl begins the process of asking
  // safebrowsing for the blacklisted extensions. The set of blacklisted
  // extensions returned by SafeBrowsing will then be passed to
  // GetBlacklistStateIDs to get the particular BlacklistState for each id.
  new SafeBrowsingClientImpl(
      ids, base::Bind(&Blacklist::GetBlacklistStateForIDs, AsWeakPtr(),
                      callback));
}

void Blacklist::GetMalwareIDs(const std::set<std::string>& ids,
                              const GetMalwareIDsCallback& callback) {
  GetBlacklistedIDs(ids, base::Bind(&GetMalwareFromBlacklistStateMap,
                                    callback));
}


void Blacklist::IsBlacklisted(const std::string& extension_id,
                              const IsBlacklistedCallback& callback) {
  std::set<std::string> check;
  check.insert(extension_id);
  GetBlacklistedIDs(check, base::Bind(&CheckOneExtensionState, callback));
}

void Blacklist::GetBlacklistStateForIDs(
    const GetBlacklistedIDsCallback& callback,
    const std::set<std::string>& blacklisted_ids) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  std::set<std::string> ids_unknown_state;
  BlacklistStateMap extensions_state;
  for (std::set<std::string>::const_iterator it = blacklisted_ids.begin();
       it != blacklisted_ids.end(); ++it) {
    BlacklistStateMap::const_iterator cache_it =
        blacklist_state_cache_.find(*it);
    if (cache_it == blacklist_state_cache_.end() ||
        cache_it->second == BLACKLISTED_UNKNOWN)  // Do not return UNKNOWN
                                                  // from cache, retry request.
      ids_unknown_state.insert(*it);
    else
      extensions_state[*it] = cache_it->second;
  }

  if (ids_unknown_state.empty()) {
    callback.Run(extensions_state);
  } else {
    // After the extension blacklist states have been downloaded, call this
    // functions again, but prevent infinite cycle in case server is offline
    // or some other reason prevents us from receiving the blacklist state for
    // these extensions.
    RequestExtensionsBlacklistState(
        ids_unknown_state,
        base::Bind(&Blacklist::ReturnBlacklistStateMap, AsWeakPtr(),
                   callback, blacklisted_ids));
  }
}

void Blacklist::ReturnBlacklistStateMap(
    const GetBlacklistedIDsCallback& callback,
    const std::set<std::string>& blacklisted_ids) {
  BlacklistStateMap extensions_state;
  for (std::set<std::string>::const_iterator it = blacklisted_ids.begin();
       it != blacklisted_ids.end(); ++it) {
    BlacklistStateMap::const_iterator cache_it =
        blacklist_state_cache_.find(*it);
    if (cache_it != blacklist_state_cache_.end())
      extensions_state[*it] = cache_it->second;
    // If for some reason we still haven't cached the state of this extension,
    // we silently skip it.
  }

  callback.Run(extensions_state);
}

void Blacklist::RequestExtensionsBlacklistState(
    const std::set<std::string>& ids, const base::Callback<void()>& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!state_fetcher_)
    state_fetcher_.reset(new BlacklistStateFetcher());

  state_requests_.push_back(
      make_pair(std::vector<std::string>(ids.begin(), ids.end()), callback));
  for (std::set<std::string>::const_iterator it = ids.begin();
       it != ids.end();
       ++it) {
    state_fetcher_->Request(
        *it,
        base::Bind(&Blacklist::OnBlacklistStateReceived, AsWeakPtr(), *it));
  }
}

void Blacklist::OnBlacklistStateReceived(const std::string& id,
                                         BlacklistState state) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  blacklist_state_cache_[id] = state;

  // Go through the opened requests and call the callbacks for those requests
  // for which we already got all the required blacklist states.
  StateRequestsList::iterator requests_it = state_requests_.begin();
  while (requests_it != state_requests_.end()) {
    const std::vector<std::string>& ids = requests_it->first;

    bool have_all_in_cache = true;
    for (std::vector<std::string>::const_iterator ids_it = ids.begin();
         ids_it != ids.end();
         ++ids_it) {
      if (!ContainsKey(blacklist_state_cache_, *ids_it)) {
        have_all_in_cache = false;
        break;
      }
    }

    if (have_all_in_cache) {
      requests_it->second.Run();
      requests_it = state_requests_.erase(requests_it); // returns next element
    } else {
      ++requests_it;
    }
  }
}

void Blacklist::SetBlacklistStateFetcherForTest(
    BlacklistStateFetcher* fetcher) {
  state_fetcher_.reset(fetcher);
}

BlacklistStateFetcher* Blacklist::ResetBlacklistStateFetcherForTest() {
  return state_fetcher_.release();
}

void Blacklist::AddObserver(Observer* observer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  observers_.AddObserver(observer);
}

void Blacklist::RemoveObserver(Observer* observer) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  observers_.RemoveObserver(observer);
}

// static
void Blacklist::SetDatabaseManager(
    scoped_refptr<SafeBrowsingDatabaseManager> database_manager) {
  g_database_manager.Get().set(database_manager);
}

// static
scoped_refptr<SafeBrowsingDatabaseManager> Blacklist::GetDatabaseManager() {
  return g_database_manager.Get().get();
}

void Blacklist::Observe(int type,
                        const content::NotificationSource& source,
                        const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE, type);
  FOR_EACH_OBSERVER(Observer, observers_, OnBlacklistUpdated());
}

}  // namespace extensions