// 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/autofill_profile_syncable_service.h" #include "base/guid.h" #include "base/location.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_country.h" #include "components/autofill/core/browser/autofill_profile.h" #include "components/autofill/core/browser/form_group.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 "sync/api/sync_error.h" #include "sync/api/sync_error_factory.h" #include "sync/protocol/sync.pb.h" using autofill::AutofillCountry; using autofill::ServerFieldType; using autofill::AutofillProfile; using autofill::AutofillProfileChange; using autofill::AutofillTable; using autofill::AutofillWebDataService; using content::BrowserThread; namespace { 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; } 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(&user_data_key); } } // namespace const char kAutofillProfileTag[] = "google_chrome_autofill_profiles"; AutofillProfileSyncableService::AutofillProfileSyncableService( autofill::AutofillWebDataBackend* webdata_backend, const std::string& app_locale) : webdata_backend_(webdata_backend), app_locale_(app_locale), scoped_observer_(this) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK(webdata_backend_); scoped_observer_.Add(webdata_backend_); } AutofillProfileSyncableService::~AutofillProfileSyncableService() { DCHECK(CalledOnValidThread()); } // static void AutofillProfileSyncableService::CreateForWebDataServiceAndBackend( AutofillWebDataService* web_data_service, autofill::AutofillWebDataBackend* webdata_backend, const std::string& app_locale) { web_data_service->GetDBUserData()->SetUserData( UserDataKey(), new AutofillProfileSyncableService(webdata_backend, app_locale)); } // static AutofillProfileSyncableService* AutofillProfileSyncableService::FromWebDataService( AutofillWebDataService* web_data_service) { return static_cast( web_data_service->GetDBUserData()->GetUserData(UserDataKey())); } AutofillProfileSyncableService::AutofillProfileSyncableService() : webdata_backend_(NULL), scoped_observer_(this) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); } syncer::SyncMergeResult AutofillProfileSyncableService::MergeDataAndStartSyncing( syncer::ModelType type, const syncer::SyncDataList& initial_sync_data, scoped_ptr sync_processor, scoped_ptr sync_error_factory) { DCHECK(CalledOnValidThread()); DCHECK(!sync_processor_.get()); DCHECK(sync_processor.get()); DCHECK(sync_error_factory.get()); DVLOG(1) << "Associating Autofill: MergeDataAndStartSyncing"; syncer::SyncMergeResult merge_result(type); sync_error_factory_ = sync_error_factory.Pass(); if (!LoadAutofillData(&profiles_.get())) { merge_result.set_error(sync_error_factory_->CreateAndUploadError( FROM_HERE, "Could not get the autofill data from WebDatabase.")); return merge_result; } if (DLOG_IS_ON(INFO)) { DVLOG(2) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; for (ScopedVector::const_iterator ix = profiles_.begin(); ix != profiles_.end(); ++ix) { AutofillProfile* p = *ix; DVLOG(2) << "[AUTOFILL MIGRATION] " << p->GetRawInfo(autofill::NAME_FIRST) << p->GetRawInfo(autofill::NAME_LAST) << p->guid(); } } sync_processor_ = sync_processor.Pass(); GUIDToProfileMap remaining_profiles; CreateGUIDToProfileMap(profiles_.get(), &remaining_profiles); DataBundle bundle; // Go through and check for all the profiles that sync already knows about. for (syncer::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); // |it| points to created/updated profile. Add it to the |profiles_map_| and // then remove it from |remaining_profiles|. After this loop is completed // |remaining_profiles| will have only those profiles that are not in the // sync. profiles_map_[it->first] = it->second; remaining_profiles.erase(it); } // Check for similar unmatched profiles - they are created independently on // two systems, so merge them. for (GUIDToProfileMap::iterator it = bundle.candidates_to_merge.begin(); it != bundle.candidates_to_merge.end(); ++it) { GUIDToProfileMap::iterator profile_to_merge = remaining_profiles.find(it->first); if (profile_to_merge != remaining_profiles.end()) { bundle.profiles_to_delete.push_back(profile_to_merge->second->guid()); if (MergeProfile(*(profile_to_merge->second), it->second, app_locale_)) bundle.profiles_to_sync_back.push_back(it->second); DVLOG(2) << "[AUTOFILL SYNC]" << "Found similar profile in sync db but with a different guid: " << UTF16ToUTF8(it->second->GetRawInfo(autofill::NAME_FIRST)) << UTF16ToUTF8(it->second->GetRawInfo(autofill::NAME_LAST)) << "New guid " << it->second->guid() << ". Profile to be deleted " << profile_to_merge->second->guid(); remaining_profiles.erase(profile_to_merge); } } if (!SaveChangesToWebData(bundle)) { merge_result.set_error(sync_error_factory_->CreateAndUploadError( FROM_HERE, "Failed to update webdata.")); return merge_result; } syncer::SyncChangeList new_changes; for (GUIDToProfileMap::iterator i = remaining_profiles.begin(); i != remaining_profiles.end(); ++i) { new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, CreateData(*(i->second)))); profiles_map_[i->first] = i->second; } for (size_t i = 0; i < bundle.profiles_to_sync_back.size(); ++i) { new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, CreateData(*(bundle.profiles_to_sync_back[i])))); } if (!new_changes.empty()) { merge_result.set_error( sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); } if (webdata_backend_) webdata_backend_->NotifyOfMultipleAutofillChanges(); return merge_result; } void AutofillProfileSyncableService::StopSyncing(syncer::ModelType type) { DCHECK(CalledOnValidThread()); DCHECK_EQ(type, syncer::AUTOFILL_PROFILE); sync_processor_.reset(); sync_error_factory_.reset(); profiles_.clear(); profiles_map_.clear(); } syncer::SyncDataList AutofillProfileSyncableService::GetAllSyncData( syncer::ModelType type) const { DCHECK(CalledOnValidThread()); DCHECK(sync_processor_.get()); DCHECK_EQ(type, syncer::AUTOFILL_PROFILE); syncer::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; } syncer::SyncError AutofillProfileSyncableService::ProcessSyncChanges( const tracked_objects::Location& from_here, const syncer::SyncChangeList& change_list) { DCHECK(CalledOnValidThread()); if (!sync_processor_.get()) { syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Models not yet associated.", syncer::AUTOFILL_PROFILE); return error; } DataBundle bundle; for (syncer::SyncChangeList::const_iterator i = change_list.begin(); i != change_list.end(); ++i) { DCHECK(i->IsValid()); switch (i->change_type()) { case syncer::SyncChange::ACTION_ADD: case syncer::SyncChange::ACTION_UPDATE: CreateOrUpdateProfile(i->sync_data(), &profiles_map_, &bundle); break; case syncer::SyncChange::ACTION_DELETE: { std::string guid = i->sync_data().GetSpecifics(). autofill_profile().guid(); bundle.profiles_to_delete.push_back(guid); profiles_map_.erase(guid); } break; default: NOTREACHED() << "Unexpected sync change state."; return sync_error_factory_->CreateAndUploadError( FROM_HERE, "ProcessSyncChanges failed on ChangeType " + syncer::SyncChange::ChangeTypeToString(i->change_type())); } } if (!SaveChangesToWebData(bundle)) { return sync_error_factory_->CreateAndUploadError( FROM_HERE, "Failed to update webdata."); } if (webdata_backend_) webdata_backend_->NotifyOfMultipleAutofillChanges(); return syncer::SyncError(); } void AutofillProfileSyncableService::AutofillProfileChanged( const AutofillProfileChange& change) { // 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()) { ActOnChange(change); } else if (!flare_.is_null()) { flare_.Run(syncer::AUTOFILL_PROFILE); flare_.Reset(); } } bool AutofillProfileSyncableService::LoadAutofillData( std::vector* profiles) { return GetAutofillTable()->GetAutofillProfiles(profiles); } bool AutofillProfileSyncableService::SaveChangesToWebData( const DataBundle& bundle) { DCHECK(CalledOnValidThread()); autofill::AutofillTable* autofill_table = GetAutofillTable(); bool success = true; for (size_t i = 0; i< bundle.profiles_to_delete.size(); ++i) { if (!autofill_table->RemoveAutofillProfile(bundle.profiles_to_delete[i])) success = false; } for (size_t i = 0; i < bundle.profiles_to_add.size(); i++) { if (!autofill_table->AddAutofillProfile(*bundle.profiles_to_add[i])) success = false; } for (size_t i = 0; i < bundle.profiles_to_update.size(); i++) { if (!autofill_table->UpdateAutofillProfile(*bundle.profiles_to_update[i])) success = false; } return success; } // static bool AutofillProfileSyncableService::OverwriteProfileWithServerData( const sync_pb::AutofillProfileSpecifics& specifics, AutofillProfile* profile, const std::string& app_locale) { bool diff = false; if (profile->origin() != specifics.origin()) { bool was_verified = profile->IsVerified(); profile->set_origin(specifics.origin()); diff = true; // Verified profiles should never be overwritten by unverified ones. DCHECK(!was_verified || profile->IsVerified()); } diff = UpdateMultivaluedField(autofill::NAME_FIRST, specifics.name_first(), profile) || diff; diff = UpdateMultivaluedField(autofill::NAME_MIDDLE, specifics.name_middle(), profile) || diff; diff = UpdateMultivaluedField(autofill::NAME_LAST, specifics.name_last(), profile) || diff; diff = UpdateField(autofill::ADDRESS_HOME_LINE1, specifics.address_home_line1(), profile) || diff; diff = UpdateField(autofill::ADDRESS_HOME_LINE2, specifics.address_home_line2(), profile) || diff; diff = UpdateField(autofill::ADDRESS_HOME_CITY, specifics.address_home_city(), profile) || diff; diff = UpdateField(autofill::ADDRESS_HOME_STATE, specifics.address_home_state(), profile) || diff; string16 country_name_or_code = ASCIIToUTF16(specifics.address_home_country()); std::string country_code = AutofillCountry::GetCountryCode( country_name_or_code, app_locale); diff = UpdateField( autofill::ADDRESS_HOME_COUNTRY, country_code, profile) || diff; diff = UpdateField(autofill::ADDRESS_HOME_ZIP, specifics.address_home_zip(), profile) || diff; diff = UpdateMultivaluedField(autofill::EMAIL_ADDRESS, specifics.email_address(), profile) || diff; diff = UpdateField( autofill::COMPANY_NAME, specifics.company_name(), profile) || diff; diff = UpdateMultivaluedField(autofill::PHONE_HOME_WHOLE_NUMBER, specifics.phone_home_whole_number(), profile) || diff; return diff; } // static void AutofillProfileSyncableService::WriteAutofillProfile( const AutofillProfile& profile, sync_pb::EntitySpecifics* profile_specifics) { sync_pb::AutofillProfileSpecifics* specifics = profile_specifics->mutable_autofill_profile(); DCHECK(base::IsValidGUID(profile.guid())); // Reset all multi-valued fields in the protobuf. specifics->clear_name_first(); specifics->clear_name_middle(); specifics->clear_name_last(); specifics->clear_email_address(); specifics->clear_phone_home_whole_number(); specifics->set_guid(profile.guid()); specifics->set_origin(profile.origin()); std::vector values; profile.GetRawMultiInfo(autofill::NAME_FIRST, &values); for (size_t i = 0; i < values.size(); ++i) { specifics->add_name_first(LimitData(UTF16ToUTF8(values[i]))); } profile.GetRawMultiInfo(autofill::NAME_MIDDLE, &values); for (size_t i = 0; i < values.size(); ++i) { specifics->add_name_middle(LimitData(UTF16ToUTF8(values[i]))); } profile.GetRawMultiInfo(autofill::NAME_LAST, &values); for (size_t i = 0; i < values.size(); ++i) { specifics->add_name_last(LimitData(UTF16ToUTF8(values[i]))); } specifics->set_address_home_line1( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_LINE1)))); specifics->set_address_home_line2( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_LINE2)))); specifics->set_address_home_city( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_CITY)))); specifics->set_address_home_state( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_STATE)))); specifics->set_address_home_country( LimitData( UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)))); specifics->set_address_home_zip( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_ZIP)))); profile.GetRawMultiInfo(autofill::EMAIL_ADDRESS, &values); for (size_t i = 0; i < values.size(); ++i) { specifics->add_email_address(LimitData(UTF16ToUTF8(values[i]))); } specifics->set_company_name( LimitData(UTF16ToUTF8(profile.GetRawInfo(autofill::COMPANY_NAME)))); profile.GetRawMultiInfo(autofill::PHONE_HOME_WHOLE_NUMBER, &values); for (size_t i = 0; i < values.size(); ++i) { specifics->add_phone_home_whole_number(LimitData(UTF16ToUTF8(values[i]))); } } 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 syncer::SyncData& data, GUIDToProfileMap* profile_map, DataBundle* bundle) { DCHECK(profile_map); DCHECK(bundle); DCHECK_EQ(syncer::AUTOFILL_PROFILE, data.GetDataType()); const sync_pb::EntitySpecifics& specifics = data.GetSpecifics(); const sync_pb::AutofillProfileSpecifics& autofill_specifics( specifics.autofill_profile()); GUIDToProfileMap::iterator existing_profile = profile_map->find( autofill_specifics.guid()); if (existing_profile != profile_map->end()) { // The synced profile already exists locally. It might need to be updated. if (OverwriteProfileWithServerData( autofill_specifics, existing_profile->second, app_locale_)) { bundle->profiles_to_update.push_back(existing_profile->second); } return existing_profile; } // New profile synced. AutofillProfile* new_profile = new AutofillProfile( autofill_specifics.guid(), autofill_specifics.origin()); OverwriteProfileWithServerData(autofill_specifics, new_profile, app_locale_); // Check if profile appears under a different guid. // Unverified profiles should never overwrite verified ones. for (GUIDToProfileMap::iterator it = profile_map->begin(); it != profile_map->end(); ++it) { AutofillProfile* local_profile = it->second; if (local_profile->Compare(*new_profile) == 0) { // Ensure that a verified profile can never revert back to an unverified // one. if (local_profile->IsVerified() && !new_profile->IsVerified()) { new_profile->set_origin(local_profile->origin()); bundle->profiles_to_sync_back.push_back(new_profile); } bundle->profiles_to_delete.push_back(local_profile->guid()); DVLOG(2) << "[AUTOFILL SYNC]" << "Found in sync db but with a different guid: " << UTF16ToUTF8(local_profile->GetRawInfo(autofill::NAME_FIRST)) << UTF16ToUTF8(local_profile->GetRawInfo(autofill::NAME_LAST)) << "New guid " << new_profile->guid() << ". Profile to be deleted " << local_profile->guid(); profile_map->erase(it); break; } else if (!local_profile->IsVerified() && !new_profile->IsVerified() && !local_profile->PrimaryValue().empty() && local_profile->PrimaryValue() == new_profile->PrimaryValue()) { // Add it to candidates for merge - if there is no profile with this // guid we will merge them. bundle->candidates_to_merge.insert( std::make_pair(local_profile->guid(), new_profile)); } } profiles_.push_back(new_profile); bundle->profiles_to_add.push_back(new_profile); return profile_map->insert(std::make_pair(new_profile->guid(), new_profile)).first; } void AutofillProfileSyncableService::ActOnChange( const AutofillProfileChange& change) { DCHECK((change.type() == AutofillProfileChange::REMOVE && !change.profile()) || (change.type() != AutofillProfileChange::REMOVE && change.profile())); DCHECK(sync_processor_.get()); syncer::SyncChangeList new_changes; DataBundle bundle; switch (change.type()) { case AutofillProfileChange::ADD: new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::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( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, CreateData(*(change.profile())))); break; } case AutofillProfileChange::REMOVE: { AutofillProfile empty_profile(change.key(), std::string()); new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_DELETE, CreateData(empty_profile))); profiles_map_.erase(change.key()); break; } default: NOTREACHED(); } syncer::SyncError error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); if (error.IsSet()) { // TODO(isherman): Investigating http://crbug.com/121592 VLOG(1) << "[AUTOFILL SYNC] " << "Failed processing change:\n" << " Error: " << error.message() << "\n" << " Guid: " << change.key(); } } syncer::SyncData AutofillProfileSyncableService::CreateData( const AutofillProfile& profile) { sync_pb::EntitySpecifics specifics; WriteAutofillProfile(profile, &specifics); return syncer::SyncData::CreateLocalData( profile.guid(), profile.guid(), specifics); } bool AutofillProfileSyncableService::UpdateField( ServerFieldType field_type, const std::string& new_value, AutofillProfile* autofill_profile) { if (UTF16ToUTF8(autofill_profile->GetRawInfo(field_type)) == new_value) return false; autofill_profile->SetRawInfo(field_type, UTF8ToUTF16(new_value)); return true; } bool AutofillProfileSyncableService::UpdateMultivaluedField( ServerFieldType field_type, const ::google::protobuf::RepeatedPtrField& new_values, AutofillProfile* autofill_profile) { std::vector values; autofill_profile->GetRawMultiInfo(field_type, &values); bool changed = false; if (static_cast(new_values.size()) != values.size()) { values.clear(); values.resize(static_cast(new_values.size())); changed = true; } for (size_t i = 0; i < values.size(); ++i) { string16 synced_value( UTF8ToUTF16(new_values.Get(static_cast(i)))); if (values[i] != synced_value) { values[i] = synced_value; changed = true; } } if (changed) autofill_profile->SetRawMultiInfo(field_type, values); return changed; } bool AutofillProfileSyncableService::MergeProfile( const AutofillProfile& merge_from, AutofillProfile* merge_into, const std::string& app_locale) { merge_into->OverwriteWithOrAddTo(merge_from, app_locale); return (merge_into->Compare(merge_from) != 0 || merge_into->origin() != merge_from.origin()); } AutofillTable* AutofillProfileSyncableService::GetAutofillTable() const { return AutofillTable::FromWebDatabase(webdata_backend_->GetDatabase()); } void AutofillProfileSyncableService::InjectStartSyncFlare( const syncer::SyncableService::StartSyncFlare& flare) { flare_ = flare; } AutofillProfileSyncableService::DataBundle::DataBundle() {} AutofillProfileSyncableService::DataBundle::~DataBundle() {}