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.
5 #include "components/suggestions/blacklist_store.h"
11 #include "base/base64.h"
12 #include "base/metrics/histogram.h"
13 #include "base/prefs/pref_service.h"
14 #include "components/pref_registry/pref_registry_syncable.h"
15 #include "components/suggestions/suggestions_pref_names.h"
17 using base::TimeDelta
;
18 using base::TimeTicks
;
20 namespace suggestions
{
23 void PopulateBlacklistSet(const SuggestionsBlacklist
& blacklist_proto
,
24 std::set
<std::string
>* blacklist_set
) {
25 blacklist_set
->clear();
26 for (int i
= 0; i
< blacklist_proto
.urls_size(); ++i
) {
27 blacklist_set
->insert(blacklist_proto
.urls(i
));
31 void PopulateBlacklistProto(const std::set
<std::string
>& blacklist_set
,
32 SuggestionsBlacklist
* blacklist_proto
) {
33 blacklist_proto
->Clear();
34 for (std::set
<std::string
>::const_iterator it
= blacklist_set
.begin();
35 it
!= blacklist_set
.end(); ++it
) {
36 blacklist_proto
->add_urls(*it
);
42 BlacklistStore::BlacklistStore(PrefService
* profile_prefs
,
43 const base::TimeDelta
& upload_delay
)
44 : pref_service_(profile_prefs
), upload_delay_(upload_delay
) {
45 DCHECK(pref_service_
);
47 // Log the blacklist's size. A single BlacklistStore is created for the
48 // SuggestionsService; this will run once.
49 SuggestionsBlacklist blacklist_proto
;
50 LoadBlacklist(&blacklist_proto
);
51 UMA_HISTOGRAM_COUNTS_10000("Suggestions.LocalBlacklistSize",
52 blacklist_proto
.urls_size());
55 BlacklistStore::~BlacklistStore() {}
57 bool BlacklistStore::BlacklistUrl(const GURL
& url
) {
58 if (!url
.is_valid()) return false;
60 SuggestionsBlacklist blacklist_proto
;
61 LoadBlacklist(&blacklist_proto
);
62 std::set
<std::string
> blacklist_set
;
63 PopulateBlacklistSet(blacklist_proto
, &blacklist_set
);
66 if (blacklist_set
.insert(url
.spec()).second
) {
67 PopulateBlacklistProto(blacklist_set
, &blacklist_proto
);
68 success
= StoreBlacklist(blacklist_proto
);
70 // |url| was already in the blacklist.
75 // Update the blacklist time.
76 blacklist_times_
[url
.spec()] = TimeTicks::Now();
82 bool BlacklistStore::GetTimeUntilReadyForUpload(TimeDelta
* delta
) {
83 SuggestionsBlacklist blacklist
;
84 LoadBlacklist(&blacklist
);
85 if (!blacklist
.urls_size())
88 // Note: the size is non-negative.
89 if (blacklist_times_
.size() < static_cast<size_t>(blacklist
.urls_size())) {
90 // A url is not in the timestamp map: it's candidate for upload. This can
91 // happen after a restart. Another (undesired) case when this could happen
92 // is if more than one instance were created.
93 *delta
= TimeDelta::FromSeconds(0);
97 // Find the minimum blacklist time. Note: blacklist_times_ is NOT empty since
98 // blacklist is non-empty and blacklist_times_ contains as many items.
99 TimeDelta min_delay
= TimeDelta::Max();
100 for (const auto& kv
: blacklist_times_
) {
101 min_delay
= std::min(upload_delay_
- (TimeTicks::Now() - kv
.second
),
104 DCHECK(min_delay
!= TimeDelta::Max());
105 *delta
= std::max(min_delay
, TimeDelta::FromSeconds(0));
110 bool BlacklistStore::GetTimeUntilURLReadyForUpload(const GURL
& url
,
112 auto it
= blacklist_times_
.find(url
.spec());
113 if (it
!= blacklist_times_
.end()) {
114 // The url is in the timestamps map.
115 *delta
= std::max(upload_delay_
- (TimeTicks::Now() - it
->second
),
116 TimeDelta::FromSeconds(0));
120 // The url still might be in the blacklist.
121 SuggestionsBlacklist blacklist
;
122 LoadBlacklist(&blacklist
);
123 for (int i
= 0; i
< blacklist
.urls_size(); ++i
) {
124 if (blacklist
.urls(i
) == url
.spec()) {
125 *delta
= TimeDelta::FromSeconds(0);
133 bool BlacklistStore::GetCandidateForUpload(GURL
* url
) {
134 SuggestionsBlacklist blacklist
;
135 LoadBlacklist(&blacklist
);
137 for (int i
= 0; i
< blacklist
.urls_size(); ++i
) {
138 bool is_candidate
= true;
139 auto it
= blacklist_times_
.find(blacklist
.urls(i
));
140 if (it
!= blacklist_times_
.end() &&
141 TimeTicks::Now() < it
->second
+ upload_delay_
) {
142 // URL was added too recently.
143 is_candidate
= false;
146 GURL
blacklisted(blacklist
.urls(i
));
147 url
->Swap(&blacklisted
);
155 bool BlacklistStore::RemoveUrl(const GURL
& url
) {
156 if (!url
.is_valid()) return false;
157 const std::string removal_candidate
= url
.spec();
159 SuggestionsBlacklist blacklist
;
160 LoadBlacklist(&blacklist
);
162 bool removed
= false;
163 SuggestionsBlacklist updated_blacklist
;
164 for (int i
= 0; i
< blacklist
.urls_size(); ++i
) {
165 if (blacklist
.urls(i
) == removal_candidate
) {
168 updated_blacklist
.add_urls(blacklist
.urls(i
));
172 if (removed
&& StoreBlacklist(updated_blacklist
)) {
173 blacklist_times_
.erase(url
.spec());
180 void BlacklistStore::FilterSuggestions(SuggestionsProfile
* profile
) {
181 if (!profile
->suggestions_size())
182 return; // Empty profile, nothing to filter.
184 SuggestionsBlacklist blacklist_proto
;
185 if (!LoadBlacklist(&blacklist_proto
)) {
186 // There was an error loading the blacklist. The blacklist was cleared and
187 // there's nothing to be done about it.
190 if (!blacklist_proto
.urls_size())
191 return; // Empty blacklist, nothing to filter.
193 std::set
<std::string
> blacklist_set
;
194 PopulateBlacklistSet(blacklist_proto
, &blacklist_set
);
196 // Populate the filtered suggestions.
197 SuggestionsProfile filtered_profile
;
198 for (int i
= 0; i
< profile
->suggestions_size(); ++i
) {
199 if (blacklist_set
.find(profile
->suggestions(i
).url()) ==
200 blacklist_set
.end()) {
201 // This suggestion is not blacklisted.
202 ChromeSuggestion
* suggestion
= filtered_profile
.add_suggestions();
204 suggestion
->Swap(profile
->mutable_suggestions(i
));
208 // Swap |profile| and |filtered_profile|.
209 profile
->Swap(&filtered_profile
);
213 void BlacklistStore::RegisterProfilePrefs(
214 user_prefs::PrefRegistrySyncable
* registry
) {
215 registry
->RegisterStringPref(
216 prefs::kSuggestionsBlacklist
, std::string(),
217 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
221 // Test seam. For simplicity of mock creation.
222 BlacklistStore::BlacklistStore() {
225 bool BlacklistStore::LoadBlacklist(SuggestionsBlacklist
* blacklist
) {
228 const std::string base64_blacklist_data
=
229 pref_service_
->GetString(prefs::kSuggestionsBlacklist
);
230 if (base64_blacklist_data
.empty()) {
235 // If the decode process fails, assume the pref value is corrupt and clear it.
236 std::string blacklist_data
;
237 if (!base::Base64Decode(base64_blacklist_data
, &blacklist_data
) ||
238 !blacklist
->ParseFromString(blacklist_data
)) {
239 VLOG(1) << "Suggestions blacklist data in profile pref is corrupt, "
249 bool BlacklistStore::StoreBlacklist(const SuggestionsBlacklist
& blacklist
) {
250 std::string blacklist_data
;
251 if (!blacklist
.SerializeToString(&blacklist_data
)) return false;
253 std::string base64_blacklist_data
;
254 base::Base64Encode(blacklist_data
, &base64_blacklist_data
);
256 pref_service_
->SetString(prefs::kSuggestionsBlacklist
, base64_blacklist_data
);
260 void BlacklistStore::ClearBlacklist() {
261 pref_service_
->ClearPref(prefs::kSuggestionsBlacklist
);
264 } // namespace suggestions