blob: 664b9e74879a3ca391704e38f41e23007be81dfe [file] [log] [blame]
// Copyright 2016 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/autofill/core/browser/webdata/autocomplete_sync_bridge.h"
#include <algorithm>
#include <memory>
#include <set>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/proto/autofill_sync.pb.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/model/model_type_change_processor.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
#include "components/sync/model_impl/sync_metadata_store_change_list.h"
#include "net/base/escape.h"
using base::Optional;
using base::Time;
using sync_pb::AutofillSpecifics;
using syncer::ClientTagBasedModelTypeProcessor;
using syncer::EntityChange;
using syncer::EntityChangeList;
using syncer::EntityData;
using syncer::MetadataChangeList;
using syncer::ModelError;
using syncer::ModelTypeChangeProcessor;
using syncer::ModelTypeSyncBridge;
using syncer::MutableDataBatch;
namespace autofill {
namespace {
const char kAutocompleteEntryNamespaceTag[] = "autofill_entry|";
const char kAutocompleteTagDelimiter[] = "|";
// Simplify checking for optional errors and returning only when present.
#define RETURN_IF_ERROR(x) \
if (Optional<ModelError> ret_val = x) { \
return ret_val; \
}
void* AutocompleteSyncBridgeUserDataKey() {
// Use the address of a static that COMDAT folding won't ever collide
// with something else.
static int user_data_key = 0;
return reinterpret_cast<void*>(&user_data_key);
}
std::string EscapeIdentifiers(const AutofillSpecifics& specifics) {
return net::EscapePath(specifics.name()) +
std::string(kAutocompleteTagDelimiter) +
net::EscapePath(specifics.value());
}
std::unique_ptr<EntityData> CreateEntityData(const AutofillEntry& entry) {
auto entity_data = std::make_unique<EntityData>();
AutofillSpecifics* autofill = entity_data->specifics.mutable_autofill();
autofill->set_name(base::UTF16ToUTF8(entry.key().name()));
autofill->set_value(base::UTF16ToUTF8(entry.key().value()));
autofill->add_usage_timestamp(entry.date_created().ToInternalValue());
if (entry.date_created() != entry.date_last_used())
autofill->add_usage_timestamp(entry.date_last_used().ToInternalValue());
entity_data->non_unique_name = EscapeIdentifiers(*autofill);
return entity_data;
}
std::string BuildSerializedStorageKey(const std::string& name,
const std::string& value) {
AutofillSyncStorageKey proto;
proto.set_name(name);
proto.set_value(value);
return proto.SerializeAsString();
}
std::string GetStorageKeyFromModel(const AutofillKey& key) {
return BuildSerializedStorageKey(base::UTF16ToUTF8(key.name()),
base::UTF16ToUTF8(key.value()));
}
AutofillEntry MergeEntryDates(const AutofillEntry& entry1,
const AutofillEntry& entry2) {
DCHECK(entry1.key() == entry2.key());
return AutofillEntry(
entry1.key(), std::min(entry1.date_created(), entry2.date_created()),
std::max(entry1.date_last_used(), entry2.date_last_used()));
}
bool ParseStorageKey(const std::string& storage_key, AutofillKey* out_key) {
AutofillSyncStorageKey proto;
if (proto.ParseFromString(storage_key)) {
*out_key = AutofillKey(base::UTF8ToUTF16(proto.name()),
base::UTF8ToUTF16((proto.value())));
return true;
}
return false;
}
AutofillEntry CreateAutofillEntry(const AutofillSpecifics& autofill_specifics) {
AutofillKey key(base::UTF8ToUTF16(autofill_specifics.name()),
base::UTF8ToUTF16(autofill_specifics.value()));
Time date_created, date_last_used;
const google::protobuf::RepeatedField<int64_t>& timestamps =
autofill_specifics.usage_timestamp();
if (!timestamps.empty()) {
auto iter_pair = std::minmax_element(timestamps.begin(), timestamps.end());
date_created = Time::FromInternalValue(*iter_pair.first);
date_last_used = Time::FromInternalValue(*iter_pair.second);
}
return AutofillEntry(key, date_created, date_last_used);
}
// This is used to respond to ApplySyncChanges() and MergeSyncData(). Attempts
// to lazily load local data, and then react to sync data by maintaining
// internal state until flush calls are made, at which point the applicable
// modification should be sent towards local and sync directions.
class SyncDifferenceTracker {
public:
explicit SyncDifferenceTracker(AutofillTable* table) : table_(table) {}
Optional<ModelError> IncorporateRemoteSpecifics(
const std::string& storage_key,
const AutofillSpecifics& specifics) {
if (!specifics.has_value()) {
// A long time ago autofill had a different format, and it's possible we
// could encounter some of that legacy data. It is not useful to us,
// because an autofill entry with no value will not place any text in a
// form for the user. So drop all of these on the floor.
DVLOG(1) << "Dropping old-style autofill profile change.";
return {};
}
const AutofillEntry remote = CreateAutofillEntry(specifics);
DCHECK_EQ(storage_key, GetStorageKeyFromModel(remote.key()));
Optional<AutofillEntry> local;
if (!ReadEntry(remote.key(), &local))
return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
if (!local) {
save_to_local_.push_back(remote);
} else {
unique_to_local_.erase(local.value());
if (remote != local.value()) {
if (specifics.usage_timestamp().empty()) {
// Skip merging if there are no timestamps. We don't want to wipe out
// a local value of |date_created| if the remote copy is oddly formed.
save_to_sync_.push_back(local.value());
} else {
const AutofillEntry merged = MergeEntryDates(local.value(), remote);
save_to_local_.push_back(merged);
save_to_sync_.push_back(merged);
}
}
}
return {};
}
Optional<ModelError> IncorporateRemoteDelete(const std::string& storage_key) {
AutofillKey key;
if (!ParseStorageKey(storage_key, &key)) {
return ModelError(FROM_HERE, "Failed parsing storage key.");
}
delete_from_local_.insert(key);
return {};
}
Optional<ModelError> FlushToLocal(AutofillWebDataBackend* web_data_backend) {
for (const AutofillKey& key : delete_from_local_) {
if (!table_->RemoveFormElement(key.name(), key.value())) {
return ModelError(FROM_HERE, "Failed deleting from WebDatabase");
}
}
if (!table_->UpdateAutofillEntries(save_to_local_)) {
return ModelError(FROM_HERE, "Failed updating WebDatabase");
}
if (!delete_from_local_.empty() || !save_to_local_.empty()) {
web_data_backend->NotifyOfMultipleAutofillChanges();
}
return {};
}
Optional<ModelError> FlushToSync(
bool include_local_only,
std::unique_ptr<MetadataChangeList> metadata_change_list,
ModelTypeChangeProcessor* change_processor) {
for (const AutofillEntry& entry : save_to_sync_) {
change_processor->Put(GetStorageKeyFromModel(entry.key()),
CreateEntityData(entry),
metadata_change_list.get());
}
if (include_local_only) {
if (!InitializeIfNeeded()) {
return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
}
for (const AutofillEntry& entry : unique_to_local_) {
// This should never be true because only ApplySyncChanges should be
// calling IncorporateRemoteDelete, while only MergeSyncData should be
// passing in true for |include_local_only|. If this requirement
// changes, this DCHECK can change to act as a filter.
DCHECK(delete_from_local_.find(entry.key()) ==
delete_from_local_.end());
change_processor->Put(GetStorageKeyFromModel(entry.key()),
CreateEntityData(entry),
metadata_change_list.get());
}
}
return static_cast<syncer::SyncMetadataStoreChangeList*>(
metadata_change_list.get())
->TakeError();
}
private:
// There are three major outcomes of this method.
// 1. An error is encountered reading from the db, false is returned.
// 2. The entry is not found, |entry| will not be touched.
// 3. The entry is found, |entry| will be set.
bool ReadEntry(const AutofillKey& key, Optional<AutofillEntry>* entry) {
if (!InitializeIfNeeded()) {
return false;
}
auto iter = unique_to_local_.find(AutofillEntry(key, Time(), Time()));
if (iter != unique_to_local_.end()) {
*entry = *iter;
}
return true;
}
bool InitializeIfNeeded() {
if (initialized_) {
return true;
}
std::vector<AutofillEntry> vector;
if (!table_->GetAllAutofillEntries(&vector)) {
return false;
}
unique_to_local_ = std::set<AutofillEntry>(vector.begin(), vector.end());
initialized_ = true;
return true;
}
AutofillTable* table_;
// This class attempts to lazily load data from |table_|. This field tracks
// if that has happened or not yet. To facilitate this, the first usage of
// |unique_to_local_| should typically be done through ReadEntry().
bool initialized_ = false;
// Important to note that because AutofillEntry's operator < simply compares
// contained AutofillKeys, this acts as a map<AutofillKey, AutofillEntry>.
// Shouldn't be accessed until either ReadEntry() or InitializeIfNeeded() is
// called, afterward it will start with all the local data. As sync data is
// encountered entries are removed from here, leaving only entries that exist
// solely on the local client.
std::set<AutofillEntry> unique_to_local_;
std::set<AutofillKey> delete_from_local_;
std::vector<AutofillEntry> save_to_local_;
// Contains merged data for entries that existed on both sync and local sides
// and need to be saved back to sync.
std::vector<AutofillEntry> save_to_sync_;
DISALLOW_COPY_AND_ASSIGN(SyncDifferenceTracker);
};
} // namespace
// static
void AutocompleteSyncBridge::CreateForWebDataServiceAndBackend(
AutofillWebDataService* web_data_service,
AutofillWebDataBackend* web_data_backend) {
web_data_service->GetDBUserData()->SetUserData(
AutocompleteSyncBridgeUserDataKey(),
std::make_unique<AutocompleteSyncBridge>(
web_data_backend,
std::make_unique<ClientTagBasedModelTypeProcessor>(
syncer::AUTOFILL, /*dump_stack=*/base::RepeatingClosure())));
}
// static
ModelTypeSyncBridge* AutocompleteSyncBridge::FromWebDataService(
AutofillWebDataService* web_data_service) {
return static_cast<AutocompleteSyncBridge*>(
web_data_service->GetDBUserData()->GetUserData(
AutocompleteSyncBridgeUserDataKey()));
}
AutocompleteSyncBridge::AutocompleteSyncBridge(
AutofillWebDataBackend* backend,
std::unique_ptr<ModelTypeChangeProcessor> change_processor)
: ModelTypeSyncBridge(std::move(change_processor)),
web_data_backend_(backend),
scoped_observer_(this) {
DCHECK(web_data_backend_);
scoped_observer_.Add(web_data_backend_);
LoadMetadata();
}
AutocompleteSyncBridge::~AutocompleteSyncBridge() {
DCHECK(thread_checker_.CalledOnValidThread());
}
std::unique_ptr<MetadataChangeList>
AutocompleteSyncBridge::CreateMetadataChangeList() {
DCHECK(thread_checker_.CalledOnValidThread());
return std::make_unique<syncer::SyncMetadataStoreChangeList>(
GetAutofillTable(), syncer::AUTOFILL);
}
Optional<syncer::ModelError> AutocompleteSyncBridge::MergeSyncData(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_data) {
DCHECK(thread_checker_.CalledOnValidThread());
SyncDifferenceTracker tracker(GetAutofillTable());
for (const auto& change : entity_data) {
DCHECK(change.data().specifics.has_autofill());
RETURN_IF_ERROR(tracker.IncorporateRemoteSpecifics(
change.storage_key(), change.data().specifics.autofill()));
}
RETURN_IF_ERROR(tracker.FlushToLocal(web_data_backend_));
RETURN_IF_ERROR(tracker.FlushToSync(true, std::move(metadata_change_list),
change_processor()));
web_data_backend_->RemoveExpiredFormElements();
web_data_backend_->NotifyThatSyncHasStarted(syncer::AUTOFILL);
return {};
}
Optional<ModelError> AutocompleteSyncBridge::ApplySyncChanges(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes) {
DCHECK(thread_checker_.CalledOnValidThread());
SyncDifferenceTracker tracker(GetAutofillTable());
for (const EntityChange& change : entity_changes) {
if (change.type() == EntityChange::ACTION_DELETE) {
RETURN_IF_ERROR(tracker.IncorporateRemoteDelete(change.storage_key()));
} else {
DCHECK(change.data().specifics.has_autofill());
RETURN_IF_ERROR(tracker.IncorporateRemoteSpecifics(
change.storage_key(), change.data().specifics.autofill()));
}
}
RETURN_IF_ERROR(tracker.FlushToLocal(web_data_backend_));
RETURN_IF_ERROR(tracker.FlushToSync(false, std::move(metadata_change_list),
change_processor()));
web_data_backend_->RemoveExpiredFormElements();
return {};
}
void AutocompleteSyncBridge::AutocompleteSyncBridge::GetData(
StorageKeyList storage_keys,
DataCallback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<AutofillEntry> entries;
if (!GetAutofillTable()->GetAllAutofillEntries(&entries)) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load entries from table."});
return;
}
std::unordered_set<std::string> keys_set(storage_keys.begin(),
storage_keys.end());
auto batch = std::make_unique<MutableDataBatch>();
for (const AutofillEntry& entry : entries) {
std::string key = GetStorageKeyFromModel(entry.key());
if (keys_set.find(key) != keys_set.end()) {
batch->Put(key, CreateEntityData(entry));
}
}
std::move(callback).Run(std::move(batch));
}
void AutocompleteSyncBridge::GetAllDataForDebugging(DataCallback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<AutofillEntry> entries;
if (!GetAutofillTable()->GetAllAutofillEntries(&entries)) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load entries from table."});
return;
}
auto batch = std::make_unique<MutableDataBatch>();
for (const AutofillEntry& entry : entries) {
batch->Put(GetStorageKeyFromModel(entry.key()), CreateEntityData(entry));
}
std::move(callback).Run(std::move(batch));
}
void AutocompleteSyncBridge::ActOnLocalChanges(
const AutofillChangeList& changes) {
if (!change_processor()->IsTrackingMetadata()) {
return;
}
auto metadata_change_list =
std::make_unique<syncer::SyncMetadataStoreChangeList>(GetAutofillTable(),
syncer::AUTOFILL);
for (const auto& change : changes) {
const std::string storage_key = GetStorageKeyFromModel(change.key());
switch (change.type()) {
case AutofillChange::ADD:
case AutofillChange::UPDATE: {
base::Time date_created, date_last_used;
bool success = GetAutofillTable()->GetAutofillTimestamps(
change.key().name(), change.key().value(), &date_created,
&date_last_used);
if (!success) {
change_processor()->ReportError(
{FROM_HERE, "Failed reading autofill entry from WebDatabase."});
return;
}
const AutofillEntry entry(change.key(), date_created, date_last_used);
change_processor()->Put(storage_key, CreateEntityData(entry),
metadata_change_list.get());
break;
}
case AutofillChange::REMOVE: {
change_processor()->Delete(storage_key, metadata_change_list.get());
break;
}
}
}
if (Optional<ModelError> error = metadata_change_list->TakeError())
change_processor()->ReportError(*error);
}
void AutocompleteSyncBridge::LoadMetadata() {
if (!web_data_backend_ || !web_data_backend_->GetDatabase() ||
!GetAutofillTable()) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load AutofillWebDatabase."});
return;
}
auto batch = std::make_unique<syncer::MetadataBatch>();
if (!GetAutofillTable()->GetAllSyncMetadata(syncer::AUTOFILL, batch.get())) {
change_processor()->ReportError(
{FROM_HERE, "Failed reading autofill metadata from WebDatabase."});
return;
}
change_processor()->ModelReadyToSync(std::move(batch));
}
std::string AutocompleteSyncBridge::GetClientTag(
const EntityData& entity_data) {
DCHECK(entity_data.specifics.has_autofill());
// Must have the format "autofill_entry|$name|$value" where $name and $value
// are URL escaped. This is to maintain compatibility with the previous sync
// integration (Directory and SyncableService).
return std::string(kAutocompleteEntryNamespaceTag) +
EscapeIdentifiers(entity_data.specifics.autofill());
}
std::string AutocompleteSyncBridge::GetStorageKey(
const EntityData& entity_data) {
DCHECK(entity_data.specifics.has_autofill());
// Marginally more space efficient than GetClientTag() by omitting the
// kAutocompleteEntryNamespaceTag prefix and using protobuf serialization
// instead of URL escaping for Unicode characters.
const AutofillSpecifics specifics = entity_data.specifics.autofill();
return BuildSerializedStorageKey(specifics.name(), specifics.value());
}
void AutocompleteSyncBridge::AutofillEntriesChanged(
const AutofillChangeList& changes) {
DCHECK(thread_checker_.CalledOnValidThread());
ActOnLocalChanges(changes);
}
AutofillTable* AutocompleteSyncBridge::GetAutofillTable() const {
return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase());
}
} // namespace autofill