blob: 0ca5e4502662f3db5459227dd3add8e01e50d39c [file] [log] [blame]
// Copyright (c) 2010 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.h"
#include <string>
#ifndef ANDROID
// FIXME: Need l10n on Android?
#include "app/l10n_util.h"
#endif
#include "base/basictypes.h"
#include "base/string_util.h"
#include "base/string_number_conversions.h"
#include "base/string16.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autofill/autofill_type.h"
#include "chrome/browser/autofill/field_types.h"
#include "chrome/browser/guid.h"
#ifndef ANDROID
#include "grit/generated_resources.h"
#endif
namespace {
const char* kCreditCardObfuscationString = "************";
const AutoFillFieldType kAutoFillCreditCardTypes[] = {
CREDIT_CARD_NAME,
CREDIT_CARD_NUMBER,
CREDIT_CARD_TYPE,
CREDIT_CARD_EXP_MONTH,
CREDIT_CARD_EXP_4_DIGIT_YEAR,
};
const int kAutoFillCreditCardLength = arraysize(kAutoFillCreditCardTypes);
// These values must match the values in WebKitClientImpl in webkit/glue. We
// send these strings to WK, which then asks WebKitClientImpl to load the image
// data.
const char* const kAmericanExpressCard = "americanExpressCC";
const char* const kDinersCard = "dinersCC";
const char* const kDiscoverCard = "discoverCC";
const char* const kGenericCard = "genericCC";
const char* const kJCBCard = "jcbCC";
const char* const kMasterCard = "masterCardCC";
const char* const kSoloCard = "soloCC";
const char* const kVisaCard = "visaCC";
std::string GetCreditCardType(const string16& number) {
// Credit card number specifications taken from:
// http://en.wikipedia.org/wiki/Credit_card_numbers and
// http://www.beachnet.com/~hstiles/cardtype.html
// Card Type Prefix(es) Length
// ---------------------------------------------------------------
// Visa 4 13,16
// American Express 34,37 15
// Diners Club 300-305,2014,2149,36, 14,15
// Discover Card 6011,65 16
// JCB 3 16
// JCB 2131,1800 15
// MasterCard 51-55 16
// Solo (debit card) 6334,6767 16,18,19
// We need at least 4 digits to work with.
if (number.length() < 4)
return kGenericCard;
int first_four_digits = 0;
if (!base::StringToInt(number.substr(0, 4), &first_four_digits))
return kGenericCard;
int first_three_digits = first_four_digits / 10;
int first_two_digits = first_three_digits / 10;
int first_digit = first_two_digits / 10;
switch (number.length()) {
case 13:
if (first_digit == 4)
return kVisaCard;
break;
case 14:
if (first_three_digits >= 300 && first_three_digits <=305)
return kDinersCard;
if (first_digit == 36)
return kDinersCard;
break;
case 15:
if (first_two_digits == 34 || first_two_digits == 37)
return kAmericanExpressCard;
if (first_four_digits == 2131 || first_four_digits == 1800)
return kJCBCard;
if (first_four_digits == 2014 || first_four_digits == 2149)
return kDinersCard;
break;
case 16:
if (first_four_digits == 6011 || first_two_digits == 65)
return kDiscoverCard;
if (first_four_digits == 6334 || first_four_digits == 6767)
return kSoloCard;
if (first_two_digits >= 51 && first_two_digits <= 55)
return kMasterCard;
if (first_digit == 3)
return kJCBCard;
if (first_digit == 4)
return kVisaCard;
break;
case 18:
case 19:
if (first_four_digits == 6334 || first_four_digits == 6767)
return kSoloCard;
break;
}
return kGenericCard;
}
// Return a version of |number| that has had any non-digit values removed.
const string16 RemoveNonAsciiDigits(const string16& number) {
string16 stripped;
for (size_t i = 0; i < number.size(); ++i) {
if (IsAsciiDigit(number[i]))
stripped.append(1, number[i]);
}
return stripped;
}
} // namespace
CreditCard::CreditCard(const std::string& guid)
: expiration_month_(0),
expiration_year_(0),
guid_(guid) {
}
CreditCard::CreditCard()
: expiration_month_(0),
expiration_year_(0),
guid_(guid::GenerateGUID()) {
}
CreditCard::CreditCard(const CreditCard& credit_card) : FormGroup() {
operator=(credit_card);
}
CreditCard::~CreditCard() {}
FormGroup* CreditCard::Clone() const {
return new CreditCard(*this);
}
void CreditCard::GetPossibleFieldTypes(const string16& text,
FieldTypeSet* possible_types) const {
if (IsNameOnCard(text))
possible_types->insert(CREDIT_CARD_NAME);
if (IsCreditCardNumber(text))
possible_types->insert(CREDIT_CARD_NUMBER);
if (IsExpirationMonth(text))
possible_types->insert(CREDIT_CARD_EXP_MONTH);
if (Is2DigitExpirationYear(text))
possible_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR);
if (Is4DigitExpirationYear(text))
possible_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR);
}
void CreditCard::GetAvailableFieldTypes(FieldTypeSet* available_types) const {
DCHECK(available_types);
if (!name_on_card().empty())
available_types->insert(CREDIT_CARD_NAME);
if (!ExpirationMonthAsString().empty())
available_types->insert(CREDIT_CARD_EXP_MONTH);
if (!Expiration2DigitYearAsString().empty())
available_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR);
if (!Expiration4DigitYearAsString().empty())
available_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR);
if (!number().empty())
available_types->insert(CREDIT_CARD_NUMBER);
}
void CreditCard::FindInfoMatches(const AutoFillType& type,
const string16& info,
std::vector<string16>* matched_text) const {
DCHECK(matched_text);
string16 match;
switch (type.field_type()) {
case CREDIT_CARD_NUMBER: {
// Because the credit card number is encrypted and we are not able to do
// comparisons with it we will say that any field that is known to be a
// credit card number field will match all credit card numbers.
string16 text = GetPreviewText(AutoFillType(CREDIT_CARD_NUMBER));
if (!text.empty())
matched_text->push_back(text);
break;
}
case CREDIT_CARD_VERIFICATION_CODE:
NOTREACHED();
break;
case UNKNOWN_TYPE:
for (int i = 0; i < kAutoFillCreditCardLength; ++i) {
if (FindInfoMatchesHelper(kAutoFillCreditCardTypes[i], info, &match))
matched_text->push_back(match);
}
break;
default:
if (FindInfoMatchesHelper(type.field_type(), info, &match))
matched_text->push_back(match);
break;
}
}
string16 CreditCard::GetFieldText(const AutoFillType& type) const {
switch (type.field_type()) {
case CREDIT_CARD_NAME:
return name_on_card();
case CREDIT_CARD_EXP_MONTH:
return ExpirationMonthAsString();
case CREDIT_CARD_EXP_2_DIGIT_YEAR:
return Expiration2DigitYearAsString();
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
return Expiration4DigitYearAsString();
case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: {
string16 month = ExpirationMonthAsString();
string16 year = Expiration2DigitYearAsString();
if (!month.empty() && !year.empty())
return month + ASCIIToUTF16("/") + year;
return string16();
}
case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: {
string16 month = ExpirationMonthAsString();
string16 year = Expiration4DigitYearAsString();
if (!month.empty() && !year.empty())
return month + ASCIIToUTF16("/") + year;
return string16();
}
case CREDIT_CARD_TYPE:
// We don't handle this case.
return string16();
case CREDIT_CARD_NUMBER:
return number();
case CREDIT_CARD_VERIFICATION_CODE:
NOTREACHED();
return string16();
default:
// ComputeDataPresentForArray will hit this repeatedly.
return string16();
}
}
string16 CreditCard::GetPreviewText(const AutoFillType& type) const {
switch (type.field_type()) {
case CREDIT_CARD_NUMBER:
return last_four_digits();
case CREDIT_CARD_VERIFICATION_CODE:
NOTREACHED();
return string16();
default:
return GetFieldText(type);
}
}
void CreditCard::SetInfo(const AutoFillType& type, const string16& value) {
switch (type.field_type()) {
case CREDIT_CARD_NAME:
set_name_on_card(value);
break;
case CREDIT_CARD_EXP_MONTH:
SetExpirationMonthFromString(value);
break;
case CREDIT_CARD_EXP_2_DIGIT_YEAR:
// This is a read-only attribute.
break;
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
SetExpirationYearFromString(value);
break;
case CREDIT_CARD_TYPE:
// We determine the type based on the number.
break;
case CREDIT_CARD_NUMBER: {
if (StartsWith(value, ASCIIToUTF16(kCreditCardObfuscationString), true)) {
// this is an obfuscated string. Do not change the real value.
break;
}
set_number(value);
set_type(ASCIIToUTF16(GetCreditCardType(number())));
// Update last four digits as well.
if (value.length() > 4)
set_last_four_digits(value.substr(value.length() - 4));
else
set_last_four_digits(string16());
} break;
case CREDIT_CARD_VERIFICATION_CODE:
NOTREACHED();
break;
default:
DLOG(ERROR) << "Attempting to set unknown info-type "
<< type.field_type();
break;
}
}
string16 CreditCard::ObfuscatedNumber() const {
if (number().empty())
return string16(); // No CC number, means empty preview.
string16 result(ASCIIToUTF16(kCreditCardObfuscationString));
result.append(last_four_digits());
return result;
}
string16 CreditCard::PreviewSummary() const {
#ifdef ANDROID
// TODO: Hook up credit card support on Android.
// What is the Android UX for autofill previews?
// This has to be #if #else #endif as we can't
// compile the Chromium code that uses l10n functions.
return string16(ASCIIToUTF16("1234 5678 9123 4567"));
#else
string16 preview;
if (number().empty())
return preview; // No CC number, means empty preview.
string16 obfuscated_cc_number = ObfuscatedNumber();
if (!expiration_month() || !expiration_year())
return obfuscated_cc_number; // No expiration date set.
// TODO(georgey): Internationalize date.
string16 formatted_date(ExpirationMonthAsString());
formatted_date.append(ASCIIToUTF16("/"));
formatted_date.append(Expiration4DigitYearAsString());
preview = l10n_util::GetStringFUTF16(
IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT,
obfuscated_cc_number,
formatted_date);
return preview;
#endif
}
string16 CreditCard::LastFourDigits() const {
static const size_t kNumLastDigits = 4;
if (number().size() < kNumLastDigits)
return string16();
return number().substr(number().size() - kNumLastDigits, kNumLastDigits);
}
void CreditCard::operator=(const CreditCard& credit_card) {
number_ = credit_card.number_;
name_on_card_ = credit_card.name_on_card_;
type_ = credit_card.type_;
last_four_digits_ = credit_card.last_four_digits_;
expiration_month_ = credit_card.expiration_month_;
expiration_year_ = credit_card.expiration_year_;
label_ = credit_card.label_;
guid_ = credit_card.guid_;
}
int CreditCard::Compare(const CreditCard& credit_card) const {
// The following CreditCard field types are the only types we store in the
// WebDB so far, so we're only concerned with matching these types in the
// credit card.
const AutoFillFieldType types[] = { CREDIT_CARD_NAME,
CREDIT_CARD_NUMBER,
CREDIT_CARD_EXP_MONTH,
CREDIT_CARD_EXP_4_DIGIT_YEAR };
for (size_t index = 0; index < arraysize(types); ++index) {
int comparison = GetFieldText(AutoFillType(types[index])).compare(
credit_card.GetFieldText(AutoFillType(types[index])));
if (comparison != 0)
return comparison;
}
return 0;
}
bool CreditCard::operator==(const CreditCard& credit_card) const {
if (label_ != credit_card.label_ || guid_ != credit_card.guid_)
return false;
return Compare(credit_card) == 0;
}
bool CreditCard::operator!=(const CreditCard& credit_card) const {
return !operator==(credit_card);
}
// Use the Luhn formula to validate the number.
// static
bool CreditCard::IsCreditCardNumber(const string16& text) {
string16 number = RemoveNonAsciiDigits(text);
if (number.empty())
return false;
int sum = 0;
bool odd = false;
string16::reverse_iterator iter;
for (iter = number.rbegin(); iter != number.rend(); ++iter) {
if (!IsAsciiDigit(*iter))
return false;
int digit = *iter - '0';
if (odd) {
digit *= 2;
sum += digit / 10 + digit % 10;
} else {
sum += digit;
}
odd = !odd;
}
return (sum % 10) == 0;
}
bool CreditCard::IsEmpty() const {
FieldTypeSet types;
GetAvailableFieldTypes(&types);
return types.empty();
}
string16 CreditCard::ExpirationMonthAsString() const {
if (expiration_month_ == 0)
return string16();
string16 month = base::IntToString16(expiration_month_);
if (expiration_month_ >= 10)
return month;
string16 zero = ASCIIToUTF16("0");
zero.append(month);
return zero;
}
string16 CreditCard::Expiration4DigitYearAsString() const {
if (expiration_year_ == 0)
return string16();
return base::IntToString16(Expiration4DigitYear());
}
string16 CreditCard::Expiration2DigitYearAsString() const {
if (expiration_year_ == 0)
return string16();
return base::IntToString16(Expiration2DigitYear());
}
void CreditCard::SetExpirationMonthFromString(const string16& text) {
int month;
if (!ConvertDate(text, &month))
return;
set_expiration_month(month);
}
void CreditCard::SetExpirationYearFromString(const string16& text) {
int year;
if (!ConvertDate(text, &year))
return;
set_expiration_year(year);
}
void CreditCard::set_number(const string16& number) {
number_ = RemoveNonAsciiDigits(number);
}
void CreditCard::set_expiration_month(int expiration_month) {
if (expiration_month < 0 || expiration_month > 12)
return;
expiration_month_ = expiration_month;
}
void CreditCard::set_expiration_year(int expiration_year) {
if (expiration_year != 0 &&
(expiration_year < 2006 || expiration_year > 10000)) {
return;
}
expiration_year_ = expiration_year;
}
bool CreditCard::FindInfoMatchesHelper(const AutoFillFieldType& field_type,
const string16& info,
string16* match) const {
DCHECK(match);
match->clear();
switch (field_type) {
case CREDIT_CARD_NAME: {
if (StartsWith(name_on_card(), info, false))
*match = name_on_card();
break;
}
case CREDIT_CARD_EXP_MONTH: {
string16 exp_month(ExpirationMonthAsString());
if (StartsWith(exp_month, info, true))
*match = exp_month;
break;
}
case CREDIT_CARD_EXP_2_DIGIT_YEAR: {
string16 exp_year(Expiration2DigitYearAsString());
if (StartsWith(exp_year, info, true))
*match = exp_year;
break;
}
case CREDIT_CARD_EXP_4_DIGIT_YEAR: {
string16 exp_year(Expiration4DigitYearAsString());
if (StartsWith(exp_year, info, true))
*match = exp_year;
break;
}
case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: {
string16 exp_date(ExpirationMonthAsString() + ASCIIToUTF16("/") +
Expiration2DigitYearAsString());
if (StartsWith(exp_date, info, true))
*match = exp_date;
break;
}
case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: {
string16 exp_date(ExpirationMonthAsString() + ASCIIToUTF16("/") +
Expiration4DigitYearAsString());
if (StartsWith(exp_date, info, true))
*match = exp_date;
break;
}
case CREDIT_CARD_TYPE:
// We don't handle this case.
break;
case CREDIT_CARD_VERIFICATION_CODE:
NOTREACHED();
break;
default:
break;
}
return !match->empty();
}
bool CreditCard::IsNameOnCard(const string16& text) const {
return StringToLowerASCII(text) == StringToLowerASCII(name_on_card_);
}
bool CreditCard::IsExpirationMonth(const string16& text) const {
int month;
if (!base::StringToInt(text, &month))
return false;
return expiration_month_ == month;
}
bool CreditCard::Is2DigitExpirationYear(const string16& text) const {
int year;
if (!base::StringToInt(text, &year))
return false;
return year < 100 && (expiration_year_ % 100) == year;
}
bool CreditCard::Is4DigitExpirationYear(const string16& text) const {
int year;
if (!base::StringToInt(text, &year))
return false;
return expiration_year_ == year;
}
bool CreditCard::ConvertDate(const string16& date, int* num) const {
if (!date.empty()) {
bool converted = base::StringToInt(date, num);
DCHECK(converted);
if (!converted)
return false;
} else {
// Clear the value.
*num = 0;
}
return true;
}
// So we can compare CreditCards with EXPECT_EQ().
std::ostream& operator<<(std::ostream& os, const CreditCard& credit_card) {
return os
<< UTF16ToUTF8(credit_card.Label())
<< " "
<< credit_card.guid()
<< " "
<< UTF16ToUTF8(credit_card.GetFieldText(AutoFillType(CREDIT_CARD_NAME)))
<< " "
<< UTF16ToUTF8(credit_card.GetFieldText(AutoFillType(CREDIT_CARD_TYPE)))
<< " "
<< UTF16ToUTF8(credit_card.GetFieldText(AutoFillType(CREDIT_CARD_NUMBER)))
<< " "
<< UTF16ToUTF8(credit_card.GetFieldText(
AutoFillType(CREDIT_CARD_EXP_MONTH)))
<< " "
<< UTF16ToUTF8(credit_card.GetFieldText(
AutoFillType(CREDIT_CARD_EXP_4_DIGIT_YEAR)));
}