// 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/geo/phone_number_i18n.h" #include #include "base/check_op.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_data_util.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/geo/autofill_country.h" #include "third_party/libphonenumber/phonenumber_api.h" namespace autofill { namespace { using ::i18n::phonenumbers::PhoneNumberUtil; // Formats the |phone_number| to the specified |format|. Returns the original // number if the operation is not possible. std::string FormatPhoneNumber(const std::string& phone_number, const std::string& country_code, PhoneNumberUtil::PhoneNumberFormat format) { ::i18n::phonenumbers::PhoneNumber parsed_number; PhoneNumberUtil* phone_number_util = PhoneNumberUtil::GetInstance(); if (phone_number_util->Parse(phone_number, country_code, &parsed_number) != PhoneNumberUtil::NO_PARSING_ERROR) { return phone_number; } std::string formatted_number; phone_number_util->Format(parsed_number, format, &formatted_number); return formatted_number; } std::string SanitizeRegion(const std::string& region, const std::string& app_locale) { if (region.length() == 2) return region; return AutofillCountry::CountryCodeForLocale(app_locale); } // Formats the given |number| as a human-readable string, and writes the result // into |formatted_number|. Also, normalizes the formatted number, and writes // that result into |normalized_number|. This function should only be called // with numbers already known to be valid, i.e. validation should be done prior // to calling this function. Note that the |country_code|, which determines // whether to format in the national or in the international format, is passed // in explicitly, as |number| might have an implicit country code set, even // though the original input lacked a country code. void FormatValidatedNumber(const ::i18n::phonenumbers::PhoneNumber& number, const base::string16& country_code, base::string16* formatted_number, base::string16* normalized_number) { PhoneNumberUtil::PhoneNumberFormat format = country_code.empty() ? PhoneNumberUtil::NATIONAL : PhoneNumberUtil::INTERNATIONAL; PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); std::string processed_number; phone_util->Format(number, format, &processed_number); std::string region_code; phone_util->GetRegionCodeForNumber(number, ®ion_code); // Drop the leading '+' for US/CA numbers as some sites can't handle the "+", // and in these regions dialing "+1..." is the same as dialing "1...". // TODO(crbug/226778): Investigate whether the leading "+" is desirable in // other regions. Closed bug crbug/98911 contains additional context. std::string prefix; if (processed_number[0] == '+') { processed_number = processed_number.substr(1); if (region_code != "US" && region_code != "CA") prefix = "+"; } if (formatted_number) *formatted_number = base::UTF8ToUTF16(prefix + processed_number); if (normalized_number) { phone_util->NormalizeDigitsOnly(&processed_number); *normalized_number = base::UTF8ToUTF16(prefix + processed_number); } } // Returns false iff |str| contains any characters from the range 0-31 // (inclusive) or the "delete" character (127). This allows the characters in // 128-255 because |str| is assumed to be in UTF-8. Note that for all // multi-byte UTF-8 characters, every byte has its most significant bit set // (i.e., is in the range 128-255, inclusive), so all bytes <=127 are // single-byte characters. bool IsPrintable(base::StringPiece str) { for (unsigned char c : str) { if (c < 32 || c == 127) return false; } return true; } } // namespace namespace i18n { const size_t kMaxPhoneNumberSize = 40u; // Returns true if |phone_number| is a possible number. bool IsPossiblePhoneNumber( const ::i18n::phonenumbers::PhoneNumber& phone_number) { PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); return phone_util->IsPossibleNumber(phone_number); } bool IsPossiblePhoneNumber(const std::string& phone_number, const std::string& country_code) { ::i18n::phonenumbers::PhoneNumber parsed_number; PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); auto result = phone_util->ParseAndKeepRawInput(phone_number, country_code, &parsed_number); return result == ::i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR && phone_util->IsPossibleNumber(parsed_number); } bool IsValidPhoneNumber(const std::string& phone_number, const std::string& country_code) { ::i18n::phonenumbers::PhoneNumber parsed_number; PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); auto result = phone_util->ParseAndKeepRawInput(phone_number, country_code, &parsed_number); return result == ::i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR && phone_util->IsValidNumber(parsed_number); } // Parses the number stored in |value| as it should be interpreted in the given // |default_region|, and stores the results into the remaining arguments. // The |default_region| should be sanitized prior to calling this function. bool ParsePhoneNumber(const base::string16& value, const std::string& default_region, base::string16* country_code, base::string16* city_code, base::string16* number, std::string* inferred_region, ::i18n::phonenumbers::PhoneNumber* i18n_number) { country_code->clear(); city_code->clear(); number->clear(); *i18n_number = ::i18n::phonenumbers::PhoneNumber(); if (value.size() > kMaxPhoneNumberSize) return false; std::string number_text; if (!base::UTF16ToUTF8(value.data(), value.size(), &number_text) || !base::IsStringUTF8(number_text) || !IsPrintable(number_text)) { return false; } // Parse phone number based on the region. PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); // The |default_region| should already be sanitized. DCHECK_EQ(2U, default_region.size()); if (phone_util->ParseAndKeepRawInput(number_text, default_region, i18n_number) != PhoneNumberUtil::NO_PARSING_ERROR) { return false; } if (!IsPossiblePhoneNumber(*i18n_number)) return false; std::string national_significant_number; phone_util->GetNationalSignificantNumber(*i18n_number, &national_significant_number); int area_length = phone_util->GetLengthOfGeographicalAreaCode(*i18n_number); int destination_length = phone_util->GetLengthOfNationalDestinationCode(*i18n_number); // Some phones have a destination code in lieu of area code: mobile operators // in Europe, toll and toll-free numbers in USA, etc. From our point of view // these two types of codes are the same. if (destination_length > area_length) area_length = destination_length; if (area_length >= static_cast(national_significant_number.size())) { // For some non-ASCII strings |destination_length| is bigger than phone // string size. It might be because of incorrect treating of non-ASCII // characters. return false; } std::string area_code; std::string subscriber_number; if (area_length > 0) { area_code = national_significant_number.substr(0, area_length); subscriber_number = national_significant_number.substr(area_length); } else { subscriber_number = national_significant_number; } *number = base::UTF8ToUTF16(subscriber_number); *city_code = base::UTF8ToUTF16(area_code); // Check if parsed number has a country code that was not inferred from the // region. if (i18n_number->has_country_code() && i18n_number->country_code_source() != ::i18n::phonenumbers::PhoneNumber::FROM_DEFAULT_COUNTRY) { *country_code = base::UTF8ToUTF16(base::NumberToString(i18n_number->country_code())); } // The region might be different from what we started with. phone_util->GetRegionCodeForNumber(*i18n_number, inferred_region); return true; } base::string16 NormalizePhoneNumber(const base::string16& value, const std::string& region) { DCHECK_EQ(2u, region.size()); base::string16 country_code, unused_city_code, unused_number; std::string unused_region; ::i18n::phonenumbers::PhoneNumber phone_number; if (!ParsePhoneNumber(value, region, &country_code, &unused_city_code, &unused_number, &unused_region, &phone_number)) { return base::string16(); // Parsing failed - do not store phone. } base::string16 normalized_number; FormatValidatedNumber(phone_number, country_code, nullptr, &normalized_number); return normalized_number; } bool ConstructPhoneNumber(const base::string16& country_code, const base::string16& city_code, const base::string16& number, const std::string& region, base::string16* whole_number) { DCHECK_EQ(2u, region.size()); whole_number->clear(); base::string16 unused_country_code, unused_city_code, unused_number; std::string unused_region; ::i18n::phonenumbers::PhoneNumber phone_number; if (!ParsePhoneNumber(country_code + city_code + number, region, &unused_country_code, &unused_city_code, &unused_number, &unused_region, &phone_number)) { return false; } FormatValidatedNumber(phone_number, country_code, whole_number, nullptr); return true; } bool PhoneNumbersMatch(const base::string16& number_a, const base::string16& number_b, const std::string& raw_region, const std::string& app_locale) { if (number_a.empty() && number_b.empty()) { return true; } if (number_a.empty() || number_b.empty()) { return false; } // Sanitize the provided |raw_region| before trying to use it for parsing. const std::string region = SanitizeRegion(raw_region, app_locale); PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); // Parse phone numbers based on the region ::i18n::phonenumbers::PhoneNumber i18n_number1; if (phone_util->Parse(base::UTF16ToUTF8(number_a), region, &i18n_number1) != PhoneNumberUtil::NO_PARSING_ERROR) { return false; } ::i18n::phonenumbers::PhoneNumber i18n_number2; if (phone_util->Parse(base::UTF16ToUTF8(number_b), region, &i18n_number2) != PhoneNumberUtil::NO_PARSING_ERROR) { return false; } switch (phone_util->IsNumberMatch(i18n_number1, i18n_number2)) { case PhoneNumberUtil::INVALID_NUMBER: case PhoneNumberUtil::NO_MATCH: return false; case PhoneNumberUtil::SHORT_NSN_MATCH: return false; case PhoneNumberUtil::NSN_MATCH: case PhoneNumberUtil::EXACT_MATCH: return true; } NOTREACHED(); return false; } base::string16 GetFormattedPhoneNumberForDisplay(const AutofillProfile& profile, const std::string& locale) { // Since the "+" is removed for some country's phone numbers, try to add a "+" // and see if it is a valid phone number for a country. // Having two "+" in front of a number has no effect on the formatted number. // The reason for this is international phone numbers for another country. For // example, without adding a "+", the US number 1-415-123-1234 for an AU // address would be wrongly formatted as +61 1-415-123-1234 which is invalid. std::string phone = base::UTF16ToUTF8( profile.GetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), locale)); std::string tentative_intl_phone = "+" + phone; // Always favor the tentative international phone number if it's determined as // being a valid number. const std::string country_code = autofill::data_util::GetCountryCodeWithFallback(profile, locale); if (IsValidPhoneNumber(tentative_intl_phone, country_code)) { return base::UTF8ToUTF16( FormatPhoneNumber(tentative_intl_phone, country_code, PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL)); } if (IsValidPhoneNumber(phone, country_code)) { return base::UTF8ToUTF16( FormatPhoneNumber(phone, country_code, PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL)); } return base::UTF8ToUTF16(phone); } std::string FormatPhoneNationallyForDisplay(const std::string& phone_number, const std::string& country_code) { if (IsValidPhoneNumber(phone_number, country_code)) { return FormatPhoneNumber(phone_number, country_code, PhoneNumberUtil::PhoneNumberFormat::NATIONAL); } return phone_number; } std::string FormatPhoneForDisplay(const std::string& phone_number, const std::string& country_code) { if (IsValidPhoneNumber(phone_number, country_code)) { return FormatPhoneNumber(phone_number, country_code, PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL); } return phone_number; } std::string FormatPhoneForResponse(const std::string& phone_number, const std::string& country_code) { if (IsValidPhoneNumber(phone_number, country_code)) { return FormatPhoneNumber(phone_number, country_code, PhoneNumberUtil::PhoneNumberFormat::E164); } return phone_number; } PhoneObject::PhoneObject(const base::string16& number, const std::string& region) { DCHECK_EQ(2u, region.size()); // TODO(isherman): Autofill profiles should always have a |region| set, but in // some cases it should be marked as implicit. Otherwise, phone numbers // might behave differently when they are synced across computers: // [ http://crbug.com/100845 ]. Once the bug is fixed, add a DCHECK here to // verify. std::unique_ptr<::i18n::phonenumbers::PhoneNumber> i18n_number( new ::i18n::phonenumbers::PhoneNumber); if (ParsePhoneNumber(number, region, &country_code_, &city_code_, &number_, ®ion_, i18n_number.get())) { // The phone number was successfully parsed, so store the parsed version. // The formatted and normalized versions will be set on the first call to // the coresponding methods. i18n_number_ = std::move(i18n_number); } else { // Parsing failed. Store passed phone "as is" into |whole_number_|. whole_number_ = number; } } PhoneObject::PhoneObject(const PhoneObject& other) { *this = other; } PhoneObject::PhoneObject() {} PhoneObject::~PhoneObject() {} const base::string16& PhoneObject::GetFormattedNumber() const { if (i18n_number_ && formatted_number_.empty()) { FormatValidatedNumber(*i18n_number_, country_code_, &formatted_number_, &whole_number_); } return formatted_number_; } base::string16 PhoneObject::GetNationallyFormattedNumber() const { base::string16 formatted = whole_number_; if (i18n_number_) FormatValidatedNumber(*i18n_number_, base::string16(), &formatted, nullptr); return formatted; } const base::string16& PhoneObject::GetWholeNumber() const { if (i18n_number_ && whole_number_.empty()) { FormatValidatedNumber(*i18n_number_, country_code_, &formatted_number_, &whole_number_); } return whole_number_; } PhoneObject& PhoneObject::operator=(const PhoneObject& other) { if (this == &other) return *this; region_ = other.region_; if (other.i18n_number_) i18n_number_.reset( new ::i18n::phonenumbers::PhoneNumber(*other.i18n_number_)); else i18n_number_.reset(); country_code_ = other.country_code_; city_code_ = other.city_code_; number_ = other.number_; formatted_number_ = other.formatted_number_; whole_number_ = other.whole_number_; return *this; } } // namespace i18n } // namespace autofill