1// Copyright 2014 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 "components/suggestions/blacklist_store.h"
6
7#include <set>
8#include <string>
9
10#include "base/base64.h"
11#include "base/metrics/histogram.h"
12#include "base/prefs/pref_service.h"
13#include "components/pref_registry/pref_registry_syncable.h"
14#include "components/suggestions/suggestions_pref_names.h"
15
16namespace suggestions {
17
18namespace {
19
20void PopulateBlacklistSet(const SuggestionsBlacklist& blacklist_proto,
21                          std::set<std::string>* blacklist_set) {
22  blacklist_set->clear();
23  for (int i = 0; i < blacklist_proto.urls_size(); ++i) {
24    blacklist_set->insert(blacklist_proto.urls(i));
25  }
26}
27
28void PopulateBlacklistProto(const std::set<std::string>& blacklist_set,
29                            SuggestionsBlacklist* blacklist_proto) {
30  blacklist_proto->Clear();
31  for (std::set<std::string>::const_iterator it = blacklist_set.begin();
32       it != blacklist_set.end(); ++it) {
33    blacklist_proto->add_urls(*it);
34  }
35}
36
37}  // namespace
38
39BlacklistStore::BlacklistStore(PrefService* profile_prefs)
40    : pref_service_(profile_prefs) {
41  DCHECK(pref_service_);
42
43  // Log the blacklist's size. A single BlacklistStore is created for the
44  // SuggestionsService; this will run once.
45  SuggestionsBlacklist blacklist_proto;
46  LoadBlacklist(&blacklist_proto);
47  UMA_HISTOGRAM_COUNTS_10000("Suggestions.LocalBlacklistSize",
48                             blacklist_proto.urls_size());
49}
50
51BlacklistStore::~BlacklistStore() {}
52
53bool BlacklistStore::BlacklistUrl(const GURL& url) {
54  if (!url.is_valid()) return false;
55
56  SuggestionsBlacklist blacklist_proto;
57  LoadBlacklist(&blacklist_proto);
58
59  std::set<std::string> blacklist_set;
60  PopulateBlacklistSet(blacklist_proto, &blacklist_set);
61
62  if (!blacklist_set.insert(url.spec()).second) {
63    // |url| was already in the blacklist.
64    return true;
65  }
66
67  PopulateBlacklistProto(blacklist_set, &blacklist_proto);
68  return StoreBlacklist(blacklist_proto);
69}
70
71bool BlacklistStore::GetFirstUrlFromBlacklist(GURL* url) {
72  SuggestionsBlacklist blacklist;
73  LoadBlacklist(&blacklist);
74  if (!blacklist.urls_size()) return false;
75  GURL blacklisted(blacklist.urls(0));
76  url->Swap(&blacklisted);
77  return true;
78}
79
80bool BlacklistStore::RemoveUrl(const GURL& url) {
81  if (!url.is_valid()) return false;
82  const std::string removal_candidate = url.spec();
83
84  SuggestionsBlacklist blacklist;
85  LoadBlacklist(&blacklist);
86
87  SuggestionsBlacklist updated_blacklist;
88  for (int i = 0; i < blacklist.urls_size(); ++i) {
89    if (blacklist.urls(i) != removal_candidate)
90      updated_blacklist.add_urls(blacklist.urls(i));
91  }
92
93  return StoreBlacklist(updated_blacklist);
94}
95
96void BlacklistStore::FilterSuggestions(SuggestionsProfile* profile) {
97  if (!profile->suggestions_size())
98    return;  // Empty profile, nothing to filter.
99
100  SuggestionsBlacklist blacklist_proto;
101  if (!LoadBlacklist(&blacklist_proto)) {
102    // There was an error loading the blacklist. The blacklist was cleared and
103    // there's nothing to be done about it.
104    return;
105  }
106  if (!blacklist_proto.urls_size())
107    return;  // Empty blacklist, nothing to filter.
108
109  std::set<std::string> blacklist_set;
110  PopulateBlacklistSet(blacklist_proto, &blacklist_set);
111
112  // Populate the filtered suggestions.
113  SuggestionsProfile filtered_profile;
114  for (int i = 0; i < profile->suggestions_size(); ++i) {
115    if (blacklist_set.find(profile->suggestions(i).url()) ==
116        blacklist_set.end()) {
117      // This suggestion is not blacklisted.
118      ChromeSuggestion* suggestion = filtered_profile.add_suggestions();
119      // Note: swapping!
120      suggestion->Swap(profile->mutable_suggestions(i));
121    }
122  }
123
124  // Swap |profile| and |filtered_profile|.
125  profile->Swap(&filtered_profile);
126}
127
128// static
129void BlacklistStore::RegisterProfilePrefs(
130    user_prefs::PrefRegistrySyncable* registry) {
131  registry->RegisterStringPref(
132      prefs::kSuggestionsBlacklist, std::string(),
133      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
134}
135
136bool BlacklistStore::LoadBlacklist(SuggestionsBlacklist* blacklist) {
137  DCHECK(blacklist);
138
139  const std::string base64_blacklist_data =
140      pref_service_->GetString(prefs::kSuggestionsBlacklist);
141  if (base64_blacklist_data.empty()) {
142    blacklist->Clear();
143    return false;
144  }
145
146  // If the decode process fails, assume the pref value is corrupt and clear it.
147  std::string blacklist_data;
148  if (!base::Base64Decode(base64_blacklist_data, &blacklist_data) ||
149      !blacklist->ParseFromString(blacklist_data)) {
150    VLOG(1) << "Suggestions blacklist data in profile pref is corrupt, "
151            << " clearing it.";
152    blacklist->Clear();
153    ClearBlacklist();
154    return false;
155  }
156
157  return true;
158}
159
160bool BlacklistStore::StoreBlacklist(const SuggestionsBlacklist& blacklist) {
161  std::string blacklist_data;
162  if (!blacklist.SerializeToString(&blacklist_data)) return false;
163
164  std::string base64_blacklist_data;
165  base::Base64Encode(blacklist_data, &base64_blacklist_data);
166
167  pref_service_->SetString(prefs::kSuggestionsBlacklist, base64_blacklist_data);
168  return true;
169}
170
171void BlacklistStore::ClearBlacklist() {
172  pref_service_->ClearPref(prefs::kSuggestionsBlacklist);
173}
174
175}  // namespace suggestions
176