blob: c24e0a6bfaaa0c31b21648750b8da090107b7b15 [file] [log] [blame]
// Copyright 2014 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 "components/suggestions/blocklist_store.h"
#include <stddef.h>
#include <algorithm>
#include <set>
#include <string>
#include "base/base64.h"
#include "base/logging.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/suggestions/suggestions_pref_names.h"
using base::TimeDelta;
using base::TimeTicks;
namespace suggestions {
namespace {
void PopulateBlocklistSet(const SuggestionsBlocklist& blocklist_proto,
std::set<std::string>* blocklist_set) {
blocklist_set->clear();
for (int i = 0; i < blocklist_proto.urls_size(); ++i) {
blocklist_set->insert(blocklist_proto.urls(i));
}
}
void PopulateBlocklistProto(const std::set<std::string>& blocklist_set,
SuggestionsBlocklist* blocklist_proto) {
blocklist_proto->Clear();
for (auto& url : blocklist_set) {
blocklist_proto->add_urls(url);
}
}
} // namespace
BlocklistStore::BlocklistStore(PrefService* profile_prefs,
const base::TimeDelta& upload_delay)
: pref_service_(profile_prefs), upload_delay_(upload_delay) {
DCHECK(pref_service_);
}
BlocklistStore::~BlocklistStore() = default;
bool BlocklistStore::BlocklistUrl(const GURL& url) {
if (!url.is_valid())
return false;
SuggestionsBlocklist blocklist_proto;
LoadBlocklist(&blocklist_proto);
std::set<std::string> blocklist_set;
PopulateBlocklistSet(blocklist_proto, &blocklist_set);
bool success = false;
if (blocklist_set.insert(url.spec()).second) {
PopulateBlocklistProto(blocklist_set, &blocklist_proto);
success = StoreBlocklist(blocklist_proto);
} else {
// |url| was already in the blocklist.
success = true;
}
if (success) {
// Update the blocklist time.
blocklist_times_[url.spec()] = TimeTicks::Now();
}
return success;
}
bool BlocklistStore::GetTimeUntilReadyForUpload(TimeDelta* delta) {
SuggestionsBlocklist blocklist;
LoadBlocklist(&blocklist);
if (!blocklist.urls_size())
return false;
// Note: the size is non-negative.
if (blocklist_times_.size() < static_cast<size_t>(blocklist.urls_size())) {
// A url is not in the timestamp map: it's candidate for upload. This can
// happen after a restart. Another (undesired) case when this could happen
// is if more than one instance were created.
*delta = TimeDelta::FromSeconds(0);
return true;
}
// Find the minimum blocklist time. Note: blocklist_times_ is NOT empty since
// blocklist is non-empty and blocklist_times_ contains as many items.
TimeDelta min_delay = TimeDelta::Max();
for (const auto& kv : blocklist_times_) {
min_delay =
std::min(upload_delay_ - (TimeTicks::Now() - kv.second), min_delay);
}
DCHECK(min_delay != TimeDelta::Max());
*delta = std::max(min_delay, TimeDelta::FromSeconds(0));
return true;
}
bool BlocklistStore::GetTimeUntilURLReadyForUpload(const GURL& url,
TimeDelta* delta) {
auto it = blocklist_times_.find(url.spec());
if (it != blocklist_times_.end()) {
// The url is in the timestamps map.
*delta = std::max(upload_delay_ - (TimeTicks::Now() - it->second),
TimeDelta::FromSeconds(0));
return true;
}
// The url still might be in the blocklist.
SuggestionsBlocklist blocklist;
LoadBlocklist(&blocklist);
for (int i = 0; i < blocklist.urls_size(); ++i) {
if (blocklist.urls(i) == url.spec()) {
*delta = TimeDelta::FromSeconds(0);
return true;
}
}
return false;
}
bool BlocklistStore::GetCandidateForUpload(GURL* url) {
SuggestionsBlocklist blocklist;
LoadBlocklist(&blocklist);
for (int i = 0; i < blocklist.urls_size(); ++i) {
bool is_candidate = true;
auto it = blocklist_times_.find(blocklist.urls(i));
if (it != blocklist_times_.end() &&
TimeTicks::Now() < it->second + upload_delay_) {
// URL was added too recently.
is_candidate = false;
}
if (is_candidate) {
GURL blocklisted(blocklist.urls(i));
url->Swap(&blocklisted);
return true;
}
}
return false;
}
bool BlocklistStore::RemoveUrl(const GURL& url) {
if (!url.is_valid())
return false;
const std::string removal_candidate = url.spec();
SuggestionsBlocklist blocklist;
LoadBlocklist(&blocklist);
bool removed = false;
SuggestionsBlocklist updated_blocklist;
for (int i = 0; i < blocklist.urls_size(); ++i) {
if (blocklist.urls(i) == removal_candidate) {
removed = true;
} else {
updated_blocklist.add_urls(blocklist.urls(i));
}
}
if (removed && StoreBlocklist(updated_blocklist)) {
blocklist_times_.erase(url.spec());
return true;
}
return false;
}
void BlocklistStore::FilterSuggestions(SuggestionsProfile* profile) {
if (!profile->suggestions_size())
return; // Empty profile, nothing to filter.
SuggestionsBlocklist blocklist_proto;
if (!LoadBlocklist(&blocklist_proto)) {
// There was an error loading the blocklist. The blocklist was cleared and
// there's nothing to be done about it.
return;
}
if (!blocklist_proto.urls_size())
return; // Empty blocklist, nothing to filter.
std::set<std::string> blocklist_set;
PopulateBlocklistSet(blocklist_proto, &blocklist_set);
// Populate the filtered suggestions.
SuggestionsProfile filtered_profile;
filtered_profile.set_timestamp(profile->timestamp());
for (int i = 0; i < profile->suggestions_size(); ++i) {
if (blocklist_set.find(profile->suggestions(i).url()) ==
blocklist_set.end()) {
// This suggestion is not blocklisted.
ChromeSuggestion* suggestion = filtered_profile.add_suggestions();
// Note: swapping!
suggestion->Swap(profile->mutable_suggestions(i));
}
}
// Swap |profile| and |filtered_profile|.
profile->Swap(&filtered_profile);
}
// static
void BlocklistStore::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterStringPref(prefs::kSuggestionsBlocklist, std::string());
}
// Test seam. For simplicity of mock creation.
BlocklistStore::BlocklistStore() = default;
bool BlocklistStore::LoadBlocklist(SuggestionsBlocklist* blocklist) {
DCHECK(blocklist);
const std::string base64_blocklist_data =
pref_service_->GetString(prefs::kSuggestionsBlocklist);
if (base64_blocklist_data.empty()) {
blocklist->Clear();
return false;
}
// If the decode process fails, assume the pref value is corrupt and clear it.
std::string blocklist_data;
if (!base::Base64Decode(base64_blocklist_data, &blocklist_data) ||
!blocklist->ParseFromString(blocklist_data)) {
VLOG(1) << "Suggestions blocklist data in profile pref is corrupt, "
<< " clearing it.";
blocklist->Clear();
ClearBlocklist();
return false;
}
return true;
}
bool BlocklistStore::StoreBlocklist(const SuggestionsBlocklist& blocklist) {
std::string blocklist_data;
if (!blocklist.SerializeToString(&blocklist_data))
return false;
std::string base64_blocklist_data;
base::Base64Encode(blocklist_data, &base64_blocklist_data);
pref_service_->SetString(prefs::kSuggestionsBlocklist, base64_blocklist_data);
return true;
}
void BlocklistStore::ClearBlocklist() {
pref_service_->ClearPref(prefs::kSuggestionsBlocklist);
}
} // namespace suggestions