// Copyright 2013 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 "components/autofill/core/browser/personal_data_manager.h" #include #include #include #include #include #include #include "base/i18n/case_conversion.h" #include "base/i18n/timezone.h" #include "base/profiler/scoped_tracker.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "components/autofill/core/browser/address_i18n.h" #include "components/autofill/core/browser/autofill-inl.h" #include "components/autofill/core/browser/autofill_country.h" #include "components/autofill/core/browser/autofill_experiments.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/autofill_metrics.h" #include "components/autofill/core/browser/country_data.h" #include "components/autofill/core/browser/country_names.h" #include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/personal_data_manager_observer.h" #include "components/autofill/core/browser/phone_number.h" #include "components/autofill/core/browser/phone_number_i18n.h" #include "components/autofill/core/browser/validation.h" #include "components/autofill/core/common/autofill_pref_names.h" #include "components/autofill/core/common/autofill_switches.h" #include "components/autofill/core/common/autofill_util.h" #include "components/prefs/pref_service.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/signin_manager.h" #include "components/signin/core/common/signin_pref_names.h" #include "components/variations/variations_associated_data.h" #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h" #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h" namespace autofill { namespace { using ::i18n::addressinput::AddressField; using ::i18n::addressinput::GetStreetAddressLinesAsSingleLine; using ::i18n::addressinput::STREET_ADDRESS; template class FormGroupMatchesByGUIDFunctor { public: explicit FormGroupMatchesByGUIDFunctor(const std::string& guid) : guid_(guid) { } bool operator()(const T& form_group) { return form_group.guid() == guid_; } bool operator()(const T* form_group) { return form_group->guid() == guid_; } private: const std::string guid_; }; template typename C::const_iterator FindElementByGUID(const C& container, const std::string& guid) { return std::find_if(container.begin(), container.end(), FormGroupMatchesByGUIDFunctor(guid)); } template bool FindByGUID(const C& container, const std::string& guid) { return FindElementByGUID(container, guid) != container.end(); } template class IsEmptyFunctor { public: explicit IsEmptyFunctor(const std::string& app_locale) : app_locale_(app_locale) { } bool operator()(const T& form_group) { return form_group.IsEmpty(app_locale_); } private: const std::string app_locale_; }; // Returns true if minimum requirements for import of a given |profile| have // been met. An address submitted via a form must have at least the fields // required as determined by its country code. // No verification of validity of the contents is preformed. This is an // existence check only. bool IsMinimumAddress(const AutofillProfile& profile, const std::string& app_locale) { // All countries require at least one address line. if (profile.GetRawInfo(ADDRESS_HOME_LINE1).empty()) return false; std::string country_code = base::UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)); if (country_code.empty()) country_code = AutofillCountry::CountryCodeForLocale(app_locale); AutofillCountry country(country_code, app_locale); if (country.requires_city() && profile.GetRawInfo(ADDRESS_HOME_CITY).empty()) return false; if (country.requires_state() && profile.GetRawInfo(ADDRESS_HOME_STATE).empty()) return false; if (country.requires_zip() && profile.GetRawInfo(ADDRESS_HOME_ZIP).empty()) return false; return true; } // Return true if the |field_type| and |value| are valid within the context // of importing a form. bool IsValidFieldTypeAndValue(const std::set& types_seen, ServerFieldType field_type, const base::string16& value) { // Abandon the import if two fields of the same type are encountered. // This indicates ambiguous data or miscategorization of types. // Make an exception for PHONE_HOME_NUMBER however as both prefix and // suffix are stored against this type, and for EMAIL_ADDRESS because it is // common to see second 'confirm email address' fields on forms. if (types_seen.count(field_type) && field_type != PHONE_HOME_NUMBER && field_type != EMAIL_ADDRESS) return false; // Abandon the import if an email address value shows up in a field that is // not an email address. if (field_type != EMAIL_ADDRESS && IsValidEmailAddress(value)) return false; return true; } // In addition to just getting the values out of the autocomplete profile, this // function handles formatting of the street address into a single string. base::string16 GetInfoInOneLine(const AutofillProfile* profile, const AutofillType& type, const std::string app_locale) { std::vector results; AddressField address_field; if (i18n::FieldForType(type.GetStorableType(), &address_field) && address_field == STREET_ADDRESS) { std::string street_address_line; GetStreetAddressLinesAsSingleLine( *i18n::CreateAddressDataFromAutofillProfile(*profile, app_locale), &street_address_line); return base::UTF8ToUTF16(street_address_line); } return profile->GetInfo(type, app_locale); } // Receives the loaded profiles from the web data service and stores them in // |*dest|. The pending handle is the address of the pending handle // corresponding to this request type. This function is used to save both // server and local profiles and credit cards. template void ReceiveLoadedDbValues(WebDataServiceBase::Handle h, const WDTypedResult* result, WebDataServiceBase::Handle* pending_handle, ScopedVector* dest) { DCHECK_EQ(*pending_handle, h); *pending_handle = 0; const WDResult>* r = static_cast>*>(result); dest->clear(); for (ValueType* value : r->GetValue()) dest->push_back(value); } // A helper function for finding the maximum value in a string->int map. static bool CompareVotes(const std::pair& a, const std::pair& b) { return a.second < b.second; } } // namespace const char kFrecencyFieldTrialName[] = "AutofillProfileOrderByFrecency"; const char kFrecencyFieldTrialLimitParam[] = "limit"; PersonalDataManager::PersonalDataManager(const std::string& app_locale) : database_(NULL), is_data_loaded_(false), pending_profiles_query_(0), pending_server_profiles_query_(0), pending_creditcards_query_(0), pending_server_creditcards_query_(0), app_locale_(app_locale), pref_service_(NULL), account_tracker_(NULL), is_off_the_record_(false), has_logged_profile_count_(false) {} void PersonalDataManager::Init(scoped_refptr database, PrefService* pref_service, AccountTrackerService* account_tracker, SigninManagerBase* signin_manager, bool is_off_the_record) { CountryNames::SetLocaleString(app_locale_); database_ = database; SetPrefService(pref_service); account_tracker_ = account_tracker; signin_manager_ = signin_manager; is_off_the_record_ = is_off_the_record; if (!is_off_the_record_) AutofillMetrics::LogIsAutofillEnabledAtStartup(IsAutofillEnabled()); // WebDataService may not be available in tests. if (!database_.get()) return; LoadProfiles(); LoadCreditCards(); database_->AddObserver(this); } PersonalDataManager::~PersonalDataManager() { CancelPendingQuery(&pending_profiles_query_); CancelPendingQuery(&pending_server_profiles_query_); CancelPendingQuery(&pending_creditcards_query_); CancelPendingQuery(&pending_server_creditcards_query_); if (database_.get()) database_->RemoveObserver(this); } void PersonalDataManager::OnWebDataServiceRequestDone( WebDataServiceBase::Handle h, const WDTypedResult* result) { DCHECK(pending_profiles_query_ || pending_server_profiles_query_ || pending_creditcards_query_ || pending_server_creditcards_query_); // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is // fixed. tracked_objects::ScopedTracker tracking_profile( FROM_HERE_WITH_EXPLICIT_FUNCTION( "422460 PersonalDataManager::OnWebDataServiceRequestDone")); if (!result) { // Error from the web database. if (h == pending_creditcards_query_) pending_creditcards_query_ = 0; else if (h == pending_profiles_query_) pending_profiles_query_ = 0; return; } switch (result->GetType()) { case AUTOFILL_PROFILES_RESULT: if (h == pending_profiles_query_) { ReceiveLoadedDbValues(h, result, &pending_profiles_query_, &web_profiles_); LogProfileCount(); // This only logs local profiles. } else { ReceiveLoadedDbValues(h, result, &pending_server_profiles_query_, &server_profiles_); if (!server_profiles_.empty()) { std::string account_id = signin_manager_->GetAuthenticatedAccountId(); base::string16 email = base::UTF8ToUTF16( account_tracker_->GetAccountInfo(account_id).email); DCHECK(!email.empty()); for (AutofillProfile* profile : server_profiles_) profile->SetRawInfo(EMAIL_ADDRESS, email); } } break; case AUTOFILL_CREDITCARDS_RESULT: if (h == pending_creditcards_query_) { ReceiveLoadedDbValues(h, result, &pending_creditcards_query_, &local_credit_cards_); } else { ReceiveLoadedDbValues(h, result, &pending_server_creditcards_query_, &server_credit_cards_); // If the user has a saved unmasked server card and the experiment is // disabled, force mask all cards back to the unsaved state. if (!OfferStoreUnmaskedCards()) ResetFullServerCards(); } break; default: NOTREACHED(); } // If all requests have responded, then all personal data is loaded. if (pending_profiles_query_ == 0 && pending_creditcards_query_ == 0 && pending_server_profiles_query_ == 0 && pending_server_creditcards_query_ == 0) { is_data_loaded_ = true; NotifyPersonalDataChanged(); } } void PersonalDataManager::AutofillMultipleChanged() { Refresh(); } void PersonalDataManager::AddObserver(PersonalDataManagerObserver* observer) { observers_.AddObserver(observer); } void PersonalDataManager::RemoveObserver( PersonalDataManagerObserver* observer) { observers_.RemoveObserver(observer); } bool PersonalDataManager::ImportFormData( const FormStructure& form, bool should_return_local_card, scoped_ptr* imported_credit_card) { // We try the same |form| for both credit card and address import/update. // - ImportCreditCard may update an existing card, or fill // |imported_credit_card| with an extracted card. See .h for details of // |should_return_local_card|. bool cc_import = ImportCreditCard(form, should_return_local_card, imported_credit_card); // - ImportAddressProfiles may eventually save or update one or more address // profiles. bool address_import = ImportAddressProfiles(form); if (cc_import || address_import) return true; FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, OnInsufficientFormData()); return false; } void PersonalDataManager::RecordUseOf(const AutofillDataModel& data_model) { if (is_off_the_record_ || !database_.get()) return; CreditCard* credit_card = GetCreditCardByGUID(data_model.guid()); if (credit_card) { credit_card->RecordAndLogUse(); if (credit_card->record_type() == CreditCard::LOCAL_CARD) database_->UpdateCreditCard(*credit_card); else database_->UpdateServerCardUsageStats(*credit_card); Refresh(); return; } AutofillProfile* profile = GetProfileByGUID(data_model.guid()); if (profile) { profile->RecordAndLogUse(); if (profile->record_type() == AutofillProfile::LOCAL_PROFILE) database_->UpdateAutofillProfile(*profile); else if (profile->record_type() == AutofillProfile::SERVER_PROFILE) database_->UpdateServerAddressUsageStats(*profile); Refresh(); } } void PersonalDataManager::AddProfile(const AutofillProfile& profile) { if (is_off_the_record_) return; if (profile.IsEmpty(app_locale_)) return; // Don't add an existing profile. if (FindByGUID(web_profiles_, profile.guid())) return; if (!database_.get()) return; // Don't add a duplicate. if (FindByContents(web_profiles_, profile)) return; // Add the new profile to the web database. database_->AddAutofillProfile(profile); // Refresh our local cache and send notifications to observers. Refresh(); } void PersonalDataManager::UpdateProfile(const AutofillProfile& profile) { if (is_off_the_record_) return; AutofillProfile* existing_profile = GetProfileByGUID(profile.guid()); if (!existing_profile) return; // Don't overwrite the origin for a profile that is already stored. if (existing_profile->EqualsSansOrigin(profile)) return; if (profile.IsEmpty(app_locale_)) { RemoveByGUID(profile.guid()); return; } if (!database_.get()) return; // Make the update. database_->UpdateAutofillProfile(profile); // Refresh our local cache and send notifications to observers. Refresh(); } AutofillProfile* PersonalDataManager::GetProfileByGUID( const std::string& guid) { const std::vector& profiles = GetProfiles(); std::vector::const_iterator iter = FindElementByGUID(profiles, guid); return (iter != profiles.end()) ? *iter : NULL; } void PersonalDataManager::AddCreditCard(const CreditCard& credit_card) { if (is_off_the_record_) return; if (credit_card.IsEmpty(app_locale_)) return; if (FindByGUID(local_credit_cards_, credit_card.guid())) return; if (!database_.get()) return; // Don't add a duplicate. if (FindByContents(local_credit_cards_, credit_card)) return; // Add the new credit card to the web database. database_->AddCreditCard(credit_card); // Refresh our local cache and send notifications to observers. Refresh(); } void PersonalDataManager::UpdateCreditCard(const CreditCard& credit_card) { DCHECK_EQ(CreditCard::LOCAL_CARD, credit_card.record_type()); if (is_off_the_record_) return; CreditCard* existing_credit_card = GetCreditCardByGUID(credit_card.guid()); if (!existing_credit_card) return; // Don't overwrite the origin for a credit card that is already stored. if (existing_credit_card->Compare(credit_card) == 0) return; if (credit_card.IsEmpty(app_locale_)) { RemoveByGUID(credit_card.guid()); return; } if (!database_.get()) return; // Make the update. database_->UpdateCreditCard(credit_card); // Refresh our local cache and send notifications to observers. Refresh(); } void PersonalDataManager::UpdateServerCreditCard( const CreditCard& credit_card) { DCHECK_NE(CreditCard::LOCAL_CARD, credit_card.record_type()); if (is_off_the_record_ || !database_.get()) return; // Look up by server id, not GUID. CreditCard* existing_credit_card = nullptr; for (auto it : server_credit_cards_) { if (credit_card.server_id() == it->server_id()) { existing_credit_card = it; break; } } if (!existing_credit_card) return; DCHECK_NE(existing_credit_card->record_type(), credit_card.record_type()); DCHECK_EQ(existing_credit_card->Label(), credit_card.Label()); if (existing_credit_card->record_type() == CreditCard::MASKED_SERVER_CARD) { database_->UnmaskServerCreditCard(credit_card, credit_card.number()); } else { database_->MaskServerCreditCard(credit_card.server_id()); } Refresh(); } void PersonalDataManager::ResetFullServerCard(const std::string& guid) { for (const CreditCard* card : server_credit_cards_) { if (card->guid() == guid) { DCHECK_EQ(card->record_type(), CreditCard::FULL_SERVER_CARD); CreditCard card_copy = *card; card_copy.set_record_type(CreditCard::MASKED_SERVER_CARD); card_copy.SetNumber(card->LastFourDigits()); UpdateServerCreditCard(card_copy); break; } } } void PersonalDataManager::ResetFullServerCards() { for (const CreditCard* card : server_credit_cards_) { if (card->record_type() == CreditCard::FULL_SERVER_CARD) { CreditCard card_copy = *card; card_copy.set_record_type(CreditCard::MASKED_SERVER_CARD); card_copy.SetNumber(card->LastFourDigits()); UpdateServerCreditCard(card_copy); } } } void PersonalDataManager::ClearAllServerData() { // This could theoretically be called before we get the data back from the // database on startup, and it could get called when the wallet pref is // off (meaning this class won't even query for the server data) so don't // check the server_credit_cards_/profiles_ before posting to the DB. database_->ClearAllServerData(); // The above call will eventually clear our server data by notifying us // that the data changed and then this class will re-fetch. Preemptively // clear so that tests can synchronously verify that this data was cleared. server_credit_cards_.clear(); server_profiles_.clear(); } void PersonalDataManager::RemoveByGUID(const std::string& guid) { if (is_off_the_record_) return; bool is_credit_card = FindByGUID(local_credit_cards_, guid); bool is_profile = !is_credit_card && FindByGUID(web_profiles_, guid); if (!is_credit_card && !is_profile) return; if (!database_.get()) return; if (is_credit_card) database_->RemoveCreditCard(guid); else database_->RemoveAutofillProfile(guid); // Refresh our local cache and send notifications to observers. Refresh(); } CreditCard* PersonalDataManager::GetCreditCardByGUID(const std::string& guid) { const std::vector& credit_cards = GetCreditCards(); std::vector::const_iterator iter = FindElementByGUID(credit_cards, guid); return (iter != credit_cards.end()) ? *iter : NULL; } void PersonalDataManager::GetNonEmptyTypes( ServerFieldTypeSet* non_empty_types) { for (AutofillProfile* profile : GetProfiles()) profile->GetNonEmptyTypes(app_locale_, non_empty_types); for (CreditCard* card : GetCreditCards()) card->GetNonEmptyTypes(app_locale_, non_empty_types); } bool PersonalDataManager::IsDataLoaded() const { return is_data_loaded_; } const std::vector& PersonalDataManager::GetProfiles() const { return GetProfiles(false); } const std::vector& PersonalDataManager::web_profiles() const { return web_profiles_.get(); } const std::vector& PersonalDataManager::GetLocalCreditCards() const { return local_credit_cards_.get(); } const std::vector& PersonalDataManager::GetCreditCards() const { credit_cards_.clear(); credit_cards_.insert(credit_cards_.end(), local_credit_cards_.begin(), local_credit_cards_.end()); if (pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) { credit_cards_.insert(credit_cards_.end(), server_credit_cards_.begin(), server_credit_cards_.end()); } return credit_cards_; } bool PersonalDataManager::HasServerData() const { return !server_credit_cards_.empty() || !server_profiles_.empty(); } void PersonalDataManager::Refresh() { LoadProfiles(); LoadCreditCards(); } std::vector PersonalDataManager::GetProfileSuggestions( const AutofillType& type, const base::string16& field_contents, bool field_is_autofilled, const std::vector& other_field_types) { if (IsInAutofillSuggestionsDisabledExperiment()) return std::vector(); base::string16 field_contents_canon = AutofillProfile::CanonicalizeProfileString(field_contents); std::vector profiles = GetProfiles(true); // Rank the suggestions by frecency (see AutofillDataModel for details). base::Time comparison_time = base::Time::Now(); std::sort(profiles.begin(), profiles.end(), [comparison_time](const AutofillDataModel* a, const AutofillDataModel* b) { return a->CompareFrecency(b, comparison_time); }); std::vector suggestions; // Match based on a prefix search. std::vector matched_profiles; for (AutofillProfile* profile : profiles) { base::string16 value = GetInfoInOneLine(profile, type, app_locale_); if (value.empty()) continue; base::string16 value_canon = AutofillProfile::CanonicalizeProfileString(value); bool prefix_matched_suggestion = base::StartsWith( value_canon, field_contents_canon, base::CompareCase::SENSITIVE); if (prefix_matched_suggestion || FieldIsSuggestionSubstringStartingOnTokenBoundary(value, field_contents, false)) { matched_profiles.push_back(profile); suggestions.push_back(Suggestion(value)); suggestions.back().backend_id = profile->guid(); suggestions.back().match = prefix_matched_suggestion ? Suggestion::PREFIX_MATCH : Suggestion::SUBSTRING_MATCH; } } // Prefix matches should precede other token matches. if (IsFeatureSubstringMatchEnabled()) { std::stable_sort(suggestions.begin(), suggestions.end(), [](const Suggestion& a, const Suggestion& b) { return a.match < b.match; }); } // Don't show two suggestions if one is a subset of the other. std::vector unique_matched_profiles; std::vector unique_suggestions; ServerFieldTypeSet types(other_field_types.begin(), other_field_types.end()); for (size_t i = 0; i < matched_profiles.size(); ++i) { bool include = true; AutofillProfile* profile_a = matched_profiles[i]; for (size_t j = 0; j < matched_profiles.size(); ++j) { AutofillProfile* profile_b = matched_profiles[j]; // Check if profile A is a subset of profile B. If not, continue. if (i == j || suggestions[i].value != suggestions[j].value || !profile_a->IsSubsetOfForFieldSet(*profile_b, app_locale_, types)) { continue; } // Check if profile B is also a subset of profile A. If so, the // profiles are identical. Include the first one but not the second. if (i < j && profile_b->IsSubsetOfForFieldSet(*profile_a, app_locale_, types)) { continue; } // One-way subset. Don't include profile A. include = false; break; } if (include) { unique_matched_profiles.push_back(matched_profiles[i]); unique_suggestions.push_back(suggestions[i]); } } // Generate disambiguating labels based on the list of matches. std::vector labels; AutofillProfile::CreateInferredLabels( unique_matched_profiles, &other_field_types, type.GetStorableType(), 1, app_locale_, &labels); DCHECK_EQ(unique_suggestions.size(), labels.size()); for (size_t i = 0; i < labels.size(); i++) unique_suggestions[i].label = labels[i]; // Get the profile suggestions limit value set for the current frecency field // trial group or SIZE_MAX if no limit is defined. std::string limit_str = variations::GetVariationParamValue( kFrecencyFieldTrialName, kFrecencyFieldTrialLimitParam); size_t limit = base::StringToSizeT(limit_str, &limit) ? limit : SIZE_MAX; unique_suggestions.resize(std::min(unique_suggestions.size(), limit)); return unique_suggestions; } std::vector PersonalDataManager::GetCreditCardSuggestions( const AutofillType& type, const base::string16& field_contents) { if (IsInAutofillSuggestionsDisabledExperiment()) return std::vector(); std::list cards_to_suggest; std::list substring_matched_cards; base::string16 field_contents_lower = base::i18n::ToLower(field_contents); for (const CreditCard* credit_card : GetCreditCards()) { // The value of the stored data for this field type in the |credit_card|. base::string16 creditcard_field_value = credit_card->GetInfo(type, app_locale_); if (creditcard_field_value.empty()) continue; base::string16 creditcard_field_lower = base::i18n::ToLower(creditcard_field_value); // For card number fields, suggest the card if: // - the number matches any part of the card, or // - it's a masked card and there are 6 or fewers typed so far. // For other fields, require that the field contents match the beginning of // the stored data. if (type.GetStorableType() == CREDIT_CARD_NUMBER) { if (creditcard_field_lower.find(field_contents_lower) == base::string16::npos && (credit_card->record_type() != CreditCard::MASKED_SERVER_CARD || field_contents.size() >= 6)) { continue; } cards_to_suggest.push_back(credit_card); } else if (base::StartsWith(creditcard_field_lower, field_contents_lower, base::CompareCase::SENSITIVE)) { cards_to_suggest.push_back(credit_card); } else if (FieldIsSuggestionSubstringStartingOnTokenBoundary( creditcard_field_lower, field_contents_lower, true)) { substring_matched_cards.push_back(credit_card); } } // Rank the suggestions by frecency (see AutofillDataModel for details). base::Time comparison_time = base::Time::Now(); cards_to_suggest.sort([comparison_time](const AutofillDataModel* a, const AutofillDataModel* b) { return a->CompareFrecency(b, comparison_time); }); // Prefix matches should precede other token matches. if (IsFeatureSubstringMatchEnabled()) { substring_matched_cards.sort([comparison_time](const AutofillDataModel* a, const AutofillDataModel* b) { return a->CompareFrecency(b, comparison_time); }); cards_to_suggest.insert(cards_to_suggest.end(), substring_matched_cards.begin(), substring_matched_cards.end()); } DedupeCreditCardSuggestions(&cards_to_suggest); std::vector suggestions; for (const CreditCard* credit_card : cards_to_suggest) { // Make a new suggestion. suggestions.push_back(Suggestion()); Suggestion* suggestion = &suggestions.back(); suggestion->value = credit_card->GetInfo(type, app_locale_); suggestion->icon = base::UTF8ToUTF16(credit_card->type()); suggestion->backend_id = credit_card->guid(); // If the value is the card number, the label is the expiration date. // Otherwise the label is the card number, or if that is empty the // cardholder name. The label should never repeat the value. if (type.GetStorableType() == CREDIT_CARD_NUMBER) { suggestion->value = credit_card->TypeAndLastFourDigits(); suggestion->label = credit_card->GetInfo( AutofillType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR), app_locale_); } else if (credit_card->number().empty()) { if (type.GetStorableType() != CREDIT_CARD_NAME_FULL) { suggestion->label = credit_card->GetInfo( AutofillType(CREDIT_CARD_NAME_FULL), app_locale_); } } else { #if defined(OS_ANDROID) // Since Android places the label on its own row, there's more horizontal // space to work with. Show "Amex - 1234" rather than desktop's "*1234". suggestion->label = credit_card->TypeAndLastFourDigits(); #else suggestion->label = base::ASCIIToUTF16("*"); suggestion->label.append(credit_card->LastFourDigits()); #endif } } return suggestions; } bool PersonalDataManager::IsAutofillEnabled() const { return ::autofill::IsAutofillEnabled(pref_service_); } std::string PersonalDataManager::CountryCodeForCurrentTimezone() const { return base::CountryCodeForCurrentTimezone(); } void PersonalDataManager::SetPrefService(PrefService* pref_service) { enabled_pref_.reset(new BooleanPrefMember); wallet_enabled_pref_.reset(new BooleanPrefMember); pref_service_ = pref_service; // |pref_service_| can be NULL in tests. if (pref_service_) { enabled_pref_->Init(prefs::kAutofillEnabled, pref_service_, base::Bind(&PersonalDataManager::EnabledPrefChanged, base::Unretained(this))); wallet_enabled_pref_->Init(prefs::kAutofillWalletImportEnabled, pref_service_, base::Bind(&PersonalDataManager::EnabledPrefChanged, base::Unretained(this))); } } // static bool PersonalDataManager::IsValidLearnableProfile( const AutofillProfile& profile, const std::string& app_locale) { if (!IsMinimumAddress(profile, app_locale)) return false; base::string16 email = profile.GetRawInfo(EMAIL_ADDRESS); if (!email.empty() && !IsValidEmailAddress(email)) return false; // Reject profiles with invalid US state information. if (profile.IsPresentButInvalid(ADDRESS_HOME_STATE)) return false; // Reject profiles with invalid US zip information. if (profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)) return false; return true; } // static std::string PersonalDataManager::MergeProfile( const AutofillProfile& new_profile, const std::vector& existing_profiles, const std::string& app_locale, std::vector* merged_profiles) { merged_profiles->clear(); // Set to true if |existing_profiles| already contains an equivalent profile. bool matching_profile_found = false; std::string guid = new_profile.guid(); // If we have already saved this address, merge in any missing values. // Only merge with the first match. for (AutofillProfile* existing_profile : existing_profiles) { if (!matching_profile_found && !new_profile.PrimaryValue().empty() && existing_profile->SaveAdditionalInfo(new_profile, app_locale)) { // Unverified profiles should always be updated with the newer data, // whereas verified profiles should only ever be overwritten by verified // data. If an automatically aggregated profile would overwrite a // verified profile, just drop it. matching_profile_found = true; guid = existing_profile->guid(); // We set the modification date so that immediate requests for profiles // will properly reflect the fact that this profile has been modified // recently. After writing to the database and refreshing the local copies // the profile will have a very slightly newer time reflecting what's // actually stored in the database. existing_profile->set_modification_date(base::Time::Now()); existing_profile->RecordAndLogUse(); } merged_profiles->push_back(*existing_profile); } // If the new profile was not merged with an existing one, add it to the list. if (!matching_profile_found) { merged_profiles->push_back(new_profile); // Similar to updating merged profiles above, set the modification date on // new profiles. merged_profiles->back().set_modification_date(base::Time::Now()); AutofillMetrics::LogProfileActionOnFormSubmitted( AutofillMetrics::NEW_PROFILE_CREATED); } return guid; } bool PersonalDataManager::IsCountryOfInterest(const std::string& country_code) const { DCHECK_EQ(2U, country_code.size()); const std::vector& profiles = web_profiles(); std::list country_codes; for (size_t i = 0; i < profiles.size(); ++i) { country_codes.push_back(base::ToLowerASCII(base::UTF16ToASCII( profiles[i]->GetRawInfo(ADDRESS_HOME_COUNTRY)))); } std::string timezone_country = CountryCodeForCurrentTimezone(); if (!timezone_country.empty()) country_codes.push_back(base::ToLowerASCII(timezone_country)); // Only take the locale into consideration if all else fails. if (country_codes.empty()) { country_codes.push_back(base::ToLowerASCII( AutofillCountry::CountryCodeForLocale(app_locale()))); } return std::find(country_codes.begin(), country_codes.end(), base::ToLowerASCII(country_code)) != country_codes.end(); } const std::string& PersonalDataManager::GetDefaultCountryCodeForNewAddress() const { if (default_country_code_.empty()) default_country_code_ = MostCommonCountryCodeFromProfiles(); // Failing that, guess based on system timezone. if (default_country_code_.empty()) default_country_code_ = CountryCodeForCurrentTimezone(); // Failing that, guess based on locale. if (default_country_code_.empty()) default_country_code_ = AutofillCountry::CountryCodeForLocale(app_locale()); return default_country_code_; } // static void PersonalDataManager::DedupeCreditCardSuggestions( std::list* cards_to_suggest) { for (auto outer_it = cards_to_suggest->begin(); outer_it != cards_to_suggest->end(); ++outer_it) { // If considering a full server card, look for local cards that are // duplicates of it and remove them. if ((*outer_it)->record_type() == CreditCard::FULL_SERVER_CARD) { for (auto inner_it = cards_to_suggest->begin(); inner_it != cards_to_suggest->end();) { auto inner_it_copy = inner_it++; if ((*inner_it_copy)->IsLocalDuplicateOfServerCard(**outer_it)) cards_to_suggest->erase(inner_it_copy); } // If considering a local card, look for masked server cards that are // duplicates of it and remove them. } else if ((*outer_it)->record_type() == CreditCard::LOCAL_CARD) { for (auto inner_it = cards_to_suggest->begin(); inner_it != cards_to_suggest->end();) { auto inner_it_copy = inner_it++; if ((*inner_it_copy)->record_type() == CreditCard::MASKED_SERVER_CARD && (*outer_it)->IsLocalDuplicateOfServerCard(**inner_it_copy)) { cards_to_suggest->erase(inner_it_copy); } } } } } void PersonalDataManager::SetProfiles(std::vector* profiles) { if (is_off_the_record_) return; // Remove empty profiles from input. profiles->erase(std::remove_if(profiles->begin(), profiles->end(), IsEmptyFunctor(app_locale_)), profiles->end()); if (!database_.get()) return; // Any profiles that are not in the new profile list should be removed from // the web database. for (const AutofillProfile* it : web_profiles_) { if (!FindByGUID(*profiles, it->guid())) database_->RemoveAutofillProfile(it->guid()); } // Update the web database with the existing profiles. for (const AutofillProfile& it : *profiles) { if (FindByGUID(web_profiles_, it.guid())) database_->UpdateAutofillProfile(it); } // Add the new profiles to the web database. Don't add a duplicate. for (const AutofillProfile& it : *profiles) { if (!FindByGUID(web_profiles_, it.guid()) && !FindByContents(web_profiles_, it)) database_->AddAutofillProfile(it); } // Copy in the new profiles. web_profiles_.clear(); for (const AutofillProfile& it : *profiles) { web_profiles_.push_back(new AutofillProfile(it)); } // Refresh our local cache and send notifications to observers. Refresh(); } void PersonalDataManager::SetCreditCards( std::vector* credit_cards) { if (is_off_the_record_) return; // Remove empty credit cards from input. credit_cards->erase(std::remove_if(credit_cards->begin(), credit_cards->end(), IsEmptyFunctor(app_locale_)), credit_cards->end()); if (!database_.get()) return; // Any credit cards that are not in the new credit card list should be // removed. for (const CreditCard* card : local_credit_cards_) { if (!FindByGUID(*credit_cards, card->guid())) database_->RemoveCreditCard(card->guid()); } // Update the web database with the existing credit cards. for (const CreditCard& card : *credit_cards) { if (FindByGUID(local_credit_cards_, card.guid())) database_->UpdateCreditCard(card); } // Add the new credit cards to the web database. Don't add a duplicate. for (const CreditCard& card : *credit_cards) { if (!FindByGUID(local_credit_cards_, card.guid()) && !FindByContents(local_credit_cards_, card)) database_->AddCreditCard(card); } // Copy in the new credit cards. local_credit_cards_.clear(); for (const CreditCard& card : *credit_cards) local_credit_cards_.push_back(new CreditCard(card)); // Refresh our local cache and send notifications to observers. Refresh(); } void PersonalDataManager::LoadProfiles() { if (!database_.get()) { NOTREACHED(); return; } CancelPendingQuery(&pending_profiles_query_); CancelPendingQuery(&pending_server_profiles_query_); pending_profiles_query_ = database_->GetAutofillProfiles(this); pending_server_profiles_query_ = database_->GetServerProfiles(this); } void PersonalDataManager::LoadCreditCards() { if (!database_.get()) { NOTREACHED(); return; } CancelPendingQuery(&pending_creditcards_query_); CancelPendingQuery(&pending_server_creditcards_query_); pending_creditcards_query_ = database_->GetCreditCards(this); pending_server_creditcards_query_ = database_->GetServerCreditCards(this); } void PersonalDataManager::CancelPendingQuery( WebDataServiceBase::Handle* handle) { if (*handle) { if (!database_.get()) { NOTREACHED(); return; } database_->CancelRequest(*handle); } *handle = 0; } std::string PersonalDataManager::SaveImportedProfile( const AutofillProfile& imported_profile) { if (is_off_the_record_) return std::string(); // Don't save a web profile if the data in the profile is a subset of a // server profile, but do record the fact that it was used. for (const AutofillProfile* profile : server_profiles_) { if (imported_profile.IsSubsetOf(*profile, app_locale_)) { RecordUseOf(*profile); return profile->guid(); } } std::vector profiles; std::string guid = MergeProfile(imported_profile, web_profiles_.get(), app_locale_, &profiles); SetProfiles(&profiles); return guid; } void PersonalDataManager::NotifyPersonalDataChanged() { FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, OnPersonalDataChanged()); } std::string PersonalDataManager::SaveImportedCreditCard( const CreditCard& imported_card) { DCHECK(!imported_card.number().empty()); if (is_off_the_record_) return std::string(); // Set to true if |imported_card| is merged into the credit card list. bool merged = false; std::string guid = imported_card.guid(); std::vector credit_cards; for (CreditCard* card : local_credit_cards_) { // If |imported_card| has not yet been merged, check whether it should be // with the current |card|. if (!merged && card->UpdateFromImportedCard(imported_card, app_locale_)) { guid = card->guid(); merged = true; } credit_cards.push_back(*card); } if (!merged) credit_cards.push_back(imported_card); SetCreditCards(&credit_cards); return guid; } void PersonalDataManager::LogProfileCount() const { if (!has_logged_profile_count_) { AutofillMetrics::LogStoredProfileCount(web_profiles_.size()); has_logged_profile_count_ = true; } } std::string PersonalDataManager::MostCommonCountryCodeFromProfiles() const { if (!IsAutofillEnabled()) return std::string(); // Count up country codes from existing profiles. std::map votes; // TODO(estade): can we make this GetProfiles() instead? It seems to cause // errors in tests on mac trybots. See http://crbug.com/57221 const std::vector& profiles = web_profiles(); const std::vector& country_codes = CountryDataMap::GetInstance()->country_codes(); for (size_t i = 0; i < profiles.size(); ++i) { std::string country_code = base::ToUpperASCII(base::UTF16ToASCII( profiles[i]->GetRawInfo(ADDRESS_HOME_COUNTRY))); if (std::find(country_codes.begin(), country_codes.end(), country_code) != country_codes.end()) { // Verified profiles count 100x more than unverified ones. votes[country_code] += profiles[i]->IsVerified() ? 100 : 1; } } // Take the most common country code. if (!votes.empty()) { std::map::iterator iter = std::max_element(votes.begin(), votes.end(), CompareVotes); return iter->first; } return std::string(); } void PersonalDataManager::EnabledPrefChanged() { default_country_code_.clear(); if (!pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) { // Re-mask all server cards when the user turns off wallet card // integration. ResetFullServerCards(); } NotifyPersonalDataChanged(); } bool PersonalDataManager::ImportAddressProfiles(const FormStructure& form) { if (!form.field_count()) return false; // Relevant sections for address fields. std::set sections; for (const AutofillField* field : form) { if (field->Type().group() != CREDIT_CARD) sections.insert(field->section()); } // We save a maximum of 2 profiles per submitted form (e.g. for shipping and // billing). static const size_t kMaxNumAddressProfilesSaved = 2; size_t num_saved_profiles = 0; for (const std::string& section : sections) { if (num_saved_profiles == kMaxNumAddressProfilesSaved) break; if (ImportAddressProfileForSection(form, section)) num_saved_profiles++; } return num_saved_profiles > 0; } bool PersonalDataManager::ImportAddressProfileForSection( const FormStructure& form, const std::string& section) { // The candidate for profile import. There are many ways for the candidate to // be rejected (see everywhere this function returns false). AutofillProfile candidate_profile; candidate_profile.set_origin(form.source_url().spec()); // We only set complete phone, so aggregate phone parts in these vars and set // complete at the end. PhoneNumber::PhoneCombineHelper combined_phone; // Used to detect and discard address forms with multiple fields of the same // type. std::set types_seen; // Go through each |form| field and attempt to constitute a valid profile. for (const AutofillField* field : form) { // Reject fields that are not within the specified |section|. if (field->section() != section) continue; base::string16 value; base::TrimWhitespace(field->value, base::TRIM_ALL, &value); // If we don't know the type of the field, or the user hasn't entered any // information into the field, or the field is non-focusable (hidden), then // skip it. if (!field->IsFieldFillable() || !field->is_focusable || value.empty()) continue; AutofillType field_type = field->Type(); // Credit card fields are handled by ImportCreditCard(). if (field_type.group() == CREDIT_CARD) continue; // There can be multiple email fields (e.g. in the case of 'confirm email' // fields) but they must all contain the same value, else the profile is // invalid. ServerFieldType server_field_type = field_type.GetStorableType(); if (server_field_type == EMAIL_ADDRESS && types_seen.count(server_field_type) && candidate_profile.GetRawInfo(EMAIL_ADDRESS) != value) return false; // If the field type and |value| don't pass basic validity checks then // abandon the import. if (!IsValidFieldTypeAndValue(types_seen, server_field_type, value)) return false; types_seen.insert(server_field_type); // We need to store phone data in the variables, before building the whole // number at the end. If |value| is not from a phone field, home.SetInfo() // returns false and data is stored directly in |candidate_profile|. if (!combined_phone.SetInfo(field_type, value)) candidate_profile.SetInfo(field_type, value, app_locale_); // Reject profiles with invalid country information. if (server_field_type == ADDRESS_HOME_COUNTRY && candidate_profile.GetRawInfo(ADDRESS_HOME_COUNTRY).empty()) return false; } // Construct the phone number. Reject the whole profile if the number is // invalid. if (!combined_phone.IsEmpty()) { base::string16 constructed_number; if (!combined_phone.ParseNumber(candidate_profile, app_locale_, &constructed_number) || !candidate_profile.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), constructed_number, app_locale_)) { return false; } } // Reject the profile if minimum address and validation requirements are not // met. if (!IsValidLearnableProfile(candidate_profile, app_locale_)) return false; SaveImportedProfile(candidate_profile); return true; } bool PersonalDataManager::ImportCreditCard( const FormStructure& form, bool should_return_local_card, scoped_ptr* imported_credit_card) { DCHECK(!imported_credit_card->get()); // The candidate for credit card import. There are many ways for the candidate // to be rejected (see everywhere this function returns false, below). CreditCard candidate_credit_card; candidate_credit_card.set_origin(form.source_url().spec()); std::set types_seen; for (const AutofillField* field : form) { base::string16 value; base::TrimWhitespace(field->value, base::TRIM_ALL, &value); // If we don't know the type of the field, or the user hasn't entered any // information into the field, or the field is non-focusable (hidden), then // skip it. if (!field->IsFieldFillable() || !field->is_focusable || value.empty()) continue; AutofillType field_type = field->Type(); // Field was not identified as a credit card field. if (field_type.group() != CREDIT_CARD) continue; // If we've seen the same credit card field type twice in the same form, // abort credit card import/update. ServerFieldType server_field_type = field_type.GetStorableType(); if (types_seen.count(server_field_type)) return false; types_seen.insert(server_field_type); // If |field| is an HTML5 month input, handle it as a special case. if (base::LowerCaseEqualsASCII(field->form_control_type, "month")) { DCHECK_EQ(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, server_field_type); candidate_credit_card.SetInfoForMonthInputType(value); continue; } // CreditCard handles storing the |value| according to |field_type|. bool saved = candidate_credit_card.SetInfo(field_type, value, app_locale_); // Saving with the option text (here |value|) may fail for the expiration // month. Attempt to save with the option value. First find the index of the // option text in the select options and try the corresponding value. if (!saved && server_field_type == CREDIT_CARD_EXP_MONTH) { for (size_t i = 0; i < field->option_contents.size(); ++i) { if (value == field->option_contents[i]) { candidate_credit_card.SetInfo(field_type, field->option_values[i], app_locale_); break; } } } } // Reject the credit card if we did not detect enough filled credit card // fields (such as valid number, month, year). if (!candidate_credit_card.IsComplete()) return false; // Attempt to merge with an existing credit card. Don't present a prompt if we // have already saved this card number, unless |should_return_local_card| is // true which indicates that upload is enabled. In this case, it's useful to // present the upload prompt to the user to promote the card from a local card // to a synced server card. for (const CreditCard* card : local_credit_cards_) { // Make a local copy so that the data in |local_credit_cards_| isn't // modified directly by the UpdateFromImportedCard() call. CreditCard card_copy(*card); if (card_copy.UpdateFromImportedCard(candidate_credit_card, app_locale_)) { UpdateCreditCard(card_copy); // If we should not return the local card, return that we merged it, // without setting |imported_credit_card|. if (!should_return_local_card) return true; break; } } // Also don't offer to save if we already have this stored as a server card. // We only check the number because if the new card has the same number as the // server card, upload is guaranteed to fail. There's no mechanism for entries // with the same number but different names or expiration dates as there is // for local cards. for (const CreditCard* card : server_credit_cards_) { if (candidate_credit_card.HasSameNumberAs(*card)) return false; } imported_credit_card->reset(new CreditCard(candidate_credit_card)); return true; } const std::vector& PersonalDataManager::GetProfiles( bool record_metrics) const { profiles_.clear(); profiles_.insert(profiles_.end(), web_profiles().begin(), web_profiles().end()); if (pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) { profiles_.insert( profiles_.end(), server_profiles_.begin(), server_profiles_.end()); } return profiles_; } } // namespace autofill