Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 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 "components/password_manager/core/browser/login_database.h"
      6 
      7 #include <algorithm>
      8 #include <limits>
      9 
     10 #include "base/bind.h"
     11 #include "base/files/file_path.h"
     12 #include "base/logging.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/pickle.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/time/time.h"
     17 #include "components/autofill/core/common/password_form.h"
     18 #include "google_apis/gaia/gaia_auth_util.h"
     19 #include "google_apis/gaia/gaia_urls.h"
     20 #include "sql/connection.h"
     21 #include "sql/statement.h"
     22 #include "sql/transaction.h"
     23 
     24 using autofill::PasswordForm;
     25 
     26 namespace password_manager {
     27 
     28 static const int kCurrentVersionNumber = 7;
     29 static const int kCompatibleVersionNumber = 1;
     30 
     31 Pickle SerializeVector(const std::vector<base::string16>& vec) {
     32   Pickle p;
     33   for (size_t i = 0; i < vec.size(); ++i) {
     34     p.WriteString16(vec[i]);
     35   }
     36   return p;
     37 }
     38 
     39 std::vector<base::string16> DeserializeVector(const Pickle& p) {
     40   std::vector<base::string16> ret;
     41   base::string16 str;
     42 
     43   PickleIterator iterator(p);
     44   while (iterator.ReadString16(&str)) {
     45     ret.push_back(str);
     46   }
     47   return ret;
     48 }
     49 
     50 namespace {
     51 
     52 // Convenience enum for interacting with SQL queries that use all the columns.
     53 enum LoginTableColumns {
     54   COLUMN_ORIGIN_URL = 0,
     55   COLUMN_ACTION_URL,
     56   COLUMN_USERNAME_ELEMENT,
     57   COLUMN_USERNAME_VALUE,
     58   COLUMN_PASSWORD_ELEMENT,
     59   COLUMN_PASSWORD_VALUE,
     60   COLUMN_SUBMIT_ELEMENT,
     61   COLUMN_SIGNON_REALM,
     62   COLUMN_SSL_VALID,
     63   COLUMN_PREFERRED,
     64   COLUMN_DATE_CREATED,
     65   COLUMN_BLACKLISTED_BY_USER,
     66   COLUMN_SCHEME,
     67   COLUMN_PASSWORD_TYPE,
     68   COLUMN_POSSIBLE_USERNAMES,
     69   COLUMN_TIMES_USED,
     70   COLUMN_FORM_DATA,
     71   COLUMN_USE_ADDITIONAL_AUTH,
     72   COLUMN_DATE_SYNCED,
     73   COLUMN_DISPLAY_NAME,
     74   COLUMN_AVATAR_URL,
     75   COLUMN_FEDERATION_URL,
     76   COLUMN_IS_ZERO_CLICK,
     77 };
     78 
     79 void BindAddStatement(const PasswordForm& form,
     80                       const std::string& encrypted_password,
     81                       sql::Statement* s) {
     82   s->BindString(COLUMN_ORIGIN_URL, form.origin.spec());
     83   s->BindString(COLUMN_ACTION_URL, form.action.spec());
     84   s->BindString16(COLUMN_USERNAME_ELEMENT, form.username_element);
     85   s->BindString16(COLUMN_USERNAME_VALUE, form.username_value);
     86   s->BindString16(COLUMN_PASSWORD_ELEMENT, form.password_element);
     87   s->BindBlob(COLUMN_PASSWORD_VALUE, encrypted_password.data(),
     88               static_cast<int>(encrypted_password.length()));
     89   s->BindString16(COLUMN_SUBMIT_ELEMENT, form.submit_element);
     90   s->BindString(COLUMN_SIGNON_REALM, form.signon_realm);
     91   s->BindInt(COLUMN_SSL_VALID, form.ssl_valid);
     92   s->BindInt(COLUMN_PREFERRED, form.preferred);
     93   s->BindInt64(COLUMN_DATE_CREATED, form.date_created.ToTimeT());
     94   s->BindInt(COLUMN_BLACKLISTED_BY_USER, form.blacklisted_by_user);
     95   s->BindInt(COLUMN_SCHEME, form.scheme);
     96   s->BindInt(COLUMN_PASSWORD_TYPE, form.type);
     97   Pickle usernames_pickle = SerializeVector(form.other_possible_usernames);
     98   s->BindBlob(COLUMN_POSSIBLE_USERNAMES,
     99               usernames_pickle.data(),
    100               usernames_pickle.size());
    101   s->BindInt(COLUMN_TIMES_USED, form.times_used);
    102   Pickle form_data_pickle;
    103   autofill::SerializeFormData(form.form_data, &form_data_pickle);
    104   s->BindBlob(COLUMN_FORM_DATA,
    105               form_data_pickle.data(),
    106               form_data_pickle.size());
    107   s->BindInt(COLUMN_USE_ADDITIONAL_AUTH, form.use_additional_authentication);
    108   s->BindInt64(COLUMN_DATE_SYNCED, form.date_synced.ToInternalValue());
    109   s->BindString16(COLUMN_DISPLAY_NAME, form.display_name);
    110   s->BindString(COLUMN_AVATAR_URL, form.avatar_url.spec());
    111   s->BindString(COLUMN_FEDERATION_URL, form.federation_url.spec());
    112   s->BindInt(COLUMN_IS_ZERO_CLICK, form.is_zero_click);
    113 }
    114 
    115 void AddCallback(int err, sql::Statement* /*stmt*/) {
    116   if (err == 19 /*SQLITE_CONSTRAINT*/)
    117     DLOG(WARNING) << "LoginDatabase::AddLogin updated an existing form";
    118 }
    119 
    120 }  // namespace
    121 
    122 LoginDatabase::LoginDatabase() {
    123 }
    124 
    125 LoginDatabase::~LoginDatabase() {
    126 }
    127 
    128 bool LoginDatabase::Init(const base::FilePath& db_path) {
    129   // Set pragmas for a small, private database (based on WebDatabase).
    130   db_.set_page_size(2048);
    131   db_.set_cache_size(32);
    132   db_.set_exclusive_locking();
    133   db_.set_restrict_to_user();
    134 
    135   if (!db_.Open(db_path)) {
    136     LOG(WARNING) << "Unable to open the password store database.";
    137     return false;
    138   }
    139 
    140   sql::Transaction transaction(&db_);
    141   transaction.Begin();
    142 
    143   // Check the database version.
    144   if (!meta_table_.Init(&db_, kCurrentVersionNumber,
    145                         kCompatibleVersionNumber)) {
    146     db_.Close();
    147     return false;
    148   }
    149   if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
    150     LOG(WARNING) << "Password store database is too new.";
    151     db_.Close();
    152     return false;
    153   }
    154 
    155   // Initialize the tables.
    156   if (!InitLoginsTable()) {
    157     LOG(WARNING) << "Unable to initialize the password store database.";
    158     db_.Close();
    159     return false;
    160   }
    161 
    162   // Save the path for DeleteDatabaseFile().
    163   db_path_ = db_path;
    164 
    165   // If the file on disk is an older database version, bring it up to date.
    166   if (!MigrateOldVersionsAsNeeded()) {
    167     LOG(WARNING) << "Unable to migrate database";
    168     db_.Close();
    169     return false;
    170   }
    171 
    172   if (!transaction.Commit()) {
    173     db_.Close();
    174     return false;
    175   }
    176 
    177   return true;
    178 }
    179 
    180 bool LoginDatabase::MigrateOldVersionsAsNeeded() {
    181   switch (meta_table_.GetVersionNumber()) {
    182     case 1:
    183       if (!db_.Execute("ALTER TABLE logins "
    184                        "ADD COLUMN password_type INTEGER") ||
    185           !db_.Execute("ALTER TABLE logins "
    186                        "ADD COLUMN possible_usernames BLOB")) {
    187         return false;
    188       }
    189       meta_table_.SetVersionNumber(2);
    190       // Fall through.
    191     case 2:
    192       if (!db_.Execute("ALTER TABLE logins ADD COLUMN times_used INTEGER")) {
    193         return false;
    194       }
    195       meta_table_.SetVersionNumber(3);
    196       // Fall through.
    197     case 3:
    198       // We need to check if the column exists because of
    199       // https://crbug.com/295851
    200       if (!db_.DoesColumnExist("logins", "form_data") &&
    201           !db_.Execute("ALTER TABLE logins ADD COLUMN form_data BLOB")) {
    202         return false;
    203       }
    204       meta_table_.SetVersionNumber(4);
    205       // Fall through.
    206     case 4:
    207       if (!db_.Execute(
    208           "ALTER TABLE logins ADD COLUMN use_additional_auth INTEGER")) {
    209         return false;
    210       }
    211       meta_table_.SetVersionNumber(5);
    212       // Fall through.
    213     case 5:
    214       if (!db_.Execute("ALTER TABLE logins ADD COLUMN date_synced INTEGER")) {
    215         return false;
    216       }
    217       meta_table_.SetVersionNumber(6);
    218       // Fall through.
    219     case 6:
    220       if (!db_.Execute("ALTER TABLE logins ADD COLUMN display_name VARCHAR") ||
    221           !db_.Execute("ALTER TABLE logins ADD COLUMN avatar_url VARCHAR") ||
    222           !db_.Execute("ALTER TABLE logins "
    223                        "ADD COLUMN federation_url VARCHAR") ||
    224           !db_.Execute("ALTER TABLE logins ADD COLUMN is_zero_click INTEGER")) {
    225         return false;
    226       }
    227       meta_table_.SetVersionNumber(7);
    228       // Fall through.
    229     case kCurrentVersionNumber:
    230       // Already up to date
    231       return true;
    232     default:
    233       NOTREACHED();
    234       return false;
    235   }
    236 }
    237 
    238 bool LoginDatabase::InitLoginsTable() {
    239   if (!db_.DoesTableExist("logins")) {
    240     if (!db_.Execute("CREATE TABLE logins ("
    241                      "origin_url VARCHAR NOT NULL, "
    242                      "action_url VARCHAR, "
    243                      "username_element VARCHAR, "
    244                      "username_value VARCHAR, "
    245                      "password_element VARCHAR, "
    246                      "password_value BLOB, "
    247                      "submit_element VARCHAR, "
    248                      "signon_realm VARCHAR NOT NULL,"
    249                      "ssl_valid INTEGER NOT NULL,"
    250                      "preferred INTEGER NOT NULL,"
    251                      "date_created INTEGER NOT NULL,"
    252                      "blacklisted_by_user INTEGER NOT NULL,"
    253                      "scheme INTEGER NOT NULL,"
    254                      "password_type INTEGER,"
    255                      "possible_usernames BLOB,"
    256                      "times_used INTEGER,"
    257                      "form_data BLOB,"
    258                      "use_additional_auth INTEGER,"
    259                      "date_synced INTEGER,"
    260                      "display_name VARCHAR,"
    261                      "avatar_url VARCHAR,"
    262                      "federation_url VARCHAR,"
    263                      "is_zero_click INTEGER,"
    264                      "UNIQUE "
    265                      "(origin_url, username_element, "
    266                      "username_value, password_element, "
    267                      "submit_element, signon_realm))")) {
    268       NOTREACHED();
    269       return false;
    270     }
    271     if (!db_.Execute("CREATE INDEX logins_signon ON "
    272                      "logins (signon_realm)")) {
    273       NOTREACHED();
    274       return false;
    275     }
    276   }
    277   return true;
    278 }
    279 
    280 void LoginDatabase::ReportMetrics(const std::string& sync_username) {
    281   sql::Statement s(db_.GetCachedStatement(
    282       SQL_FROM_HERE,
    283       "SELECT signon_realm, blacklisted_by_user, COUNT(username_value) "
    284       "FROM logins GROUP BY signon_realm, blacklisted_by_user"));
    285 
    286   if (!s.is_valid())
    287     return;
    288 
    289   int total_accounts = 0;
    290   int blacklisted_sites = 0;
    291   while (s.Step()) {
    292     int blacklisted = s.ColumnInt(1);
    293     int accounts_per_site = s.ColumnInt(2);
    294     if (blacklisted) {
    295       ++blacklisted_sites;
    296     } else {
    297       total_accounts += accounts_per_site;
    298       UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.AccountsPerSite",
    299                                   accounts_per_site, 0, 32, 6);
    300     }
    301   }
    302   UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.TotalAccounts",
    303                               total_accounts, 0, 32, 6);
    304   UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.BlacklistedSites",
    305                               blacklisted_sites, 0, 32, 6);
    306 
    307   sql::Statement usage_statement(db_.GetCachedStatement(
    308       SQL_FROM_HERE,
    309       "SELECT password_type, times_used FROM logins"));
    310 
    311   if (!usage_statement.is_valid())
    312     return;
    313 
    314   while (usage_statement.Step()) {
    315     PasswordForm::Type type = static_cast<PasswordForm::Type>(
    316         usage_statement.ColumnInt(0));
    317 
    318     if (type == PasswordForm::TYPE_GENERATED) {
    319       UMA_HISTOGRAM_CUSTOM_COUNTS(
    320           "PasswordManager.TimesGeneratedPasswordUsed",
    321           usage_statement.ColumnInt(1), 0, 100, 10);
    322     } else {
    323       UMA_HISTOGRAM_CUSTOM_COUNTS(
    324           "PasswordManager.TimesPasswordUsed",
    325           usage_statement.ColumnInt(1), 0, 100, 10);
    326     }
    327   }
    328 
    329   bool syncing_account_saved = false;
    330   if (!sync_username.empty()) {
    331     sql::Statement sync_statement(db_.GetCachedStatement(
    332         SQL_FROM_HERE,
    333         "SELECT username_value FROM logins "
    334         "WHERE signon_realm == ?"));
    335     sync_statement.BindString(
    336         0, GaiaUrls::GetInstance()->gaia_url().GetOrigin().spec());
    337 
    338     if (!sync_statement.is_valid())
    339       return;
    340 
    341     while (sync_statement.Step()) {
    342       std::string username = sync_statement.ColumnString(0);
    343       if (gaia::AreEmailsSame(sync_username, username)) {
    344         syncing_account_saved = true;
    345         break;
    346       }
    347     }
    348   }
    349   UMA_HISTOGRAM_ENUMERATION("PasswordManager.SyncingAccountState",
    350                             2 * sync_username.empty() + syncing_account_saved,
    351                             4);
    352 }
    353 
    354 PasswordStoreChangeList LoginDatabase::AddLogin(const PasswordForm& form) {
    355   PasswordStoreChangeList list;
    356   std::string encrypted_password;
    357   if (EncryptedString(form.password_value, &encrypted_password) !=
    358           ENCRYPTION_RESULT_SUCCESS)
    359     return list;
    360 
    361   // You *must* change LoginTableColumns if this query changes.
    362   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    363       "INSERT INTO logins "
    364       "(origin_url, action_url, username_element, username_value, "
    365       " password_element, password_value, submit_element, "
    366       " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
    367       " scheme, password_type, possible_usernames, times_used, form_data, "
    368       " use_additional_auth, date_synced, display_name, avatar_url,"
    369       " federation_url, is_zero_click) VALUES "
    370       "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    371   BindAddStatement(form, encrypted_password, &s);
    372   db_.set_error_callback(base::Bind(&AddCallback));
    373   const bool success = s.Run();
    374   db_.reset_error_callback();
    375   if (success) {
    376     list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
    377     return list;
    378   }
    379   // Repeat the same statement but with REPLACE semantic.
    380   s.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
    381       "INSERT OR REPLACE INTO logins "
    382       "(origin_url, action_url, username_element, username_value, "
    383       " password_element, password_value, submit_element, "
    384       " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
    385       " scheme, password_type, possible_usernames, times_used, form_data, "
    386       " use_additional_auth, date_synced, display_name, avatar_url,"
    387       " federation_url, is_zero_click) VALUES "
    388       "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    389   BindAddStatement(form, encrypted_password, &s);
    390   if (s.Run()) {
    391     list.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form));
    392     list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
    393   }
    394   return list;
    395 }
    396 
    397 PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form) {
    398   std::string encrypted_password;
    399   if (EncryptedString(form.password_value, &encrypted_password) !=
    400           ENCRYPTION_RESULT_SUCCESS)
    401     return PasswordStoreChangeList();
    402 
    403   // Replacement is necessary to deal with updating imported credentials. See
    404   // crbug.com/349138 for details.
    405   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    406                                           "UPDATE OR REPLACE logins SET "
    407                                           "action_url = ?, "
    408                                           "password_value = ?, "
    409                                           "ssl_valid = ?, "
    410                                           "preferred = ?, "
    411                                           "possible_usernames = ?, "
    412                                           "times_used = ?, "
    413                                           "submit_element = ?, "
    414                                           "date_synced = ?, "
    415                                           "date_created = ?, "
    416                                           "blacklisted_by_user = ?, "
    417                                           "scheme = ?, "
    418                                           "password_type = ?, "
    419                                           "display_name = ?, "
    420                                           "avatar_url = ?, "
    421                                           "federation_url = ?, "
    422                                           "is_zero_click = ? "
    423                                           "WHERE origin_url = ? AND "
    424                                           "username_element = ? AND "
    425                                           "username_value = ? AND "
    426                                           "password_element = ? AND "
    427                                           "signon_realm = ?"));
    428   s.BindString(0, form.action.spec());
    429   s.BindBlob(1, encrypted_password.data(),
    430              static_cast<int>(encrypted_password.length()));
    431   s.BindInt(2, form.ssl_valid);
    432   s.BindInt(3, form.preferred);
    433   Pickle pickle = SerializeVector(form.other_possible_usernames);
    434   s.BindBlob(4, pickle.data(), pickle.size());
    435   s.BindInt(5, form.times_used);
    436   s.BindString16(6, form.submit_element);
    437   s.BindInt64(7, form.date_synced.ToInternalValue());
    438   s.BindInt64(8, form.date_created.ToTimeT());
    439   s.BindInt(9, form.blacklisted_by_user);
    440   s.BindInt(10, form.scheme);
    441   s.BindInt(11, form.type);
    442   s.BindString16(12, form.display_name);
    443   s.BindString(13, form.avatar_url.spec());
    444   s.BindString(14, form.federation_url.spec());
    445   s.BindInt(15, form.is_zero_click);
    446 
    447   // WHERE starts here.
    448   s.BindString(16, form.origin.spec());
    449   s.BindString16(17, form.username_element);
    450   s.BindString16(18, form.username_value);
    451   s.BindString16(19, form.password_element);
    452   s.BindString(20, form.signon_realm);
    453 
    454   if (!s.Run())
    455     return PasswordStoreChangeList();
    456 
    457   PasswordStoreChangeList list;
    458   if (db_.GetLastChangeCount())
    459     list.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form));
    460 
    461   return list;
    462 }
    463 
    464 bool LoginDatabase::RemoveLogin(const PasswordForm& form) {
    465   // Remove a login by UNIQUE-constrained fields.
    466   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    467       "DELETE FROM logins WHERE "
    468       "origin_url = ? AND "
    469       "username_element = ? AND "
    470       "username_value = ? AND "
    471       "password_element = ? AND "
    472       "submit_element = ? AND "
    473       "signon_realm = ? "));
    474   s.BindString(0, form.origin.spec());
    475   s.BindString16(1, form.username_element);
    476   s.BindString16(2, form.username_value);
    477   s.BindString16(3, form.password_element);
    478   s.BindString16(4, form.submit_element);
    479   s.BindString(5, form.signon_realm);
    480 
    481   return s.Run();
    482 }
    483 
    484 bool LoginDatabase::RemoveLoginsCreatedBetween(base::Time delete_begin,
    485                                                base::Time delete_end) {
    486   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    487       "DELETE FROM logins WHERE "
    488       "date_created >= ? AND date_created < ?"));
    489   s.BindInt64(0, delete_begin.ToTimeT());
    490   s.BindInt64(1, delete_end.is_null() ? std::numeric_limits<int64>::max()
    491                                       : delete_end.ToTimeT());
    492 
    493   return s.Run();
    494 }
    495 
    496 bool LoginDatabase::RemoveLoginsSyncedBetween(base::Time delete_begin,
    497                                               base::Time delete_end) {
    498   sql::Statement s(db_.GetCachedStatement(
    499       SQL_FROM_HERE,
    500       "DELETE FROM logins WHERE date_synced >= ? AND date_synced < ?"));
    501   s.BindInt64(0, delete_begin.ToInternalValue());
    502   s.BindInt64(1,
    503               delete_end.is_null() ? base::Time::Max().ToInternalValue()
    504                                    : delete_end.ToInternalValue());
    505 
    506   return s.Run();
    507 }
    508 
    509 LoginDatabase::EncryptionResult LoginDatabase::InitPasswordFormFromStatement(
    510     PasswordForm* form,
    511     sql::Statement& s) const {
    512   std::string encrypted_password;
    513   s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password);
    514   base::string16 decrypted_password;
    515   EncryptionResult encryption_result =
    516       DecryptedString(encrypted_password, &decrypted_password);
    517   if (encryption_result != ENCRYPTION_RESULT_SUCCESS)
    518     return encryption_result;
    519 
    520   std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL);
    521   form->origin = GURL(tmp);
    522   tmp = s.ColumnString(COLUMN_ACTION_URL);
    523   form->action = GURL(tmp);
    524   form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT);
    525   form->username_value = s.ColumnString16(COLUMN_USERNAME_VALUE);
    526   form->password_element = s.ColumnString16(COLUMN_PASSWORD_ELEMENT);
    527   form->password_value = decrypted_password;
    528   form->submit_element = s.ColumnString16(COLUMN_SUBMIT_ELEMENT);
    529   tmp = s.ColumnString(COLUMN_SIGNON_REALM);
    530   form->signon_realm = tmp;
    531   form->ssl_valid = (s.ColumnInt(COLUMN_SSL_VALID) > 0);
    532   form->preferred = (s.ColumnInt(COLUMN_PREFERRED) > 0);
    533   form->date_created = base::Time::FromTimeT(
    534       s.ColumnInt64(COLUMN_DATE_CREATED));
    535   form->blacklisted_by_user = (s.ColumnInt(COLUMN_BLACKLISTED_BY_USER) > 0);
    536   int scheme_int = s.ColumnInt(COLUMN_SCHEME);
    537   DCHECK((scheme_int >= 0) && (scheme_int <= PasswordForm::SCHEME_OTHER));
    538   form->scheme = static_cast<PasswordForm::Scheme>(scheme_int);
    539   int type_int = s.ColumnInt(COLUMN_PASSWORD_TYPE);
    540   DCHECK(type_int >= 0 && type_int <= PasswordForm::TYPE_GENERATED);
    541   form->type = static_cast<PasswordForm::Type>(type_int);
    542   if (s.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES)) {
    543     Pickle pickle(
    544         static_cast<const char*>(s.ColumnBlob(COLUMN_POSSIBLE_USERNAMES)),
    545         s.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES));
    546     form->other_possible_usernames = DeserializeVector(pickle);
    547   }
    548   form->times_used = s.ColumnInt(COLUMN_TIMES_USED);
    549   if (s.ColumnByteLength(COLUMN_FORM_DATA)) {
    550     Pickle form_data_pickle(
    551         static_cast<const char*>(s.ColumnBlob(COLUMN_FORM_DATA)),
    552         s.ColumnByteLength(COLUMN_FORM_DATA));
    553     PickleIterator form_data_iter(form_data_pickle);
    554     autofill::DeserializeFormData(&form_data_iter, &form->form_data);
    555   }
    556   form->use_additional_authentication =
    557       (s.ColumnInt(COLUMN_USE_ADDITIONAL_AUTH) > 0);
    558   form->date_synced = base::Time::FromInternalValue(
    559       s.ColumnInt64(COLUMN_DATE_SYNCED));
    560   form->display_name = s.ColumnString16(COLUMN_DISPLAY_NAME);
    561   form->avatar_url = GURL(s.ColumnString(COLUMN_AVATAR_URL));
    562   form->federation_url = GURL(s.ColumnString(COLUMN_FEDERATION_URL));
    563   form->is_zero_click = (s.ColumnInt(COLUMN_IS_ZERO_CLICK) > 0);
    564   return ENCRYPTION_RESULT_SUCCESS;
    565 }
    566 
    567 bool LoginDatabase::GetLogins(const PasswordForm& form,
    568                               std::vector<PasswordForm*>* forms) const {
    569   DCHECK(forms);
    570   // You *must* change LoginTableColumns if this query changes.
    571   const std::string sql_query = "SELECT origin_url, action_url, "
    572       "username_element, username_value, "
    573       "password_element, password_value, submit_element, "
    574       "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
    575       "scheme, password_type, possible_usernames, times_used, form_data, "
    576       "use_additional_auth, date_synced, display_name, avatar_url, "
    577       "federation_url, is_zero_click FROM logins WHERE signon_realm == ? ";
    578   sql::Statement s;
    579   const GURL signon_realm(form.signon_realm);
    580   std::string registered_domain = GetRegistryControlledDomain(signon_realm);
    581   PSLDomainMatchMetric psl_domain_match_metric = PSL_DOMAIN_MATCH_NONE;
    582   const bool should_PSL_matching_apply =
    583       ShouldPSLDomainMatchingApply(registered_domain);
    584   // PSL matching only applies to HTML forms.
    585   if (form.scheme == PasswordForm::SCHEME_HTML && should_PSL_matching_apply) {
    586     // We are extending the original SQL query with one that includes more
    587     // possible matches based on public suffix domain matching. Using a regexp
    588     // here is just an optimization to not have to parse all the stored entries
    589     // in the |logins| table. The result (scheme, domain and port) is verified
    590     // further down using GURL. See the functions SchemeMatches,
    591     // RegistryControlledDomainMatches and PortMatches.
    592     const std::string extended_sql_query =
    593         sql_query + "OR signon_realm REGEXP ? ";
    594     // TODO(nyquist) Re-enable usage of GetCachedStatement when
    595     // http://crbug.com/248608 is fixed.
    596     s.Assign(db_.GetUniqueStatement(extended_sql_query.c_str()));
    597     // We need to escape . in the domain. Since the domain has already been
    598     // sanitized using GURL, we do not need to escape any other characters.
    599     base::ReplaceChars(registered_domain, ".", "\\.", &registered_domain);
    600     std::string scheme = signon_realm.scheme();
    601     // We need to escape . in the scheme. Since the scheme has already been
    602     // sanitized using GURL, we do not need to escape any other characters.
    603     // The scheme soap.beep is an example with '.'.
    604     base::ReplaceChars(scheme, ".", "\\.", &scheme);
    605     const std::string port = signon_realm.port();
    606     // For a signon realm such as http://foo.bar/, this regexp will match
    607     // domains on the form http://foo.bar/, http://www.foo.bar/,
    608     // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/.
    609     // The scheme and port has to be the same as the observed form.
    610     std::string regexp = "^(" + scheme + ":\\/\\/)([\\w-]+\\.)*" +
    611                          registered_domain + "(:" + port + ")?\\/$";
    612     s.BindString(0, form.signon_realm);
    613     s.BindString(1, regexp);
    614   } else {
    615     psl_domain_match_metric = PSL_DOMAIN_MATCH_NOT_USED;
    616     s.Assign(db_.GetCachedStatement(SQL_FROM_HERE, sql_query.c_str()));
    617     s.BindString(0, form.signon_realm);
    618   }
    619 
    620   while (s.Step()) {
    621     scoped_ptr<PasswordForm> new_form(new PasswordForm());
    622     EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
    623     if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
    624       return false;
    625     if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
    626       continue;
    627     DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
    628     if (should_PSL_matching_apply) {
    629       if (!IsPublicSuffixDomainMatch(new_form->signon_realm,
    630                                      form.signon_realm)) {
    631         // The database returned results that should not match. Skipping result.
    632         continue;
    633       }
    634       if (form.signon_realm != new_form->signon_realm) {
    635         // Ignore non-HTML matches.
    636         if (new_form->scheme != PasswordForm::SCHEME_HTML)
    637           continue;
    638 
    639         psl_domain_match_metric = PSL_DOMAIN_MATCH_FOUND;
    640         // This is not a perfect match, so we need to create a new valid result.
    641         // We do this by copying over origin, signon realm and action from the
    642         // observed form and setting the original signon realm to what we found
    643         // in the database. We use the fact that |original_signon_realm| is
    644         // non-empty to communicate that this match was found using public
    645         // suffix matching.
    646         new_form->original_signon_realm = new_form->signon_realm;
    647         new_form->origin = form.origin;
    648         new_form->signon_realm = form.signon_realm;
    649         new_form->action = form.action;
    650       }
    651     }
    652     forms->push_back(new_form.release());
    653   }
    654   UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
    655                             psl_domain_match_metric,
    656                             PSL_DOMAIN_MATCH_COUNT);
    657   return s.Succeeded();
    658 }
    659 
    660 bool LoginDatabase::GetLoginsCreatedBetween(
    661     const base::Time begin,
    662     const base::Time end,
    663     std::vector<autofill::PasswordForm*>* forms) const {
    664   DCHECK(forms);
    665   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    666       "SELECT origin_url, action_url, "
    667       "username_element, username_value, "
    668       "password_element, password_value, submit_element, "
    669       "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
    670       "scheme, password_type, possible_usernames, times_used, form_data, "
    671       "use_additional_auth, date_synced, display_name, avatar_url, "
    672       "federation_url, is_zero_click FROM logins "
    673       "WHERE date_created >= ? AND date_created < ?"
    674       "ORDER BY origin_url"));
    675   s.BindInt64(0, begin.ToTimeT());
    676   s.BindInt64(1, end.is_null() ? std::numeric_limits<int64>::max()
    677                                : end.ToTimeT());
    678 
    679   while (s.Step()) {
    680     scoped_ptr<PasswordForm> new_form(new PasswordForm());
    681     EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
    682     if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
    683       return false;
    684     if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
    685       continue;
    686     DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
    687     forms->push_back(new_form.release());
    688   }
    689   return s.Succeeded();
    690 }
    691 
    692 bool LoginDatabase::GetLoginsSyncedBetween(
    693     const base::Time begin,
    694     const base::Time end,
    695     std::vector<autofill::PasswordForm*>* forms) const {
    696   DCHECK(forms);
    697   sql::Statement s(db_.GetCachedStatement(
    698       SQL_FROM_HERE,
    699       "SELECT origin_url, action_url, username_element, username_value, "
    700       "password_element, password_value, submit_element, signon_realm, "
    701       "ssl_valid, preferred, date_created, blacklisted_by_user, "
    702       "scheme, password_type, possible_usernames, times_used, form_data, "
    703       "use_additional_auth, date_synced, display_name, avatar_url, "
    704       "federation_url, is_zero_click FROM logins "
    705       "WHERE date_synced >= ? AND date_synced < ?"
    706       "ORDER BY origin_url"));
    707   s.BindInt64(0, begin.ToInternalValue());
    708   s.BindInt64(1,
    709               end.is_null() ? base::Time::Max().ToInternalValue()
    710                             : end.ToInternalValue());
    711 
    712   while (s.Step()) {
    713     scoped_ptr<PasswordForm> new_form(new PasswordForm());
    714     EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
    715     if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
    716       return false;
    717     if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
    718       continue;
    719     DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
    720     forms->push_back(new_form.release());
    721   }
    722   return s.Succeeded();
    723 }
    724 
    725 bool LoginDatabase::GetAutofillableLogins(
    726     std::vector<PasswordForm*>* forms) const {
    727   return GetAllLoginsWithBlacklistSetting(false, forms);
    728 }
    729 
    730 bool LoginDatabase::GetBlacklistLogins(
    731     std::vector<PasswordForm*>* forms) const {
    732   return GetAllLoginsWithBlacklistSetting(true, forms);
    733 }
    734 
    735 bool LoginDatabase::GetAllLoginsWithBlacklistSetting(
    736     bool blacklisted, std::vector<PasswordForm*>* forms) const {
    737   DCHECK(forms);
    738   // You *must* change LoginTableColumns if this query changes.
    739   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    740       "SELECT origin_url, action_url, "
    741       "username_element, username_value, "
    742       "password_element, password_value, submit_element, "
    743       "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
    744       "scheme, password_type, possible_usernames, times_used, form_data, "
    745       "use_additional_auth, date_synced, display_name, avatar_url, "
    746       "federation_url, is_zero_click FROM logins "
    747       "WHERE blacklisted_by_user == ? ORDER BY origin_url"));
    748   s.BindInt(0, blacklisted ? 1 : 0);
    749 
    750   while (s.Step()) {
    751     scoped_ptr<PasswordForm> new_form(new PasswordForm());
    752     EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
    753     if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
    754       return false;
    755     if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
    756       continue;
    757     DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
    758     forms->push_back(new_form.release());
    759   }
    760   return s.Succeeded();
    761 }
    762 
    763 bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
    764   DCHECK(db_.is_open());
    765   meta_table_.Reset();
    766   db_.Close();
    767   sql::Connection::Delete(db_path_);
    768   return Init(db_path_);
    769 }
    770 
    771 }  // namespace password_manager
    772