blob: 0b0bd50a125de795f6c56a79a0f98255ec3b62bb [file] [log] [blame]
// Copyright 2020 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_assistant/browser/field_formatter.h"
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/state_names.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill_assistant/browser/generic_ui.pb.h"
#include "third_party/re2/src/re2/re2.h"
#include "third_party/re2/src/re2/stringpiece.h"
namespace autofill_assistant {
namespace field_formatter {
namespace {
// Regex to find placeholders of the form ${key}, where key is an arbitrary
// string that does not contain curly braces. The first capture group is for
// the prefix before the key, the second for the key itself.
const char kPlaceholderExtractor[] = R"re((.*?)\$\{([^{}]+)\})re";
template <typename T>
absl::optional<std::string> GetFieldValue(
const base::flat_map<T, std::string>& mappings,
const T& key) {
auto it = mappings.find(key);
if (it == mappings.end()) {
return absl::nullopt;
}
return it->second;
}
base::flat_map<Key, std::string> CreateFormGroupMappings(
const autofill::FormGroup& form_group,
const std::string& locale) {
std::vector<std::pair<Key, std::string>> mappings;
autofill::ServerFieldTypeSet available_fields;
form_group.GetNonEmptyTypes(locale, &available_fields);
for (const auto field : available_fields) {
mappings.emplace_back(Key(field),
base::UTF16ToUTF8(form_group.GetInfo(
autofill::AutofillType(field), locale)));
}
return base::flat_map<Key, std::string>(std::move(mappings));
}
void GetNameAndAbbreviationViaAlternativeStateNameMap(
const std::string& country_code,
const std::u16string& state_from_profile,
std::u16string* name,
std::u16string* abbreviation) {
absl::optional<autofill::StateEntry> state_entry =
autofill::AlternativeStateNameMap::GetInstance()->GetEntry(
autofill::AlternativeStateNameMap::CountryCode(country_code),
autofill::AlternativeStateNameMap::StateName(state_from_profile));
if (!state_entry) {
// Name and abbreviation are already prefilled.
return;
}
if (state_entry->has_canonical_name() &&
!state_entry->canonical_name().empty()) {
std::u16string full = base::ASCIIToUTF16(state_entry->canonical_name());
std::u16string abbr;
size_t curr_min_abbr_size = INT_MAX;
for (const auto& it_abbr : state_entry->abbreviations()) {
if (!it_abbr.empty() && it_abbr.size() < curr_min_abbr_size) {
abbr = base::ASCIIToUTF16(it_abbr);
curr_min_abbr_size = it_abbr.size();
}
}
if (name) {
name->swap(full);
}
if (abbreviation) {
abbreviation->swap(abbr);
}
}
}
std::string ApplyChunkReplacement(
const google::protobuf::Map<std::string, std::string>& replacements,
const std::string& value) {
const auto& it = replacements.find(value);
if (it != replacements.end()) {
return it->second;
}
return value;
}
std::string GetMaybeQuotedChunk(const std::string& value, bool quote_meta) {
if (quote_meta) {
return re2::RE2::QuoteMeta(value);
}
return value;
}
} // namespace
Key::Key(int key) : int_key(key) {}
Key::Key(AutofillFormatProto::AutofillAssistantCustomField custom_field)
: int_key(static_cast<int>(custom_field)) {}
Key::Key(autofill::ServerFieldType autofill_field)
: int_key(static_cast<int>(autofill_field)) {}
Key::Key(std::string key) : string_key(key) {}
Key::~Key() = default;
Key::Key(const Key&) = default;
bool Key::operator<(const Key& other) const {
return std::make_tuple(this->int_key.value_or(0),
this->string_key.value_or(std::string())) <
std::make_tuple(other.int_key.value_or(0),
other.string_key.value_or(std::string()));
}
bool Key::operator==(const Key& other) const {
return !(*this < other) && !(other < *this);
}
absl::optional<std::string> FormatString(
const std::string& pattern,
const base::flat_map<std::string, std::string>& mappings,
bool strict) {
if (pattern.empty()) {
return std::string();
}
std::string out;
re2::StringPiece input(pattern);
std::string prefix;
std::string key;
while (
re2::RE2::FindAndConsume(&input, kPlaceholderExtractor, &prefix, &key)) {
auto rewrite_value = GetFieldValue(mappings, key);
if (!rewrite_value.has_value()) {
if (strict) {
VLOG(2) << "No value for " << key << " in " << pattern;
return absl::nullopt;
}
// Leave placeholder unchanged.
rewrite_value = "${" + key + "}";
}
out = out + prefix + *rewrite_value;
}
// Append remaining unmatched suffix (if any).
out = out + input.ToString();
return out;
}
ClientStatus FormatExpression(const ValueExpression& value_expression,
const base::flat_map<Key, std::string>& mappings,
bool quote_meta,
std::string* out_value) {
out_value->clear();
for (const auto& chunk : value_expression.chunk()) {
std::string chunk_value;
switch (chunk.chunk_case()) {
case ValueExpression::Chunk::kText:
chunk_value = chunk.text();
break;
case ValueExpression::Chunk::kKey: {
auto rewrite_value = GetFieldValue(mappings, Key(chunk.key()));
if (!rewrite_value.has_value()) {
return ClientStatus(AUTOFILL_INFO_NOT_AVAILABLE);
}
chunk_value = GetMaybeQuotedChunk(*rewrite_value, quote_meta);
break;
}
case ValueExpression::Chunk::kMemoryKey: {
auto rewrite_value = GetFieldValue(mappings, Key(chunk.memory_key()));
if (!rewrite_value.has_value()) {
return ClientStatus(CLIENT_MEMORY_KEY_NOT_AVAILABLE);
}
chunk_value = GetMaybeQuotedChunk(*rewrite_value, quote_meta);
break;
}
case ValueExpression::Chunk::CHUNK_NOT_SET:
return ClientStatus(INVALID_ACTION);
}
out_value->append(ApplyChunkReplacement(chunk.replacements(), chunk_value));
}
return OkClientStatus();
}
std::string GetHumanReadableValueExpression(
const ValueExpression& value_expression) {
std::string out;
for (const auto& chunk : value_expression.chunk()) {
switch (chunk.chunk_case()) {
case ValueExpression::Chunk::kText:
out += chunk.text();
break;
case ValueExpression::Chunk::kKey:
out += base::StrCat({"${", base::NumberToString(chunk.key()), "}"});
break;
case ValueExpression::Chunk::kMemoryKey:
out += base::StrCat({"${", chunk.memory_key(), "}"});
break;
case ValueExpression::Chunk::CHUNK_NOT_SET:
out += "<CHUNK_NOT_SET>";
break;
}
}
return out;
}
template <>
base::flat_map<Key, std::string>
CreateAutofillMappings<autofill::AutofillProfile>(
const autofill::AutofillProfile& profile,
const std::string& locale) {
auto mappings = CreateFormGroupMappings(profile, locale);
std::string country_code =
base::UTF16ToUTF8(profile.GetRawInfo(autofill::ADDRESS_HOME_COUNTRY));
if (!country_code.empty()) {
mappings.emplace(Key(AutofillFormatProto::ADDRESS_HOME_COUNTRY_CODE),
country_code);
}
auto state = profile.GetInfo(
autofill::AutofillType(autofill::ADDRESS_HOME_STATE), locale);
if (!state.empty()) {
std::u16string full_name;
std::u16string abbreviation;
autofill::state_names::GetNameAndAbbreviation(state, &full_name,
&abbreviation);
DCHECK(!full_name.empty());
full_name = full_name.length() > 1
? base::StrCat({base::i18n::ToUpper(full_name.substr(0, 1)),
full_name.substr(1)})
: base::i18n::ToUpper(full_name);
if (abbreviation.empty() && !country_code.empty() &&
base::FeatureList::IsEnabled(
autofill::features::kAutofillUseAlternativeStateNameMap)) {
GetNameAndAbbreviationViaAlternativeStateNameMap(
country_code, state, &full_name, &abbreviation);
}
mappings.emplace(Key(AutofillFormatProto::ADDRESS_HOME_STATE_NAME),
base::UTF16ToUTF8(full_name));
if (abbreviation.empty()) {
mappings.erase(Key(autofill::ADDRESS_HOME_STATE));
} else {
mappings[Key(autofill::ADDRESS_HOME_STATE)] =
base::UTF16ToUTF8(base::i18n::ToUpper(abbreviation));
}
}
return mappings;
}
template <>
base::flat_map<Key, std::string> CreateAutofillMappings<autofill::CreditCard>(
const autofill::CreditCard& credit_card,
const std::string& locale) {
auto mappings = CreateFormGroupMappings(credit_card, locale);
auto network = std::string(
autofill::data_util::GetPaymentRequestData(credit_card.network())
.basic_card_issuer_network);
if (!network.empty()) {
mappings.emplace(Key(AutofillFormatProto::CREDIT_CARD_NETWORK), network);
}
auto network_for_display = base::UTF16ToUTF8(credit_card.NetworkForDisplay());
if (!network_for_display.empty()) {
mappings.emplace(Key(AutofillFormatProto::CREDIT_CARD_NETWORK_FOR_DISPLAY),
network_for_display);
}
auto last_four_digits = base::UTF16ToUTF8(credit_card.LastFourDigits());
if (!last_four_digits.empty()) {
mappings.emplace(
Key(AutofillFormatProto::CREDIT_CARD_NUMBER_LAST_FOUR_DIGITS),
last_four_digits);
}
int month;
if (base::StringToInt(
credit_card.GetInfo(autofill::CREDIT_CARD_EXP_MONTH, locale),
&month)) {
mappings.emplace(Key(AutofillFormatProto::CREDIT_CARD_NON_PADDED_EXP_MONTH),
base::NumberToString(month));
}
return mappings;
}
} // namespace field_formatter
std::ostream& operator<<(std::ostream& out,
const ValueExpression& value_expression) {
return out << field_formatter::GetHumanReadableValueExpression(
value_expression);
}
} // namespace autofill_assistant