// Copyright (c) 2011 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/utf_string_conversions.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/api/sync_error.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/autofill_table.h" #include "chrome/browser/webdata/web_data_service.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/guid.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "net/base/escape.h" 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. bool MergeTimestamps(const sync_pb::AutofillSpecifics& autofill, const std::vector& timestamps, std::vector* new_timestamps) { DCHECK(new_timestamps); std::set timestamp_union(timestamps.begin(), timestamps.end()); size_t timestamps_count = autofill.usage_timestamp_size(); bool different = timestamps.size() != timestamps_count; for (size_t i = 0; i < timestamps_count; ++i) { if (timestamp_union.insert(base::Time::FromInternalValue( autofill.usage_timestamp(i))).second) { different = true; } } if (different) { new_timestamps->insert(new_timestamps->begin(), timestamp_union.begin(), timestamp_union.end()); } return different; } } // namespace AutocompleteSyncableService::AutocompleteSyncableService( WebDataService* web_data_service) : web_data_service_(web_data_service) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK(web_data_service_); notification_registrar_.Add( this, chrome::NOTIFICATION_AUTOFILL_ENTRIES_CHANGED, content::Source(web_data_service)); } AutocompleteSyncableService::~AutocompleteSyncableService() { DCHECK(CalledOnValidThread()); } AutocompleteSyncableService::AutocompleteSyncableService() : web_data_service_(NULL) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); } SyncError AutocompleteSyncableService::MergeDataAndStartSyncing( syncable::ModelType type, const SyncDataList& initial_sync_data, SyncChangeProcessor* sync_processor) { DCHECK(CalledOnValidThread()); DCHECK(!sync_processor_.get()); VLOG(1) << "Associating Autocomplete: MergeDataAndStartSyncing"; std::vector entries; if (!LoadAutofillData(&entries)) { return SyncError( FROM_HERE, "Could not get the autocomplete data from WebDatabase.", model_type()); } AutocompleteEntryMap new_db_entries; for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) { new_db_entries[it->key()] = std::make_pair(SyncChange::ACTION_ADD, it); } sync_processor_.reset(sync_processor); std::vector 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 (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)) return SyncError(FROM_HERE, "Failed to update webdata.", model_type()); WebDataService::NotifyOfMultipleAutofillChanges(web_data_service_); SyncChangeList new_changes; for (AutocompleteEntryMap::iterator i = new_db_entries.begin(); i != new_db_entries.end(); ++i) { new_changes.push_back( SyncChange(i->second.first, CreateSyncData(*(i->second.second)))); } SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); return error; } void AutocompleteSyncableService::StopSyncing(syncable::ModelType type) { DCHECK(CalledOnValidThread()); DCHECK_EQ(syncable::AUTOFILL, type); sync_processor_.reset(NULL); } SyncDataList AutocompleteSyncableService::GetAllSyncData( syncable::ModelType type) const { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); DCHECK_EQ(type, syncable::AUTOFILL); SyncDataList current_data; std::vector entries; if (!LoadAutofillData(&entries)) return current_data; for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) { current_data.push_back(CreateSyncData(*it)); } return current_data; } SyncError AutocompleteSyncableService::ProcessSyncChanges( const tracked_objects::Location& from_here, const SyncChangeList& change_list) { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); if (!sync_processor_.get()) { SyncError error(FROM_HERE, "Models not yet associated.", syncable::AUTOFILL); return error; } // Data is loaded only if we get new ADD/UPDATE change. std::vector entries; scoped_ptr db_entries; std::vector new_entries; SyncError list_processing_error; for (SyncChangeList::const_iterator i = change_list.begin(); i != change_list.end() && !list_processing_error.IsSet(); ++i) { DCHECK(i->IsValid()); switch (i->change_type()) { case SyncChange::ACTION_ADD: case SyncChange::ACTION_UPDATE: if (!db_entries.get()) { if (!LoadAutofillData(&entries)) { return SyncError( FROM_HERE, "Could not get the autocomplete data from WebDatabase.", model_type()); } db_entries.reset(new AutocompleteEntryMap); for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) { (*db_entries)[it->key()] = std::make_pair(SyncChange::ACTION_ADD, it); } } CreateOrUpdateEntry(i->sync_data(), db_entries.get(), &new_entries); break; case SyncChange::ACTION_DELETE: { DCHECK(i->sync_data().GetSpecifics().HasExtension(sync_pb::autofill)) << "Autofill specifics data not present on delete!"; const sync_pb::AutofillSpecifics& autofill = i->sync_data().GetSpecifics().GetExtension(sync_pb::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 SyncError(FROM_HERE, "ProcessSyncChanges failed on ChangeType " + SyncChange::ChangeTypeToString(i->change_type()), syncable::AUTOFILL); } } if (!SaveChangesToWebData(new_entries)) return SyncError(FROM_HERE, "Failed to update webdata.", model_type()); WebDataService::NotifyOfMultipleAutofillChanges(web_data_service_); return list_processing_error; } void AutocompleteSyncableService::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(chrome::NOTIFICATION_AUTOFILL_ENTRIES_CHANGED, type); // Check if sync is on. If we receive notification prior to the sync being set // up we are going to process all when MergeData..() is called. If we receive // notification after the sync exited, it will be sinced next time Chrome // starts. if (!sync_processor_.get()) return; WebDataService* wds = content::Source(source).ptr(); DCHECK_EQ(web_data_service_, wds); AutofillChangeList* changes = content::Details(details).ptr(); ActOnChanges(*changes); } bool AutocompleteSyncableService::LoadAutofillData( std::vector* entries) const { return web_data_service_->GetDatabase()-> GetAutofillTable()->GetAllAutofillEntries(entries); } bool AutocompleteSyncableService::SaveChangesToWebData( const std::vector& new_entries) { DCHECK(CalledOnValidThread()); if (!new_entries.empty() && !web_data_service_->GetDatabase()-> GetAutofillTable()->UpdateAutofillEntries(new_entries)) { return false; } return true; } // Creates or updates an autocomplete entry based on |data|. void AutocompleteSyncableService::CreateOrUpdateEntry( const SyncData& data, AutocompleteEntryMap* loaded_data, std::vector* new_entries) { const sync_pb::EntitySpecifics& specifics = data.GetSpecifics(); const sync_pb::AutofillSpecifics& autofill_specifics( specifics.GetExtension(sync_pb::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 timestamps; size_t timestamps_count = autofill_specifics.usage_timestamp_size(); timestamps.resize(timestamps_count); for (size_t ts = 0; ts < timestamps_count; ++ts) { timestamps[ts] = base::Time::FromInternalValue( autofill_specifics.usage_timestamp(ts)); } new_entries->push_back(AutofillEntry(key, timestamps)); } else { // Entry already present - merge if necessary. std::vector 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 = 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->MutableExtension(sync_pb::autofill); autofill->set_name(UTF16ToUTF8(entry.key().name())); autofill->set_value(UTF16ToUTF8(entry.key().value())); const std::vector& ts(entry.timestamps()); for (std::vector::const_iterator timestamp = ts.begin(); timestamp != ts.end(); ++timestamp) { autofill->add_usage_timestamp(timestamp->ToInternalValue()); } } SyncError AutocompleteSyncableService::AutofillEntryDelete( const sync_pb::AutofillSpecifics& autofill) { if (!web_data_service_->GetDatabase()->GetAutofillTable()->RemoveFormElement( UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { return SyncError(FROM_HERE, "Could not remove autocomplete entry from WebDatabase.", model_type()); } return SyncError(); } void AutocompleteSyncableService::ActOnChanges( const AutofillChangeList& changes) { DCHECK(sync_processor_.get()); 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 timestamps; if (!web_data_service_->GetDatabase()-> GetAutofillTable()->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { NOTREACHED(); return; } AutofillEntry entry(change->key(), timestamps); SyncChange::SyncChangeType change_type = (change->type() == AutofillChange::ADD) ? SyncChange::ACTION_ADD : SyncChange::ACTION_UPDATE; new_changes.push_back(SyncChange(change_type, CreateSyncData(entry))); break; } case AutofillChange::REMOVE: { std::vector timestamps; AutofillEntry entry(change->key(), timestamps); new_changes.push_back(SyncChange(SyncChange::ACTION_DELETE, CreateSyncData(entry))); break; } default: NOTREACHED(); break; } } SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); if (error.IsSet()) { DLOG(WARNING) << "[AUTOCOMPLETE SYNC]" << " Failed processing change:" << " Error:" << error.message(); } } 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 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); }