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