// Copyright (c) 2012 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 "chrome/browser/webdata/autocomplete_syncable_service.h" #include "base/location.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/webdata/autofill_table.h" #include "components/autofill/core/browser/webdata/autofill_webdata_service.h" #include "components/webdata/common/web_database.h" #include "content/public/browser/browser_thread.h" #include "net/base/escape.h" #include "sync/api/sync_error.h" #include "sync/api/sync_error_factory.h" #include "sync/protocol/autofill_specifics.pb.h" #include "sync/protocol/sync.pb.h" using autofill::AutofillChange; using autofill::AutofillChangeList; using autofill::AutofillEntry; using autofill::AutofillKey; using autofill::AutofillTable; using autofill::AutofillWebDataService; using autofill::AutofillWebDataBackend; using content::BrowserThread; namespace { const char kAutofillEntryNamespaceTag[] = "autofill_entry|"; // Merges timestamps from the |autofill| entry and |timestamps|. Returns // true if they were different, false if they were the same. // All of the timestamp vectors are assummed to be sorted, resulting vector is // sorted as well. Only two timestamps - the earliest and the latest are stored. bool MergeTimestamps(const sync_pb::AutofillSpecifics& autofill, const std::vector<base::Time>& timestamps, std::vector<base::Time>* new_timestamps) { DCHECK(new_timestamps); new_timestamps->clear(); size_t timestamps_count = autofill.usage_timestamp_size(); if (timestamps_count == 0 && timestamps.empty()) { return false; } else if (timestamps_count == 0) { new_timestamps->insert(new_timestamps->begin(), timestamps.begin(), timestamps.end()); return true; } else if (timestamps.empty()) { new_timestamps->reserve(2); new_timestamps->push_back(base::Time::FromInternalValue( autofill.usage_timestamp(0))); if (timestamps_count > 1) { new_timestamps->push_back(base::Time::FromInternalValue( autofill.usage_timestamp(timestamps_count - 1))); } return true; } else { base::Time sync_time_begin = base::Time::FromInternalValue( autofill.usage_timestamp(0)); base::Time sync_time_end = base::Time::FromInternalValue( autofill.usage_timestamp(timestamps_count - 1)); if (timestamps.front() != sync_time_begin || timestamps.back() != sync_time_end) { new_timestamps->push_back( timestamps.front() < sync_time_begin ? timestamps.front() : sync_time_begin); if (new_timestamps->back() != timestamps.back() || new_timestamps->back() != sync_time_end) { new_timestamps->push_back( timestamps.back() > sync_time_end ? timestamps.back() : sync_time_end); } return true; } else { new_timestamps->insert(new_timestamps->begin(), timestamps.begin(), timestamps.end()); return false; } } } void* UserDataKey() { // Use the address of a static that COMDAT folding won't ever fold // with something else. static int user_data_key = 0; return reinterpret_cast<void*>(&user_data_key); } } // namespace AutocompleteSyncableService::AutocompleteSyncableService( AutofillWebDataBackend* webdata_backend) : webdata_backend_(webdata_backend), scoped_observer_(this), cull_expired_entries_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK(webdata_backend_); scoped_observer_.Add(webdata_backend_); } AutocompleteSyncableService::~AutocompleteSyncableService() { DCHECK(CalledOnValidThread()); } // static void AutocompleteSyncableService::CreateForWebDataServiceAndBackend( AutofillWebDataService* web_data_service, AutofillWebDataBackend* webdata_backend) { web_data_service->GetDBUserData()->SetUserData( UserDataKey(), new AutocompleteSyncableService(webdata_backend)); } // static AutocompleteSyncableService* AutocompleteSyncableService::FromWebDataService( AutofillWebDataService* web_data_service) { return static_cast<AutocompleteSyncableService*>( web_data_service->GetDBUserData()->GetUserData(UserDataKey())); } AutocompleteSyncableService::AutocompleteSyncableService() : webdata_backend_(NULL), scoped_observer_(this), cull_expired_entries_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); } void AutocompleteSyncableService::InjectStartSyncFlare( const syncer::SyncableService::StartSyncFlare& flare) { flare_ = flare; } syncer::SyncMergeResult AutocompleteSyncableService::MergeDataAndStartSyncing( syncer::ModelType type, const syncer::SyncDataList& initial_sync_data, scoped_ptr<syncer::SyncChangeProcessor> sync_processor, scoped_ptr<syncer::SyncErrorFactory> error_handler) { DCHECK(CalledOnValidThread()); DCHECK(!sync_processor_.get()); DCHECK(sync_processor.get()); DCHECK(error_handler.get()); VLOG(1) << "Associating Autocomplete: MergeDataAndStartSyncing"; syncer::SyncMergeResult merge_result(type); error_handler_ = error_handler.Pass(); std::vector<AutofillEntry> entries; if (!LoadAutofillData(&entries)) { merge_result.set_error(error_handler_->CreateAndUploadError( FROM_HERE, "Could not get the autocomplete data from WebDatabase.")); return merge_result; } AutocompleteEntryMap new_db_entries; for (std::vector<AutofillEntry>::iterator it = entries.begin(); it != entries.end(); ++it) { new_db_entries[it->key()] = std::make_pair(syncer::SyncChange::ACTION_ADD, it); } sync_processor_ = sync_processor.Pass(); std::vector<AutofillEntry> new_synced_entries; // Go through and check for all the entries that sync already knows about. // CreateOrUpdateEntry() will remove entries that are same with the synced // ones from |new_db_entries|. for (syncer::SyncDataList::const_iterator sync_iter = initial_sync_data.begin(); sync_iter != initial_sync_data.end(); ++sync_iter) { CreateOrUpdateEntry(*sync_iter, &new_db_entries, &new_synced_entries); } if (!SaveChangesToWebData(new_synced_entries)) { merge_result.set_error(error_handler_->CreateAndUploadError( FROM_HERE, "Failed to update webdata.")); return merge_result; } webdata_backend_->NotifyOfMultipleAutofillChanges(); syncer::SyncChangeList new_changes; for (AutocompleteEntryMap::iterator i = new_db_entries.begin(); i != new_db_entries.end(); ++i) { new_changes.push_back( syncer::SyncChange(FROM_HERE, i->second.first, CreateSyncData(*(i->second.second)))); } if (cull_expired_entries_) { // This will schedule a deletion operation on the DB thread, which will // trigger a notification to propagate the deletion to Sync. webdata_backend_->RemoveExpiredFormElements(); } merge_result.set_error( sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); return merge_result; } void AutocompleteSyncableService::StopSyncing(syncer::ModelType type) { DCHECK(CalledOnValidThread()); DCHECK_EQ(syncer::AUTOFILL, type); sync_processor_.reset(NULL); error_handler_.reset(); } syncer::SyncDataList AutocompleteSyncableService::GetAllSyncData( syncer::ModelType type) const { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); DCHECK_EQ(type, syncer::AUTOFILL); syncer::SyncDataList current_data; std::vector<AutofillEntry> entries; if (!LoadAutofillData(&entries)) return current_data; for (std::vector<AutofillEntry>::iterator it = entries.begin(); it != entries.end(); ++it) { current_data.push_back(CreateSyncData(*it)); } return current_data; } syncer::SyncError AutocompleteSyncableService::ProcessSyncChanges( const tracked_objects::Location& from_here, const syncer::SyncChangeList& change_list) { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); if (!sync_processor_.get()) { syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Models not yet associated.", syncer::AUTOFILL); return error; } // Data is loaded only if we get new ADD/UPDATE change. std::vector<AutofillEntry> entries; scoped_ptr<AutocompleteEntryMap> db_entries; std::vector<AutofillEntry> new_entries; syncer::SyncError list_processing_error; for (syncer::SyncChangeList::const_iterator i = change_list.begin(); i != change_list.end() && !list_processing_error.IsSet(); ++i) { DCHECK(i->IsValid()); switch (i->change_type()) { case syncer::SyncChange::ACTION_ADD: case syncer::SyncChange::ACTION_UPDATE: if (!db_entries.get()) { if (!LoadAutofillData(&entries)) { return error_handler_->CreateAndUploadError( FROM_HERE, "Could not get the autocomplete data from WebDatabase."); } db_entries.reset(new AutocompleteEntryMap); for (std::vector<AutofillEntry>::iterator it = entries.begin(); it != entries.end(); ++it) { (*db_entries)[it->key()] = std::make_pair(syncer::SyncChange::ACTION_ADD, it); } } CreateOrUpdateEntry(i->sync_data(), db_entries.get(), &new_entries); break; case syncer::SyncChange::ACTION_DELETE: { DCHECK(i->sync_data().GetSpecifics().has_autofill()) << "Autofill specifics data not present on delete!"; const sync_pb::AutofillSpecifics& autofill = i->sync_data().GetSpecifics().autofill(); if (autofill.has_value()) { list_processing_error = AutofillEntryDelete(autofill); } else { DLOG(WARNING) << "Delete for old-style autofill profile being dropped!"; } } break; default: NOTREACHED() << "Unexpected sync change state."; return error_handler_->CreateAndUploadError( FROM_HERE, "ProcessSyncChanges failed on ChangeType " + syncer::SyncChange::ChangeTypeToString(i->change_type())); } } if (!SaveChangesToWebData(new_entries)) { return error_handler_->CreateAndUploadError( FROM_HERE, "Failed to update webdata."); } webdata_backend_->NotifyOfMultipleAutofillChanges(); if (cull_expired_entries_) { // This will schedule a deletion operation on the DB thread, which will // trigger a notification to propagate the deletion to Sync. webdata_backend_->RemoveExpiredFormElements(); } return list_processing_error; } void AutocompleteSyncableService::AutofillEntriesChanged( const AutofillChangeList& changes) { // Check if sync is on. If we recieve this notification prior to sync being // started, we'll notify sync to start as soon as it can and later process // all entries when MergeData..() is called. If we receive this notification // sync has exited, it will be synced next time Chrome starts. if (sync_processor_.get()) { ActOnChanges(changes); } else if (!flare_.is_null()) { flare_.Run(syncer::AUTOFILL); flare_.Reset(); } } bool AutocompleteSyncableService::LoadAutofillData( std::vector<AutofillEntry>* entries) const { return AutofillTable::FromWebDatabase( webdata_backend_->GetDatabase())->GetAllAutofillEntries(entries); } bool AutocompleteSyncableService::SaveChangesToWebData( const std::vector<AutofillEntry>& new_entries) { DCHECK(CalledOnValidThread()); if (!new_entries.empty() && !AutofillTable::FromWebDatabase( webdata_backend_->GetDatabase())->UpdateAutofillEntries( new_entries)) { return false; } return true; } // Creates or updates an autocomplete entry based on |data|. void AutocompleteSyncableService::CreateOrUpdateEntry( const syncer::SyncData& data, AutocompleteEntryMap* loaded_data, std::vector<AutofillEntry>* new_entries) { const sync_pb::EntitySpecifics& specifics = data.GetSpecifics(); const sync_pb::AutofillSpecifics& autofill_specifics( specifics.autofill()); if (!autofill_specifics.has_value()) { DLOG(WARNING) << "Add/Update for old-style autofill profile being dropped!"; return; } AutofillKey key(autofill_specifics.name().c_str(), autofill_specifics.value().c_str()); AutocompleteEntryMap::iterator it = loaded_data->find(key); if (it == loaded_data->end()) { // New entry. std::vector<base::Time> timestamps; size_t timestamps_count = autofill_specifics.usage_timestamp_size(); timestamps.reserve(2); if (timestamps_count) { timestamps.push_back(base::Time::FromInternalValue( autofill_specifics.usage_timestamp(0))); } if (timestamps_count > 1) { timestamps.push_back(base::Time::FromInternalValue( autofill_specifics.usage_timestamp(timestamps_count - 1))); } AutofillEntry new_entry(key, timestamps); new_entries->push_back(new_entry); } else { // Entry already present - merge if necessary. std::vector<base::Time> timestamps; bool different = MergeTimestamps( autofill_specifics, it->second.second->timestamps(), ×tamps); if (different) { AutofillEntry new_entry(it->second.second->key(), timestamps); new_entries->push_back(new_entry); // Update the sync db if the list of timestamps have changed. *(it->second.second) = new_entry; it->second.first = syncer::SyncChange::ACTION_UPDATE; } else { loaded_data->erase(it); } } } // static void AutocompleteSyncableService::WriteAutofillEntry( const AutofillEntry& entry, sync_pb::EntitySpecifics* autofill_specifics) { sync_pb::AutofillSpecifics* autofill = autofill_specifics->mutable_autofill(); autofill->set_name(UTF16ToUTF8(entry.key().name())); autofill->set_value(UTF16ToUTF8(entry.key().value())); const std::vector<base::Time>& ts(entry.timestamps()); for (std::vector<base::Time>::const_iterator timestamp = ts.begin(); timestamp != ts.end(); ++timestamp) { autofill->add_usage_timestamp(timestamp->ToInternalValue()); } } syncer::SyncError AutocompleteSyncableService::AutofillEntryDelete( const sync_pb::AutofillSpecifics& autofill) { if (!AutofillTable::FromWebDatabase( webdata_backend_->GetDatabase())->RemoveFormElement( UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { return error_handler_->CreateAndUploadError( FROM_HERE, "Could not remove autocomplete entry from WebDatabase."); } return syncer::SyncError(); } void AutocompleteSyncableService::ActOnChanges( const AutofillChangeList& changes) { DCHECK(sync_processor_.get()); syncer::SyncChangeList new_changes; for (AutofillChangeList::const_iterator change = changes.begin(); change != changes.end(); ++change) { switch (change->type()) { case AutofillChange::ADD: case AutofillChange::UPDATE: { std::vector<base::Time> timestamps; WebDatabase* db = webdata_backend_->GetDatabase(); if (!AutofillTable::FromWebDatabase(db)->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { NOTREACHED(); return; } AutofillEntry entry(change->key(), timestamps); syncer::SyncChange::SyncChangeType change_type = (change->type() == AutofillChange::ADD) ? syncer::SyncChange::ACTION_ADD : syncer::SyncChange::ACTION_UPDATE; new_changes.push_back(syncer::SyncChange(FROM_HERE, change_type, CreateSyncData(entry))); break; } case AutofillChange::REMOVE: { std::vector<base::Time> timestamps; AutofillEntry entry(change->key(), timestamps); new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_DELETE, CreateSyncData(entry))); break; } default: NOTREACHED(); break; } } syncer::SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); if (error.IsSet()) { DLOG(WARNING) << "[AUTOCOMPLETE SYNC]" << " Failed processing change:" << " Error:" << error.message(); } } void AutocompleteSyncableService::UpdateCullSetting( bool cull_expired_entries) { DCHECK(CalledOnValidThread()); cull_expired_entries_ = cull_expired_entries; } syncer::SyncData AutocompleteSyncableService::CreateSyncData( const AutofillEntry& entry) const { sync_pb::EntitySpecifics autofill_specifics; WriteAutofillEntry(entry, &autofill_specifics); std::string tag(KeyToTag(UTF16ToUTF8(entry.key().name()), UTF16ToUTF8(entry.key().value()))); return syncer::SyncData::CreateLocalData(tag, tag, autofill_specifics); } // static std::string AutocompleteSyncableService::KeyToTag(const std::string& name, const std::string& value) { std::string ns(kAutofillEntryNamespaceTag); return ns + net::EscapePath(name) + "|" + net::EscapePath(value); }