blob: 15b2d5926e418ddd2f8cf58774c19eaea49433ea [file] [log] [blame]
// Copyright 2017 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/autofill_profile_validation_util.h"
#include <string>
#include <utility>
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/address_i18n.h"
#include "components/autofill/core/browser/country_data.h"
#include "components/autofill/core/browser/validation.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_validator.h"
#include "third_party/libphonenumber/dist/cpp/src/phonenumbers/phonenumberutil.h"
namespace autofill {
namespace {
using ::i18n::addressinput::COUNTRY;
using ::i18n::addressinput::ADMIN_AREA;
using ::i18n::addressinput::LOCALITY;
using ::i18n::addressinput::DEPENDENT_LOCALITY;
using ::i18n::addressinput::SORTING_CODE;
using ::i18n::addressinput::POSTAL_CODE;
using ::i18n::addressinput::STREET_ADDRESS;
using ::i18n::addressinput::RECIPIENT;
using ::i18n::addressinput::AddressData;
using ::i18n::addressinput::AddressField;
using ::i18n::addressinput::AddressProblem;
using ::i18n::addressinput::FieldProblemMap;
using ::i18n::addressinput::INVALID_FORMAT;
using ::i18n::addressinput::MISMATCHING_VALUE;
using ::i18n::addressinput::MISSING_REQUIRED_FIELD;
using ::i18n::addressinput::UNEXPECTED_FIELD;
using ::i18n::addressinput::UNKNOWN_VALUE;
using ::i18n::phonenumbers::PhoneNumberUtil;
const AddressField kFields[] = {COUNTRY, ADMIN_AREA, LOCALITY,
DEPENDENT_LOCALITY, POSTAL_CODE};
const AddressProblem kProblems[] = {UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD,
UNKNOWN_VALUE, INVALID_FORMAT,
MISMATCHING_VALUE};
// If the |address_field| is valid, set the validity state of the
// |address_field| in the |profile| to the |state| and return true.
// Otherwise, return false.
bool SetValidityStateForAddressField(const AutofillProfile* profile,
AddressField address_field,
AutofillProfile::ValidityState state) {
ServerFieldType server_field = i18n::TypeForField(address_field,
/*billing=*/false);
if (server_field == UNKNOWN_TYPE)
return false;
DCHECK(profile);
profile->SetValidityState(server_field, state, AutofillProfile::CLIENT);
return true;
}
// Set the validity state of all address fields in the |profile| to |state|.
void SetAllAddressValidityStates(const AutofillProfile* profile,
AutofillProfile::ValidityState state) {
DCHECK(profile);
for (auto field : kFields)
SetValidityStateForAddressField(profile, field, state);
}
// Returns all relevant pairs of (field, problem), where field is in
// |kFields|, and problem is in |kProblems|.
FieldProblemMap* CreateFieldProblemMap() {
FieldProblemMap* filter = new FieldProblemMap();
for (auto field : kFields) {
for (auto problem : kProblems) {
filter->insert(std::make_pair(field, problem));
}
}
return filter;
}
// GetFilter() will make sure that the validation only returns problems that
// are relevant.
const FieldProblemMap* GetFilter() {
static const FieldProblemMap* const filter = CreateFieldProblemMap();
return filter;
}
// Initializes |address| data from the address info in the |profile|.
void InitializeAddressFromProfile(const AutofillProfile& profile,
AddressData* address) {
address->region_code =
base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY));
address->administrative_area =
base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_STATE));
address->locality = base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_CITY));
address->dependent_locality =
base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY));
// The validation is case insensitive, and the postal codes are always upper
// case.
address->postal_code = base::UTF16ToUTF8(
base::i18n::ToUpper(profile.GetRawInfo(ADDRESS_HOME_ZIP)));
}
void SetEmptyValidityIfEmpty(const AutofillProfile* profile) {
if (profile->GetRawInfo(ADDRESS_HOME_COUNTRY).empty())
profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
if (profile->GetRawInfo(ADDRESS_HOME_STATE).empty())
profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
if (profile->GetRawInfo(ADDRESS_HOME_CITY).empty())
profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
if (profile->GetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY).empty())
profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::EMPTY, AutofillProfile::CLIENT);
if (profile->GetRawInfo(ADDRESS_HOME_ZIP).empty())
profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
}
void SetInvalidIfUnvalidated(const AutofillProfile* profile) {
if (profile->GetValidityState(ADDRESS_HOME_COUNTRY,
AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
}
void MaybeApplyValidToFields(const AutofillProfile* profile) {
// The metadata works from top to bottom. Therefore, a so far UNVALIDATED
// subregion can only be validated if its super-region is VALID. In this
// case, it's VALID if it has not been marked as INVALID or EMPTY.
if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::VALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED &&
profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
AutofillProfile::VALID) {
profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::VALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED &&
profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
AutofillProfile::VALID) {
profile->SetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::VALID, AutofillProfile::CLIENT);
}
// ZIP only depends on COUNTRY. If it's not so far marked as INVALID or EMPTY,
// then it's VALID.
if (profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
AutofillProfile::UNVALIDATED) {
profile->SetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::VALID,
AutofillProfile::CLIENT);
}
}
void ApplyValidOnlyIfAllChildrenNotInvalid(const AutofillProfile* profile) {
if (profile->GetValidityState(ADDRESS_HOME_STATE, AutofillProfile::CLIENT) ==
AutofillProfile::INVALID &&
profile->GetValidityState(ADDRESS_HOME_ZIP, AutofillProfile::CLIENT) ==
AutofillProfile::INVALID) {
profile->SetValidityState(ADDRESS_HOME_COUNTRY, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_CITY, AutofillProfile::CLIENT) ==
AutofillProfile::INVALID) {
profile->SetValidityState(ADDRESS_HOME_STATE, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
if (profile->GetValidityState(ADDRESS_HOME_DEPENDENT_LOCALITY,
AutofillProfile::CLIENT) ==
AutofillProfile::INVALID) {
profile->SetValidityState(ADDRESS_HOME_CITY, AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
}
} // namespace
namespace profile_validation_util {
void ValidateProfile(const AutofillProfile* profile,
AddressValidator* address_validator) {
DCHECK(address_validator);
DCHECK(profile);
ValidateAddressStrictly(profile, address_validator);
ValidatePhoneNumber(profile);
ValidateEmailAddress(profile);
}
AddressValidator::Status ValidateAddress(const AutofillProfile* profile,
AddressValidator* address_validator) {
DCHECK(address_validator);
DCHECK(profile);
SetAllAddressValidityStates(profile, AutofillProfile::UNVALIDATED);
if (!base::ContainsValue(
CountryDataMap::GetInstance()->country_codes(),
base::UTF16ToUTF8(profile->GetRawInfo(ADDRESS_HOME_COUNTRY)))) {
// If the country code is not in the database, the country code and the
// profile are invalid, and other fields cannot be validated, because it is
// unclear which, if any, rule should apply.
SetValidityStateForAddressField(profile, COUNTRY, AutofillProfile::INVALID);
SetEmptyValidityIfEmpty(profile);
return AddressValidator::SUCCESS;
}
// The COUNTRY was already listed in the CountryDataMap, therefore it's valid.
SetValidityStateForAddressField(profile, COUNTRY, AutofillProfile::VALID);
AddressData address;
InitializeAddressFromProfile(*profile, &address);
FieldProblemMap problems;
// status denotes if the rule was successfully loaded before validation.
AddressValidator::Status status =
address_validator->ValidateAddress(address, GetFilter(), &problems);
for (auto problem : problems)
SetValidityStateForAddressField(profile, problem.first,
AutofillProfile::INVALID);
SetEmptyValidityIfEmpty(profile);
// Fields (except COUNTRY) could be VALID, only if the rules were available.
if (status == AddressValidator::SUCCESS)
MaybeApplyValidToFields(profile);
return status;
}
void ValidateAddressStrictly(const AutofillProfile* profile,
AddressValidator* address_validator) {
DCHECK(address_validator);
DCHECK(profile);
// If the rules were loaded successfully, add a second layer of validation:
// 1. For a field to stay valid after the first run, all the fields that
// depend on that field for validation need to not be invalid on the first
// run, otherwise there is a chance that the data on that field was also
// invalid (incorrect.)
// Example: 1225 Notre-Dame Ouest, Montreal, Quebec, H3C 2A3, United States.
// A human validator can see that the country is most probably the invalid
// field. The first step helps us validate the rules interdependently.
// 2. All the address fields that could not be validated (UNVALIDATED),
// should be considered as invalid.
if (ValidateAddress(profile, address_validator) ==
AddressValidator::SUCCESS) {
ApplyValidOnlyIfAllChildrenNotInvalid(profile);
SetInvalidIfUnvalidated(profile);
}
}
void ValidateEmailAddress(const AutofillProfile* profile) {
const base::string16& email = profile->GetRawInfo(EMAIL_ADDRESS);
if (email.empty()) {
profile->SetValidityState(EMAIL_ADDRESS, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
return;
}
profile->SetValidityState(EMAIL_ADDRESS,
autofill::IsValidEmailAddress(email)
? AutofillProfile::VALID
: AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
void ValidatePhoneNumber(const AutofillProfile* profile) {
const std::string& phone_number =
base::UTF16ToUTF8(profile->GetRawInfo(PHONE_HOME_WHOLE_NUMBER));
if (phone_number.empty()) {
profile->SetValidityState(PHONE_HOME_WHOLE_NUMBER, AutofillProfile::EMPTY,
AutofillProfile::CLIENT);
return;
}
const std::string& country_code =
base::UTF16ToUTF8(profile->GetRawInfo(ADDRESS_HOME_COUNTRY));
if (!base::ContainsValue(CountryDataMap::GetInstance()->country_codes(),
country_code)) {
// If the country code is not in the database, the phone number cannot be
// validated.
profile->SetValidityState(PHONE_HOME_WHOLE_NUMBER,
AutofillProfile::UNVALIDATED,
AutofillProfile::CLIENT);
return;
}
PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance();
profile->SetValidityState(
PHONE_HOME_WHOLE_NUMBER,
phone_util->IsPossibleNumberForString(phone_number, country_code)
? AutofillProfile::VALID
: AutofillProfile::INVALID,
AutofillProfile::CLIENT);
}
} // namespace profile_validation_util
} // namespace autofill