// 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_change_processor.h" #include <string> #include <vector> #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/glue/autofill_model_associator.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/webdata/autofill_change.h" #include "chrome/browser/webdata/web_data_service.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/guid.h" #include "content/common/notification_service.h" namespace browser_sync { struct AutofillChangeProcessor::AutofillChangeRecord { sync_api::SyncManager::ChangeRecord::Action action_; int64 id_; sync_pb::AutofillSpecifics autofill_; AutofillChangeRecord(sync_api::SyncManager::ChangeRecord::Action action, int64 id, const sync_pb::AutofillSpecifics& autofill) : action_(action), id_(id), autofill_(autofill) { } }; AutofillChangeProcessor::AutofillChangeProcessor( AutofillModelAssociator* model_associator, WebDatabase* web_database, PersonalDataManager* personal_data, UnrecoverableErrorHandler* error_handler) : ChangeProcessor(error_handler), model_associator_(model_associator), web_database_(web_database), personal_data_(personal_data), observing_(false) { DCHECK(model_associator); DCHECK(web_database); DCHECK(error_handler); DCHECK(personal_data); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); StartObserving(); } AutofillChangeProcessor::~AutofillChangeProcessor() {} void AutofillChangeProcessor::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { // Ensure this notification came from our web database. WebDataService* wds = Source<WebDataService>(source).ptr(); if (!wds || wds->GetDatabase() != web_database_) return; DCHECK(running()); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!observing_) return; sync_api::WriteTransaction trans(share_handle()); sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(kAutofillTag)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Server did not create the top-level autofill node. " "We might be running against an out-of-date server."); return; } DCHECK(type.value == NotificationType::AUTOFILL_ENTRIES_CHANGED); AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr(); ObserveAutofillEntriesChanged(changes, &trans, autofill_root); } void AutofillChangeProcessor::PostOptimisticRefreshTask() { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, new DoOptimisticRefreshForAutofill( personal_data_)); } void AutofillChangeProcessor::ObserveAutofillEntriesChanged( AutofillChangeList* changes, sync_api::WriteTransaction* trans, const sync_api::ReadNode& autofill_root) { for (AutofillChangeList::iterator change = changes->begin(); change != changes->end(); ++change) { switch (change->type()) { case AutofillChange::ADD: { sync_api::WriteNode sync_node(trans); std::string tag = AutofillModelAssociator::KeyToTag(change->key().name(), change->key().value()); if (!sync_node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Failed to create autofill sync node."); return; } std::vector<base::Time> timestamps; if (!web_database_->GetAutofillTable()->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Failed to get timestamps."); return; } sync_node.SetTitle(UTF8ToWide(tag)); WriteAutofillEntry(AutofillEntry(change->key(), timestamps), &sync_node); model_associator_->Associate(&tag, sync_node.GetId()); } break; case AutofillChange::UPDATE: { sync_api::WriteNode sync_node(trans); std::string tag = AutofillModelAssociator::KeyToTag( change->key().name(), change->key().value()); int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); if (sync_api::kInvalidId == sync_id) { std::string err = "Unexpected notification for: " + UTF16ToUTF8(change->key().name()); error_handler()->OnUnrecoverableError(FROM_HERE, err); return; } else { if (!sync_node.InitByIdLookup(sync_id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Autofill node lookup failed."); return; } } std::vector<base::Time> timestamps; if (!web_database_->GetAutofillTable()->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Failed to get timestamps."); return; } WriteAutofillEntry(AutofillEntry(change->key(), timestamps), &sync_node); } break; case AutofillChange::REMOVE: { std::string tag = AutofillModelAssociator::KeyToTag( change->key().name(), change->key().value()); RemoveSyncNode(tag, trans); } break; } } } void AutofillChangeProcessor::RemoveSyncNode(const std::string& tag, sync_api::WriteTransaction* trans) { sync_api::WriteNode sync_node(trans); int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); if (sync_api::kInvalidId == sync_id) { // This could happen because web db might have duplicates and when an entry // and its duplicate is deleted. LOG(WARNING) << "Bogus delete notification generate for autofill entry " + tag; return; } else { if (!sync_node.InitByIdLookup(sync_id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Autofill node lookup failed."); return; } model_associator_->Disassociate(sync_node.GetId()); sync_node.Remove(); } } void AutofillChangeProcessor::ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, int change_count) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!running()) return; StopObserving(); bool autofill_profile_not_migrated = HasNotMigratedYet(trans); sync_api::ReadNode autofill_root(trans); if (!autofill_root.InitByTagLookup(kAutofillTag)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Autofill root node lookup failed."); return; } for (int i = 0; i < change_count; ++i) { sync_api::SyncManager::ChangeRecord::Action action(changes[i].action); if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { DCHECK(changes[i].specifics.HasExtension(sync_pb::autofill)) << "Autofill specifics data not present on delete!"; const sync_pb::AutofillSpecifics& autofill = changes[i].specifics.GetExtension(sync_pb::autofill); if (autofill.has_value() || (autofill_profile_not_migrated && autofill.has_profile())) { autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, changes[i].id, autofill)); } else { NOTREACHED() << "Autofill specifics has no data!"; } continue; } // Handle an update or add. sync_api::ReadNode sync_node(trans); if (!sync_node.InitByIdLookup(changes[i].id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Autofill node lookup failed."); return; } // Check that the changed node is a child of the autofills folder. DCHECK(autofill_root.GetId() == sync_node.GetParentId()); DCHECK(syncable::AUTOFILL == sync_node.GetModelType()); const sync_pb::AutofillSpecifics& autofill( sync_node.GetAutofillSpecifics()); int64 sync_id = sync_node.GetId(); if (autofill.has_value() || (autofill_profile_not_migrated && autofill.has_profile())) { autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, sync_id, autofill)); } else { NOTREACHED() << "Autofill specifics has no data!"; } } StartObserving(); } void AutofillChangeProcessor::CommitChangesFromSyncModel() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!running()) return; StopObserving(); std::vector<AutofillEntry> new_entries; for (unsigned int i = 0; i < autofill_changes_.size(); i++) { // Handle deletions. if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == autofill_changes_[i].action_) { if (autofill_changes_[i].autofill_.has_value()) { ApplySyncAutofillEntryDelete(autofill_changes_[i].autofill_); } else if (autofill_changes_[i].autofill_.has_profile()) { ApplySyncAutofillProfileDelete(autofill_changes_[i].id_); } else { NOTREACHED() << "Autofill's CommitChanges received change with no" " data!"; } continue; } // Handle update/adds. if (autofill_changes_[i].autofill_.has_value()) { ApplySyncAutofillEntryChange(autofill_changes_[i].action_, autofill_changes_[i].autofill_, &new_entries, autofill_changes_[i].id_); } else if (autofill_changes_[i].autofill_.has_profile()) { ApplySyncAutofillProfileChange(autofill_changes_[i].action_, autofill_changes_[i].autofill_.profile(), autofill_changes_[i].id_); } else { NOTREACHED() << "Autofill's CommitChanges received change with no data!"; } } autofill_changes_.clear(); // Make changes if (!web_database_->GetAutofillTable()->UpdateAutofillEntries(new_entries)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Could not update autofill entries."); return; } PostOptimisticRefreshTask(); StartObserving(); } void AutofillChangeProcessor::ApplySyncAutofillEntryDelete( const sync_pb::AutofillSpecifics& autofill) { if (!web_database_->GetAutofillTable()->RemoveFormElement( UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { error_handler()->OnUnrecoverableError(FROM_HERE, "Could not remove autofill node."); return; } } void AutofillChangeProcessor::ApplySyncAutofillEntryChange( sync_api::SyncManager::ChangeRecord::Action action, const sync_pb::AutofillSpecifics& autofill, std::vector<AutofillEntry>* new_entries, int64 sync_id) { DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action); std::vector<base::Time> timestamps; size_t timestamps_size = autofill.usage_timestamp_size(); for (size_t c = 0; c < timestamps_size; ++c) { timestamps.push_back( base::Time::FromInternalValue(autofill.usage_timestamp(c))); } AutofillKey k(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value())); AutofillEntry new_entry(k, timestamps); new_entries->push_back(new_entry); std::string tag(AutofillModelAssociator::KeyToTag(k.name(), k.value())); if (action == sync_api::SyncManager::ChangeRecord::ACTION_ADD) model_associator_->Associate(&tag, sync_id); } void AutofillChangeProcessor::ApplySyncAutofillProfileChange( sync_api::SyncManager::ChangeRecord::Action action, const sync_pb::AutofillProfileSpecifics& profile, int64 sync_id) { DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action); switch (action) { case sync_api::SyncManager::ChangeRecord::ACTION_ADD: { std::string guid(guid::GenerateGUID()); if (guid::IsValidGUID(guid) == false) { DCHECK(false) << "Guid generated is invalid " << guid; return; } scoped_ptr<AutofillProfile> p(new AutofillProfile); p->set_guid(guid); AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); if (!web_database_->GetAutofillTable()->AddAutofillProfile(*p.get())) { NOTREACHED() << "Couldn't add autofill profile: " << guid; return; } model_associator_->Associate(&guid, sync_id); break; } case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: { const std::string* guid = model_associator_->GetChromeNodeFromSyncId( sync_id); if (guid == NULL) { LOG(ERROR) << " Model association has not happened for " << sync_id; error_handler()->OnUnrecoverableError(FROM_HERE, "model association has not happened"); return; } AutofillProfile *temp_ptr; if (!web_database_->GetAutofillTable()->GetAutofillProfile( *guid, &temp_ptr)) { LOG(ERROR) << "Autofill profile not found for " << *guid; return; } scoped_ptr<AutofillProfile> p(temp_ptr); AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( *(p.get()))) { LOG(ERROR) << "Couldn't update autofill profile: " << guid; return; } break; } default: NOTREACHED(); } } void AutofillChangeProcessor::ApplySyncAutofillProfileDelete( int64 sync_id) { const std::string *guid = model_associator_->GetChromeNodeFromSyncId(sync_id); if (guid == NULL) { LOG(ERROR)<< "The profile is not associated"; return; } if (!web_database_->GetAutofillTable()->RemoveAutofillProfile(*guid)) { LOG(ERROR) << "Could not remove the profile"; return; } model_associator_->Disassociate(sync_id); } void AutofillChangeProcessor::StartImpl(Profile* profile) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); observing_ = true; } void AutofillChangeProcessor::StopImpl() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); observing_ = false; } void AutofillChangeProcessor::StartObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED, NotificationService::AllSources()); } void AutofillChangeProcessor::StopObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); notification_registrar_.RemoveAll(); } // static void AutofillChangeProcessor::WriteAutofillEntry( const AutofillEntry& entry, sync_api::WriteNode* node) { sync_pb::AutofillSpecifics 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()); } node->SetAutofillSpecifics(autofill); } bool AutofillChangeProcessor::HasNotMigratedYet( const sync_api::BaseTransaction* trans) { return model_associator_->HasNotMigratedYet(trans); } } // namespace browser_sync