credit_card_field.cc revision d8e800e5cd4b5ff1c84124f27b98f9c174ec2738
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/autofill/credit_card_field.h"
6
7#include "app/l10n_util.h"
8#include "base/scoped_ptr.h"
9#include "base/string16.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/autofill/autofill_field.h"
12#ifndef ANDROID
13#include "grit/autofill_resources.h"
14#endif
15
16bool CreditCardField::GetFieldInfo(FieldTypeMap* field_type_map) const {
17  bool ok = Add(field_type_map, number_, AutoFillType(CREDIT_CARD_NUMBER));
18  DCHECK(ok);
19
20  // If the heuristics detected first and last name in separate fields,
21  // then ignore both fields. Putting them into separate fields is probably
22  // wrong, because the credit card can also contain a middle name or middle
23  // initial.
24  if (cardholder_last_ == NULL) {
25    // Add() will check if cardholder_ is != NULL.
26    ok = ok && Add(field_type_map, cardholder_, AutoFillType(CREDIT_CARD_NAME));
27    DCHECK(ok);
28  }
29
30  ok = ok && Add(field_type_map, type_, AutoFillType(CREDIT_CARD_TYPE));
31  DCHECK(ok);
32  ok = ok && Add(field_type_map, expiration_month_,
33      AutoFillType(CREDIT_CARD_EXP_MONTH));
34  DCHECK(ok);
35  ok = ok && Add(field_type_map, expiration_year_,
36      AutoFillType(CREDIT_CARD_EXP_4_DIGIT_YEAR));
37  DCHECK(ok);
38
39  return ok;
40}
41
42FormFieldType CreditCardField::GetFormFieldType() const {
43  return kCreditCardType;
44}
45
46// static
47CreditCardField* CreditCardField::Parse(
48    std::vector<AutoFillField*>::const_iterator* iter,
49    bool is_ecml) {
50  scoped_ptr<CreditCardField> credit_card_field(new CreditCardField);
51  std::vector<AutoFillField*>::const_iterator q = *iter;
52  string16 pattern;
53
54  // Credit card fields can appear in many different orders.
55  // We loop until no more credit card related fields are found, see |break| at
56  // bottom of the loop.
57  for (int fields = 0; true; ++fields) {
58    // Sometimes the cardholder field is just labeled "name". Unfortunately this
59    // is a dangerously generic word to search for, since it will often match a
60    // name (not cardholder name) field before or after credit card fields. So
61    // we search for "name" only when we've already parsed at least one other
62    // credit card field and haven't yet parsed the expiration date (which
63    // usually appears at the end).
64    if (credit_card_field->cardholder_ == NULL) {
65      string16 name_pattern;
66      if (is_ecml) {
67        name_pattern = GetEcmlPattern(kEcmlCardHolder);
68      } else {
69        if (fields == 0 || credit_card_field->expiration_month_) {
70          // at beginning or end
71          name_pattern = l10n_util::GetStringUTF16(
72              IDS_AUTOFILL_NAME_ON_CARD_RE);
73        } else {
74          name_pattern = l10n_util::GetStringUTF16(
75              IDS_AUTOFILL_NAME_ON_CARD_CONTEXTUAL_RE);
76        }
77      }
78
79      if (ParseText(&q, name_pattern, &credit_card_field->cardholder_))
80        continue;
81
82      // As a hard-coded hack for Expedia's billing pages (expedia_checkout.html
83      // and ExpediaBilling.html in our test suite), recognize separate fields
84      // for the cardholder's first and last name if they have the labels "cfnm"
85      // and "clnm".
86      std::vector<AutoFillField*>::const_iterator p = q;
87      AutoFillField* first;
88      if (!is_ecml && ParseText(&p, ASCIIToUTF16("^cfnm"), &first) &&
89          ParseText(&p, ASCIIToUTF16("^clnm"),
90                    &credit_card_field->cardholder_last_)) {
91        credit_card_field->cardholder_ = first;
92        q = p;
93        continue;
94      }
95    }
96
97    // We look for a card security code before we look for a credit
98    // card number and match the general term "number".  The security code
99    // has a plethora of names; we've seen "verification #",
100    // "verification number", "card identification number" and others listed
101    // in the |pattern| below.
102    if (is_ecml) {
103      pattern = GetEcmlPattern(kEcmlCardVerification);
104    } else {
105      pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_CVC_RE);
106    }
107
108    if (credit_card_field->verification_ == NULL &&
109        ParseText(&q, pattern, &credit_card_field->verification_))
110      continue;
111
112    // TODO(jhawkins): Parse the type select control.
113
114    if (is_ecml)
115      pattern = GetEcmlPattern(kEcmlCardNumber);
116    else
117      pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_NUMBER_RE);
118
119    if (credit_card_field->number_ == NULL && ParseText(&q, pattern,
120        &credit_card_field->number_))
121      continue;
122
123    // "Expiration date" is the most common label here, but some pages have
124    // "Expires", "exp. date" or "exp. month" and "exp. year".  We also look for
125    // the field names ccmonth and ccyear, which appear on at least 4 of our
126    // test pages.
127    //
128    // -> On at least one page (The China Shop2.html) we find only the labels
129    // "month" and "year".  So for now we match these words directly; we'll
130    // see if this turns out to be too general.
131    //
132    // Toolbar Bug 51451: indeed, simply matching "month" is too general for
133    //   https://rps.fidelity.com/ftgw/rps/RtlCust/CreatePIN/Init.
134    // Instead, we match only words beginning with "month".
135    if (is_ecml)
136      pattern = GetEcmlPattern(kEcmlCardExpireMonth);
137    else
138      pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_EXPIRATION_MONTH_RE);
139
140    if ((!credit_card_field->expiration_month_ ||
141        credit_card_field->expiration_month_->IsEmpty()) &&
142        ParseText(&q, pattern, &credit_card_field->expiration_month_)) {
143      if (is_ecml)
144        pattern = GetEcmlPattern(kEcmlCardExpireYear);
145      else
146        pattern = l10n_util::GetStringUTF16(IDS_AUTOFILL_EXPIRATION_DATE_RE);
147
148      if (!ParseText(&q, pattern, &credit_card_field->expiration_year_))
149        return NULL;
150
151      continue;
152    }
153
154    if (ParseText(&q, GetEcmlPattern(kEcmlCardExpireDay)))
155      continue;
156
157    // Some pages (e.g. ExpediaBilling.html) have a "card description"
158    // field; we parse this field but ignore it.
159    // We also ignore any other fields within a credit card block that
160    // start with "card", under the assumption that they are related to
161    // the credit card section being processed but are uninteresting to us.
162    if (ParseText(&q, l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_IGNORED_RE)))
163      continue;
164
165    break;
166  }
167
168  // Some pages have a billing address field after the cardholder name field.
169  // For that case, allow only just the cardholder name field.  The remaining
170  // CC fields will be picked up in a following CreditCardField.
171  if (credit_card_field->cardholder_) {
172    *iter = q;
173    return credit_card_field.release();
174  }
175
176  // On some pages, the user selects a card type using radio buttons
177  // (e.g. test page Apple Store Billing.html).  We can't handle that yet,
178  // so we treat the card type as optional for now.
179  // The existence of a number or cvc in combination with expiration date is
180  // a strong enough signal that this is a credit card.  It is possible that
181  // the number and name were parsed in a separate part of the form.  So if
182  // the cvc and date were found independently they are returned.
183  if ((credit_card_field->number_ || credit_card_field->verification_) &&
184      credit_card_field->expiration_month_ &&
185      credit_card_field->expiration_year_) {
186      *iter = q;
187      return credit_card_field.release();
188  }
189
190  return NULL;
191}
192
193CreditCardField::CreditCardField()
194    : cardholder_(NULL),
195      cardholder_last_(NULL),
196      type_(NULL),
197      number_(NULL),
198      verification_(NULL),
199      expiration_month_(NULL),
200      expiration_year_(NULL) {
201}
202