1// Copyright 2012 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/extensions/blacklist.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/lazy_instance.h"
11#include "base/memory/ref_counted.h"
12#include "base/prefs/pref_service.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/extensions/extension_prefs.h"
16#include "chrome/browser/safe_browsing/safe_browsing_service.h"
17#include "chrome/browser/safe_browsing/safe_browsing_util.h"
18#include "chrome/common/pref_names.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
21
22using content::BrowserThread;
23
24namespace extensions {
25
26namespace {
27
28// The safe browsing database manager to use. Make this a global/static variable
29// rather than a member of Blacklist because Blacklist accesses the real
30// database manager before it has a chance to get a fake one.
31class LazySafeBrowsingDatabaseManager {
32 public:
33  LazySafeBrowsingDatabaseManager() {
34#if defined(FULL_SAFE_BROWSING) || defined(MOBILE_SAFE_BROWSING)
35    if (g_browser_process && g_browser_process->safe_browsing_service()) {
36      instance_ =
37          g_browser_process->safe_browsing_service()->database_manager();
38    }
39#endif
40  }
41
42  scoped_refptr<SafeBrowsingDatabaseManager> get() {
43    return instance_;
44  }
45
46  void set(scoped_refptr<SafeBrowsingDatabaseManager> instance) {
47    instance_ = instance;
48  }
49
50 private:
51  scoped_refptr<SafeBrowsingDatabaseManager> instance_;
52};
53
54static base::LazyInstance<LazySafeBrowsingDatabaseManager> g_database_manager =
55    LAZY_INSTANCE_INITIALIZER;
56
57// Implementation of SafeBrowsingDatabaseManager::Client, the class which is
58// called back from safebrowsing queries.
59//
60// Constructed on any thread but lives on the IO from then on.
61class SafeBrowsingClientImpl
62    : public SafeBrowsingDatabaseManager::Client,
63      public base::RefCountedThreadSafe<SafeBrowsingClientImpl> {
64 public:
65  typedef base::Callback<void(const std::set<std::string>&)> OnResultCallback;
66
67  // Constructs a client to query the database manager for |extension_ids| and
68  // run |callback| with the IDs of those which have been blacklisted.
69  SafeBrowsingClientImpl(
70      const std::set<std::string>& extension_ids,
71      const OnResultCallback& callback)
72      : callback_message_loop_(base::MessageLoopProxy::current()),
73        callback_(callback) {
74    BrowserThread::PostTask(
75        BrowserThread::IO,
76        FROM_HERE,
77        base::Bind(&SafeBrowsingClientImpl::StartCheck, this,
78                   g_database_manager.Get().get(),
79                   extension_ids));
80  }
81
82 private:
83  friend class base::RefCountedThreadSafe<SafeBrowsingClientImpl>;
84  virtual ~SafeBrowsingClientImpl() {}
85
86  // Pass |database_manager| as a parameter to avoid touching
87  // SafeBrowsingService on the IO thread.
88  void StartCheck(scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
89                  const std::set<std::string>& extension_ids) {
90    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
91    if (database_manager->CheckExtensionIDs(extension_ids, this)) {
92      // Definitely not blacklisted. Callback immediately.
93      callback_message_loop_->PostTask(
94          FROM_HERE,
95          base::Bind(callback_, std::set<std::string>()));
96      return;
97    }
98    // Something might be blacklisted, response will come in
99    // OnCheckExtensionsResult.
100    AddRef();  // Balanced in OnCheckExtensionsResult
101  }
102
103  virtual void OnCheckExtensionsResult(
104      const std::set<std::string>& hits) OVERRIDE {
105    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
106    callback_message_loop_->PostTask(FROM_HERE, base::Bind(callback_, hits));
107    Release();  // Balanced in StartCheck.
108  }
109
110  scoped_refptr<base::MessageLoopProxy> callback_message_loop_;
111  OnResultCallback callback_;
112
113  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingClientImpl);
114};
115
116void IsNotEmpty(const Blacklist::IsBlacklistedCallback& callback,
117                const std::set<std::string>& set) {
118  callback.Run(set.empty() ? Blacklist::NOT_BLACKLISTED
119                           : Blacklist::BLACKLISTED);
120}
121
122}  // namespace
123
124Blacklist::Observer::Observer(Blacklist* blacklist) : blacklist_(blacklist) {
125  blacklist_->AddObserver(this);
126}
127
128Blacklist::Observer::~Observer() {
129  blacklist_->RemoveObserver(this);
130}
131
132Blacklist::ScopedDatabaseManagerForTest::ScopedDatabaseManagerForTest(
133    scoped_refptr<SafeBrowsingDatabaseManager> database_manager)
134    : original_(GetDatabaseManager()) {
135  SetDatabaseManager(database_manager);
136}
137
138Blacklist::ScopedDatabaseManagerForTest::~ScopedDatabaseManagerForTest() {
139  SetDatabaseManager(original_);
140}
141
142Blacklist::Blacklist(ExtensionPrefs* prefs) : prefs_(prefs) {
143  scoped_refptr<SafeBrowsingDatabaseManager> database_manager =
144      g_database_manager.Get().get();
145  if (database_manager.get()) {
146    registrar_.Add(
147        this,
148        chrome::NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE,
149        content::Source<SafeBrowsingDatabaseManager>(database_manager.get()));
150  }
151
152  // TODO(kalman): Delete anything from the pref blacklist that is in the
153  // safebrowsing blacklist (of course, only entries for which the extension
154  // hasn't been installed).
155  //
156  // Or maybe just wait until we're able to delete the pref blacklist
157  // altogether (when we're sure it's a strict subset of the safebrowsing one).
158}
159
160Blacklist::~Blacklist() {
161}
162
163void Blacklist::GetBlacklistedIDs(const std::set<std::string>& ids,
164                                  const GetBlacklistedIDsCallback& callback) {
165  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166
167  if (ids.empty()) {
168    base::MessageLoopProxy::current()->PostTask(
169        FROM_HERE,
170        base::Bind(callback, std::set<std::string>()));
171    return;
172  }
173
174  // The blacklisted IDs are the union of those blacklisted in prefs and
175  // those blacklisted from safe browsing.
176  std::set<std::string> pref_blacklisted_ids;
177  for (std::set<std::string>::const_iterator it = ids.begin();
178       it != ids.end(); ++it) {
179    if (prefs_->IsExtensionBlacklisted(*it))
180      pref_blacklisted_ids.insert(*it);
181  }
182
183  if (!g_database_manager.Get().get().get()) {
184    base::MessageLoopProxy::current()->PostTask(
185        FROM_HERE, base::Bind(callback, pref_blacklisted_ids));
186    return;
187  }
188
189  // Constructing the SafeBrowsingClientImpl begins the process of asking
190  // safebrowsing for the blacklisted extensions.
191  new SafeBrowsingClientImpl(
192      ids,
193      base::Bind(&Blacklist::OnSafeBrowsingResponse, AsWeakPtr(),
194                 pref_blacklisted_ids,
195                 callback));
196}
197
198void Blacklist::IsBlacklisted(const std::string& extension_id,
199                              const IsBlacklistedCallback& callback) {
200  std::set<std::string> check;
201  check.insert(extension_id);
202  GetBlacklistedIDs(check, base::Bind(&IsNotEmpty, callback));
203}
204
205void Blacklist::SetFromUpdater(const std::vector<std::string>& ids,
206                               const std::string& version) {
207  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208
209  std::set<std::string> ids_as_set;
210  for (std::vector<std::string>::const_iterator it = ids.begin();
211       it != ids.end(); ++it) {
212    if (Extension::IdIsValid(*it))
213      ids_as_set.insert(*it);
214    else
215      LOG(WARNING) << "Got invalid extension ID \"" << *it << "\"";
216  }
217
218  std::set<std::string> from_prefs = prefs_->GetBlacklistedExtensions();
219
220  std::set<std::string> no_longer_blacklisted;
221  std::set_difference(from_prefs.begin(), from_prefs.end(),
222                      ids_as_set.begin(), ids_as_set.end(),
223                      std::inserter(no_longer_blacklisted,
224                                    no_longer_blacklisted.begin()));
225  std::set<std::string> not_yet_blacklisted;
226  std::set_difference(ids_as_set.begin(), ids_as_set.end(),
227                      from_prefs.begin(), from_prefs.end(),
228                      std::inserter(not_yet_blacklisted,
229                                    not_yet_blacklisted.begin()));
230
231  for (std::set<std::string>::iterator it = no_longer_blacklisted.begin();
232       it != no_longer_blacklisted.end(); ++it) {
233    prefs_->SetExtensionBlacklisted(*it, false);
234  }
235  for (std::set<std::string>::iterator it = not_yet_blacklisted.begin();
236       it != not_yet_blacklisted.end(); ++it) {
237    prefs_->SetExtensionBlacklisted(*it, true);
238  }
239
240  prefs_->pref_service()->SetString(prefs::kExtensionBlacklistUpdateVersion,
241                                    version);
242
243  FOR_EACH_OBSERVER(Observer, observers_, OnBlacklistUpdated());
244}
245
246void Blacklist::AddObserver(Observer* observer) {
247  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
248  observers_.AddObserver(observer);
249}
250
251void Blacklist::RemoveObserver(Observer* observer) {
252  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
253  observers_.RemoveObserver(observer);
254}
255
256// static
257void Blacklist::SetDatabaseManager(
258    scoped_refptr<SafeBrowsingDatabaseManager> database_manager) {
259  g_database_manager.Get().set(database_manager);
260}
261
262// static
263scoped_refptr<SafeBrowsingDatabaseManager> Blacklist::GetDatabaseManager() {
264  return g_database_manager.Get().get();
265}
266
267void Blacklist::OnSafeBrowsingResponse(
268    const std::set<std::string>& pref_blacklisted_ids,
269    const GetBlacklistedIDsCallback& callback,
270    const std::set<std::string>& safebrowsing_blacklisted_ids) {
271  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
272
273  std::set<std::string> blacklist = pref_blacklisted_ids;
274  blacklist.insert(safebrowsing_blacklisted_ids.begin(),
275                   safebrowsing_blacklisted_ids.end());
276
277  callback.Run(blacklist);
278}
279
280void Blacklist::Observe(int type,
281                        const content::NotificationSource& source,
282                        const content::NotificationDetails& details) {
283  DCHECK_EQ(chrome::NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE, type);
284  FOR_EACH_OBSERVER(Observer, observers_, OnBlacklistUpdated());
285}
286
287}  // namespace extensions
288