// 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/autofill_profile_syncable_service.h" #include "base/location.h" #include "base/logging.h" #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/autofill/form_group.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/api/sync_error.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_details.h" #include "content/public/browser/notification_source.h" using content::BrowserThread; namespace { // Helper to compare the local value and cloud value of a field, merge into // the local value if they differ, and return whether the merge happened. bool MergeField(FormGroup* form_group, AutofillFieldType field_type, const std::string& specifics_field) { if (UTF16ToUTF8(form_group->GetInfo(field_type)) == specifics_field) return false; form_group->SetInfo(field_type, UTF8ToUTF16(specifics_field)); return true; } std::string LimitData(const std::string& data) { std::string sanitized_value(data); if (sanitized_value.length() > AutofillTable::kMaxDataLength) sanitized_value.resize(AutofillTable::kMaxDataLength); return sanitized_value; } } // namespace const char kAutofillProfileTag[] = "google_chrome_autofill_profiles"; AutofillProfileSyncableService::AutofillProfileSyncableService( 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_PROFILE_CHANGED, content::Source(web_data_service_)); } AutofillProfileSyncableService::~AutofillProfileSyncableService() { DCHECK(CalledOnValidThread()); } AutofillProfileSyncableService::AutofillProfileSyncableService() : web_data_service_(NULL) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); } SyncError AutofillProfileSyncableService::MergeDataAndStartSyncing( syncable::ModelType type, const SyncDataList& initial_sync_data, SyncChangeProcessor* sync_processor) { DCHECK(CalledOnValidThread()); DCHECK(!sync_processor_.get()); VLOG(1) << "Associating Autofill: MergeDataAndStartSyncing"; if (!LoadAutofillData(&profiles_.get())) { return SyncError( FROM_HERE, "Could not get the autofill data from WebDatabase.", model_type()); } if (VLOG_IS_ON(2)) { VLOG(2) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; for (ScopedVector::const_iterator ix = profiles_.begin(); ix != profiles_.end(); ++ix) { AutofillProfile* p = *ix; VLOG(2) << "[AUTOFILL MIGRATION] " << p->GetInfo(NAME_FIRST) << p->GetInfo(NAME_LAST) << p->guid(); } } sync_processor_.reset(sync_processor); GUIDToProfileMap remaining_profiles; CreateGUIDToProfileMap(profiles_.get(), &remaining_profiles); DataBundle bundle; // Go through and check for all the profiles that sync already knows about. for (SyncDataList::const_iterator sync_iter = initial_sync_data.begin(); sync_iter != initial_sync_data.end(); ++sync_iter) { GUIDToProfileMap::iterator it = CreateOrUpdateProfile(*sync_iter, &remaining_profiles, &bundle); profiles_map_[it->first] = it->second; remaining_profiles.erase(it); } if (!SaveChangesToWebData(bundle)) return SyncError(FROM_HERE, "Failed to update webdata.", model_type()); SyncChangeList new_changes; for (GUIDToProfileMap::iterator i = remaining_profiles.begin(); i != remaining_profiles.end(); ++i) { new_changes.push_back( SyncChange(SyncChange::ACTION_ADD, CreateData(*(i->second)))); profiles_map_[i->first] = i->second; } SyncError error; if (!new_changes.empty()) error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); WebDataService::NotifyOfMultipleAutofillChanges(web_data_service_); return error; } void AutofillProfileSyncableService::StopSyncing(syncable::ModelType type) { DCHECK(CalledOnValidThread()); DCHECK_EQ(type, syncable::AUTOFILL_PROFILE); sync_processor_.reset(); profiles_.reset(); profiles_map_.clear(); } SyncDataList AutofillProfileSyncableService::GetAllSyncData( syncable::ModelType type) const { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); DCHECK_EQ(type, syncable::AUTOFILL_PROFILE); SyncDataList current_data; for (GUIDToProfileMap::const_iterator i = profiles_map_.begin(); i != profiles_map_.end(); ++i) { current_data.push_back(CreateData(*(i->second))); } return current_data; } SyncError AutofillProfileSyncableService::ProcessSyncChanges( const tracked_objects::Location& from_here, const SyncChangeList& change_list) { DCHECK(CalledOnValidThread()); if (!sync_processor_.get()) { SyncError error(FROM_HERE, "Models not yet associated.", syncable::AUTOFILL_PROFILE); return error; } DataBundle bundle; for (SyncChangeList::const_iterator i = change_list.begin(); i != change_list.end(); ++i) { DCHECK(i->IsValid()); switch (i->change_type()) { case SyncChange::ACTION_ADD: case SyncChange::ACTION_UPDATE: CreateOrUpdateProfile(i->sync_data(), &profiles_map_, &bundle); break; case SyncChange::ACTION_DELETE: { std::string guid = i->sync_data().GetSpecifics(). GetExtension(sync_pb::autofill_profile).guid(); bundle.profiles_to_delete.push_back(guid); profiles_map_.erase(guid); } break; default: NOTREACHED() << "Unexpected sync change state."; return SyncError(FROM_HERE, "ProcessSyncChanges failed on ChangeType " + SyncChange::ChangeTypeToString(i->change_type()), syncable::AUTOFILL_PROFILE); } } if (!SaveChangesToWebData(bundle)) return SyncError(FROM_HERE, "Failed to update webdata.", model_type()); WebDataService::NotifyOfMultipleAutofillChanges(web_data_service_); return SyncError(); } void AutofillProfileSyncableService::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(type, chrome::NOTIFICATION_AUTOFILL_PROFILE_CHANGED); DCHECK_EQ(web_data_service_, content::Source(source).ptr()); // 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; AutofillProfileChange* change = content::Details(details).ptr(); ActOnChange(*change); } bool AutofillProfileSyncableService::LoadAutofillData( std::vector* profiles) { return GetAutofillTable()->GetAutofillProfiles(profiles); } bool AutofillProfileSyncableService::SaveChangesToWebData( const DataBundle& bundle) { DCHECK(CalledOnValidThread()); for (size_t i = 0; i < bundle.profiles_to_add.size(); i++) { if (!GetAutofillTable()->AddAutofillProfile( *bundle.profiles_to_add[i])) return false; } for (size_t i = 0; i < bundle.profiles_to_update.size(); i++) { if (!GetAutofillTable()->UpdateAutofillProfile( *bundle.profiles_to_update[i])) return false; } for (size_t i = 0; i< bundle.profiles_to_delete.size(); ++i) { if (!GetAutofillTable()->RemoveAutofillProfile( bundle.profiles_to_delete[i])) return false; } return true; } // static bool AutofillProfileSyncableService::OverwriteProfileWithServerData( const sync_pb::AutofillProfileSpecifics& specifics, AutofillProfile* profile) { bool diff = false; diff = MergeField(profile, NAME_FIRST, specifics.name_first()) || diff; diff = MergeField(profile, NAME_LAST, specifics.name_last()) || diff; diff = MergeField(profile, NAME_MIDDLE, specifics.name_middle()) || diff; diff = MergeField(profile, ADDRESS_HOME_LINE1, specifics.address_home_line1()) || diff; diff = MergeField(profile, ADDRESS_HOME_LINE2, specifics.address_home_line2()) || diff; diff = MergeField(profile, ADDRESS_HOME_CITY, specifics.address_home_city()) || diff; diff = MergeField(profile, ADDRESS_HOME_STATE, specifics.address_home_state()) || diff; diff = MergeField(profile, ADDRESS_HOME_COUNTRY, specifics.address_home_country()) || diff; diff = MergeField(profile, ADDRESS_HOME_ZIP, specifics.address_home_zip()) || diff; diff = MergeField(profile, EMAIL_ADDRESS, specifics.email_address()) || diff; diff = MergeField(profile, COMPANY_NAME, specifics.company_name()) || diff; diff = MergeField(profile, PHONE_HOME_WHOLE_NUMBER, specifics.phone_home_whole_number()) || diff; return diff; } // static void AutofillProfileSyncableService::WriteAutofillProfile( const AutofillProfile& profile, sync_pb::EntitySpecifics* profile_specifics) { sync_pb::AutofillProfileSpecifics* specifics = profile_specifics->MutableExtension(sync_pb::autofill_profile); DCHECK(guid::IsValidGUID(profile.guid())); specifics->set_guid(profile.guid()); specifics->set_name_first( LimitData(UTF16ToUTF8(profile.GetInfo(NAME_FIRST)))); specifics->set_name_middle( LimitData(UTF16ToUTF8(profile.GetInfo(NAME_MIDDLE)))); specifics->set_name_last(LimitData(UTF16ToUTF8(profile.GetInfo(NAME_LAST)))); specifics->set_address_home_line1( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_LINE1)))); specifics->set_address_home_line2( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_LINE2)))); specifics->set_address_home_city( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_CITY)))); specifics->set_address_home_state( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_STATE)))); specifics->set_address_home_country( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_COUNTRY)))); specifics->set_address_home_zip( LimitData(UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_ZIP)))); specifics->set_email_address( LimitData(UTF16ToUTF8(profile.GetInfo(EMAIL_ADDRESS)))); specifics->set_company_name( LimitData(UTF16ToUTF8(profile.GetInfo(COMPANY_NAME)))); specifics->set_phone_home_whole_number( LimitData(UTF16ToUTF8(profile.GetInfo(PHONE_HOME_WHOLE_NUMBER)))); } void AutofillProfileSyncableService::CreateGUIDToProfileMap( const std::vector& profiles, GUIDToProfileMap* profile_map) { DCHECK(profile_map); profile_map->clear(); for (size_t i = 0; i < profiles.size(); ++i) (*profile_map)[profiles[i]->guid()] = profiles[i]; } AutofillProfileSyncableService::GUIDToProfileMap::iterator AutofillProfileSyncableService::CreateOrUpdateProfile( const SyncData& data, GUIDToProfileMap* profile_map, DataBundle* bundle) { DCHECK(profile_map); DCHECK(bundle); DCHECK_EQ(syncable::AUTOFILL_PROFILE, data.GetDataType()); const sync_pb::EntitySpecifics& specifics = data.GetSpecifics(); const sync_pb::AutofillProfileSpecifics& autofill_specifics( specifics.GetExtension(sync_pb::autofill_profile)); GUIDToProfileMap::iterator it = profile_map->find( autofill_specifics.guid()); if (it != profile_map->end()) { // Some profile that already present is synced. if (OverwriteProfileWithServerData(autofill_specifics, it->second)) bundle->profiles_to_update.push_back(it->second); } else { // New profile synced. AutofillProfile* new_profile( new AutofillProfile(autofill_specifics.guid())); OverwriteProfileWithServerData(autofill_specifics, new_profile); // Check if profile appears under a different guid. for (GUIDToProfileMap::iterator i = profile_map->begin(); i != profile_map->end(); ++i) { if (i->second->Compare(*new_profile) == 0) { bundle->profiles_to_delete.push_back(i->second->guid()); VLOG(2) << "[AUTOFILL SYNC]" << "Found in sync db but with a different guid: " << UTF16ToUTF8(it->second->GetInfo(NAME_FIRST)) << UTF16ToUTF8(it->second->GetInfo(NAME_LAST)) << "New guid " << new_profile->guid() << ". Profile to be deleted " << it->second->guid(); profile_map->erase(i); break; } } profiles_.push_back(new_profile); it = profile_map->insert(std::make_pair(new_profile->guid(), new_profile)).first; bundle->profiles_to_add.push_back(new_profile); } return it; } void AutofillProfileSyncableService::ActOnChange( const AutofillProfileChange& change) { DCHECK((change.type() == AutofillProfileChange::REMOVE && !change.profile()) || (change.type() != AutofillProfileChange::REMOVE && change.profile())); DCHECK(sync_processor_.get()); SyncChangeList new_changes; DataBundle bundle; switch (change.type()) { case AutofillProfileChange::ADD: new_changes.push_back( SyncChange(SyncChange::ACTION_ADD, CreateData(*(change.profile())))); DCHECK(profiles_map_.find(change.profile()->guid()) == profiles_map_.end()); profiles_.push_back(new AutofillProfile(*(change.profile()))); profiles_map_[change.profile()->guid()] = profiles_.get().back(); break; case AutofillProfileChange::UPDATE: { GUIDToProfileMap::iterator it = profiles_map_.find( change.profile()->guid()); DCHECK(it != profiles_map_.end()); *(it->second) = *(change.profile()); new_changes.push_back( SyncChange(SyncChange::ACTION_UPDATE, CreateData(*(change.profile())))); break; } case AutofillProfileChange::REMOVE: { AutofillProfile empty_profile(change.key()); new_changes.push_back(SyncChange(SyncChange::ACTION_DELETE, CreateData(empty_profile))); profiles_map_.erase(change.key()); break; } default: NOTREACHED(); } SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); if (error.IsSet()) { LOG(WARNING) << "[AUTOFILL SYNC]" << " Failed processing change:" << " Error:" << error.message() << " Guid:" << change.key(); } } SyncData AutofillProfileSyncableService::CreateData( const AutofillProfile& profile) { sync_pb::EntitySpecifics specifics; WriteAutofillProfile(profile, &specifics); return SyncData::CreateLocalData(profile.guid(), profile.guid(), specifics); } AutofillTable* AutofillProfileSyncableService::GetAutofillTable() const { return web_data_service_->GetDatabase()->GetAutofillTable(); } AutofillProfileSyncableService::DataBundle::DataBundle() {} AutofillProfileSyncableService::DataBundle::~DataBundle() { }