From 23e152e917c70e0399770433d564fe2f54c5ea95 Mon Sep 17 00:00:00 2001 From: "andybons@chromium.org" Date: Wed, 30 Mar 2011 15:52:34 +0000 Subject: Split out Keywords and Autofill logic from WebDatabase. This is simply moving things around. No underlying logic has changed. Since the new class AutofillTable actually encapsulates more than one table within the schema, I'm open to naming suggestions. No plans yet for the migration code. BUG=none TEST=WebDatabaseTest*,AutofillTableTest*,KeywordTableTest*,ProfileSyncServiceAutofillTest.* Review URL: http://codereview.chromium.org/6708110 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@79834 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/webdata/autofill_table.cc | 1146 +++++++++++++ chrome/browser/webdata/autofill_table.h | 315 ++++ chrome/browser/webdata/autofill_table_unittest.cc | 1578 +++++++++++++++++ chrome/browser/webdata/autofill_util.cc | 358 ++++ chrome/browser/webdata/autofill_util.h | 68 + chrome/browser/webdata/keyword_table.cc | 224 +++ chrome/browser/webdata/keyword_table.h | 88 + chrome/browser/webdata/keyword_table_unittest.cc | 247 +++ chrome/browser/webdata/web_data_service.cc | 65 +- chrome/browser/webdata/web_database.cc | 1878 +-------------------- chrome/browser/webdata/web_database.h | 259 +-- chrome/browser/webdata/web_database_table.h | 41 + chrome/browser/webdata/web_database_unittest.cc | 1730 +------------------ 13 files changed, 4180 insertions(+), 3817 deletions(-) create mode 100644 chrome/browser/webdata/autofill_table.cc create mode 100644 chrome/browser/webdata/autofill_table.h create mode 100644 chrome/browser/webdata/autofill_table_unittest.cc create mode 100644 chrome/browser/webdata/autofill_util.cc create mode 100644 chrome/browser/webdata/autofill_util.h create mode 100644 chrome/browser/webdata/keyword_table.cc create mode 100644 chrome/browser/webdata/keyword_table.h create mode 100644 chrome/browser/webdata/keyword_table_unittest.cc create mode 100644 chrome/browser/webdata/web_database_table.h (limited to 'chrome/browser/webdata') diff --git a/chrome/browser/webdata/autofill_table.cc b/chrome/browser/webdata/autofill_table.cc new file mode 100644 index 0000000..0433b44 --- /dev/null +++ b/chrome/browser/webdata/autofill_table.cc @@ -0,0 +1,1146 @@ +// 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/webdata/autofill_table.h" + +#include "app/sql/statement.h" +#include "base/time.h" +#include "base/tuple.h" +#include "chrome/browser/autofill/autofill_country.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/autofill/autofill_type.h" +#include "chrome/browser/autofill/credit_card.h" +#include "chrome/browser/autofill/personal_data_manager.h" +#include "chrome/browser/webdata/autofill_change.h" +#include "chrome/browser/webdata/autofill_util.h" +#include "chrome/common/guid.h" +#include "ui/base/l10n/l10n_util.h" +#include "webkit/glue/form_field.h" + +using base::Time; +using webkit_glue::FormField; + +namespace { +typedef std::vector > AutofillElementList; +} // anonymous namespace + +bool AutofillTable::Init() { + return (InitMainTable() && InitCreditCardsTable() && InitDatesTable() && + InitProfilesTable() && InitProfileNamesTable() && + InitProfileEmailsTable() && InitProfilePhonesTable() && + InitProfileTrashTable()); +} + +bool AutofillTable::AddFormFieldValues(const std::vector& elements, + std::vector* changes) { + return AddFormFieldValuesTime(elements, changes, Time::Now()); +} + +bool AutofillTable::AddFormFieldValue(const FormField& element, + std::vector* changes) { + return AddFormFieldValueTime(element, changes, base::Time::Now()); +} + +bool AutofillTable::GetFormValuesForElementName(const string16& name, + const string16& prefix, + std::vector* values, + int limit) { + DCHECK(values); + sql::Statement s; + + if (prefix.empty()) { + s.Assign(db_->GetUniqueStatement( + "SELECT value FROM autofill " + "WHERE name = ? " + "ORDER BY count DESC " + "LIMIT ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, name); + s.BindInt(1, limit); + } else { + string16 prefix_lower = l10n_util::ToLower(prefix); + string16 next_prefix = prefix_lower; + next_prefix[next_prefix.length() - 1]++; + + s.Assign(db_->GetUniqueStatement( + "SELECT value FROM autofill " + "WHERE name = ? AND " + "value_lower >= ? AND " + "value_lower < ? " + "ORDER BY count DESC " + "LIMIT ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, name); + s.BindString16(1, prefix_lower); + s.BindString16(2, next_prefix); + s.BindInt(3, limit); + } + + values->clear(); + while (s.Step()) + values->push_back(s.ColumnString16(0)); + return s.Succeeded(); +} + +bool AutofillTable::RemoveFormElementsAddedBetween( + base::Time delete_begin, + base::Time delete_end, + std::vector* changes) { + DCHECK(changes); + // Query for the pair_id, name, and value of all form elements that + // were used between the given times. + sql::Statement s(db_->GetUniqueStatement( + "SELECT DISTINCT a.pair_id, a.name, a.value " + "FROM autofill_dates ad JOIN autofill a ON ad.pair_id = a.pair_id " + "WHERE ad.date_created >= ? AND ad.date_created < ?")); + if (!s) { + NOTREACHED() << "Statement 1 prepare failed"; + return false; + } + s.BindInt64(0, delete_begin.ToTimeT()); + s.BindInt64(1, + delete_end.is_null() ? + std::numeric_limits::max() : + delete_end.ToTimeT()); + + AutofillElementList elements; + while (s.Step()) { + elements.push_back(MakeTuple(s.ColumnInt64(0), + s.ColumnString16(1), + s.ColumnString16(2))); + } + + if (!s.Succeeded()) { + NOTREACHED(); + return false; + } + + for (AutofillElementList::iterator itr = elements.begin(); + itr != elements.end(); itr++) { + int how_many = 0; + if (!RemoveFormElementForTimeRange(itr->a, delete_begin, delete_end, + &how_many)) { + return false; + } + bool was_removed = false; + if (!AddToCountOfFormElement(itr->a, -how_many, &was_removed)) + return false; + AutofillChange::Type change_type = + was_removed ? AutofillChange::REMOVE : AutofillChange::UPDATE; + changes->push_back(AutofillChange(change_type, + AutofillKey(itr->b, itr->c))); + } + + return true; +} + +bool AutofillTable::RemoveFormElementForTimeRange(int64 pair_id, + const Time delete_begin, + const Time delete_end, + int* how_many) { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_dates WHERE pair_id = ? AND " + "date_created >= ? AND date_created < ?")); + if (!s) { + NOTREACHED() << "Statement 1 prepare failed"; + return false; + } + s.BindInt64(0, pair_id); + s.BindInt64(1, delete_begin.is_null() ? 0 : delete_begin.ToTimeT()); + s.BindInt64(2, delete_end.is_null() ? std::numeric_limits::max() : + delete_end.ToTimeT()); + + bool result = s.Run(); + if (how_many) + *how_many = db_->GetLastChangeCount(); + + return result; +} + +bool AutofillTable::AddToCountOfFormElement(int64 pair_id, + int delta, + bool* was_removed) { + DCHECK(was_removed); + int count = 0; + *was_removed = false; + + if (!GetCountOfFormElement(pair_id, &count)) + return false; + + if (count + delta == 0) { + if (!RemoveFormElementForID(pair_id)) + return false; + *was_removed = true; + } else { + if (!SetCountOfFormElement(pair_id, count + delta)) + return false; + } + return true; +} + +bool AutofillTable::GetIDAndCountOfFormElement( + const FormField& element, + int64* pair_id, + int* count) { + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id, count FROM autofill " + "WHERE name = ? AND value = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, element.name); + s.BindString16(1, element.value); + + *pair_id = 0; + *count = 0; + + if (s.Step()) { + *pair_id = s.ColumnInt64(0); + *count = s.ColumnInt(1); + } + + return true; +} + +bool AutofillTable::GetCountOfFormElement(int64 pair_id, int* count) { + sql::Statement s(db_->GetUniqueStatement( + "SELECT count FROM autofill WHERE pair_id = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindInt64(0, pair_id); + + if (s.Step()) { + *count = s.ColumnInt(0); + return true; + } + return false; +} + +bool AutofillTable::SetCountOfFormElement(int64 pair_id, int count) { + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill SET count = ? WHERE pair_id = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindInt(0, count); + s.BindInt64(1, pair_id); + if (!s.Run()) { + NOTREACHED(); + return false; + } + + return true; +} + +bool AutofillTable::InsertFormElement(const FormField& element, + int64* pair_id) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill (name, value, value_lower) VALUES (?,?,?)")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, element.name); + s.BindString16(1, element.value); + s.BindString16(2, l10n_util::ToLower(element.value)); + + if (!s.Run()) { + NOTREACHED(); + return false; + } + + *pair_id = db_->GetLastInsertRowId(); + return true; +} + +bool AutofillTable::InsertPairIDAndDate(int64 pair_id, + base::Time date_created) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_dates " + "(pair_id, date_created) VALUES (?, ?)")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindInt64(0, pair_id); + s.BindInt64(1, date_created.ToTimeT()); + + if (!s.Run()) { + NOTREACHED(); + return false; + } + + return true; +} + +bool AutofillTable::AddFormFieldValuesTime( + const std::vector& elements, + std::vector* changes, + base::Time time) { + // Only add one new entry for each unique element name. Use |seen_names| to + // track this. Add up to |kMaximumUniqueNames| unique entries per form. + const size_t kMaximumUniqueNames = 256; + std::set seen_names; + bool result = true; + for (std::vector::const_iterator + itr = elements.begin(); + itr != elements.end(); + itr++) { + if (seen_names.size() >= kMaximumUniqueNames) + break; + if (seen_names.find(itr->name) != seen_names.end()) + continue; + result = result && AddFormFieldValueTime(*itr, changes, time); + seen_names.insert(itr->name); + } + return result; +} + +bool AutofillTable::ClearAutofillEmptyValueElements() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id FROM autofill WHERE TRIM(value)= \"\"")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + std::set ids; + while (s.Step()) + ids.insert(s.ColumnInt64(0)); + + bool success = true; + for (std::set::const_iterator iter = ids.begin(); iter != ids.end(); + ++iter) { + if (!RemoveFormElementForID(*iter)) + success = false; + } + + return success; +} + +bool AutofillTable::GetAllAutofillEntries(std::vector* entries) { + DCHECK(entries); + sql::Statement s(db_->GetUniqueStatement( + "SELECT name, value, date_created FROM autofill a JOIN " + "autofill_dates ad ON a.pair_id=ad.pair_id")); + + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + bool first_entry = true; + AutofillKey* current_key_ptr = NULL; + std::vector* timestamps_ptr = NULL; + string16 name, value; + base::Time time; + while (s.Step()) { + name = s.ColumnString16(0); + value = s.ColumnString16(1); + time = Time::FromTimeT(s.ColumnInt64(2)); + + if (first_entry) { + current_key_ptr = new AutofillKey(name, value); + + timestamps_ptr = new std::vector; + timestamps_ptr->push_back(time); + + first_entry = false; + } else { + // we've encountered the next entry + if (current_key_ptr->name().compare(name) != 0 || + current_key_ptr->value().compare(value) != 0) { + AutofillEntry entry(*current_key_ptr, *timestamps_ptr); + entries->push_back(entry); + + delete current_key_ptr; + delete timestamps_ptr; + + current_key_ptr = new AutofillKey(name, value); + timestamps_ptr = new std::vector; + } + timestamps_ptr->push_back(time); + } + } + // If there is at least one result returned, first_entry will be false. + // For this case we need to do a final cleanup step. + if (!first_entry) { + AutofillEntry entry(*current_key_ptr, *timestamps_ptr); + entries->push_back(entry); + delete current_key_ptr; + delete timestamps_ptr; + } + + return s.Succeeded(); +} + +bool AutofillTable::GetAutofillTimestamps(const string16& name, + const string16& value, + std::vector* timestamps) { + DCHECK(timestamps); + sql::Statement s(db_->GetUniqueStatement( + "SELECT date_created FROM autofill a JOIN " + "autofill_dates ad ON a.pair_id=ad.pair_id " + "WHERE a.name = ? AND a.value = ?")); + + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, name); + s.BindString16(1, value); + while (s.Step()) { + timestamps->push_back(Time::FromTimeT(s.ColumnInt64(0))); + } + + return s.Succeeded(); +} + +bool AutofillTable::UpdateAutofillEntries( + const std::vector& entries) { + if (!entries.size()) + return true; + + // Remove all existing entries. + for (size_t i = 0; i < entries.size(); i++) { + std::string sql = "SELECT pair_id FROM autofill " + "WHERE name = ? AND value = ?"; + sql::Statement s(db_->GetUniqueStatement(sql.c_str())); + if (!s.is_valid()) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, entries[i].key().name()); + s.BindString16(1, entries[i].key().value()); + if (s.Step()) { + if (!RemoveFormElementForID(s.ColumnInt64(0))) + return false; + } + } + + // Insert all the supplied autofill entries. + for (size_t i = 0; i < entries.size(); i++) { + if (!InsertAutofillEntry(entries[i])) + return false; + } + + return true; +} + +bool AutofillTable::InsertAutofillEntry(const AutofillEntry& entry) { + std::string sql = "INSERT INTO autofill (name, value, value_lower, count) " + "VALUES (?, ?, ?, ?)"; + sql::Statement s(db_->GetUniqueStatement(sql.c_str())); + if (!s.is_valid()) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString16(0, entry.key().name()); + s.BindString16(1, entry.key().value()); + s.BindString16(2, l10n_util::ToLower(entry.key().value())); + s.BindInt(3, entry.timestamps().size()); + + if (!s.Run()) { + NOTREACHED(); + return false; + } + + int64 pair_id = db_->GetLastInsertRowId(); + for (size_t i = 0; i < entry.timestamps().size(); i++) { + if (!InsertPairIDAndDate(pair_id, entry.timestamps()[i])) + return false; + } + + return true; +} + +bool AutofillTable::AddFormFieldValueTime(const FormField& element, + std::vector* changes, + base::Time time) { + int count = 0; + int64 pair_id; + + if (!GetIDAndCountOfFormElement(element, &pair_id, &count)) + return false; + + if (count == 0 && !InsertFormElement(element, &pair_id)) + return false; + + if (!SetCountOfFormElement(pair_id, count + 1)) + return false; + + if (!InsertPairIDAndDate(pair_id, time)) + return false; + + AutofillChange::Type change_type = + count == 0 ? AutofillChange::ADD : AutofillChange::UPDATE; + changes->push_back( + AutofillChange(change_type, + AutofillKey(element.name, element.value))); + return true; +} + + +bool AutofillTable::RemoveFormElement(const string16& name, + const string16& value) { + // Find the id for that pair. + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id FROM autofill WHERE name = ? AND value= ?")); + if (!s) { + NOTREACHED() << "Statement 1 prepare failed"; + return false; + } + s.BindString16(0, name); + s.BindString16(1, value); + + if (s.Step()) + return RemoveFormElementForID(s.ColumnInt64(0)); + return false; +} + +bool AutofillTable::AddAutofillProfile(const AutofillProfile& profile) { + if (IsAutofillGUIDInTrash(profile.guid())) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles" + "(guid, company_name, address_line_1, address_line_2, city, state," + " zipcode, country, country_code, date_modified)" + "VALUES (?,?,?,?,?,?,?,?,?,?)")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + autofill_util::BindAutofillProfileToStatement(profile, &s); + + if (!s.Run()) { + NOTREACHED(); + return false; + } + + if (!s.Succeeded()) + return false; + + return autofill_util::AddAutofillProfilePieces(profile, db_); +} + +bool AutofillTable::GetAutofillProfile(const std::string& guid, + AutofillProfile** profile) { + DCHECK(guid::IsValidGUID(guid)); + DCHECK(profile); + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, city, state," + " zipcode, country, country_code, date_modified " + "FROM autofill_profiles " + "WHERE guid=?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString(0, guid); + if (!s.Step()) + return false; + + if (!s.Succeeded()) + return false; + + scoped_ptr p(autofill_util::AutofillProfileFromStatement(s)); + + // Get associated name info. + autofill_util::AddAutofillProfileNamesToProfile(db_, p.get()); + + // Get associated email info. + autofill_util::AddAutofillProfileEmailsToProfile(db_, p.get()); + + // Get associated phone info. + autofill_util::AddAutofillProfilePhonesToProfile(db_, p.get()); + + // Get associated fax info. + autofill_util::AddAutofillProfileFaxesToProfile(db_, p.get()); + + *profile = p.release(); + return true; +} + +bool AutofillTable::GetAutofillProfiles( + std::vector* profiles) { + DCHECK(profiles); + profiles->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + while (s.Step()) { + std::string guid = s.ColumnString(0); + AutofillProfile* profile = NULL; + if (!GetAutofillProfile(guid, &profile)) + return false; + profiles->push_back(profile); + } + + return s.Succeeded(); +} + +bool AutofillTable::UpdateAutofillProfile(const AutofillProfile& profile) { + DCHECK(guid::IsValidGUID(profile.guid())); + + // Don't update anything until the trash has been emptied. There may be + // pending modifications to process. + if (!IsAutofillProfilesTrashEmpty()) + return true; + + AutofillProfile* tmp_profile = NULL; + if (!GetAutofillProfile(profile.guid(), &tmp_profile)) + return false; + + // Preserve appropriate modification dates by not updating unchanged profiles. + scoped_ptr old_profile(tmp_profile); + if (old_profile->Compare(profile) == 0) + return true; + + AutofillProfile new_profile(profile); + std::vector values; + + old_profile->GetMultiInfo(NAME_FULL, &values); + values[0] = new_profile.GetInfo(NAME_FULL); + new_profile.SetMultiInfo(NAME_FULL, values); + + old_profile->GetMultiInfo(EMAIL_ADDRESS, &values); + values[0] = new_profile.GetInfo(EMAIL_ADDRESS); + new_profile.SetMultiInfo(EMAIL_ADDRESS, values); + + old_profile->GetMultiInfo(PHONE_HOME_WHOLE_NUMBER, &values); + values[0] = new_profile.GetInfo(PHONE_HOME_WHOLE_NUMBER); + new_profile.SetMultiInfo(PHONE_HOME_WHOLE_NUMBER, values); + + old_profile->GetMultiInfo(PHONE_FAX_WHOLE_NUMBER, &values); + values[0] = new_profile.GetInfo(PHONE_FAX_WHOLE_NUMBER); + new_profile.SetMultiInfo(PHONE_FAX_WHOLE_NUMBER, values); + + return UpdateAutofillProfileMulti(new_profile); +} + +bool AutofillTable::UpdateAutofillProfileMulti(const AutofillProfile& profile) { + DCHECK(guid::IsValidGUID(profile.guid())); + + // Don't update anything until the trash has been emptied. There may be + // pending modifications to process. + if (!IsAutofillProfilesTrashEmpty()) + return true; + + AutofillProfile* tmp_profile = NULL; + if (!GetAutofillProfile(profile.guid(), &tmp_profile)) + return false; + + // Preserve appropriate modification dates by not updating unchanged profiles. + scoped_ptr old_profile(tmp_profile); + if (old_profile->CompareMulti(profile) == 0) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill_profiles " + "SET guid=?, company_name=?, address_line_1=?, address_line_2=?, " + " city=?, state=?, zipcode=?, country=?, country_code=?, " + " date_modified=? " + "WHERE guid=?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + autofill_util::BindAutofillProfileToStatement(profile, &s); + s.BindString(10, profile.guid()); + bool result = s.Run(); + DCHECK_GT(db_->GetLastChangeCount(), 0); + if (!result) + return result; + + // Remove the old names, emails, and phone/fax numbers. + if (!autofill_util::RemoveAutofillProfilePieces(profile.guid(), db_)) + return false; + + return autofill_util::AddAutofillProfilePieces(profile, db_); +} + +bool AutofillTable::RemoveAutofillProfile(const std::string& guid) { + DCHECK(guid::IsValidGUID(guid)); + + if (IsAutofillGUIDInTrash(guid)) { + sql::Statement s_trash(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles_trash WHERE guid = ?")); + if (!s_trash) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s_trash.BindString(0, guid); + if (!s_trash.Run()) { + NOTREACHED() << "Expected item in trash."; + return false; + } + + return true; + } + + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles WHERE guid = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString(0, guid); + if (!s.Run()) + return false; + + return autofill_util::RemoveAutofillProfilePieces(guid, db_); +} + +bool AutofillTable::ClearAutofillProfiles() { + sql::Statement s1(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles")); + if (!s1) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + if (!s1.Run()) + return false; + + sql::Statement s2(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_names")); + if (!s2) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + if (!s2.Run()) + return false; + + sql::Statement s3(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_emails")); + if (!s3) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + if (!s3.Run()) + return false; + + sql::Statement s4(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_phones")); + if (!s4) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + if (!s4.Run()) + return false; + + return true; +} + +bool AutofillTable::AddCreditCard(const CreditCard& credit_card) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO credit_cards" + "(guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified)" + "VALUES (?,?,?,?,?,?)")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + autofill_util::BindCreditCardToStatement(credit_card, &s); + + if (!s.Run()) { + NOTREACHED(); + return false; + } + + DCHECK_GT(db_->GetLastChangeCount(), 0); + return s.Succeeded(); +} + +bool AutofillTable::GetCreditCard(const std::string& guid, + CreditCard** credit_card) { + DCHECK(guid::IsValidGUID(guid)); + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards " + "WHERE guid = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString(0, guid); + if (!s.Step()) + return false; + + *credit_card = autofill_util::CreditCardFromStatement(s); + + return s.Succeeded(); +} + +bool AutofillTable::GetCreditCards( + std::vector* credit_cards) { + DCHECK(credit_cards); + credit_cards->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM credit_cards")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + while (s.Step()) { + std::string guid = s.ColumnString(0); + CreditCard* credit_card = NULL; + if (!GetCreditCard(guid, &credit_card)) + return false; + credit_cards->push_back(credit_card); + } + + return s.Succeeded(); +} + +bool AutofillTable::UpdateCreditCard(const CreditCard& credit_card) { + DCHECK(guid::IsValidGUID(credit_card.guid())); + + CreditCard* tmp_credit_card = NULL; + if (!GetCreditCard(credit_card.guid(), &tmp_credit_card)) + return false; + + // Preserve appropriate modification dates by not updating unchanged cards. + scoped_ptr old_credit_card(tmp_credit_card); + if (*old_credit_card == credit_card) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE credit_cards " + "SET guid=?, name_on_card=?, expiration_month=?, " + " expiration_year=?, card_number_encrypted=?, date_modified=? " + "WHERE guid=?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + autofill_util::BindCreditCardToStatement(credit_card, &s); + s.BindString(6, credit_card.guid()); + bool result = s.Run(); + DCHECK_GT(db_->GetLastChangeCount(), 0); + return result; +} + +bool AutofillTable::RemoveCreditCard(const std::string& guid) { + DCHECK(guid::IsValidGUID(guid)); + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM credit_cards WHERE guid = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString(0, guid); + return s.Run(); +} + +bool AutofillTable::RemoveAutofillProfilesAndCreditCardsModifiedBetween( + base::Time delete_begin, + base::Time delete_end) { + DCHECK(delete_end.is_null() || delete_begin < delete_end); + + time_t delete_begin_t = delete_begin.ToTimeT(); + time_t delete_end_t = delete_end.is_null() ? + std::numeric_limits::max() : + delete_end.ToTimeT(); + + // Remove Autofill profiles in the time range. + sql::Statement s_profiles(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles " + "WHERE date_modified >= ? AND date_modified < ?")); + if (!s_profiles) { + NOTREACHED() << "Autofill profiles statement prepare failed"; + return false; + } + + s_profiles.BindInt64(0, delete_begin_t); + s_profiles.BindInt64(1, delete_end_t); + s_profiles.Run(); + + if (!s_profiles.Succeeded()) { + NOTREACHED(); + return false; + } + + // Remove Autofill profiles in the time range. + sql::Statement s_credit_cards(db_->GetUniqueStatement( + "DELETE FROM credit_cards " + "WHERE date_modified >= ? AND date_modified < ?")); + if (!s_credit_cards) { + NOTREACHED() << "Autofill credit cards statement prepare failed"; + return false; + } + + s_credit_cards.BindInt64(0, delete_begin_t); + s_credit_cards.BindInt64(1, delete_end_t); + s_credit_cards.Run(); + + if (!s_credit_cards.Succeeded()) { + NOTREACHED(); + return false; + } + + return true; +} + +bool AutofillTable::GetAutofillProfilesInTrash( + std::vector* guids) { + guids->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + while (s.Step()) { + std::string guid = s.ColumnString(0); + guids->push_back(guid); + } + + return s.Succeeded(); +} + +bool AutofillTable::EmptyAutofillProfilesTrash() { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles_trash")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + return s.Run(); +} + + +bool AutofillTable::RemoveFormElementForID(int64 pair_id) { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill WHERE pair_id = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + s.BindInt64(0, pair_id); + if (s.Run()) { + return RemoveFormElementForTimeRange(pair_id, base::Time(), base::Time(), + NULL); + } + return false; +} + +bool AutofillTable::AddAutofillGUIDToTrash(const std::string& guid) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles_trash" + " (guid) " + "VALUES (?)")); + if (!s) { + NOTREACHED(); + return sql::INIT_FAILURE; + } + + s.BindString(0, guid); + if (!s.Run()) { + NOTREACHED(); + return false; + } + return true; +} + +bool AutofillTable::IsAutofillProfilesTrashEmpty() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + return !s.Step(); +} + +bool AutofillTable::IsAutofillGUIDInTrash(const std::string& guid) { + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash " + "WHERE guid = ?")); + if (!s) { + NOTREACHED() << "Statement prepare failed"; + return false; + } + + s.BindString(0, guid); + return s.Step(); +} + +bool AutofillTable::InitMainTable() { + if (!db_->DoesTableExist("autofill")) { + if (!db_->Execute("CREATE TABLE autofill (" + "name VARCHAR, " + "value VARCHAR, " + "value_lower VARCHAR, " + "pair_id INTEGER PRIMARY KEY, " + "count INTEGER DEFAULT 1)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_name ON autofill (name)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_name_value_lower ON " + "autofill (name, value_lower)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitCreditCardsTable() { + if (!db_->DoesTableExist("credit_cards")) { + if (!db_->Execute("CREATE TABLE credit_cards ( " + "guid VARCHAR PRIMARY KEY, " + "name_on_card VARCHAR, " + "expiration_month INTEGER, " + "expiration_year INTEGER, " + "card_number_encrypted BLOB, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + NOTREACHED(); + return false; + } + } + + return true; +} + +bool AutofillTable::InitDatesTable() { + if (!db_->DoesTableExist("autofill_dates")) { + if (!db_->Execute("CREATE TABLE autofill_dates ( " + "pair_id INTEGER DEFAULT 0, " + "date_created INTEGER DEFAULT 0)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_dates_pair_id ON " + "autofill_dates (pair_id)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfilesTable() { + if (!db_->DoesTableExist("autofill_profiles")) { + if (!db_->Execute("CREATE TABLE autofill_profiles ( " + "guid VARCHAR PRIMARY KEY, " + "company_name VARCHAR, " + "address_line_1 VARCHAR, " + "address_line_2 VARCHAR, " + "city VARCHAR, " + "state VARCHAR, " + "zipcode VARCHAR, " + "country VARCHAR, " + "country_code VARCHAR, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileNamesTable() { + if (!db_->DoesTableExist("autofill_profile_names")) { + if (!db_->Execute("CREATE TABLE autofill_profile_names ( " + "guid VARCHAR, " + "first_name VARCHAR, " + "middle_name VARCHAR, " + "last_name VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileEmailsTable() { + if (!db_->DoesTableExist("autofill_profile_emails")) { + if (!db_->Execute("CREATE TABLE autofill_profile_emails ( " + "guid VARCHAR, " + "email VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfilePhonesTable() { + if (!db_->DoesTableExist("autofill_profile_phones")) { + if (!db_->Execute("CREATE TABLE autofill_profile_phones ( " + "guid VARCHAR, " + "type INTEGER DEFAULT 0, " + "number VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileTrashTable() { + if (!db_->DoesTableExist("autofill_profiles_trash")) { + if (!db_->Execute("CREATE TABLE autofill_profiles_trash ( " + "guid VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} diff --git a/chrome/browser/webdata/autofill_table.h b/chrome/browser/webdata/autofill_table.h new file mode 100644 index 0000000..584b949 --- /dev/null +++ b/chrome/browser/webdata/autofill_table.h @@ -0,0 +1,315 @@ +// 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. + +#ifndef CHROME_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ +#define CHROME_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ +#pragma once + +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "chrome/browser/webdata/web_database_table.h" + +#include + +class AutofillChange; +class AutofillEntry; +class AutofillProfile; +class AutofillTableTest; +class CreditCard; + +namespace webkit_glue { +struct FormField; +} + +// This class manages the various autofill tables within the SQLite database +// passed to the constructor. It expects the following schemas: +// +// Note: The database stores time in seconds, UTC. +// +// autofill +// name The name of the input as specified in the html. +// value The literal contents of the text field. +// value_lower The contents of the text field made lower_case. +// pair_id An ID number unique to the row in the table. +// count How many times the user has entered the string |value| +// in a field of name |name|. +// +// autofill_dates This table associates a row to each separate time the +// user submits a form containing a certain name/value +// pair. The |pair_id| should match the |pair_id| field +// in the appropriate row of the autofill table. +// pair_id +// date_created +// +// autofill_profiles This table contains Autofill profile data added by the +// user with the Autofill dialog. Most of the columns are +// standard entries in a contact information form. +// +// guid A guid string to uniquely identify the profile. +// Added in version 31. +// company_name +// address_line_1 +// address_line_2 +// city +// state +// zipcode +// country The country name. Deprecated, should be removed once +// the stable channel reaches version 11. +// country_code +// date_modified The date on which this profile was last modified. +// Added in version 30. +// +// autofill_profile_names +// This table contains the multi-valued name fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the name belongs. +// first_name +// middle_name +// last_name +// +// autofill_profile_emails +// This table contains the multi-valued email fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the email belongs. +// email +// +// autofill_profile_phones +// This table contains the multi-valued phone fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the phone or fax number belongs. +// type An integer constant designating either phone or fax type +// of the number. +// number +// +// autofill_profiles_trash +// This table contains guids of "trashed" autofill +// profiles. When a profile is removed its guid is added +// to this table so that Sync can perform deferred removal. +// +// guid The guid string that identifies the trashed profile. +// +// credit_cards This table contains credit card data added by the user +// with the Autofill dialog. Most of the columns are +// standard entries in a credit card form. +// +// guid A guid string to uniquely identify the profile. +// Added in version 31. +// name_on_card +// expiration_month +// expiration_year +// card_number_encrypted Stores encrypted credit card number. +// date_modified The date on which this entry was last modified. +// Added in version 30. +// +class AutofillTable : public WebDatabaseTable { + public: + AutofillTable(sql::Connection* db, sql::MetaTable* meta_table) + : WebDatabaseTable(db, meta_table) {} + virtual ~AutofillTable() {} + virtual bool Init(); + virtual bool IsSyncable() { return true; } + + // Records the form elements in |elements| in the database in the + // autofill table. A list of all added and updated autofill entries + // is returned in the changes out parameter. + bool AddFormFieldValues(const std::vector& elements, + std::vector* changes); + + // Records a single form element in the database in the autofill table. A list + // of all added and updated autofill entries is returned in the changes out + // parameter. + bool AddFormFieldValue(const webkit_glue::FormField& element, + std::vector* changes); + + // Retrieves a vector of all values which have been recorded in the autofill + // table as the value in a form element with name |name| and which start with + // |prefix|. The comparison of the prefix is case insensitive. + bool GetFormValuesForElementName(const string16& name, + const string16& prefix, + std::vector* values, + int limit); + + // Removes rows from autofill_dates if they were created on or after + // |delete_begin| and strictly before |delete_end|. Decrements the + // count of the corresponding rows in the autofill table, and + // removes those rows if the count goes to 0. A list of all changed + // keys and whether each was updater or removed is returned in the + // changes out parameter. + bool RemoveFormElementsAddedBetween(base::Time delete_begin, + base::Time delete_end, + std::vector* changes); + + // Removes from autofill_dates rows with given pair_id where date_created lies + // between delte_begin and delte_end. + bool RemoveFormElementForTimeRange(int64 pair_id, + base::Time delete_begin, + base::Time delete_end, + int* how_many); + + // Increments the count in the row corresponding to |pair_id| by + // |delta|. Removes the row from the table and sets the + // |was_removed| out parameter to true if the count becomes 0. + bool AddToCountOfFormElement(int64 pair_id, int delta, bool* was_removed); + + // Gets the pair_id and count entries from name and value specified in + // |element|. Sets *pair_id and *count to 0 if there is no such row in + // the table. + bool GetIDAndCountOfFormElement(const webkit_glue::FormField& element, + int64* pair_id, + int* count); + + // Gets the count only given the pair_id. + bool GetCountOfFormElement(int64 pair_id, int* count); + + // Updates the count entry in the row corresponding to |pair_id| to |count|. + bool SetCountOfFormElement(int64 pair_id, int count); + + // Adds a new row to the autofill table with name and value given in + // |element|. Sets *pair_id to the pair_id of the new row. + bool InsertFormElement(const webkit_glue::FormField& element, int64* pair_id); + + // Adds a new row to the autofill_dates table. + bool InsertPairIDAndDate(int64 pair_id, base::Time date_created); + + // Removes row from the autofill tables given |pair_id|. + bool RemoveFormElementForID(int64 pair_id); + + // Removes row from the autofill tables for the given |name| |value| pair. + virtual bool RemoveFormElement(const string16& name, const string16& value); + + // Retrieves all of the entries in the autofill table. + virtual bool GetAllAutofillEntries(std::vector* entries); + + // Retrieves a single entry from the autofill table. + virtual bool GetAutofillTimestamps(const string16& name, + const string16& value, + std::vector* timestamps); + + // Replaces existing autofill entries with the entries supplied in + // the argument. If the entry does not already exist, it will be + // added. + virtual bool UpdateAutofillEntries(const std::vector& entries); + + // Records a single Autofill profile in the autofill_profiles table. + virtual bool AddAutofillProfile(const AutofillProfile& profile); + + // Updates the database values for the specified profile. + // DEPRECATED: Use |UpdateAutofillProfileMulti| instead. + virtual bool UpdateAutofillProfile(const AutofillProfile& profile); + + // Updates the database values for the specified profile. Mulit-value aware. + virtual bool UpdateAutofillProfileMulti(const AutofillProfile& profile); + + // Removes a row from the autofill_profiles table. |guid| is the identifier + // of the profile to remove. + virtual bool RemoveAutofillProfile(const std::string& guid); + + // Retrieves a profile with guid |guid|. The caller owns |profile|. + bool GetAutofillProfile(const std::string& guid, AutofillProfile** profile); + + // Retrieves all profiles in the database. Caller owns the returned profiles. + virtual bool GetAutofillProfiles(std::vector* profiles); + + // Records a single credit card in the credit_cards table. + bool AddCreditCard(const CreditCard& credit_card); + + // Updates the database values for the specified credit card. + bool UpdateCreditCard(const CreditCard& credit_card); + + // Removes a row from the credit_cards table. |guid| is the identifer of the + // credit card to remove. + bool RemoveCreditCard(const std::string& guid); + + // Retrieves a credit card with guid |guid|. The caller owns + // |credit_card_id|. + bool GetCreditCard(const std::string& guid, CreditCard** credit_card); + + // Retrieves all credit cards in the database. Caller owns the returned + // credit cards. + virtual bool GetCreditCards(std::vector* credit_cards); + + // Removes rows from autofill_profiles and credit_cards if they were created + // on or after |delete_begin| and strictly before |delete_end|. + bool RemoveAutofillProfilesAndCreditCardsModifiedBetween( + base::Time delete_begin, + base::Time delete_end); + + // Retrieves all profiles in the database that have been deleted since last + // "empty" of the trash. + bool GetAutofillProfilesInTrash(std::vector* guids); + + // Empties the Autofill profiles "trash can". + bool EmptyAutofillProfilesTrash(); + + // Removes empty values for autofill that were incorrectly stored in the DB + // See bug http://crbug.com/6111 + bool ClearAutofillEmptyValueElements(); + + // Retrieves all profiles in the database that have been deleted since last + // "empty" of the trash. + bool AddAutofillGUIDToTrash(const std::string& guid); + + // Clear all profiles. + bool ClearAutofillProfiles(); + + private: + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddChanges); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_RemoveBetweenChanges); + + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_UpdateDontReplace); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddFormFieldValues); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfile); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, UpdateAutofillProfile); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfileTrash); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfileTrashInteraction); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + RemoveAutofillProfilesAndCreditCardsModifiedBetween); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, CreditCard); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, UpdateCreditCard); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_OneResult); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_TwoDistinct); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_TwoSame); + + // Methods for adding autofill entries at a specified time. For + // testing only. + bool AddFormFieldValuesTime( + const std::vector& elements, + std::vector* changes, + base::Time time); + bool AddFormFieldValueTime(const webkit_glue::FormField& element, + std::vector* changes, + base::Time time); + + // Insert a single AutofillEntry into the autofill/autofill_dates tables. + bool InsertAutofillEntry(const AutofillEntry& entry); + + // Checks if the trash is empty. + bool IsAutofillProfilesTrashEmpty(); + + // Checks if the guid is in the trash. + bool IsAutofillGUIDInTrash(const std::string& guid); + + bool InitMainTable(); + bool InitCreditCardsTable(); + bool InitDatesTable(); + bool InitProfilesTable(); + bool InitProfileNamesTable(); + bool InitProfileEmailsTable(); + bool InitProfilePhonesTable(); + bool InitProfileTrashTable(); + + DISALLOW_COPY_AND_ASSIGN(AutofillTable); +}; + +#endif // CHROME_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ diff --git a/chrome/browser/webdata/autofill_table_unittest.cc b/chrome/browser/webdata/autofill_table_unittest.cc new file mode 100644 index 0000000..22d2ad5 --- /dev/null +++ b/chrome/browser/webdata/autofill_table_unittest.cc @@ -0,0 +1,1578 @@ +// 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 + +#include "app/sql/statement.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/autofill/autofill_type.h" +#include "chrome/browser/autofill/credit_card.h" +#include "chrome/browser/password_manager/encryptor.h" +#include "chrome/browser/webdata/autofill_change.h" +#include "chrome/browser/webdata/autofill_entry.h" +#include "chrome/browser/webdata/web_database.h" +#include "chrome/common/guid.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/form_field.h" + +using base::Time; +using base::TimeDelta; +using webkit_glue::FormField; + +// So we can compare AutofillKeys with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillKey& key) { + return os << UTF16ToASCII(key.name()) << ", " << UTF16ToASCII(key.value()); +} + +// So we can compare AutofillChanges with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillChange& change) { + switch (change.type()) { + case AutofillChange::ADD: { + os << "ADD"; + break; + } + case AutofillChange::UPDATE: { + os << "UPDATE"; + break; + } + case AutofillChange::REMOVE: { + os << "REMOVE"; + break; + } + } + return os << " " << change.key(); +} + +namespace { + +bool CompareAutofillEntries(const AutofillEntry& a, const AutofillEntry& b) { + std::set