1 // Copyright (c) 2012 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/webdata/common/web_database.h"
6 
7 #include <algorithm>
8 
9 #include "base/logging.h"
10 #include "base/stl_util.h"
11 #include "sql/transaction.h"
12 
13 // Current version number.  Note: when changing the current version number,
14 // corresponding changes must happen in the unit tests, and new migration test
15 // added.  See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
16 // static
17 const int WebDatabase::kCurrentVersionNumber = 90;
18 
19 const int WebDatabase::kDeprecatedVersionNumber = 51;
20 
21 const base::FilePath::CharType WebDatabase::kInMemoryPath[] =
22     FILE_PATH_LITERAL(":memory");
23 
24 namespace {
25 
26 const int kCompatibleVersionNumber = 83;
27 
28 // Change the version number and possibly the compatibility version of
29 // |meta_table_|.
ChangeVersion(sql::MetaTable * meta_table,int version_num,bool update_compatible_version_num)30 void ChangeVersion(sql::MetaTable* meta_table,
31                    int version_num,
32                    bool update_compatible_version_num) {
33   meta_table->SetVersionNumber(version_num);
34   if (update_compatible_version_num) {
35     meta_table->SetCompatibleVersionNumber(
36         std::min(version_num, kCompatibleVersionNumber));
37   }
38 }
39 
40 // Outputs the failed version number as a warning and always returns
41 // |sql::INIT_FAILURE|.
FailedMigrationTo(int version_num)42 sql::InitStatus FailedMigrationTo(int version_num) {
43   LOG(WARNING) << "Unable to update web database to version " << version_num
44                << ".";
45   NOTREACHED();
46   return sql::INIT_FAILURE;
47 }
48 
49 }  // namespace
50 
WebDatabase()51 WebDatabase::WebDatabase()
52     : db_({// Run the database in exclusive mode. Nobody else should be
53            // accessing the database while we're running, and this will give
54            // somewhat improved perf.
55            .exclusive_locking = true,
56            // We don't store that much data in the tables so use a small page
57            // size. This provides a large benefit for empty tables (which is
58            // very likely with the tables we create).
59            .page_size = 2048,
60            // We shouldn't have much data and what access we currently have is
61            // quite infrequent. So we go with a small cache size.
62            .cache_size = 32}) {}
63 
~WebDatabase()64 WebDatabase::~WebDatabase() {}
65 
AddTable(WebDatabaseTable * table)66 void WebDatabase::AddTable(WebDatabaseTable* table) {
67   tables_[table->GetTypeKey()] = table;
68 }
69 
GetTable(WebDatabaseTable::TypeKey key)70 WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
71   return tables_[key];
72 }
73 
BeginTransaction()74 void WebDatabase::BeginTransaction() {
75   db_.BeginTransaction();
76 }
77 
CommitTransaction()78 void WebDatabase::CommitTransaction() {
79   db_.CommitTransaction();
80 }
81 
GetDiagnosticInfo(int extended_error,sql::Statement * statement)82 std::string WebDatabase::GetDiagnosticInfo(int extended_error,
83                                            sql::Statement* statement) {
84   return db_.GetDiagnosticInfo(extended_error, statement);
85 }
86 
GetSQLConnection()87 sql::Database* WebDatabase::GetSQLConnection() {
88   return &db_;
89 }
90 
Init(const base::FilePath & db_name)91 sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) {
92   db_.set_histogram_tag("Web");
93 
94   if ((db_name.value() == kInMemoryPath) ? !db_.OpenInMemory()
95                                          : !db_.Open(db_name)) {
96     return sql::INIT_FAILURE;
97   }
98 
99   // Clobber really old databases.
100   static_assert(kDeprecatedVersionNumber < kCurrentVersionNumber,
101                 "Deprecation version must be less than current");
102   sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber);
103 
104   // Scope initialization in a transaction so we can't be partially
105   // initialized.
106   sql::Transaction transaction(&db_);
107   if (!transaction.Begin())
108     return sql::INIT_FAILURE;
109 
110   // Version check.
111   if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber))
112     return sql::INIT_FAILURE;
113   if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
114     LOG(WARNING) << "Web database is too new.";
115     return sql::INIT_TOO_NEW;
116   }
117 
118   // Initialize the tables.
119   for (auto it = tables_.begin(); it != tables_.end(); ++it) {
120     it->second->Init(&db_, &meta_table_);
121   }
122 
123   // If the file on disk is an older database version, bring it up to date.
124   // If the migration fails we return an error to caller and do not commit
125   // the migration.
126   sql::InitStatus migration_status = MigrateOldVersionsAsNeeded();
127   if (migration_status != sql::INIT_OK)
128     return migration_status;
129 
130   // Create the desired SQL tables if they do not already exist.
131   // It's important that this happen *after* the migration code runs.
132   // Otherwise, the migration code would have to explicitly check for empty
133   // tables created in the new format, and skip the migration in that case.
134   for (auto it = tables_.begin(); it != tables_.end(); ++it) {
135     if (!it->second->CreateTablesIfNecessary()) {
136       LOG(WARNING) << "Unable to initialize the web database.";
137       return sql::INIT_FAILURE;
138     }
139   }
140 
141   return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
142 }
143 
MigrateOldVersionsAsNeeded()144 sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() {
145   // Some malware used to lower the version number, causing migration to
146   // fail. Ensure the version number is at least as high as the compatible
147   // version number.
148   int current_version = std::max(meta_table_.GetVersionNumber(),
149                                  meta_table_.GetCompatibleVersionNumber());
150   if (current_version > meta_table_.GetVersionNumber())
151     ChangeVersion(&meta_table_, current_version, false);
152 
153   DCHECK_GT(current_version, kDeprecatedVersionNumber);
154 
155   for (int next_version = current_version + 1;
156        next_version <= kCurrentVersionNumber; ++next_version) {
157     // Do any database-wide migrations.
158     bool update_compatible_version = false;
159     if (!MigrateToVersion(next_version, &update_compatible_version))
160       return FailedMigrationTo(next_version);
161 
162     ChangeVersion(&meta_table_, next_version, update_compatible_version);
163 
164     // Give each table a chance to migrate to this version.
165     for (auto it = tables_.begin(); it != tables_.end(); ++it) {
166       // Any of the tables may set this to true, but by default it is false.
167       update_compatible_version = false;
168       if (!it->second->MigrateToVersion(next_version,
169                                         &update_compatible_version)) {
170         return FailedMigrationTo(next_version);
171       }
172 
173       ChangeVersion(&meta_table_, next_version, update_compatible_version);
174     }
175   }
176   return sql::INIT_OK;
177 }
178 
MigrateToVersion(int version,bool * update_compatible_version)179 bool WebDatabase::MigrateToVersion(int version,
180                                    bool* update_compatible_version) {
181   // Migrate if necessary.
182   switch (version) {
183     case 58:
184       *update_compatible_version = true;
185       return MigrateToVersion58DropWebAppsAndIntents();
186     case 79:
187       *update_compatible_version = true;
188       return MigrateToVersion79DropLoginsTable();
189   }
190 
191   return true;
192 }
193 
MigrateToVersion58DropWebAppsAndIntents()194 bool WebDatabase::MigrateToVersion58DropWebAppsAndIntents() {
195   sql::Transaction transaction(&db_);
196   return transaction.Begin() && db_.Execute("DROP TABLE IF EXISTS web_apps") &&
197          db_.Execute("DROP TABLE IF EXISTS web_app_icons") &&
198          db_.Execute("DROP TABLE IF EXISTS web_intents") &&
199          db_.Execute("DROP TABLE IF EXISTS web_intents_defaults") &&
200          transaction.Commit();
201 }
202 
MigrateToVersion79DropLoginsTable()203 bool WebDatabase::MigrateToVersion79DropLoginsTable() {
204   sql::Transaction transaction(&db_);
205   return transaction.Begin() &&
206          db_.Execute("DROP TABLE IF EXISTS ie7_logins") &&
207          db_.Execute("DROP TABLE IF EXISTS logins") && transaction.Commit();
208 }
209