| // Copyright 2017 The Chromium Authors |
| // 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/ui/addresses/autofill_address_util.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <utility> |
| |
| #include "autofill_address_util.h" |
| #include "base/check.h" |
| #include "base/containers/to_vector.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/not_fatal_until.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_profile_comparator.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/geo/address_i18n.h" |
| #include "components/autofill/core/browser/geo/autofill_country.h" |
| #include "components/autofill/core/browser/ui/country_combobox_model.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui.h" |
| #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_ui_component.h" |
| #include "third_party/re2/src/re2/re2.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using i18n::addressinput::AddressField; |
| using i18n::addressinput::Localization; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| using ::i18n::addressinput::AddressUiComponent; |
| |
| constexpr char kAddressComponentsFallbackCountryCode[] = "US"; |
| |
| AutofillAddressUIComponent::LengthHint ConvertLengthHint( |
| AddressUiComponent::LengthHint length_hint) { |
| switch (length_hint) { |
| case AddressUiComponent::LengthHint::HINT_LONG: |
| return AutofillAddressUIComponent::LengthHint::HINT_LONG; |
| case AddressUiComponent::LengthHint::HINT_SHORT: |
| return AutofillAddressUIComponent::LengthHint::HINT_SHORT; |
| } |
| NOTREACHED(); |
| } |
| |
| bool IsLiteral(const AutofillAddressUIComponent& component) { |
| return !component.literal.empty(); |
| } |
| } // namespace |
| |
| std::vector<AutofillAddressUIComponent> ConvertAddressUiComponents( |
| const std::vector<AddressUiComponent>& addressinput_components, |
| const AutofillCountry& country) { |
| return base::ToVector( |
| addressinput_components, [&country](const AddressUiComponent& component) { |
| // The component's field property may not be initialized if the |
| // component is literal, so it should not be used to avoid |
| // memory sanitizer's errors (`use-of-uninitialized-value`). |
| if (!component.literal.empty()) { |
| return AutofillAddressUIComponent{ |
| .literal = component.literal, |
| }; |
| } |
| FieldType field = i18n::TypeForField(component.field); |
| return AutofillAddressUIComponent{ |
| .field = field, |
| .name = component.name, |
| .length_hint = ConvertLengthHint(component.length_hint), |
| .literal = component.literal, |
| .is_required = country.IsAddressFieldRequired(field), |
| }; |
| }); |
| } |
| |
| void ExtendAddressComponents( |
| std::vector<AutofillAddressUIComponent>& components, |
| const AutofillCountry& country, |
| const Localization& localization, |
| bool include_literals) { |
| for (const AutofillCountry::AddressFormatExtension& rule : |
| country.address_format_extensions()) { |
| // Find the location of `rule.placed_after` in `components`. |
| // `components.field` is only valid if `components.literal.empty()`. |
| auto prev_component = std::ranges::find_if( |
| components, [&rule](const AutofillAddressUIComponent& component) { |
| return component.literal.empty() && |
| component.field == rule.placed_after; |
| }); |
| CHECK(prev_component != components.end()); |
| |
| // Insert the separator and `rule.type` after `prev_component`. |
| if (include_literals) { |
| prev_component = components.insert( |
| ++prev_component, |
| AutofillAddressUIComponent{ |
| .literal = std::string(rule.separator_before_label)}); |
| } |
| |
| components.insert( |
| ++prev_component, |
| AutofillAddressUIComponent{ |
| .field = rule.type, |
| .name = localization.GetString(rule.label_id), |
| .length_hint = rule.large_sized |
| ? AutofillAddressUIComponent::HINT_LONG |
| : AutofillAddressUIComponent::HINT_SHORT, |
| .is_required = country.IsAddressFieldRequired(rule.type)}); |
| } |
| } |
| |
| void GetAddressComponents( |
| const std::string& country_code, |
| const std::string& ui_language_code, |
| bool include_literals, |
| std::vector<std::vector<AutofillAddressUIComponent>>* address_components, |
| std::string* components_language_code) { |
| std::string not_used; |
| // The `include_literals` used below is different |
| // from the one passed to this function. The latter controls whether to |
| // include line separators, while the former controls whether to split |
| // components into lines. |
| std::vector<AutofillAddressUIComponent> components = GetAddressComponents( |
| country_code, ui_language_code, |
| /*enable_field_labels_localization=*/true, |
| /*include_literals=*/true, |
| components_language_code ? *components_language_code : not_used); |
| std::vector<AutofillAddressUIComponent>* line_components = nullptr; |
| for (const AutofillAddressUIComponent& component : components) { |
| // Start a new line if this is the first line, or a new line literal exists. |
| if (!line_components || component.literal == "\n") { |
| address_components->push_back(std::vector<AutofillAddressUIComponent>()); |
| line_components = &address_components->back(); |
| } |
| |
| if (!component.literal.empty()) { |
| if (!include_literals) { |
| continue; |
| } |
| // No need to return new line literals since components are split into |
| // different lines anyway (one line per vector). |
| if (component.literal == "\n") { |
| continue; |
| } |
| } |
| |
| line_components->push_back(component); |
| } |
| // Filter empty lines. Those can appear e.g. when the line consists only of |
| // literals and |include_literals| is false. |
| auto to_remove = std::ranges::remove_if( |
| *address_components, [](auto line) { return line.empty(); }); |
| address_components->erase(to_remove.begin(), to_remove.end()); |
| } |
| |
| std::vector<AutofillAddressUIComponent> GetAddressComponents( |
| const std::string& country_code, |
| const std::string& ui_language_code, |
| bool enable_field_labels_localization, |
| bool include_literals, |
| std::string& components_language_code) { |
| // Return strings in the current application locale. |
| Localization localization; |
| if (enable_field_labels_localization) { |
| localization.SetGetter(l10n_util::GetStringUTF8); |
| } |
| AutofillCountry country(country_code); |
| |
| std::vector<AutofillAddressUIComponent> components; |
| if (include_literals) { |
| components = ConvertAddressUiComponents( |
| ::i18n::addressinput::BuildComponentsWithLiterals( |
| country_code, localization, ui_language_code, |
| &components_language_code), |
| country); |
| } else { |
| components = ConvertAddressUiComponents( |
| ::i18n::addressinput::BuildComponents(country_code, localization, |
| ui_language_code, |
| &components_language_code), |
| country); |
| } |
| |
| if (!components.empty()) { |
| ExtendAddressComponents(components, country, localization, |
| /*include_literals=*/include_literals); |
| return components; |
| } else if (country_code != kAddressComponentsFallbackCountryCode) { |
| return GetAddressComponents(kAddressComponentsFallbackCountryCode, |
| ui_language_code, |
| enable_field_labels_localization, |
| include_literals, components_language_code); |
| } |
| |
| NOTREACHED(); |
| } |
| |
| std::u16string GetEnvelopeStyleAddress(const AutofillProfile& profile, |
| const std::string& ui_language_code, |
| bool include_recipient, |
| bool include_country) { |
| const std::u16string& country_code = profile.GetInfo( |
| AutofillType(ADDRESS_HOME_COUNTRY, /*is_country_code=*/true), |
| ui_language_code); |
| |
| std::string not_used; |
| std::vector<AutofillAddressUIComponent> components = |
| GetAddressComponents(base::UTF16ToUTF8(country_code), ui_language_code, |
| /*enable_field_labels_localization=*/true, |
| /*include_literals=*/true, not_used); |
| |
| DCHECK(!components.empty()); |
| // Remove components that evaluate to an empty string. |
| std::erase_if(components, [&](const AutofillAddressUIComponent& component) { |
| if (IsLiteral(component)) { |
| return false; |
| } |
| if (!include_recipient && (component.field == NAME_FULL || |
| component.field == ALTERNATIVE_FULL_NAME)) { |
| return true; |
| } |
| return profile.GetInfo(component.field, ui_language_code).empty(); |
| }); |
| |
| // Remove delimiters from the beginning and the end. |
| while (!components.empty() && IsLiteral(components.front())) { |
| components.erase(components.begin()); |
| } |
| while (!components.empty() && IsLiteral(components.back())) { |
| components.pop_back(); |
| } |
| |
| std::vector<std::u16string> pieces; |
| // Indicates if the previous field component was non-empty. |
| // Literals are only added if they are preceded by a non-empty field. |
| bool last_field_was_non_empty = false; |
| for (const AutofillAddressUIComponent& component : components) { |
| if (IsLiteral(component)) { |
| if (last_field_was_non_empty) { |
| pieces.push_back(base::UTF8ToUTF16(component.literal)); |
| last_field_was_non_empty = false; |
| } |
| continue; |
| } |
| std::u16string info = profile.GetInfo(component.field, ui_language_code); |
| pieces.push_back(info); |
| last_field_was_non_empty = true; |
| } |
| |
| if (include_country) { |
| pieces.push_back(u"\n"); |
| pieces.push_back(profile.GetInfo(ADDRESS_HOME_COUNTRY, ui_language_code)); |
| } |
| |
| std::string address_utf8 = base::UTF16ToUTF8(base::StrCat(pieces)); |
| |
| // Remove all white spaces and new lines from the beginning and the end of the |
| // address. |
| base::TrimString(address_utf8, base::kWhitespaceASCII, &address_utf8); |
| |
| // Collapse new lines to remove empty lines. |
| re2::RE2::GlobalReplace(&address_utf8, re2::RE2("\\n+"), "\n"); |
| |
| // Collapse white spaces. |
| re2::RE2::GlobalReplace(&address_utf8, re2::RE2("[ ]+"), " "); |
| |
| return base::UTF8ToUTF16(address_utf8); |
| } |
| |
| std::u16string GetProfileDescription(const AutofillProfile& profile, |
| const std::string& ui_language_code, |
| bool include_address_and_contacts) { |
| // All user-visible fields. |
| static constexpr FieldType kDetailsFields[] = { |
| NAME_FULL, |
| ADDRESS_HOME_LINE1, |
| ADDRESS_HOME_LINE2, |
| ADDRESS_HOME_DEPENDENT_LOCALITY, |
| ADDRESS_HOME_CITY, |
| ADDRESS_HOME_STATE, |
| ADDRESS_HOME_ZIP, |
| EMAIL_ADDRESS, |
| PHONE_HOME_WHOLE_NUMBER, |
| COMPANY_NAME, |
| ADDRESS_HOME_COUNTRY}; |
| |
| if (!include_address_and_contacts) { |
| return profile.GetInfo(NAME_FULL, ui_language_code); |
| } |
| |
| return profile.ConstructInferredLabel( |
| kDetailsFields, /*num_fields_to_include=*/2, ui_language_code); |
| } |
| |
| std::vector<ProfileValueDifference> GetProfileDifferenceForUi( |
| const AutofillProfile& first_profile, |
| const AutofillProfile& second_profile, |
| const std::string& app_locale) { |
| std::vector<ProfileValueDifference> differences_for_ui = |
| AutofillProfileComparator::GetProfileDifference( |
| first_profile, second_profile, |
| {NAME_FULL, ALTERNATIVE_FULL_NAME, EMAIL_ADDRESS, |
| PHONE_HOME_WHOLE_NUMBER}, |
| app_locale); |
| |
| // ADDRESS_HOME_ADDRESS is handled separately. |
| std::u16string first_address = GetEnvelopeStyleAddress( |
| first_profile, app_locale, /*include_recipient=*/false, |
| /*include_country=*/true); |
| std::u16string second_address = GetEnvelopeStyleAddress( |
| second_profile, app_locale, /*include_recipient=*/false, |
| /*include_country=*/true); |
| if (first_address != second_address) { |
| differences_for_ui.push_back( |
| {ADDRESS_HOME_ADDRESS, first_address, second_address}); |
| } |
| static constexpr auto kPriorityOrder = |
| std::to_array({NAME_FULL, ALTERNATIVE_FULL_NAME, ADDRESS_HOME_ADDRESS, |
| EMAIL_ADDRESS, PHONE_HOME_WHOLE_NUMBER}); |
| |
| std::erase_if(differences_for_ui, [](const ProfileValueDifference& diff) { |
| return !std::ranges::contains(kPriorityOrder, diff.type); |
| }); |
| |
| auto get_priority = [](FieldType type) -> size_t { |
| auto it = std::ranges::find(kPriorityOrder, type); |
| CHECK(it != kPriorityOrder.end()); |
| return std::distance(kPriorityOrder.begin(), it); |
| }; |
| |
| std::ranges::sort(differences_for_ui, |
| [get_priority](const ProfileValueDifference& a, |
| const ProfileValueDifference& b) { |
| return get_priority(a.type) < get_priority(b.type); |
| }); |
| return differences_for_ui; |
| } |
| |
| std::u16string GetProfileSummaryForMigrationPrompt( |
| const AutofillProfile& profile, |
| const std::string& app_locale) { |
| std::vector<FieldType> fields = { |
| FieldType::NAME_FULL, FieldType::ADDRESS_HOME_LINE1, |
| FieldType::EMAIL_ADDRESS, FieldType::PHONE_HOME_WHOLE_NUMBER}; |
| std::vector<std::u16string> values; |
| values.reserve(fields.size()); |
| for (FieldType field : fields) { |
| std::u16string value = profile.GetInfo(field, app_locale); |
| if (!value.empty()) { |
| values.push_back(value); |
| } |
| } |
| return base::JoinString(values, u"\n"); |
| } |
| |
| AddressUIComponentIconType GetAddressUIComponentIconTypeForFieldType( |
| FieldType field_type) { |
| switch (field_type) { |
| case NAME_FULL: |
| case ALTERNATIVE_FULL_NAME: |
| return AddressUIComponentIconType::kName; |
| case ADDRESS_HOME_STREET_ADDRESS: |
| case ADDRESS_HOME_ADDRESS: |
| return AddressUIComponentIconType::kAddress; |
| case EMAIL_ADDRESS: |
| return AddressUIComponentIconType::kEmail; |
| case PHONE_HOME_WHOLE_NUMBER: |
| return AddressUIComponentIconType::kPhone; |
| case NAME_HONORIFIC_PREFIX: |
| case NAME_FIRST: |
| case NAME_MIDDLE: |
| case NAME_LAST: |
| case NAME_LAST_FIRST: |
| case NAME_LAST_CONJUNCTION: |
| case NAME_LAST_SECOND: |
| case NAME_MIDDLE_INITIAL: |
| case NAME_SUFFIX: |
| case ALTERNATIVE_GIVEN_NAME: |
| case ALTERNATIVE_FAMILY_NAME: |
| case USERNAME_AND_EMAIL_ADDRESS: |
| case PHONE_HOME_NUMBER: |
| case PHONE_HOME_NUMBER_PREFIX: |
| case PHONE_HOME_NUMBER_SUFFIX: |
| case PHONE_HOME_CITY_CODE: |
| case PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX: |
| case PHONE_HOME_COUNTRY_CODE: |
| case PHONE_HOME_CITY_AND_NUMBER: |
| case PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX: |
| case PHONE_HOME_EXTENSION: |
| case CREDIT_CARD_NAME_FULL: |
| case CREDIT_CARD_NAME_FIRST: |
| case CREDIT_CARD_NAME_LAST: |
| case CREDIT_CARD_NUMBER: |
| case CREDIT_CARD_EXP_MONTH: |
| case CREDIT_CARD_EXP_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_4_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: |
| case CREDIT_CARD_TYPE: |
| case CREDIT_CARD_VERIFICATION_CODE: |
| case CREDIT_CARD_STANDALONE_VERIFICATION_CODE: |
| case IBAN_VALUE: |
| case MERCHANT_PROMO_CODE: |
| case USERNAME: |
| case PASSWORD: |
| case ACCOUNT_CREATION_PASSWORD: |
| case CONFIRMATION_PASSWORD: |
| case SINGLE_USERNAME: |
| case SINGLE_USERNAME_FORGOT_PASSWORD: |
| case SINGLE_USERNAME_WITH_INTERMEDIATE_VALUES: |
| case NOT_PASSWORD: |
| case NOT_USERNAME: |
| case NOT_ACCOUNT_CREATION_PASSWORD: |
| case NEW_PASSWORD: |
| case PROBABLY_NEW_PASSWORD: |
| case NOT_NEW_PASSWORD: |
| case ONE_TIME_CODE: |
| case NO_SERVER_DATA: |
| case EMPTY_TYPE: |
| case AMBIGUOUS_TYPE: |
| case MERCHANT_EMAIL_SIGNUP: |
| case PRICE: |
| case NUMERIC_QUANTITY: |
| case SEARCH_TERM: |
| case PASSPORT_NUMBER: |
| case PASSPORT_ISSUING_COUNTRY: |
| case PASSPORT_EXPIRATION_DATE: |
| case PASSPORT_ISSUE_DATE: |
| case LOYALTY_MEMBERSHIP_PROGRAM: |
| case LOYALTY_MEMBERSHIP_PROVIDER: |
| case LOYALTY_MEMBERSHIP_ID: |
| case VEHICLE_LICENSE_PLATE: |
| case VEHICLE_VIN: |
| case VEHICLE_MAKE: |
| case VEHICLE_MODEL: |
| case VEHICLE_YEAR: |
| case VEHICLE_PLATE_STATE: |
| case DRIVERS_LICENSE_REGION: |
| case DRIVERS_LICENSE_NUMBER: |
| case DRIVERS_LICENSE_EXPIRATION_DATE: |
| case DRIVERS_LICENSE_ISSUE_DATE: |
| case NATIONAL_ID_CARD_NUMBER: |
| case NATIONAL_ID_CARD_EXPIRATION_DATE: |
| case NATIONAL_ID_CARD_ISSUE_DATE: |
| case NATIONAL_ID_CARD_ISSUING_COUNTRY: |
| case REDRESS_NUMBER: |
| case KNOWN_TRAVELER_NUMBER: |
| case KNOWN_TRAVELER_NUMBER_EXPIRATION_DATE: |
| case MAX_VALID_FIELD_TYPE: |
| case DELIVERY_INSTRUCTIONS: |
| case ADDRESS_HOME_SUBPREMISE: |
| case ADDRESS_HOME_OTHER_SUBUNIT: |
| case ADDRESS_HOME_ADDRESS_WITH_NAME: |
| case ADDRESS_HOME_FLOOR: |
| case ADDRESS_HOME_SORTING_CODE: |
| case UNKNOWN_TYPE: |
| case ADDRESS_HOME_LINE1: |
| case ADDRESS_HOME_LINE2: |
| case ADDRESS_HOME_LINE3: |
| case ADDRESS_HOME_APT_NUM: |
| case ADDRESS_HOME_APT: |
| case ADDRESS_HOME_APT_TYPE: |
| case ADDRESS_HOME_HOUSE_NUMBER_AND_APT: |
| case ADDRESS_HOME_CITY: |
| case ADDRESS_HOME_STATE: |
| case ADDRESS_HOME_ZIP: |
| case ADDRESS_HOME_ZIP_AND_CITY: |
| case ADDRESS_HOME_ZIP_PREFIX: |
| case ADDRESS_HOME_ZIP_SUFFIX: |
| case ADDRESS_HOME_COUNTRY: |
| case ADDRESS_HOME_DEPENDENT_LOCALITY: |
| case ADDRESS_HOME_STREET_NAME: |
| case ADDRESS_HOME_HOUSE_NUMBER: |
| case ADDRESS_HOME_STREET_LOCATION: |
| case ADDRESS_HOME_LANDMARK: |
| case ADDRESS_HOME_BETWEEN_STREETS: |
| case ADDRESS_HOME_BETWEEN_STREETS_1: |
| case ADDRESS_HOME_BETWEEN_STREETS_2: |
| case ADDRESS_HOME_ADMIN_LEVEL2: |
| case ADDRESS_HOME_OVERFLOW: |
| case ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK: |
| case ADDRESS_HOME_OVERFLOW_AND_LANDMARK: |
| case COMPANY_NAME: |
| case ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY: |
| case ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK: |
| case ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK: |
| case EMAIL_OR_LOYALTY_MEMBERSHIP_ID: |
| case FLIGHT_RESERVATION_FLIGHT_NUMBER: |
| case FLIGHT_RESERVATION_TICKET_NUMBER: |
| case FLIGHT_RESERVATION_CONFIRMATION_CODE: |
| case FLIGHT_RESERVATION_DEPARTURE_DATE: |
| case ORDER_ID: |
| case ORDER_DATE: |
| case ORDER_MERCHANT_NAME: |
| case SHIPMENT_TRACKING_NUMBER: |
| return AddressUIComponentIconType::kNoIcon; |
| } |
| } |
| |
| } // namespace autofill |