// 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/sync/glue/autofill_model_associator.h" #include <functional> #include <vector> #include "base/string_number_conversions.h" #include "base/task.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" #include "chrome/browser/sync/glue/autofill_profile_model_associator.h" #include "chrome/browser/sync/glue/do_optimistic_refresh_task.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/guid.h" #include "content/browser/browser_thread.h" #include "net/base/escape.h" using base::TimeTicks; namespace browser_sync { const char kAutofillTag[] = "google_chrome_autofill"; const char kAutofillEntryNamespaceTag[] = "autofill_entry|"; struct AutofillModelAssociator::DataBundle { std::set<AutofillKey> current_entries; std::vector<AutofillEntry> new_entries; std::set<string16> current_profiles; std::vector<AutofillProfile*> updated_profiles; std::vector<AutofillProfile*> new_profiles; // We own these pointers. ~DataBundle() { STLDeleteElements(&new_profiles); } }; AutofillModelAssociator::AutofillModelAssociator( ProfileSyncService* sync_service, WebDatabase* web_database, PersonalDataManager* personal_data) : sync_service_(sync_service), web_database_(web_database), personal_data_(personal_data), autofill_node_id_(sync_api::kInvalidId), abort_association_pending_(false), number_of_entries_created_(0) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK(sync_service_); DCHECK(web_database_); DCHECK(personal_data_); } AutofillModelAssociator::~AutofillModelAssociator() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); } bool AutofillModelAssociator::TraverseAndAssociateChromeAutofillEntries( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, const std::vector<AutofillEntry>& all_entries_from_db, std::set<AutofillKey>* current_entries, std::vector<AutofillEntry>* new_entries) { const std::vector<AutofillEntry>& entries = all_entries_from_db; for (std::vector<AutofillEntry>::const_iterator ix = entries.begin(); ix != entries.end(); ++ix) { std::string tag = KeyToTag(ix->key().name(), ix->key().value()); if (id_map_.find(tag) != id_map_.end()) { // It seems that name/value pairs are not unique in the web database. // As a result, we have to filter out duplicates here. This is probably // a bug in the database. continue; } sync_api::ReadNode node(write_trans); if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics()); DCHECK_EQ(tag, KeyToTag(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))); std::vector<base::Time> timestamps; if (MergeTimestamps(autofill, ix->timestamps(), ×tamps)) { AutofillEntry new_entry(ix->key(), timestamps); new_entries->push_back(new_entry); sync_api::WriteNode write_node(write_trans); if (!write_node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { LOG(ERROR) << "Failed to write autofill sync node."; return false; } AutofillChangeProcessor::WriteAutofillEntry(new_entry, &write_node); } Associate(&tag, node.GetId()); } else { sync_api::WriteNode node(write_trans); if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) { LOG(ERROR) << "Failed to create autofill sync node."; return false; } node.SetTitle(UTF8ToWide(tag)); AutofillChangeProcessor::WriteAutofillEntry(*ix, &node); Associate(&tag, node.GetId()); number_of_entries_created_++; } current_entries->insert(ix->key()); } return true; } bool AutofillModelAssociator::LoadAutofillData( std::vector<AutofillEntry>* entries, std::vector<AutofillProfile*>* profiles) { if (IsAbortPending()) return false; if (!web_database_->GetAutofillTable()->GetAllAutofillEntries(entries)) return false; if (IsAbortPending()) return false; if (!web_database_->GetAutofillTable()->GetAutofillProfiles(profiles)) return false; return true; } bool AutofillModelAssociator::AssociateModels() { VLOG(1) << "Associating Autofill Models"; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); { base::AutoLock lock(abort_association_pending_lock_); abort_association_pending_ = false; } // TODO(zork): Attempt to load the model association from storage. std::vector<AutofillEntry> entries; ScopedVector<AutofillProfile> profiles; if (!LoadAutofillData(&entries, &profiles.get())) { LOG(ERROR) << "Could not get the autofill data from WebDatabase."; return false; } DataBundle bundle; { sync_api::WriteTransaction trans(sync_service_->GetUserShare()); sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(kAutofillTag)) { LOG(ERROR) << "Server did not create the top-level autofill node. We " << "might be running against an out-of-date server."; return false; } if (!TraverseAndAssociateChromeAutofillEntries(&trans, autofill_root, entries, &bundle.current_entries, &bundle.new_entries)) { return false; } if (!TraverseAndAssociateAllSyncNodes( &trans, autofill_root, &bundle, profiles.get())) { return false; } } // Since we're on the DB thread, we don't have to worry about updating // the autofill database after closing the write transaction, since // this is the only thread that writes to the database. We also don't have // to worry about the sync model getting out of sync, because changes are // propagated to the ChangeProcessor on this thread. if (!SaveChangesToWebData(bundle)) { LOG(ERROR) << "Failed to update autofill entries."; return false; } if (sync_service_->GetAutofillMigrationState() != syncable::MIGRATED) { syncable::AutofillMigrationDebugInfo debug_info; debug_info.autofill_entries_added_during_migration = number_of_entries_created_; sync_service_->SetAutofillMigrationDebugInfo( syncable::AutofillMigrationDebugInfo::ENTRIES_ADDED, debug_info); } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, new DoOptimisticRefreshForAutofill(personal_data_)); return true; } bool AutofillModelAssociator::SaveChangesToWebData(const DataBundle& bundle) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (IsAbortPending()) return false; if (bundle.new_entries.size() && !web_database_->GetAutofillTable()->UpdateAutofillEntries( bundle.new_entries)) { return false; } for (size_t i = 0; i < bundle.new_profiles.size(); i++) { if (IsAbortPending()) return false; if (!web_database_->GetAutofillTable()->AddAutofillProfile( *bundle.new_profiles[i])) return false; } for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { if (IsAbortPending()) return false; if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( *bundle.updated_profiles[i])) return false; } return true; } bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, DataBundle* bundle, const std::vector<AutofillProfile*>& all_profiles_from_db) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); bool autofill_profile_not_migrated = HasNotMigratedYet(write_trans); if (VLOG_IS_ON(2) && autofill_profile_not_migrated) { VLOG(2) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; for (std::vector<AutofillProfile*>::const_iterator ix = all_profiles_from_db.begin(); ix != all_profiles_from_db.end(); ++ix) { AutofillProfile* p = *ix; VLOG(2) << "[AUTOFILL MIGRATION] " << p->GetInfo(NAME_FIRST) << p->GetInfo(NAME_LAST); } } if (autofill_profile_not_migrated) { VLOG(1) << "[AUTOFILL MIGRATION]" << "Iterating over sync db"; } int64 sync_child_id = autofill_root.GetFirstChildId(); while (sync_child_id != sync_api::kInvalidId) { sync_api::ReadNode sync_child(write_trans); if (!sync_child.InitByIdLookup(sync_child_id)) { LOG(ERROR) << "Failed to fetch child node."; return false; } const sync_pb::AutofillSpecifics& autofill( sync_child.GetAutofillSpecifics()); if (autofill.has_value()) { AddNativeEntryIfNeeded(autofill, bundle, sync_child); } else if (autofill.has_profile()) { // Ignore autofill profiles if we are not upgrading. if (autofill_profile_not_migrated) { VLOG(2) << "[AUTOFILL MIGRATION] Looking for " << autofill.profile().name_first() << autofill.profile().name_last(); AddNativeProfileIfNeeded( autofill.profile(), bundle, sync_child, all_profiles_from_db); } } else { NOTREACHED() << "AutofillSpecifics has no autofill data!"; } sync_child_id = sync_child.GetSuccessorId(); } return true; } // Define the functor to be used as the predicate in find_if call. struct CompareProfiles : public std::binary_function<AutofillProfile*, AutofillProfile*, bool> { bool operator() (AutofillProfile* p1, AutofillProfile* p2) const { if (p1->Compare(*p2) == 0) return true; else return false; } }; AutofillProfile* AutofillModelAssociator::FindCorrespondingNodeFromWebDB( const sync_pb::AutofillProfileSpecifics& profile, const std::vector<AutofillProfile*>& all_profiles_from_db) { static std::string guid(guid::GenerateGUID()); AutofillProfile p; p.set_guid(guid); if (!FillProfileWithServerData(&p, profile)) { // Not a big deal. We encountered an error. Just say this profile does not // exist. LOG(ERROR) << " Profile could not be associated"; return NULL; } // Now instantiate the functor and call find_if. std::vector<AutofillProfile*>::const_iterator ix = std::find_if(all_profiles_from_db.begin(), all_profiles_from_db.end(), std::bind2nd(CompareProfiles(), &p)); return (ix == all_profiles_from_db.end()) ? NULL : *ix; } void AutofillModelAssociator::AddNativeEntryIfNeeded( const sync_pb::AutofillSpecifics& autofill, DataBundle* bundle, const sync_api::ReadNode& node) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); AutofillKey key(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value())); if (bundle->current_entries.find(key) == bundle->current_entries.end()) { std::vector<base::Time> timestamps; int timestamps_count = autofill.usage_timestamp_size(); for (int c = 0; c < timestamps_count; ++c) { timestamps.push_back(base::Time::FromInternalValue( autofill.usage_timestamp(c))); } std::string tag(KeyToTag(key.name(), key.value())); Associate(&tag, node.GetId()); bundle->new_entries.push_back(AutofillEntry(key, timestamps)); } } void AutofillModelAssociator::AddNativeProfileIfNeeded( const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, const sync_api::ReadNode& node, const std::vector<AutofillProfile*>& all_profiles_from_db) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); AutofillProfile* profile_in_web_db = FindCorrespondingNodeFromWebDB( profile, all_profiles_from_db); if (profile_in_web_db != NULL) { VLOG(1) << "[AUTOFILL MIGRATION]" << "Node found in web db. So associating"; int64 sync_id = node.GetId(); std::string guid = profile_in_web_db->guid(); Associate(&guid, sync_id); return; } else { // Create a new node. VLOG(1) << "[AUTOFILL MIGRATION]" << "Node not found in web db so creating and associating"; std::string guid = guid::GenerateGUID(); if (guid::IsValidGUID(guid) == false) { DCHECK(false) << "Guid generated is invalid " << guid; return; } Associate(&guid, node.GetId()); AutofillProfile* p = new AutofillProfile(guid); FillProfileWithServerData(p, profile); bundle->new_profiles.push_back(p); } } bool AutofillModelAssociator::DisassociateModels() { id_map_.clear(); id_map_inverse_.clear(); return true; } bool AutofillModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { DCHECK(has_nodes); *has_nodes = false; int64 autofill_sync_id; if (!GetSyncIdForTaggedNode(kAutofillTag, &autofill_sync_id)) { LOG(ERROR) << "Server did not create the top-level autofill node. We " << "might be running against an out-of-date server."; return false; } sync_api::ReadTransaction trans(sync_service_->GetUserShare()); sync_api::ReadNode autofill_node(&trans); if (!autofill_node.InitByIdLookup(autofill_sync_id)) { LOG(ERROR) << "Server did not create the top-level autofill node. We " << "might be running against an out-of-date server."; return false; } // The sync model has user created nodes if the autofill folder has any // children. *has_nodes = sync_api::kInvalidId != autofill_node.GetFirstChildId(); return true; } void AutofillModelAssociator::AbortAssociation() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); base::AutoLock lock(abort_association_pending_lock_); abort_association_pending_ = true; } const std::string* AutofillModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) { SyncIdToAutofillMap::const_iterator iter = id_map_inverse_.find(sync_id); return iter == id_map_inverse_.end() ? NULL : &(iter->second); } bool AutofillModelAssociator::InitSyncNodeFromChromeId( const std::string& node_id, sync_api::BaseNode* sync_node) { return false; } int64 AutofillModelAssociator::GetSyncIdFromChromeId( const std::string& autofill) { AutofillToSyncIdMap::const_iterator iter = id_map_.find(autofill); return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; } void AutofillModelAssociator::Associate( const std::string* autofill, int64 sync_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK_NE(sync_api::kInvalidId, sync_id); DCHECK(id_map_.find(*autofill) == id_map_.end()); DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end()); id_map_[*autofill] = sync_id; id_map_inverse_[sync_id] = *autofill; } void AutofillModelAssociator::Disassociate(int64 sync_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id); if (iter == id_map_inverse_.end()) return; CHECK(id_map_.erase(iter->second)); id_map_inverse_.erase(iter); } bool AutofillModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { sync_api::ReadTransaction trans(sync_service_->GetUserShare()); sync_api::ReadNode sync_node(&trans); if (!sync_node.InitByTagLookup(tag.c_str())) return false; *sync_id = sync_node.GetId(); return true; } bool AutofillModelAssociator::IsAbortPending() { base::AutoLock lock(abort_association_pending_lock_); return abort_association_pending_; } // static std::string AutofillModelAssociator::KeyToTag(const string16& name, const string16& value) { std::string ns(kAutofillEntryNamespaceTag); return ns + EscapePath(UTF16ToUTF8(name)) + "|" + EscapePath(UTF16ToUTF8(value)); } // static bool AutofillModelAssociator::MergeTimestamps( const sync_pb::AutofillSpecifics& autofill, const std::vector<base::Time>& timestamps, std::vector<base::Time>* new_timestamps) { DCHECK(new_timestamps); std::set<base::Time> timestamp_union(timestamps.begin(), timestamps.end()); size_t timestamps_count = autofill.usage_timestamp_size(); bool different = timestamps.size() != timestamps_count; for (size_t c = 0; c < timestamps_count; ++c) { if (timestamp_union.insert(base::Time::FromInternalValue( autofill.usage_timestamp(c))).second) { different = true; } } if (different) { new_timestamps->insert(new_timestamps->begin(), timestamp_union.begin(), timestamp_union.end()); } return different; } // 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* f, AutofillFieldType t, const std::string& specifics_field) { if (UTF16ToUTF8(f->GetInfo(t)) == specifics_field) return false; f->SetInfo(t, UTF8ToUTF16(specifics_field)); return true; } // static bool AutofillModelAssociator::FillProfileWithServerData( AutofillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics) { bool diff = false; AutofillProfile* p = merge_into; const sync_pb::AutofillProfileSpecifics& s(specifics); diff = MergeField(p, NAME_FIRST, s.name_first()) || diff; diff = MergeField(p, NAME_LAST, s.name_last()) || diff; diff = MergeField(p, NAME_MIDDLE, s.name_middle()) || diff; diff = MergeField(p, ADDRESS_HOME_LINE1, s.address_home_line1()) || diff; diff = MergeField(p, ADDRESS_HOME_LINE2, s.address_home_line2()) || diff; diff = MergeField(p, ADDRESS_HOME_CITY, s.address_home_city()) || diff; diff = MergeField(p, ADDRESS_HOME_STATE, s.address_home_state()) || diff; diff = MergeField(p, ADDRESS_HOME_COUNTRY, s.address_home_country()) || diff; diff = MergeField(p, ADDRESS_HOME_ZIP, s.address_home_zip()) || diff; diff = MergeField(p, EMAIL_ADDRESS, s.email_address()) || diff; diff = MergeField(p, COMPANY_NAME, s.company_name()) || diff; diff = MergeField(p, PHONE_FAX_WHOLE_NUMBER, s.phone_fax_whole_number()) || diff; diff = MergeField(p, PHONE_HOME_WHOLE_NUMBER, s.phone_home_whole_number()) || diff; return diff; } bool AutofillModelAssociator::HasNotMigratedYet( const sync_api::BaseTransaction* trans) { // Now read the current value from the directory. syncable::AutofillMigrationState autofill_migration_state = sync_service_->GetAutofillMigrationState(); DCHECK_NE(autofill_migration_state, syncable::NOT_DETERMINED); if (autofill_migration_state== syncable::NOT_DETERMINED) { VLOG(1) << "Autofill migration state is not determined inside " << " model associator"; } if (autofill_migration_state == syncable::NOT_MIGRATED) { return true; } if (autofill_migration_state == syncable::INSUFFICIENT_INFO_TO_DETERMINE) { VLOG(1) << "[AUTOFILL MIGRATION]" << "current autofill migration state is insufficient info to" << "determine."; sync_api::ReadNode autofill_profile_root_node(trans); if (!autofill_profile_root_node.InitByTagLookup( browser_sync::kAutofillProfileTag) || autofill_profile_root_node.GetFirstChildId()== static_cast<int64>(0)) { sync_service_->SetAutofillMigrationState( syncable::NOT_MIGRATED); VLOG(1) << "[AUTOFILL MIGRATION]" << "Current autofill migration state is NOT Migrated because" << "legacy autofill root node is present whereas new " << "Autofill profile root node is absent."; return true; } sync_service_->SetAutofillMigrationState(syncable::MIGRATED); VLOG(1) << "[AUTOFILL MIGRATION]" << "Current autofill migration state is migrated."; } return false; } bool AutofillModelAssociator::CryptoReadyIfNecessary() { // We only access the cryptographer while holding a transaction. sync_api::ReadTransaction trans(sync_service_->GetUserShare()); syncable::ModelTypeSet encrypted_types; sync_service_->GetEncryptedDataTypes(&encrypted_types); return encrypted_types.count(syncable::AUTOFILL) == 0 || sync_service_->IsCryptographerReady(&trans); } } // namespace browser_sync