summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autofill/credit_card_field.cc
blob: f9b843b5154f2c469971442f151939b549795fdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// 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/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<CreditCardField> 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 <select> fields, because they're used e.g. on qvc.com.
        if (ParseFieldSpecifics(scanner, pattern,
                                MATCH_LABEL | MATCH_VALUE | MATCH_TEXT |
                                    MATCH_SELECT,
                                &credit_card_field->expiration_date_)) {
          credit_card_field->is_two_digit_year_ = true;
          continue;
        }

        pattern = UTF8ToUTF16(autofill::kExpirationDateRe);
        if (ParseFieldSpecifics(scanner, pattern,
                                MATCH_LABEL | MATCH_VALUE | MATCH_TEXT |
                                    MATCH_SELECT,
                                &credit_card_field->expiration_date_)) {
          continue;
        }
      }

      if (credit_card_field->expiration_month_ &&
          !credit_card_field->expiration_year_ &&
          !credit_card_field->expiration_date_) {
        // Parsed a month but couldn't parse a year; give up.
        scanner->RewindTo(saved_cursor);
        return NULL;
      }
    }

    // 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 (ParseField(scanner, UTF8ToUTF16(autofill::kCardIgnoredRe), NULL))
      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_)
    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_date_ ||
       (credit_card_field->expiration_month_ &&
        (credit_card_field->expiration_year_ ||
         (LowerCaseEqualsASCII(
             credit_card_field->expiration_month_->form_control_type,
             "month")))))) {
    return credit_card_field.release();
  }

  scanner->RewindTo(saved_cursor);
  return NULL;
}

CreditCardField::CreditCardField()
    : cardholder_(NULL),
      cardholder_last_(NULL),
      type_(NULL),
      number_(NULL),
      verification_(NULL),
      expiration_month_(NULL),
      expiration_year_(NULL),
      expiration_date_(NULL),
      is_two_digit_year_(false) {
}

bool CreditCardField::ClassifyField(FieldTypeMap* map) const {
  bool ok = AddClassification(number_, CREDIT_CARD_NUMBER, map);
  ok = ok && AddClassification(type_, CREDIT_CARD_TYPE, map);
  ok = ok && AddClassification(verification_, CREDIT_CARD_VERIFICATION_CODE,
                               map);

  // 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)
    ok = ok && AddClassification(cardholder_, CREDIT_CARD_NAME, map);

  if (expiration_date_) {
    if (is_two_digit_year_) {
      ok = ok && AddClassification(expiration_date_,
                                   CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, map);
    } else {
      ok = ok && AddClassification(expiration_date_,
                                   CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, map);
    }
  } else {
    ok = ok && AddClassification(expiration_month_, CREDIT_CARD_EXP_MONTH, map);
    if (is_two_digit_year_) {
      ok = ok && AddClassification(expiration_year_,
                                   CREDIT_CARD_EXP_2_DIGIT_YEAR,
                                   map);
    } else {
      ok = ok && AddClassification(expiration_year_,
                                   CREDIT_CARD_EXP_4_DIGIT_YEAR,
                                   map);
    }
  }

  return ok;
}