// 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 #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/autofill_regex_constants.h" #include "chrome/browser/autofill/autofill_scanner.h" #include "chrome/browser/autofill/field_types.h" #include "ui/base/l10n/l10n_util.h" // static FormField* CreditCardField::Parse(AutofillScanner* scanner, bool parse_new_field_types) { if (scanner->IsEnd()) return NULL; scoped_ptr credit_card_field(new CreditCardField); size_t saved_cursor = scanner->SaveCursor(); // 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; !scanner->IsEnd(); ++fields) { // Ignore gift card fields. if (ParseField(scanner, UTF8ToUTF16(autofill::kGiftCardRe), NULL)) break; // 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 (fields == 0 || credit_card_field->expiration_month_) { // at beginning or end name_pattern = UTF8ToUTF16(autofill::kNameOnCardRe); } else { name_pattern = UTF8ToUTF16(autofill::kNameOnCardContextualRe); } if (ParseField(scanner, 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". scanner->SaveCursor(); const AutofillField* first; if (ParseField(scanner, ASCIIToUTF16("^cfnm"), &first) && ParseField(scanner, ASCIIToUTF16("^clnm"), &credit_card_field->cardholder_last_)) { credit_card_field->cardholder_ = first; continue; } scanner->Rewind(); } // Check for a credit card type (Visa, MasterCard, etc.) field. if (parse_new_field_types) { string16 type_pattern = UTF8ToUTF16(autofill::kCardTypeRe); if (!credit_card_field->type_ && ParseFieldSpecifics(scanner, type_pattern, MATCH_DEFAULT | MATCH_SELECT, &credit_card_field->type_)) { 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. string16 pattern = UTF8ToUTF16(autofill::kCardCvcRe); if (!credit_card_field->verification_ && ParseField(scanner, pattern, &credit_card_field->verification_)) { continue; } pattern = UTF8ToUTF16(autofill::kCardNumberRe); if (!credit_card_field->number_ && ParseField(scanner, pattern, &credit_card_field->number_)) { continue; } if (LowerCaseEqualsASCII(scanner->Cursor()->form_control_type, "month")) { credit_card_field->expiration_month_ = scanner->Cursor(); scanner->Advance(); } else { // First try to parse split month/year expiration fields. scanner->SaveCursor(); pattern = UTF8ToUTF16(autofill::kExpirationMonthRe); if (!credit_card_field->expiration_month_ && ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT | MATCH_SELECT, &credit_card_field->expiration_month_)) { pattern = UTF8ToUTF16(autofill::kExpirationYearRe); if (ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT | MATCH_SELECT, &credit_card_field->expiration_year_)) { continue; } } // If that fails, try to parse a combined expiration field. if (!credit_card_field->expiration_date_) { // Look for a 2-digit year first. scanner->Rewind(); pattern = UTF8ToUTF16(autofill::kExpirationDate2DigitYearRe); // We allow