// Copyright (c) 2011 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 "chrome/browser/autofill/credit_card_field.h"
#include <stddef.h>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string16.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autofill/autofill_field.h"
#include "chrome/browser/autofill/field_types.h"
#include "grit/autofill_resources.h"
#include "ui/base/l10n/l10n_util.h"
bool CreditCardField::GetFieldInfo(FieldTypeMap* field_type_map) const {
bool ok = Add(field_type_map, number_, AutofillType(CREDIT_CARD_NUMBER));
DCHECK(ok);
// If the heuristics detected first and last name in separate fields,
// then ignore both fields. Putting them into separate fields is probably
// wrong, because the credit card can also contain a middle name or middle
// initial.
if (cardholder_last_ == NULL) {
// Add() will check if cardholder_ is != NULL.
ok = ok && Add(field_type_map, cardholder_, AutofillType(CREDIT_CARD_NAME));
DCHECK(ok);
}
ok = ok && Add(field_type_map, type_, AutofillType(CREDIT_CARD_TYPE));
DCHECK(ok);
ok = ok && Add(field_type_map, expiration_month_,
AutofillType(CREDIT_CARD_EXP_MONTH));
DCHECK(ok);
ok = ok && Add(field_type_map, expiration_year_,
AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR));
DCHECK(ok);
return ok;
}
FormFieldType CreditCardField::GetFormFieldType() const {
return kCreditCardType;
}
// static
CreditCardField* CreditCardField::Parse(
std::vector<AutofillField*>::const_iterator* iter,
bool is_ecml) {
scoped_ptr<CreditCardField> credit_card_field(new CreditCardField);
std::vector<AutofillField*>::const_iterator q = *iter;
string16 pattern;
// Credit card fields can appear in many different orders.
// We loop until no more credit card related fields are found, see |break| at
// bottom of the loop.
for (int fields = 0; true; ++fields) {
// Sometimes the cardholder field is just labeled "name". Unfortunately this
// is a dangerously generic word to search for, since it will often match a
// name (not cardholder name) field before or after credit card fields. So
// we search for "name" only when we've already parsed at least one other
// credit card field and haven't yet parsed the expiration date (which
// usually appears at the end).
if (credit_card_field->cardholder_ == NULL) {
string16 name_pattern;
if (is_ecml) {
name_pattern = GetEcmlPattern(kEcmlCardHolder);
} else {
if (fields == 0 || credit_card_field->expiration_month_) {
// at beginning or end
name_pattern = l10n_util::GetStringUTF16(
IDS_AUTOFILL_NAME_ON_CARD_RE);
} else {
name_pattern = l10n_util::GetStringUTF16(
IDS_AUTOFILL_NAME_ON_CARD_CONTEXTUAL_RE);
}
}
if (ParseText(&q, name_pattern, &credit_card_field->cardholder_))
continue;
// As a hard-coded hack for Expedia's billing pages (expedia_checkout.html
// and ExpediaBilling.html in our test suite), recognize separate fields
// for the cardholder's first and last name if they have the labels "cfnm"
// and "clnm".
std::vector<AutofillField*>::const_iterator p = q;
AutofillField* first;
if (!is_ecml && ParseText(&p, ASCIIToUTF16("^cfnm"), &first) &&
ParseText(&p, ASCIIToUTF16("^clnm"),
&credit_card_field->cardholder_last_)) {
credit_card_field->cardholder_ = first;
q = p;
continue;
}
}
// We look for a card security code before we look for a credit
// card number and match the general term "number". The security code
// has a plethora of names; we've seen "verification #",
// "verification number", "card identification number" and others listed
// in the |pattern| below.
if (is_ecml) {
pattern = GetEcmlPattern(kEcmlCardVerification);
} else {
pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_CVC_RE);
}
if (credit_card_field->verification_ == NULL &&
ParseText(&q, pattern, &credit_card_field->verification_))
continue;
// TODO(jhawkins): Parse the type select control.
if (is_ecml)
pattern = GetEcmlPattern(kEcmlCardNumber);
else
pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_NUMBER_RE);
if (credit_card_field->number_ == NULL && ParseText(&q, pattern,
&credit_card_field->number_))
continue;
if ((*q) && LowerCaseEqualsASCII((*q)->form_control_type, "month")) {
credit_card_field->expiration_month_ = *q++;
} else {
// "Expiration date" is the most common label here, but some pages have
// "Expires", "exp. date" or "exp. month" and "exp. year". We also look
// for the field names ccmonth and ccyear, which appear on at least 4 of
// our test pages.
//
// -> On at least one page (The China Shop2.html) we find only the labels
// "month" and "year". So for now we match these words directly; we'll
// see if this turns out to be too general.
//
// Toolbar Bug 51451: indeed, simply matching "month" is too general for
// https://rps.fidelity.com/ftgw/rps/RtlCust/CreatePIN/Init.
// Instead, we match only words beginning with "month".
if (is_ecml)
pattern = GetEcmlPattern(kEcmlCardExpireMonth);
else
pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_EXPIRATION_MONTH_RE);
if ((!credit_card_field->expiration_month_ ||
credit_card_field->expiration_month_->IsEmpty()) &&
ParseText(&q, pattern, &credit_card_field->expiration_month_)) {
if (is_ecml)
pattern = GetEcmlPattern(kEcmlCardExpireYear);
else
pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_EXPIRATION_DATE_RE);
if (!ParseText(&q, pattern, &credit_card_field->expiration_year_)) {
return NULL;
}
continue;
}
}
if (ParseText(&q, GetEcmlPattern(kEcmlCardExpireDay)))
continue;
// Some pages (e.g. ExpediaBilling.html) have a "card description"
// field; we parse this field but ignore it.
// We also ignore any other fields within a credit card block that
// start with "card", under the assumption that they are related to
// the credit card section being processed but are uninteresting to us.
if (ParseText(&q, l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_IGNORED_RE)))
continue;
break;
}
// Some pages have a billing address field after the cardholder name field.
// For that case, allow only just the cardholder name field. The remaining
// CC fields will be picked up in a following CreditCardField.
if (credit_card_field->cardholder_) {
*iter = q;
return credit_card_field.release();
}
// On some pages, the user selects a card type using radio buttons
// (e.g. test page Apple Store Billing.html). We can't handle that yet,
// so we treat the card type as optional for now.
// The existence of a number or cvc in combination with expiration date is
// a strong enough signal that this is a credit card. It is possible that
// the number and name were parsed in a separate part of the form. So if
// the cvc and date were found independently they are returned.
if ((credit_card_field->number_ || credit_card_field->verification_) &&
credit_card_field->expiration_month_ &&
(credit_card_field->expiration_year_ ||
(LowerCaseEqualsASCII(
credit_card_field->expiration_month_->form_control_type,
"month")))) {
*iter = q;
return credit_card_field.release();
}
return NULL;
}
CreditCardField::CreditCardField()
: cardholder_(NULL),
cardholder_last_(NULL),
type_(NULL),
number_(NULL),
verification_(NULL),
expiration_month_(NULL),
expiration_year_(NULL) {
}