Chromium Code Reviews| Index: components/autofill/core/browser/credit_card_field.cc |
| diff --git a/components/autofill/core/browser/credit_card_field.cc b/components/autofill/core/browser/credit_card_field.cc |
| index ba9c001022dc69ced0d5207c70f5d64b26d4550b..34ea09530b3cbb85c2a90869f1a467525c175572 100644 |
| --- a/components/autofill/core/browser/credit_card_field.cc |
| +++ b/components/autofill/core/browser/credit_card_field.cc |
| @@ -7,19 +7,46 @@ |
| #include <stddef.h> |
| #include "base/memory/scoped_ptr.h" |
| +#include "base/stl_util.h" |
| #include "base/strings/string16.h" |
| +#include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| +#include "base/time/time.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/autofill_regex_constants.h" |
| +#include "components/autofill/core/browser/autofill_regexes.h" |
| #include "components/autofill/core/browser/autofill_scanner.h" |
| #include "components/autofill/core/browser/field_types.h" |
| namespace autofill { |
| +namespace { |
| + |
| // Credit card numbers are at most 19 digits in length. |
| // [Ref: http://en.wikipedia.org/wiki/Bank_card_number] |
| -static const size_t kMaxValidCardNumberSize = 19; |
| +const size_t kMaxValidCardNumberSize = 19; |
| + |
| +// Look for the vector |regex_needles| in |haystack|. Returns true if a |
| +// consecutive section of |haystack| matches |regex_needles|. |
| +bool FindConsecutiveStrings(const std::vector<base::string16>& regex_needles, |
| + const std::vector<base::string16>& haystack) { |
| + if (regex_needles.empty() || haystack.empty()) |
| + return false; |
| + |
| + for (size_t i = 0; i < haystack.size() - regex_needles.size() + 1; ++i) { |
| + for (size_t j = 0; j < regex_needles.size(); ++j) { |
| + if (!MatchesPattern(haystack[i + j], regex_needles[j])) |
| + break; |
| + |
| + if (j == regex_needles.size() - 1) |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +} // namespace |
| // static |
| scoped_ptr<FormField> CreditCardField::Parse(AutofillScanner* scanner) { |
| @@ -157,6 +184,67 @@ scoped_ptr<FormField> CreditCardField::Parse(AutofillScanner* scanner) { |
| return nullptr; |
| } |
| +// static |
| +bool CreditCardField::LikelyCardMonthSelectField(AutofillScanner* scanner) { |
| + if (scanner->IsEnd()) |
| + return false; |
| + |
| + AutofillField* field = scanner->Cursor(); |
| + if (!MatchesFormControlType(field->form_control_type, MATCH_SELECT)) |
| + return false; |
| + |
| + if (field->option_values.size() < 12 || field->option_values.size() > 13) |
| + return false; |
| + |
| + // Filter out years. |
| + const base::string16 kNumericalYearRe = |
| + base::ASCIIToUTF16("[1-9][0-9][0-9][0-9]"); |
| + for (const auto& value : field->option_values) { |
| + if (MatchesPattern(value, kNumericalYearRe)) |
| + return false; |
| + } |
| + for (const auto& value : field->option_contents) { |
| + if (MatchesPattern(value, kNumericalYearRe)) |
| + return false; |
| + } |
| + |
| + // Look for numerical months. |
| + const base::string16 kNumericalMonthRe = base::ASCIIToUTF16("12"); |
| + if (MatchesPattern(field->option_values.back(), kNumericalMonthRe) || |
| + MatchesPattern(field->option_contents.back(), kNumericalMonthRe)) { |
| + return true; |
| + } |
| + |
| + // Maybe do more matches here. e.g. look for (translated) December. |
| + |
| + // Unsure? Return false. |
| + return false; |
| +} |
| + |
| +// static |
| +bool CreditCardField::LikelyCardYearSelectField(AutofillScanner* scanner) { |
| + if (scanner->IsEnd()) |
| + return false; |
| + |
| + AutofillField* field = scanner->Cursor(); |
| + if (!MatchesFormControlType(field->form_control_type, MATCH_SELECT)) |
| + return false; |
| + |
| + const base::Time time_now = base::Time::Now(); |
| + base::Time::Exploded time_exploded; |
| + time_now.UTCExplode(&time_exploded); |
| + |
| + const int kYearsToMatch = 3; |
| + std::vector<base::string16> years_to_check; |
| + for (int year = time_exploded.year; |
| + year < time_exploded.year + kYearsToMatch; |
| + ++year) { |
| + years_to_check.push_back(base::IntToString16(year)); |
| + } |
| + return (FindConsecutiveStrings(years_to_check, field->option_values) || |
| + FindConsecutiveStrings(years_to_check, field->option_contents)); |
| +} |
| + |
| CreditCardField::CreditCardField() |
| : cardholder_(nullptr), |
| cardholder_last_(nullptr), |
| @@ -215,8 +303,24 @@ bool CreditCardField::ParseExpirationDate(AutofillScanner* scanner) { |
| if (expiration_month_ || expiration_date_) |
| return false; |
| - // First try to parse split month/year expiration fields. |
| + // First try to parse split month/year expiration fields by looking for a |
| + // pair of select fields that look like month/year. |
| size_t month_year_saved_cursor = scanner->SaveCursor(); |
| + |
| + if (LikelyCardMonthSelectField(scanner)) { |
| + expiration_month_ = scanner->Cursor(); |
| + scanner->Advance(); |
| + if (LikelyCardYearSelectField(scanner)) { |
|
Evan Stade
2015/03/19 15:56:39
I wonder if some locales do year then month?
Lei Zhang
2015/03/19 18:35:57
Probably, but everything below assumes month/year
|
| + expiration_year_ = scanner->Cursor(); |
| + scanner->Advance(); |
| + return true; |
| + } |
| + expiration_month_ = nullptr; |
| + expiration_year_ = nullptr; |
| + } |
| + |
| + // If that fails, do a general regex search. |
| + scanner->RewindTo(month_year_saved_cursor); |
| const int kMatchTelAndSelect = MATCH_DEFAULT | MATCH_TELEPHONE | MATCH_SELECT; |
| if (ParseFieldSpecifics(scanner, |
| base::UTF8ToUTF16(kExpirationMonthRe), |