blob: c715abb098fc6cef37b4ac2c96639d5a86c79d54 [file] [log] [blame]
// 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/form_structure.h"
#include <stdint.h>
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/field_candidates.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/form_field.h"
#include "components/autofill/core/browser/proto/legacy_proto_bridge.h"
#include "components/autofill/core/browser/randomized_encoder.h"
#include "components/autofill/core/browser/rationalization_util.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_regex_constants.h"
#include "components/autofill/core/common/autofill_regexes.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_predictions.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/form_field_data_predictions.h"
#include "components/autofill/core/common/signatures_util.h"
#include "components/security_state/core/security_state.h"
#include "url/origin.h"
namespace autofill {
namespace {
// Version of the client sent to the server.
const char kClientVersion[] = "6.1.1715.1442/en (GGLL)";
const char kBillingMode[] = "billing";
const char kShippingMode[] = "shipping";
// Only removing common name prefixes if we have a minimum number of fields and
// a minimum prefix length. These values are chosen to avoid cases such as two
// fields with "address1" and "address2" and be effective against web frameworks
// which prepend prefixes such as "ctl01$ctl00$MainContentRegion$" on all
// fields.
const int kCommonNamePrefixRemovalFieldThreshold = 3;
const int kMinCommonNamePrefixLength = 16;
// Returns true if the scheme given by |url| is one for which autfill is allowed
// to activate. By default this only returns true for HTTP and HTTPS.
bool HasAllowedScheme(const GURL& url) {
return url.SchemeIsHTTPOrHTTPS() ||
base::FeatureList::IsEnabled(
features::kAutofillAllowNonHttpActivation);
}
// Helper for |EncodeUploadRequest()| that creates a bit field corresponding to
// |available_field_types| and returns the hex representation as a string.
std::string EncodeFieldTypes(const ServerFieldTypeSet& available_field_types) {
// There are |MAX_VALID_FIELD_TYPE| different field types and 8 bits per byte,
// so we need ceil(MAX_VALID_FIELD_TYPE / 8) bytes to encode the bit field.
const size_t kNumBytes = (MAX_VALID_FIELD_TYPE + 0x7) / 8;
// Pack the types in |available_field_types| into |bit_field|.
std::vector<uint8_t> bit_field(kNumBytes, 0);
for (const auto& field_type : available_field_types) {
// Set the appropriate bit in the field. The bit we set is the one
// |field_type| % 8 from the left of the byte.
const size_t byte = field_type / 8;
const size_t bit = 0x80 >> (field_type % 8);
DCHECK(byte < bit_field.size());
bit_field[byte] |= bit;
}
// Discard any trailing zeroes.
// If there are no available types, we return the empty string.
size_t data_end = bit_field.size();
for (; data_end > 0 && !bit_field[data_end - 1]; --data_end) {
}
// Print all meaningfull bytes into a string.
std::string data_presence;
data_presence.reserve(data_end * 2 + 1);
for (size_t i = 0; i < data_end; ++i) {
base::StringAppendF(&data_presence, "%02x", bit_field[i]);
}
return data_presence;
}
// Returns |true| iff the |token| is a type hint for a contact field, as
// specified in the implementation section of http://is.gd/whatwg_autocomplete
// Note that "fax" and "pager" are intentionally ignored, as Chrome does not
// support filling either type of information.
bool IsContactTypeHint(const std::string& token) {
return token == "home" || token == "work" || token == "mobile";
}
// Returns |true| iff the |token| is a type hint appropriate for a field of the
// given |field_type|, as specified in the implementation section of
// http://is.gd/whatwg_autocomplete
bool ContactTypeHintMatchesFieldType(const std::string& token,
HtmlFieldType field_type) {
// The "home" and "work" type hints are only appropriate for email and phone
// number field types.
if (token == "home" || token == "work") {
return field_type == HTML_TYPE_EMAIL ||
(field_type >= HTML_TYPE_TEL &&
field_type <= HTML_TYPE_TEL_LOCAL_SUFFIX);
}
// The "mobile" type hint is only appropriate for phone number field types.
// Note that "fax" and "pager" are intentionally ignored, as Chrome does not
// support filling either type of information.
if (token == "mobile") {
return field_type >= HTML_TYPE_TEL &&
field_type <= HTML_TYPE_TEL_LOCAL_SUFFIX;
}
return false;
}
// Returns the Chrome Autofill-supported field type corresponding to the given
// |autocomplete_attribute_value|, if there is one, in the context of the given
// |field|. Chrome Autofill supports a subset of the field types listed at
// http://is.gd/whatwg_autocomplete
HtmlFieldType FieldTypeFromAutocompleteAttributeValue(
const std::string& autocomplete_attribute_value,
const AutofillField& field) {
if (autocomplete_attribute_value == "")
return HTML_TYPE_UNSPECIFIED;
if (autocomplete_attribute_value == "name")
return HTML_TYPE_NAME;
if (autocomplete_attribute_value == "given-name")
return HTML_TYPE_GIVEN_NAME;
if (autocomplete_attribute_value == "additional-name") {
if (field.max_length == 1)
return HTML_TYPE_ADDITIONAL_NAME_INITIAL;
return HTML_TYPE_ADDITIONAL_NAME;
}
if (autocomplete_attribute_value == "family-name")
return HTML_TYPE_FAMILY_NAME;
if (autocomplete_attribute_value == "organization")
return HTML_TYPE_ORGANIZATION;
if (autocomplete_attribute_value == "street-address")
return HTML_TYPE_STREET_ADDRESS;
if (autocomplete_attribute_value == "address-line1")
return HTML_TYPE_ADDRESS_LINE1;
if (autocomplete_attribute_value == "address-line2")
return HTML_TYPE_ADDRESS_LINE2;
if (autocomplete_attribute_value == "address-line3")
return HTML_TYPE_ADDRESS_LINE3;
// TODO(estade): remove support for "locality" and "region".
if (autocomplete_attribute_value == "locality")
return HTML_TYPE_ADDRESS_LEVEL2;
if (autocomplete_attribute_value == "region")
return HTML_TYPE_ADDRESS_LEVEL1;
if (autocomplete_attribute_value == "address-level1")
return HTML_TYPE_ADDRESS_LEVEL1;
if (autocomplete_attribute_value == "address-level2")
return HTML_TYPE_ADDRESS_LEVEL2;
if (autocomplete_attribute_value == "address-level3")
return HTML_TYPE_ADDRESS_LEVEL3;
if (autocomplete_attribute_value == "country")
return HTML_TYPE_COUNTRY_CODE;
if (autocomplete_attribute_value == "country-name")
return HTML_TYPE_COUNTRY_NAME;
if (autocomplete_attribute_value == "postal-code")
return HTML_TYPE_POSTAL_CODE;
// content_switches.h isn't accessible from here, hence we have
// to copy the string literal. This should be removed soon anyway.
if (autocomplete_attribute_value == "address" &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
"enable-experimental-web-platform-features")) {
return HTML_TYPE_FULL_ADDRESS;
}
if (autocomplete_attribute_value == "cc-name")
return HTML_TYPE_CREDIT_CARD_NAME_FULL;
if (autocomplete_attribute_value == "cc-given-name")
return HTML_TYPE_CREDIT_CARD_NAME_FIRST;
if (autocomplete_attribute_value == "cc-family-name")
return HTML_TYPE_CREDIT_CARD_NAME_LAST;
if (autocomplete_attribute_value == "cc-number")
return HTML_TYPE_CREDIT_CARD_NUMBER;
if (autocomplete_attribute_value == "cc-exp") {
if (field.max_length == 5)
return HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR;
if (field.max_length == 7)
return HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR;
return HTML_TYPE_CREDIT_CARD_EXP;
}
if (autocomplete_attribute_value == "cc-exp-month")
return HTML_TYPE_CREDIT_CARD_EXP_MONTH;
if (autocomplete_attribute_value == "cc-exp-year") {
if (field.max_length == 2)
return HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR;
if (field.max_length == 4)
return HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR;
return HTML_TYPE_CREDIT_CARD_EXP_YEAR;
}
if (autocomplete_attribute_value == "cc-csc")
return HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE;
if (autocomplete_attribute_value == "cc-type")
return HTML_TYPE_CREDIT_CARD_TYPE;
if (autocomplete_attribute_value == "transaction-amount")
return HTML_TYPE_TRANSACTION_AMOUNT;
if (autocomplete_attribute_value == "transaction-currency")
return HTML_TYPE_TRANSACTION_CURRENCY;
if (autocomplete_attribute_value == "tel")
return HTML_TYPE_TEL;
if (autocomplete_attribute_value == "tel-country-code")
return HTML_TYPE_TEL_COUNTRY_CODE;
if (autocomplete_attribute_value == "tel-national")
return HTML_TYPE_TEL_NATIONAL;
if (autocomplete_attribute_value == "tel-area-code")
return HTML_TYPE_TEL_AREA_CODE;
if (autocomplete_attribute_value == "tel-local")
return HTML_TYPE_TEL_LOCAL;
if (autocomplete_attribute_value == "tel-local-prefix")
return HTML_TYPE_TEL_LOCAL_PREFIX;
if (autocomplete_attribute_value == "tel-local-suffix")
return HTML_TYPE_TEL_LOCAL_SUFFIX;
if (autocomplete_attribute_value == "tel-extension")
return HTML_TYPE_TEL_EXTENSION;
if (autocomplete_attribute_value == "email")
return HTML_TYPE_EMAIL;
if (autocomplete_attribute_value == "upi-vpa")
return HTML_TYPE_UPI_VPA;
return HTML_TYPE_UNRECOGNIZED;
}
// Helper function for explicit conversion between |ButtonTitleType| defined in
// "button_title_type.h" and "server.proto".
AutofillUploadContents_ButtonTitle_ButtonTitleType ToServerButtonTitleType(
autofill::ButtonTitleType input) {
switch (input) {
case ButtonTitleType::NONE:
return AutofillUploadContents::ButtonTitle::NONE;
case ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE:
return AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_SUBMIT_TYPE;
case ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE:
return AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_BUTTON_TYPE;
case ButtonTitleType::INPUT_ELEMENT_SUBMIT_TYPE:
return AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_SUBMIT_TYPE;
case ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE:
return AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_BUTTON_TYPE;
case ButtonTitleType::HYPERLINK:
return AutofillUploadContents::ButtonTitle::HYPERLINK;
case ButtonTitleType::DIV:
return AutofillUploadContents::ButtonTitle::DIV;
case ButtonTitleType::SPAN:
return AutofillUploadContents::ButtonTitle::SPAN;
}
NOTREACHED();
return AutofillUploadContents::ButtonTitle::NONE;
}
std::ostream& operator<<(
std::ostream& out,
const autofill::AutofillQueryResponseContents& response) {
for (const auto& field : response.field()) {
out << "\nautofill_type: " << field.overall_type_prediction();
}
return out;
}
// Returns true iff all form fields autofill types are in |contained_types|.
bool AllTypesCaptured(const FormStructure& form,
const ServerFieldTypeSet& contained_types) {
for (const auto& field : form) {
for (const auto& type : field->possible_types()) {
if (type != UNKNOWN_TYPE && type != EMPTY_TYPE &&
!contained_types.count(type))
return false;
}
}
return true;
}
// Encode password attributes and length into |upload|.
void EncodePasswordAttributesVote(
const std::pair<PasswordAttribute, bool>& password_attributes_vote,
const size_t password_length_vote,
AutofillUploadContents* upload) {
switch (password_attributes_vote.first) {
case PasswordAttribute::kHasLowercaseLetter:
upload->set_password_has_lowercase_letter(
password_attributes_vote.second);
break;
case PasswordAttribute::kHasUppercaseLetter:
upload->set_password_has_uppercase_letter(
password_attributes_vote.second);
break;
case PasswordAttribute::kHasNumeric:
upload->set_password_has_numeric(password_attributes_vote.second);
break;
case PasswordAttribute::kHasSpecialSymbol:
upload->set_password_has_special_symbol(password_attributes_vote.second);
break;
case PasswordAttribute::kPasswordAttributesCount:
NOTREACHED();
}
upload->set_password_length(password_length_vote);
}
void EncodeRandomizedValue(const RandomizedEncoder& encoder,
FormSignature form_signature,
FieldSignature field_signature,
base::StringPiece data_type,
base::StringPiece data_value,
AutofillRandomizedValue* output) {
DCHECK(output);
output->set_encoding_type(encoder.encoding_type());
output->set_encoded_bits(
encoder.Encode(form_signature, field_signature, data_type, data_value));
}
void EncodeRandomizedValue(const RandomizedEncoder& encoder,
FormSignature form_signature,
FieldSignature field_signature,
base::StringPiece data_type,
base::StringPiece16 data_value,
AutofillRandomizedValue* output) {
EncodeRandomizedValue(encoder, form_signature, field_signature, data_type,
base::UTF16ToUTF8(data_value), output);
}
void PopulateRandomizedFormMetadata(const RandomizedEncoder& encoder,
const FormStructure& form,
AutofillRandomizedFormMetadata* metadata) {
const FormSignature form_signature = form.form_signature();
constexpr FieldSignature kNullFieldSignature =
0; // Not relevant for form level metadata.
EncodeRandomizedValue(encoder, form_signature, kNullFieldSignature,
RandomizedEncoder::FORM_ID, form.id_attribute(),
metadata->mutable_id());
EncodeRandomizedValue(encoder, form_signature, kNullFieldSignature,
RandomizedEncoder::FORM_NAME, form.name_attribute(),
metadata->mutable_name());
}
void PopulateRandomizedFieldMetadata(
const RandomizedEncoder& encoder,
const FormStructure& form,
const AutofillField& field,
AutofillRandomizedFieldMetadata* metadata) {
const FormSignature form_signature = form.form_signature();
const FieldSignature field_signature = field.GetFieldSignature();
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_ID, field.id_attribute,
metadata->mutable_id());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_NAME, field.name_attribute,
metadata->mutable_name());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_CONTROL_TYPE,
field.form_control_type, metadata->mutable_type());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_LABEL, field.label,
metadata->mutable_label());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_ARIA_LABEL, field.aria_label,
metadata->mutable_aria_label());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_ARIA_DESCRIPTION,
field.aria_description,
metadata->mutable_aria_description());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_CSS_CLASS, field.css_classes,
metadata->mutable_css_class());
EncodeRandomizedValue(encoder, form_signature, field_signature,
RandomizedEncoder::FIELD_PLACEHOLDER, field.placeholder,
metadata->mutable_placeholder());
// TODO(rogerm): Add hash of initial value.
}
void EncodeFormMetadataForQuery(const FormStructure& form,
AutofillRandomizedFormMetadata* metadata) {
DCHECK(metadata);
metadata->mutable_id()->set_encoded_bits(
base::UTF16ToUTF8(form.id_attribute()));
metadata->mutable_name()->set_encoded_bits(
base::UTF16ToUTF8(form.name_attribute()));
}
void EncodeFieldMetadataForQuery(const FormFieldData& field,
AutofillRandomizedFieldMetadata* metadata) {
DCHECK(metadata);
metadata->mutable_id()->set_encoded_bits(
base::UTF16ToUTF8(field.id_attribute));
metadata->mutable_name()->set_encoded_bits(
base::UTF16ToUTF8(field.name_attribute));
metadata->mutable_type()->set_encoded_bits(field.form_control_type);
metadata->mutable_label()->set_encoded_bits(base::UTF16ToUTF8(field.label));
metadata->mutable_aria_label()->set_encoded_bits(
base::UTF16ToUTF8(field.aria_label));
metadata->mutable_aria_description()->set_encoded_bits(
base::UTF16ToUTF8(field.aria_description));
metadata->mutable_css_class()->set_encoded_bits(
base::UTF16ToUTF8(field.css_classes));
metadata->mutable_placeholder()->set_encoded_bits(
base::UTF16ToUTF8(field.placeholder));
}
} // namespace
FormStructure::FormStructure(const FormData& form)
: id_attribute_(form.id_attribute),
name_attribute_(form.name_attribute),
form_name_(form.name),
button_titles_(form.button_titles),
submission_event_(SubmissionIndicatorEvent::NONE),
source_url_(form.origin),
target_url_(form.action),
main_frame_origin_(form.main_frame_origin),
autofill_count_(0),
active_field_count_(0),
upload_required_(USE_UPLOAD_RATES),
has_author_specified_types_(false),
has_author_specified_sections_(false),
has_author_specified_upi_vpa_hint_(false),
was_parsed_for_autocomplete_attributes_(false),
has_password_field_(false),
is_form_tag_(form.is_form_tag),
is_formless_checkout_(form.is_formless_checkout),
all_fields_are_passwords_(!form.fields.empty()),
form_parsed_timestamp_(base::TimeTicks::Now()),
passwords_were_revealed_(false),
developer_engagement_metrics_(0) {
// Copy the form fields.
std::map<base::string16, size_t> unique_names;
for (const FormFieldData& field : form.fields) {
if (!ShouldSkipField(field))
++active_field_count_;
if (field.form_control_type == "password")
has_password_field_ = true;
else
all_fields_are_passwords_ = false;
// Generate a unique name for this field by appending a counter to the name.
// Make sure to prepend the counter with a non-numeric digit so that we are
// guaranteed to avoid collisions.
base::string16 unique_name =
field.name + base::ASCIIToUTF16("_") +
base::NumberToString16(++unique_names[field.name]);
fields_.push_back(std::make_unique<AutofillField>(field, unique_name));
}
form_signature_ = autofill::CalculateFormSignature(form);
// Do further processing on the fields, as needed.
ProcessExtractedFields();
}
FormStructure::~FormStructure() {}
void FormStructure::DetermineHeuristicTypes() {
const auto determine_heuristic_types_start_time = base::TimeTicks::Now();
// First, try to detect field types based on each field's |autocomplete|
// attribute value.
if (!was_parsed_for_autocomplete_attributes_)
ParseFieldTypesFromAutocompleteAttributes();
// Then if there are enough active fields, and if we are dealing with either a
// proper <form> or a <form>-less checkout, run the heuristics and server
// prediction routines.
if (ShouldRunHeuristics()) {
const FieldCandidatesMap field_type_map =
FormField::ParseFormFields(fields_, is_form_tag_);
for (const auto& field : fields_) {
const auto iter = field_type_map.find(field->unique_name());
if (iter != field_type_map.end()) {
field->set_heuristic_type(iter->second.BestHeuristicType());
}
}
}
UpdateAutofillCount();
IdentifySections(has_author_specified_sections_);
developer_engagement_metrics_ = 0;
if (IsAutofillable()) {
AutofillMetrics::DeveloperEngagementMetric metric =
has_author_specified_types_
? AutofillMetrics::FILLABLE_FORM_PARSED_WITH_TYPE_HINTS
: AutofillMetrics::FILLABLE_FORM_PARSED_WITHOUT_TYPE_HINTS;
developer_engagement_metrics_ |= 1 << metric;
AutofillMetrics::LogDeveloperEngagementMetric(metric);
}
if (has_author_specified_upi_vpa_hint_) {
AutofillMetrics::LogDeveloperEngagementMetric(
AutofillMetrics::FORM_CONTAINS_UPI_VPA_HINT);
developer_engagement_metrics_ |=
1 << AutofillMetrics::FORM_CONTAINS_UPI_VPA_HINT;
}
RationalizeFieldTypePredictions();
AutofillMetrics::LogDetermineHeuristicTypesTiming(
base::TimeTicks::Now() - determine_heuristic_types_start_time);
}
bool FormStructure::EncodeUploadRequest(
const ServerFieldTypeSet& available_field_types,
bool form_was_autofilled,
const std::string& login_form_signature,
bool observed_submission,
AutofillUploadContents* upload) const {
DCHECK(AllTypesCaptured(*this, available_field_types));
upload->set_submission(observed_submission);
upload->set_client_version(kClientVersion);
upload->set_form_signature(form_signature());
upload->set_autofill_used(form_was_autofilled);
upload->set_data_present(EncodeFieldTypes(available_field_types));
upload->set_passwords_revealed(passwords_were_revealed_);
upload->set_has_form_tag(is_form_tag_);
if (!page_language_.empty() && randomized_encoder_ != nullptr) {
upload->set_language(page_language_);
}
auto triggering_event = (submission_event_ != SubmissionIndicatorEvent::NONE)
? submission_event_
: ToSubmissionIndicatorEvent(submission_source_);
DCHECK_LT(submission_event_,
SubmissionIndicatorEvent::SUBMISSION_INDICATOR_EVENT_COUNT);
upload->set_submission_event(
static_cast<AutofillUploadContents_SubmissionIndicatorEvent>(
triggering_event));
if (password_attributes_vote_) {
EncodePasswordAttributesVote(*password_attributes_vote_,
password_length_vote_, upload);
}
if (IsAutofillFieldMetadataEnabled()) {
upload->set_action_signature(StrToHash64Bit(target_url_.host()));
if (!form_name().empty())
upload->set_form_name(base::UTF16ToUTF8(form_name()));
for (const ButtonTitleInfo& e : button_titles_) {
auto* button_title = upload->add_button_title();
button_title->set_title(base::UTF16ToUTF8(e.first));
button_title->set_type(ToServerButtonTitleType(e.second));
}
}
if (!login_form_signature.empty()) {
uint64_t login_sig;
if (base::StringToUint64(login_form_signature, &login_sig))
upload->set_login_form_signature(login_sig);
}
if (IsMalformed())
return false; // Malformed form, skip it.
EncodeFormForUpload(upload);
return true;
}
// static
bool FormStructure::EncodeQueryRequest(
const std::vector<FormStructure*>& forms,
std::vector<std::string>* encoded_signatures,
AutofillQueryContents* query) {
DCHECK(encoded_signatures);
encoded_signatures->clear();
encoded_signatures->reserve(forms.size());
query->set_client_version(kClientVersion);
// Some badly formatted web sites repeat forms - detect that and encode only
// one form as returned data would be the same for all the repeated forms.
std::set<std::string> processed_forms;
for (const auto* form : forms) {
std::string signature(form->FormSignatureAsStr());
if (processed_forms.find(signature) != processed_forms.end())
continue;
processed_forms.insert(signature);
UMA_HISTOGRAM_COUNTS_1000("Autofill.FieldCount", form->field_count());
if (form->IsMalformed())
continue;
form->EncodeFormForQuery(query->add_form());
encoded_signatures->push_back(signature);
}
return !encoded_signatures->empty();
}
// static
void FormStructure::ParseQueryResponse(
std::string payload,
const std::vector<FormStructure*>& forms,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
AutofillMetrics::LogServerQueryMetric(
AutofillMetrics::QUERY_RESPONSE_RECEIVED);
// Parse the response.
AutofillQueryResponseContents response;
if (!response.ParseFromString(payload))
return;
VLOG(1) << "Autofill query response was successfully parsed:\n" << response;
ProcessQueryResponse(response, forms, form_interactions_ukm_logger);
}
void FormStructure::ParseApiQueryResponse(
base::StringPiece payload,
const std::vector<FormStructure*>& forms,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
AutofillMetrics::LogServerQueryMetric(
AutofillMetrics::QUERY_RESPONSE_RECEIVED);
std::string decoded_payload;
if (!base::Base64Decode(payload, &decoded_payload)) {
VLOG(1) << "Could not decode payload from base64 to bytes";
return;
}
// Parse the response.
AutofillQueryResponse response;
if (!response.ParseFromString(decoded_payload))
return;
// TODO(vincb): Make an ostream overloaded function for this.
VLOG(1) << "Autofill query response from API was successfully parsed";
ProcessQueryResponse(CreateLegacyResponseFromApiResponse(response), forms,
form_interactions_ukm_logger);
}
// static
void FormStructure::ProcessQueryResponse(
const AutofillQueryResponseContents& response,
const std::vector<FormStructure*>& forms,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
AutofillMetrics::LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_PARSED);
bool heuristics_detected_fillable_field = false;
bool query_response_overrode_heuristics = false;
// Copy the field types into the actual form.
auto current_field = response.field().begin();
for (FormStructure* form : forms) {
bool query_response_has_no_server_data = true;
for (auto& field : form->fields_) {
if (form->ShouldSkipField(*field))
continue;
// In some cases *successful* response does not return all the fields.
// Quit the update of the types then.
if (current_field == response.field().end())
break;
ServerFieldType field_type = static_cast<ServerFieldType>(
current_field->overall_type_prediction());
query_response_has_no_server_data &= field_type == NO_SERVER_DATA;
ServerFieldType heuristic_type = field->heuristic_type();
if (heuristic_type != UNKNOWN_TYPE)
heuristics_detected_fillable_field = true;
field->set_server_type(field_type);
std::vector<AutofillQueryResponseContents::Field::FieldPrediction>
server_predictions;
if (current_field->predictions_size() == 0) {
AutofillQueryResponseContents::Field::FieldPrediction field_prediction;
field_prediction.set_type(field_type);
server_predictions.push_back(field_prediction);
} else {
server_predictions.assign(current_field->predictions().begin(),
current_field->predictions().end());
}
field->set_server_predictions(std::move(server_predictions));
if (heuristic_type != field->Type().GetStorableType())
query_response_overrode_heuristics = true;
if (current_field->has_password_requirements())
field->SetPasswordRequirements(current_field->password_requirements());
++current_field;
}
AutofillMetrics::LogServerResponseHasDataForForm(
!query_response_has_no_server_data);
form->UpdateAutofillCount();
form->RationalizeRepeatedFields(form_interactions_ukm_logger);
form->RationalizeFieldTypePredictions();
form->IdentifySections(false);
}
AutofillMetrics::ServerQueryMetric metric;
if (query_response_overrode_heuristics) {
if (heuristics_detected_fillable_field) {
metric = AutofillMetrics::QUERY_RESPONSE_OVERRODE_LOCAL_HEURISTICS;
} else {
metric = AutofillMetrics::QUERY_RESPONSE_WITH_NO_LOCAL_HEURISTICS;
}
} else {
metric = AutofillMetrics::QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS;
}
AutofillMetrics::LogServerQueryMetric(metric);
}
// static
std::vector<FormDataPredictions> FormStructure::GetFieldTypePredictions(
const std::vector<FormStructure*>& form_structures) {
std::vector<FormDataPredictions> forms;
forms.reserve(form_structures.size());
for (const FormStructure* form_structure : form_structures) {
FormDataPredictions form;
form.data.name = form_structure->form_name_;
form.data.origin = form_structure->source_url_;
form.data.action = form_structure->target_url_;
form.data.main_frame_origin = form_structure->main_frame_origin_;
form.data.is_form_tag = form_structure->is_form_tag_;
form.data.is_formless_checkout = form_structure->is_formless_checkout_;
form.signature = form_structure->FormSignatureAsStr();
for (const auto& field : form_structure->fields_) {
form.data.fields.push_back(FormFieldData(*field));
FormFieldDataPredictions annotated_field;
annotated_field.signature = field->FieldSignatureAsStr();
annotated_field.heuristic_type =
AutofillType(field->heuristic_type()).ToString();
annotated_field.server_type =
AutofillType(field->server_type()).ToString();
annotated_field.overall_type = field->Type().ToString();
annotated_field.parseable_name =
base::UTF16ToUTF8(field->parseable_name());
annotated_field.section = field->section;
form.fields.push_back(annotated_field);
}
forms.push_back(form);
}
return forms;
}
// static
bool FormStructure::IsAutofillFieldMetadataEnabled() {
const std::string group_name =
base::FieldTrialList::FindFullName("AutofillFieldMetadata");
return base::StartsWith(group_name, "Enabled", base::CompareCase::SENSITIVE);
}
std::string FormStructure::FormSignatureAsStr() const {
return base::NumberToString(form_signature());
}
bool FormStructure::IsAutofillable() const {
size_t min_required_fields =
std::min({MinRequiredFieldsForHeuristics(), MinRequiredFieldsForQuery(),
MinRequiredFieldsForUpload()});
if (autofill_count() < min_required_fields)
return false;
return ShouldBeParsed();
}
bool FormStructure::IsCompleteCreditCardForm() const {
bool found_cc_number = false;
bool found_cc_expiration = false;
for (const auto& field : fields_) {
ServerFieldType type = field->Type().GetStorableType();
if (!found_cc_expiration && data_util::IsCreditCardExpirationType(type)) {
found_cc_expiration = true;
} else if (!found_cc_number && type == CREDIT_CARD_NUMBER) {
found_cc_number = true;
}
if (found_cc_expiration && found_cc_number)
return true;
}
return false;
}
void FormStructure::UpdateAutofillCount() {
autofill_count_ = 0;
for (const auto& field : *this) {
if (field && field->IsFieldFillable())
++autofill_count_;
}
}
bool FormStructure::ShouldBeParsed() const {
// Exclude URLs not on the web via HTTP(S).
if (!HasAllowedScheme(source_url_))
return false;
size_t min_required_fields =
std::min({MinRequiredFieldsForHeuristics(), MinRequiredFieldsForQuery(),
MinRequiredFieldsForUpload()});
if (active_field_count() < min_required_fields &&
(!all_fields_are_passwords() ||
active_field_count() < kRequiredFieldsForFormsWithOnlyPasswordFields) &&
!has_author_specified_types_) {
return false;
}
// Rule out search forms.
static const base::string16 kUrlSearchActionPattern =
base::UTF8ToUTF16(kUrlSearchActionRe);
if (MatchesPattern(base::UTF8ToUTF16(target_url_.path_piece()),
kUrlSearchActionPattern)) {
return false;
}
bool has_text_field = false;
for (const auto& it : *this) {
has_text_field |= it->form_control_type != "select-one";
}
return has_text_field;
}
bool FormStructure::ShouldRunHeuristics() const {
return active_field_count() >= MinRequiredFieldsForHeuristics() &&
HasAllowedScheme(source_url_) &&
(is_form_tag_ || is_formless_checkout_ ||
!base::FeatureList::IsEnabled(
features::kAutofillRestrictUnownedFieldsToFormlessCheckout));
}
bool FormStructure::ShouldBeQueried() const {
return (has_password_field_ ||
active_field_count() >= MinRequiredFieldsForQuery()) &&
ShouldBeParsed();
}
bool FormStructure::ShouldBeUploaded() const {
return active_field_count() >= MinRequiredFieldsForUpload() &&
ShouldBeParsed();
}
void FormStructure::RetrieveFromCache(
const FormStructure& cached_form,
const bool apply_is_autofilled,
const bool only_server_and_autofill_state) {
// Map from field signatures to cached fields.
std::map<base::string16, const AutofillField*> cached_fields;
for (size_t i = 0; i < cached_form.field_count(); ++i) {
auto* const field = cached_form.field(i);
cached_fields[field->unique_name()] = field;
}
for (auto& field : *this) {
const auto& cached_field = cached_fields.find(field->unique_name());
if (cached_field != cached_fields.end()) {
if (!only_server_and_autofill_state) {
// Transfer attributes of the cached AutofillField to the newly created
// AutofillField.
field->set_heuristic_type(cached_field->second->heuristic_type());
field->SetHtmlType(cached_field->second->html_type(),
cached_field->second->html_mode());
field->section = cached_field->second->section;
field->set_only_fill_when_focused(
cached_field->second->only_fill_when_focused());
}
if (apply_is_autofilled) {
field->is_autofilled = cached_field->second->is_autofilled;
}
if (field->form_control_type != "select-one" &&
field->value == cached_field->second->value) {
// From the perspective of learning user data, text fields containing
// default values are equivalent to empty fields.
field->value = base::string16();
}
field->set_server_type(cached_field->second->server_type());
field->set_previously_autofilled(
cached_field->second->previously_autofilled());
}
}
UpdateAutofillCount();
// Update form parsed timestamp
form_parsed_timestamp_ =
std::min(form_parsed_timestamp_, cached_form.form_parsed_timestamp_);
// The form signature should match between query and upload requests to the
// server. On many websites, form elements are dynamically added, removed, or
// rearranged via JavaScript between page load and form submission, so we
// copy over the |form_signature_field_names_| corresponding to the query
// request.
form_signature_ = cached_form.form_signature_;
}
void FormStructure::LogQualityMetrics(
const base::TimeTicks& load_time,
const base::TimeTicks& interaction_time,
const base::TimeTicks& submission_time,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger,
bool did_show_suggestions,
bool observed_submission) const {
// Use the same timestamp on UKM Metrics generated within this method's scope.
AutofillMetrics::UkmTimestampPin timestamp_pin(form_interactions_ukm_logger);
size_t num_detected_field_types = 0;
size_t num_edited_autofilled_fields = 0;
bool did_autofill_all_possible_fields = true;
bool did_autofill_some_possible_fields = false;
bool is_for_credit_card = IsCompleteCreditCardForm();
bool has_upi_vpa_field = false;
// Determine the correct suffix for the metric, depending on whether or
// not a submission was observed.
const AutofillMetrics::QualityMetricType metric_type =
observed_submission ? AutofillMetrics::TYPE_SUBMISSION
: AutofillMetrics::TYPE_NO_SUBMISSION;
for (size_t i = 0; i < field_count(); ++i) {
auto* const field = this->field(i);
if (IsUPIVirtualPaymentAddress(field->value)) {
has_upi_vpa_field = true;
AutofillMetrics::LogUserHappinessMetric(
AutofillMetrics::USER_DID_ENTER_UPI_VPA, field->Type().group(),
security_state::SecurityLevel::SECURITY_LEVEL_COUNT);
}
form_interactions_ukm_logger->LogFieldFillStatus(*this, *field,
metric_type);
AutofillMetrics::LogHeuristicPredictionQualityMetrics(
form_interactions_ukm_logger, *this, *field, metric_type);
AutofillMetrics::LogServerPredictionQualityMetrics(
form_interactions_ukm_logger, *this, *field, metric_type);
AutofillMetrics::LogOverallPredictionQualityMetrics(
form_interactions_ukm_logger, *this, *field, metric_type);
// We count fields that were autofilled but later modified, regardless of
// whether the data now in the field is recognized.
if (field->previously_autofilled())
num_edited_autofilled_fields++;
const ServerFieldTypeSet& field_types = field->possible_types();
DCHECK(!field_types.empty());
if (field_types.count(EMPTY_TYPE) || field_types.count(UNKNOWN_TYPE)) {
DCHECK_EQ(field_types.size(), 1u);
continue;
}
++num_detected_field_types;
if (field->is_autofilled)
did_autofill_some_possible_fields = true;
else if (!field->only_fill_when_focused())
did_autofill_all_possible_fields = false;
}
AutofillMetrics::LogNumberOfEditedAutofilledFields(
num_edited_autofilled_fields, observed_submission);
// We log "submission" and duration metrics if we are here after observing a
// submission event.
if (observed_submission) {
AutofillMetrics::AutofillFormSubmittedState state;
if (num_detected_field_types < MinRequiredFieldsForHeuristics() &&
num_detected_field_types < MinRequiredFieldsForQuery()) {
state = AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA;
} else {
if (did_autofill_all_possible_fields) {
state = AutofillMetrics::FILLABLE_FORM_AUTOFILLED_ALL;
} else if (did_autofill_some_possible_fields) {
state = AutofillMetrics::FILLABLE_FORM_AUTOFILLED_SOME;
} else if (!did_show_suggestions) {
state = AutofillMetrics::
FILLABLE_FORM_AUTOFILLED_NONE_DID_NOT_SHOW_SUGGESTIONS;
} else {
state =
AutofillMetrics::FILLABLE_FORM_AUTOFILLED_NONE_DID_SHOW_SUGGESTIONS;
}
// Unlike the other times, the |submission_time| should always be
// available.
DCHECK(!submission_time.is_null());
// The |load_time| might be unset, in the case that the form was
// dynamically added to the DOM.
if (!load_time.is_null()) {
// Submission should always chronologically follow form load.
DCHECK_GE(submission_time, load_time);
base::TimeDelta elapsed = submission_time - load_time;
if (did_autofill_some_possible_fields)
AutofillMetrics::LogFormFillDurationFromLoadWithAutofill(elapsed);
else
AutofillMetrics::LogFormFillDurationFromLoadWithoutAutofill(elapsed);
}
// The |interaction_time| might be unset, in the case that the user
// submitted a blank form.
if (!interaction_time.is_null()) {
// Submission should always chronologically follow interaction.
DCHECK(submission_time > interaction_time);
base::TimeDelta elapsed = submission_time - interaction_time;
AutofillMetrics::LogFormFillDurationFromInteraction(
GetFormTypes(), did_autofill_some_possible_fields, elapsed);
}
}
AutofillMetrics::LogAutofillFormSubmittedState(
state, is_for_credit_card, has_upi_vpa_field, GetFormTypes(),
form_parsed_timestamp_, form_signature(), form_interactions_ukm_logger);
}
}
void FormStructure::LogQualityMetricsBasedOnAutocomplete(
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger)
const {
const AutofillMetrics::QualityMetricType metric_type =
AutofillMetrics::TYPE_AUTOCOMPLETE_BASED;
for (const auto& field : fields_) {
if (field->html_type() != HTML_TYPE_UNSPECIFIED &&
field->html_type() != HTML_TYPE_UNRECOGNIZED) {
AutofillMetrics::LogHeuristicPredictionQualityMetrics(
form_interactions_ukm_logger, *this, *field, metric_type);
AutofillMetrics::LogServerPredictionQualityMetrics(
form_interactions_ukm_logger, *this, *field, metric_type);
}
}
}
void FormStructure::ParseFieldTypesFromAutocompleteAttributes() {
const std::string kDefaultSection = "-default";
has_author_specified_types_ = false;
has_author_specified_sections_ = false;
has_author_specified_upi_vpa_hint_ = false;
for (const std::unique_ptr<AutofillField>& field : fields_) {
// To prevent potential section name collisions, add a default suffix for
// other fields. Without this, 'autocomplete' attribute values
// "section--shipping street-address" and "shipping street-address" would be
// parsed identically, given the section handling code below. We do this
// before any validation so that fields with invalid attributes still end up
// in the default section. These default section names will be overridden
// by subsequent heuristic parsing steps if there are no author-specified
// section names.
field->section = kDefaultSection;
std::vector<std::string> tokens =
LowercaseAndTokenizeAttributeString(field->autocomplete_attribute);
// The autocomplete attribute is overloaded: it can specify either a field
// type hint or whether autocomplete should be enabled at all. Ignore the
// latter type of attribute value.
if (tokens.empty() ||
(tokens.size() == 1 &&
(tokens[0] == "on" || tokens[0] == "off" || tokens[0] == "false"))) {
continue;
}
// Any other value, even it is invalid, is considered to be a type hint.
// This allows a website's author to specify an attribute like
// autocomplete="other" on a field to disable all Autofill heuristics for
// the form.
has_author_specified_types_ = true;
// Per the spec, the tokens are parsed in reverse order. The expected
// pattern is:
// [section-*] [shipping|billing] [type_hint] field_type
// (1) The final token must be the field type. If it is not one of the known
// types, abort.
std::string field_type_token = tokens.back();
tokens.pop_back();
HtmlFieldType field_type =
FieldTypeFromAutocompleteAttributeValue(field_type_token, *field);
if (field_type == HTML_TYPE_UPI_VPA) {
has_author_specified_upi_vpa_hint_ = true;
// TODO(crbug.com/702223): Flesh out support for UPI-VPA.
field_type = HTML_TYPE_UNRECOGNIZED;
}
if (field_type == HTML_TYPE_UNSPECIFIED)
continue;
// (2) The preceding token, if any, may be a type hint.
if (!tokens.empty() && IsContactTypeHint(tokens.back())) {
// If it is, it must match the field type; otherwise, abort.
// Note that an invalid token invalidates the entire attribute value, even
// if the other tokens are valid.
if (!ContactTypeHintMatchesFieldType(tokens.back(), field_type))
continue;
// Chrome Autofill ignores these type hints.
tokens.pop_back();
}
DCHECK_EQ(kDefaultSection, field->section);
std::string section = field->section;
HtmlFieldMode mode = HTML_MODE_NONE;
// (3) The preceding token, if any, may be a fixed string that is either
// "shipping" or "billing". Chrome Autofill treats these as implicit
// section name suffixes.
if (!tokens.empty()) {
if (tokens.back() == kShippingMode)
mode = HTML_MODE_SHIPPING;
else if (tokens.back() == kBillingMode)
mode = HTML_MODE_BILLING;
if (mode != HTML_MODE_NONE) {
section = "-" + tokens.back();
tokens.pop_back();
}
}
// (4) The preceding token, if any, may be a named section.
const base::StringPiece kSectionPrefix = "section-";
if (!tokens.empty() && base::StartsWith(tokens.back(), kSectionPrefix,
base::CompareCase::SENSITIVE)) {
// Prepend this section name to the suffix set in the preceding block.
section = tokens.back().substr(kSectionPrefix.size()) + section;
tokens.pop_back();
}
// (5) No other tokens are allowed. If there are any remaining, abort.
if (!tokens.empty())
continue;
if (section != kDefaultSection) {
has_author_specified_sections_ = true;
field->section = section;
}
// No errors encountered while parsing!
// Update the |field|'s type based on what was parsed from the attribute.
field->SetHtmlType(field_type, mode);
}
was_parsed_for_autocomplete_attributes_ = true;
}
std::set<base::string16> FormStructure::PossibleValues(ServerFieldType type) {
std::set<base::string16> values;
AutofillType target_type(type);
for (const auto& field : fields_) {
if (field->Type().GetStorableType() != target_type.GetStorableType() ||
field->Type().group() != target_type.group()) {
continue;
}
// No option values; anything goes.
if (field->option_values.empty()) {
values.clear();
break;
}
for (const base::string16& val : field->option_values) {
if (!val.empty())
values.insert(base::i18n::ToUpper(val));
}
for (const base::string16& content : field->option_contents) {
if (!content.empty())
values.insert(base::i18n::ToUpper(content));
}
}
return values;
}
base::string16 FormStructure::GetUniqueValue(HtmlFieldType type) const {
base::string16 value;
for (const auto& field : fields_) {
if (field->html_type() != type)
continue;
// More than one value found; abort rather than choosing one arbitrarily.
if (!value.empty() && !field->value.empty()) {
value.clear();
break;
}
value = field->value;
}
return value;
}
const AutofillField* FormStructure::field(size_t index) const {
if (index >= fields_.size()) {
NOTREACHED();
return nullptr;
}
return fields_[index].get();
}
AutofillField* FormStructure::field(size_t index) {
return const_cast<AutofillField*>(
static_cast<const FormStructure*>(this)->field(index));
}
size_t FormStructure::field_count() const {
return fields_.size();
}
size_t FormStructure::active_field_count() const {
return active_field_count_;
}
FormData FormStructure::ToFormData() const {
FormData data;
data.name = form_name_;
data.origin = source_url_;
data.action = target_url_;
data.main_frame_origin = main_frame_origin_;
for (size_t i = 0; i < fields_.size(); ++i) {
data.fields.push_back(FormFieldData(*fields_[i]));
}
return data;
}
bool FormStructure::operator==(const FormData& form) const {
// TODO(jhawkins): Is this enough to differentiate a form?
if (form_name_ == form.name && source_url_ == form.origin &&
target_url_ == form.action) {
return true;
}
// TODO(jhawkins): Compare field names, IDs and labels once we have labels
// set up.
return false;
}
bool FormStructure::operator!=(const FormData& form) const {
return !operator==(form);
}
FormStructure::SectionedFieldsIndexes::SectionedFieldsIndexes() {}
FormStructure::SectionedFieldsIndexes::~SectionedFieldsIndexes() {}
void FormStructure::RationalizeCreditCardFieldPredictions() {
bool cc_first_name_found = false;
bool cc_last_name_found = false;
bool cc_num_found = false;
bool cc_month_found = false;
bool cc_year_found = false;
bool cc_type_found = false;
bool cc_cvc_found = false;
size_t num_months_found = 0;
size_t num_other_fields_found = 0;
for (const auto& field : fields_) {
ServerFieldType current_field_type =
field->ComputedType().GetStorableType();
switch (current_field_type) {
case CREDIT_CARD_NAME_FIRST:
cc_first_name_found = true;
break;
case CREDIT_CARD_NAME_LAST:
cc_last_name_found = true;
break;
case CREDIT_CARD_NAME_FULL:
cc_first_name_found = true;
cc_last_name_found = true;
break;
case CREDIT_CARD_NUMBER:
cc_num_found = true;
break;
case CREDIT_CARD_EXP_MONTH:
cc_month_found = true;
++num_months_found;
break;
case CREDIT_CARD_EXP_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
cc_year_found = true;
break;
case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
cc_month_found = true;
cc_year_found = true;
++num_months_found;
break;
case CREDIT_CARD_TYPE:
cc_type_found = true;
break;
case CREDIT_CARD_VERIFICATION_CODE:
cc_cvc_found = true;
break;
case ADDRESS_HOME_ZIP:
case ADDRESS_BILLING_ZIP:
// Zip/Postal code often appears as part of a Credit Card form. Do
// not count it as a non-cc-related field.
break;
default:
++num_other_fields_found;
}
}
// A partial CC name is unlikely. Prefer to consider these profile names
// when partial.
bool cc_name_found = cc_first_name_found && cc_last_name_found;
// A partial CC expiry date should not be filled. These are often confused
// with quantity/height fields and/or generic year fields.
bool cc_date_found = cc_month_found && cc_year_found;
// Count the credit card related fields in the form.
size_t num_cc_fields_found =
static_cast<int>(cc_name_found) + static_cast<int>(cc_num_found) +
static_cast<int>(cc_date_found) + static_cast<int>(cc_type_found) +
static_cast<int>(cc_cvc_found);
// Retain credit card related fields if the form has multiple fields or has
// no unrelated fields (useful for single cc-field forms). Credit card number
// is permitted to be alone in an otherwise unrelated form because some
// dynamic forms reveal the remainder of the fields only after the credit
// card number is entered and identified as a credit card by the site.
bool keep_cc_fields =
cc_num_found || num_cc_fields_found >= 3 || num_other_fields_found == 0;
// Do an update pass over the fields to rewrite the types if credit card
// fields are not to be retained. Some special handling is given to expiry
// dates if the full date is not found or multiple expiry date fields are
// found. See comments inline below.
for (auto it = fields_.begin(); it != fields_.end(); ++it) {
auto& field = *it;
ServerFieldType current_field_type = field->Type().GetStorableType();
switch (current_field_type) {
case CREDIT_CARD_NAME_FIRST:
if (!keep_cc_fields)
field->SetTypeTo(AutofillType(NAME_FIRST));
break;
case CREDIT_CARD_NAME_LAST:
if (!keep_cc_fields)
field->SetTypeTo(AutofillType(NAME_LAST));
break;
case CREDIT_CARD_NAME_FULL:
if (!keep_cc_fields)
field->SetTypeTo(AutofillType(NAME_FULL));
break;
case CREDIT_CARD_NUMBER:
case CREDIT_CARD_TYPE:
case CREDIT_CARD_VERIFICATION_CODE:
case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
if (!keep_cc_fields)
field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
break;
case CREDIT_CARD_EXP_MONTH:
// Do not preserve an expiry month prediction if any of the following
// are true:
// (1) the form is determined to be be non-cc related, so all cc
// field predictions are to be discarded
// (2) the expiry month was found without a corresponding year
// (3) multiple month fields were found in a form having a full
// expiry date. This usually means the form is a checkout form
// that also has one or more quantity fields. Suppress the expiry
// month field(s) not immediately preceding an expiry year field.
if (!keep_cc_fields || !cc_date_found) {
field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
} else if (num_months_found > 1) {
auto it2 = it + 1;
if (it2 == fields_.end()) {
field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
} else {
ServerFieldType next_field_type = (*it2)->Type().GetStorableType();
if (next_field_type != CREDIT_CARD_EXP_2_DIGIT_YEAR &&
next_field_type != CREDIT_CARD_EXP_4_DIGIT_YEAR) {
field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
}
}
}
break;
case CREDIT_CARD_EXP_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
if (!keep_cc_fields || !cc_date_found)
field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
break;
default:
break;
}
}
}
void FormStructure::RationalizePhoneNumbersInSection(std::string section) {
if (phone_rationalized_[section])
return;
std::vector<AutofillField*> fields;
for (size_t i = 0; i < field_count(); ++i) {
if (field(i)->section != section)
continue;
fields.push_back(field(i));
}
rationalization_util::RationalizePhoneNumberFields(fields);
phone_rationalized_[section] = true;
}
void FormStructure::ApplyRationalizationsToFieldAndLog(
size_t field_index,
ServerFieldType new_type,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
if (field_index >= fields_.size())
return;
auto old_type = fields_[field_index]->Type().GetStorableType();
fields_[field_index]->SetTypeTo(AutofillType(new_type));
if (form_interactions_ukm_logger) {
form_interactions_ukm_logger->LogRepeatedServerTypePredictionRationalized(
form_signature_, *fields_[field_index], old_type);
}
}
void FormStructure::RationalizeAddressLineFields(
SectionedFieldsIndexes& sections_of_address_indexes,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
// The rationalization happens within sections.
for (sections_of_address_indexes.Reset();
!sections_of_address_indexes.IsFinished();
sections_of_address_indexes.WalkForwardToTheNextSection()) {
auto current_section = sections_of_address_indexes.CurrentSection();
// The rationalization only applies to sections that have 2 or 3 visible
// street address predictions.
if (current_section.size() != 2 && current_section.size() != 3) {
continue;
}
int nb_address_rationalized = 0;
for (auto field_index : current_section) {
switch (nb_address_rationalized) {
case 0:
ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE1,
form_interactions_ukm_logger);
break;
case 1:
ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE2,
form_interactions_ukm_logger);
break;
case 2:
ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE3,
form_interactions_ukm_logger);
break;
default:
NOTREACHED();
break;
}
++nb_address_rationalized;
}
}
}
void FormStructure::ApplyRationalizationsToHiddenSelects(
size_t field_index,
ServerFieldType new_type,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
ServerFieldType old_type = fields_[field_index]->Type().GetStorableType();
// Walk on the hidden select fields right after the field_index which share
// the same type with the field_index, and apply the rationalization to them
// as well. These fields, if any, function as one field with the field_index.
for (auto current_index = field_index + 1; current_index < fields_.size();
current_index++) {
if (fields_[current_index]->IsVisible() ||
fields_[current_index]->form_control_type != "select-one" ||
fields_[current_index]->Type().GetStorableType() != old_type)
break;
ApplyRationalizationsToFieldAndLog(current_index, new_type,
form_interactions_ukm_logger);
}
// Same for the fields coming right before the field_index. (No need to check
// for the fields appearing before the first field!)
if (field_index == 0)
return;
for (auto current_index = field_index - 1;; current_index--) {
if (fields_[current_index]->IsVisible() ||
fields_[current_index]->form_control_type != "select-one" ||
fields_[current_index]->Type().GetStorableType() != old_type)
break;
ApplyRationalizationsToFieldAndLog(current_index, new_type,
form_interactions_ukm_logger);
if (current_index == 0)
break;
}
}
bool FormStructure::HeuristicsPredictionsAreApplicable(
size_t upper_index,
size_t lower_index,
ServerFieldType first_type,
ServerFieldType second_type) {
// The predictions are applicable if one field has one of the two types, and
// the other has the other type.
if (fields_[upper_index]->heuristic_type() ==
fields_[lower_index]->heuristic_type())
return false;
if ((fields_[upper_index]->heuristic_type() == first_type ||
fields_[upper_index]->heuristic_type() == second_type) &&
(fields_[lower_index]->heuristic_type() == first_type ||
fields_[lower_index]->heuristic_type() == second_type))
return true;
return false;
}
void FormStructure::ApplyRationalizationsToFields(
size_t upper_index,
size_t lower_index,
ServerFieldType upper_type,
ServerFieldType lower_type,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
// Hidden fields are ignored during the rationalization, but 'select' hidden
// fields also get autofilled to support their corresponding visible
// 'synthetic fields'. So, if a field's type is rationalized, we should make
// sure that the rationalization is also applied to its corresponding hidden
// fields, if any.
ApplyRationalizationsToHiddenSelects(upper_index, upper_type,
form_interactions_ukm_logger);
ApplyRationalizationsToFieldAndLog(upper_index, upper_type,
form_interactions_ukm_logger);
ApplyRationalizationsToHiddenSelects(lower_index, lower_type,
form_interactions_ukm_logger);
ApplyRationalizationsToFieldAndLog(lower_index, lower_type,
form_interactions_ukm_logger);
}
bool FormStructure::FieldShouldBeRationalizedToCountry(size_t upper_index) {
// Upper field is country if and only if it's the first visible address field
// in its section. Otherwise, the upper field is a state, and the lower one
// is a country.
for (int field_index = upper_index - 1; field_index >= 0; --field_index) {
if (fields_[field_index]->IsVisible() &&
AutofillType(fields_[field_index]->Type().GetStorableType()).group() ==
ADDRESS_HOME &&
fields_[field_index]->section == fields_[upper_index]->section) {
return false;
}
}
return true;
}
void FormStructure::RationalizeAddressStateCountry(
SectionedFieldsIndexes& sections_of_state_indexes,
SectionedFieldsIndexes& sections_of_country_indexes,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
// Walk on the sections of state and country indexes simultaneously. If they
// both point to the same section, it means that that section includes both
// the country and the state type. This means that no that rationalization is
// needed. So, walk both pointers forward. Otherwise, look at the section that
// appears earlier on the form. That section doesn't have any field of the
// other type. Rationalize the fields on the earlier section if needed. Walk
// the pointer that points to the earlier section forward. Stop when both
// sections of indexes are processed. (This resembles the merge in the merge
// sort.)
sections_of_state_indexes.Reset();
sections_of_country_indexes.Reset();
while (!sections_of_state_indexes.IsFinished() ||
!sections_of_country_indexes.IsFinished()) {
auto current_section_of_state_indexes =
sections_of_state_indexes.CurrentSection();
auto current_section_of_country_indexes =
sections_of_country_indexes.CurrentSection();
// If there are still sections left with both country and state type, and
// state and country current sections are equal, then that section has both
// state and country. No rationalization needed.
if (!sections_of_state_indexes.IsFinished() &&
!sections_of_country_indexes.IsFinished() &&
fields_[sections_of_state_indexes.CurrentIndex()]->section ==
fields_[sections_of_country_indexes.CurrentIndex()]->section) {
sections_of_state_indexes.WalkForwardToTheNextSection();
sections_of_country_indexes.WalkForwardToTheNextSection();
continue;
}
size_t upper_index = 0, lower_index = 0;
// If country section is before the state ones, it means that that section
// misses states, and the other way around.
if (current_section_of_state_indexes < current_section_of_country_indexes) {
// We only rationalize when we have exactly two visible fields of a kind.
if (current_section_of_state_indexes.size() == 2) {
upper_index = current_section_of_state_indexes[0];
lower_index = current_section_of_state_indexes[1];
}
sections_of_state_indexes.WalkForwardToTheNextSection();
} else {
// We only rationalize when we have exactly two visible fields of a kind.
if (current_section_of_country_indexes.size() == 2) {
upper_index = current_section_of_country_indexes[0];
lower_index = current_section_of_country_indexes[1];
}
sections_of_country_indexes.WalkForwardToTheNextSection();
}
// This is when upper and lower indexes are not changed, meaning that there
// is no need for rationalization.
if (upper_index == lower_index) {
continue;
}
if (HeuristicsPredictionsAreApplicable(upper_index, lower_index,
ADDRESS_HOME_STATE,
ADDRESS_HOME_COUNTRY)) {
ApplyRationalizationsToFields(
upper_index, lower_index, fields_[upper_index]->heuristic_type(),
fields_[lower_index]->heuristic_type(), form_interactions_ukm_logger);
continue;
}
if (FieldShouldBeRationalizedToCountry(upper_index)) {
ApplyRationalizationsToFields(upper_index, lower_index,
ADDRESS_HOME_COUNTRY, ADDRESS_HOME_STATE,
form_interactions_ukm_logger);
} else {
ApplyRationalizationsToFields(upper_index, lower_index,
ADDRESS_HOME_STATE, ADDRESS_HOME_COUNTRY,
form_interactions_ukm_logger);
}
}
}
void FormStructure::RationalizeRepeatedFields(
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
// The type of every field whose index is in
// sectioned_field_indexes_by_type[|type|] is predicted by server as |type|.
// Example: sectioned_field_indexes_by_type[FULL_NAME] is a sectioned fields
// indexes of fields whose types are predicted as FULL_NAME by the server.
SectionedFieldsIndexes sectioned_field_indexes_by_type[MAX_VALID_FIELD_TYPE];
for (const auto& field : fields_) {
// The hidden fields are not considered when rationalizing.
if (!field->IsVisible())
continue;
// The billing and non-billing types are aggregated.
auto current_type = field->Type().GetStorableType();
if (current_type != UNKNOWN_TYPE && current_type < MAX_VALID_FIELD_TYPE) {
// Look at the sectioned field indexes for the current type, if the
// current field belongs to that section, then the field index should be
// added to that same section, otherwise, start a new section.
sectioned_field_indexes_by_type[current_type].AddFieldIndex(
&field - &fields_[0],
/*is_new_section*/ sectioned_field_indexes_by_type[current_type]
.Empty() ||
fields_[sectioned_field_indexes_by_type[current_type]
.LastFieldIndex()]
->section != field->section);
}
}
RationalizeAddressLineFields(
sectioned_field_indexes_by_type[ADDRESS_HOME_STREET_ADDRESS],
form_interactions_ukm_logger);
// Since the billing types are mapped to the non-billing ones, no need to
// take care of ADDRESS_BILLING_STATE and .. .
RationalizeAddressStateCountry(
sectioned_field_indexes_by_type[ADDRESS_HOME_STATE],
sectioned_field_indexes_by_type[ADDRESS_HOME_COUNTRY],
form_interactions_ukm_logger);
}
void FormStructure::RationalizeFieldTypePredictions() {
RationalizeCreditCardFieldPredictions();
for (const auto& field : fields_) {
field->SetTypeTo(field->Type());
}
}
void FormStructure::EncodeFormForQuery(
AutofillQueryContents::Form* query_form) const {
DCHECK(!IsMalformed());
query_form->set_signature(form_signature());
if (is_rich_query_enabled_) {
EncodeFormMetadataForQuery(*this, query_form->mutable_form_metadata());
}
for (const auto& field : fields_) {
if (ShouldSkipField(*field))
continue;
AutofillQueryContents::Form::Field* added_field = query_form->add_field();
added_field->set_signature(field->GetFieldSignature());
if (is_rich_query_enabled_) {
EncodeFieldMetadataForQuery(*field,
added_field->mutable_field_metadata());
}
if (IsAutofillFieldMetadataEnabled()) {
added_field->set_type(field->form_control_type);
if (!field->name.empty())
added_field->set_name(base::UTF16ToUTF8(field->name));
}
}
}
void FormStructure::EncodeFormForUpload(AutofillUploadContents* upload) const {
DCHECK(!IsMalformed());
if (randomized_encoder_) {
PopulateRandomizedFormMetadata(*randomized_encoder_, *this,
upload->mutable_randomized_form_metadata());
}
for (const auto& field : fields_) {
// Don't upload checkable fields.
if (IsCheckable(field->check_status))
continue;
// Add the same field elements as the query and a few more below.
if (ShouldSkipField(*field))
continue;
auto* added_field = upload->add_field();
for (const auto& field_type : field->possible_types()) {
added_field->add_autofill_type(field_type);
}
field->NormalizePossibleTypesValidities();
for (const auto& field_type_validities :
field->possible_types_validities()) {
auto* type_validities = added_field->add_autofill_type_validities();
type_validities->set_type(field_type_validities.first);
for (const auto& validity : field_type_validities.second) {
type_validities->add_validity(validity);
}
}
if (field->generation_type()) {
added_field->set_generation_type(field->generation_type());
added_field->set_generated_password_changed(
field->generated_password_changed());
}
if (field->vote_type()) {
added_field->set_vote_type(field->vote_type());
}
added_field->set_signature(field->GetFieldSignature());
if (field->properties_mask)
added_field->set_properties_mask(field->properties_mask);
if (randomized_encoder_) {
PopulateRandomizedFieldMetadata(
*randomized_encoder_, *this, *field,
added_field->mutable_randomized_field_metadata());
}
if (IsAutofillFieldMetadataEnabled()) {
added_field->set_type(field->form_control_type);
if (!field->name.empty())
added_field->set_name(base::UTF16ToUTF8(field->name));
if (!field->id_attribute.empty())
added_field->set_id(base::UTF16ToUTF8(field->id_attribute));
if (!field->autocomplete_attribute.empty())
added_field->set_autocomplete(field->autocomplete_attribute);
if (!field->css_classes.empty())
added_field->set_css_classes(base::UTF16ToUTF8(field->css_classes));
}
}
}
bool FormStructure::IsMalformed() const {
if (!field_count()) // Nothing to add.
return true;
// Some badly formatted web sites repeat fields - limit number of fields to
// 250, which is far larger than any valid form and proto still fits into 10K.
// Do not send requests for forms with more than this many fields, as they are
// near certainly not valid/auto-fillable.
const size_t kMaxFieldsOnTheForm = 250;
if (field_count() > kMaxFieldsOnTheForm)
return true;
return false;
}
void FormStructure::IdentifySections(bool has_author_specified_sections) {
if (fields_.empty())
return;
if (!has_author_specified_sections) {
// Name sections after the first field in the section.
base::string16 current_section = fields_.front()->unique_name();
// Keep track of the types we've seen in this section.
std::set<ServerFieldType> seen_types;
ServerFieldType previous_type = UNKNOWN_TYPE;
bool is_hidden_section = false;
base::string16 last_visible_section;
for (const auto& field : fields_) {
const ServerFieldType current_type = field->Type().GetStorableType();
// All credit card fields belong to the same section that's different
// from address sections.
if (AutofillType(current_type).group() == CREDIT_CARD) {
field->section = "credit-card";
continue;
}
bool already_saw_current_type = seen_types.count(current_type) > 0;
// Forms often ask for multiple phone numbers -- e.g. both a daytime and
// evening phone number. Our phone number detection is also generally a
// little off. Hence, ignore this field type as a signal here.
if (AutofillType(current_type).group() == PHONE_HOME)
already_saw_current_type = false;
bool ignored_field = !field->IsVisible();
// This is the first visible field after a hidden section. Consider it as
// the continuation of the last visible section.
if (!ignored_field && is_hidden_section) {
current_section = last_visible_section;
}
// Start a new section by an ignored field, only if the next field is also
// already seen.
size_t field_index = &field - &fields_[0];
if (ignored_field &&
(is_hidden_section ||
!((field_index + 1) < fields_.size() &&
seen_types.count(
fields_[field_index + 1]->Type().GetStorableType()) > 0))) {
already_saw_current_type = false;
}
// Some forms have adjacent fields of the same type. Two common examples:
// * Forms with two email fields, where the second is meant to "confirm"
// the first.
// * Forms with a <select> menu for states in some countries, and a
// freeform <input> field for states in other countries. (Usually,
// only one of these two will be visible for any given choice of
// country.)
// Generally, adjacent fields of the same type belong in the same logical
// section.
if (current_type == previous_type)
already_saw_current_type = false;
if (current_type != UNKNOWN_TYPE && already_saw_current_type) {
// Keep track of seen_types if the new section is hidden. The next
// visible section might be the continuation of the previous visible
// section.
if (ignored_field) {
is_hidden_section = true;
last_visible_section = current_section;
}
if (!is_hidden_section)
seen_types.clear();
// The end of a section, so start a new section.
current_section = field->unique_name();
}
// Only consider a type "seen" if it was not ignored. Some forms have
// sections for different locales, only one of which is enabled at a
// time. Each section may duplicate some information (e.g. postal code)
// and we don't want that to cause section splits.
// Also only set |previous_type| when the field was not ignored. This
// prevents ignored fields from breaking up fields that are otherwise
// adjacent.
if (!ignored_field) {
seen_types.insert(current_type);
previous_type = current_type;
is_hidden_section = false;
}
field->section = base::UTF16ToUTF8(current_section);
}
}
// Ensure that credit card and address fields are in separate sections.
// This simplifies the section-aware logic in autofill_manager.cc.
for (const auto& field : fields_) {
FieldTypeGroup field_type_group = field->Type().group();
if (field_type_group == CREDIT_CARD)
field->section = field->section + "-cc";
else
field->section = field->section + "-default";
}
}
bool FormStructure::ShouldSkipField(const FormFieldData& field) const {
return IsCheckable(field.check_status);
}
void FormStructure::ProcessExtractedFields() {
// Update the field name parsed by heuristics if several criteria are met.
// Several fields must be present in the form.
if (field_count() < kCommonNamePrefixRemovalFieldThreshold)
return;
// Find the longest common prefix within all the field names.
std::vector<base::string16> names;
names.reserve(field_count());
for (const auto& field : *this)
names.push_back(field->name);
const base::string16 longest_prefix = FindLongestCommonPrefix(names);
if (longest_prefix.size() < kMinCommonNamePrefixLength)
return;
// The name without the prefix will be used for heuristics parsing.
for (auto& field : *this) {
if (field->name.size() > longest_prefix.size()) {
field->set_parseable_name(
field->name.substr(longest_prefix.size(), field->name.size()));
}
}
}
// static
base::string16 FormStructure::FindLongestCommonPrefix(
const std::vector<base::string16>& strings) {
if (strings.empty())
return base::string16();
std::vector<base::string16> filtered_strings;
// Any strings less than kMinCommonNamePrefixLength are neither modified
// nor considered when processing for a common prefix.
std::copy_if(
strings.begin(), strings.end(), std::back_inserter(filtered_strings),
[](base::string16 s) { return s.size() >= kMinCommonNamePrefixLength; });
if (filtered_strings.empty())
return base::string16();
// Go through each character of the first string until there is a mismatch at
// the same position in any other string. Adapted from http://goo.gl/YGukMM.
for (size_t prefix_len = 0; prefix_len < filtered_strings[0].size();
prefix_len++) {
for (size_t i = 1; i < filtered_strings.size(); i++) {
if (prefix_len >= filtered_strings[i].size() ||
filtered_strings[i].at(prefix_len) !=
filtered_strings[0].at(prefix_len)) {
// Mismatch found.
return filtered_strings[i].substr(0, prefix_len);
}
}
}
return filtered_strings[0];
}
std::set<FormType> FormStructure::GetFormTypes() const {
std::set<FormType> form_types;
for (const auto& field : fields_) {
form_types.insert(
FormTypes::FieldTypeGroupToFormType(field->Type().group()));
}
return form_types;
}
base::string16 FormStructure::GetIdentifierForRefill() const {
if (!form_name().empty())
return form_name();
if (field_count() && !field(0)->unique_name().empty())
return field(0)->unique_name();
return base::string16();
}
void FormStructure::set_randomized_encoder(
std::unique_ptr<RandomizedEncoder> encoder) {
randomized_encoder_ = std::move(encoder);
}
} // namespace autofill