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 "content/browser/appcache/appcache_database.h"
6 
7 #include "base/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/stl_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "content/browser/appcache/appcache_backfillers.h"
15 #include "content/browser/appcache/appcache_entry.h"
16 #include "content/browser/appcache/appcache_histograms.h"
17 #include "sql/database.h"
18 #include "sql/error_delegate_util.h"
19 #include "sql/meta_table.h"
20 #include "sql/statement.h"
21 #include "sql/transaction.h"
22 #include "storage/browser/quota/padding_key.h"
23 
24 namespace content {
25 
26 // Schema -------------------------------------------------------------------
27 namespace {
28 
29 // Version number of the database.
30 //
31 // We support migrating the database schema from versions that are at most 2
32 // years old. Older versions are unsupported, and will cause the database to get
33 // nuked.
34 //
35 // Version 0 - 2009-12-28 - https://crrev.com/501033 (unsupported)
36 // Version 1 - 2010-01-20 - https://crrev.com/554008 (unsupported)
37 // Version 2 - 2010-02-23 - https://crrev.com/630009 (unsupported)
38 // Version 3 - 2010-03-17 - https://crrev.com/886003 (unsupported)
39 // Version 4 - 2011-12-12 - https://crrev.com/8396013 (unsupported)
40 // Version 5 - 2013-03-29 - https://crrev.com/12628006 (unsupported)
41 // Version 6 - 2013-09-20 - https://crrev.com/23503069 (unsupported)
42 // Version 7 - 2015-07-09 - https://crrev.com/879393002
43 // Version 8 - 2019-03-18 - https://crrev.com/c/1488059
44 // Version 9 - 2019-11-25 - https://crrev.com/c/1935034
45 // Version 10 - 2020-03-09 - https://crrev.com/c/2099463
46 const int kCurrentVersion = 10;
47 const int kCompatibleVersion = 10;
48 const bool kCreateIfNeeded = true;
49 const bool kDontCreate = false;
50 
51 // A mechanism to run experiments that may affect in data being persisted
52 // in different ways such that when the experiment is toggled on/off via
53 // cmd line flags, the database gets reset. The active flags are stored at
54 // the time of database creation and compared when reopening. If different
55 // the database is reset.
56 const char kExperimentFlagsKey[] = "ExperimentFlags";
57 
58 const char kGroupsTable[] = "Groups";
59 const char kCachesTable[] = "Caches";
60 const char kEntriesTable[] = "Entries";
61 const char kNamespacesTable[] = "Namespaces";
62 const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
63 const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
64 
65 struct TableInfo {
66   const char* table_name;
67   const char* columns;
68 };
69 
70 struct IndexInfo {
71   const char* index_name;
72   const char* table_name;
73   const char* columns;
74   bool unique;
75 };
76 
77 const TableInfo kTables[] = {
78     {kGroupsTable,
79      "(group_id INTEGER PRIMARY KEY,"
80      " origin TEXT,"
81      " manifest_url TEXT,"
82      " creation_time INTEGER,"
83      " last_access_time INTEGER,"
84      " last_full_update_check_time INTEGER,"
85      " first_evictable_error_time INTEGER,"
86      " token_expires INTEGER)"},
87 
88     {kCachesTable,
89      "(cache_id INTEGER PRIMARY KEY,"
90      " group_id INTEGER,"
91      " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
92      " update_time INTEGER,"
93      " cache_size INTEGER,"    // intentionally not normalized
94      " padding_size INTEGER,"  // intentionally not normalized
95      " manifest_parser_version INTEGER,"
96      " manifest_scope TEXT,"
97      " token_expires INTEGER)"},
98 
99     {kEntriesTable,
100      "(cache_id INTEGER,"
101      " url TEXT,"
102      " flags INTEGER,"
103      " response_id INTEGER,"
104      " response_size INTEGER,"
105      " padding_size INTEGER,"
106      " token_expires INTEGER)"},
107 
108     // The |is_pattern| field is obsolete.
109     {kNamespacesTable,
110      "(cache_id INTEGER,"
111      " origin TEXT,"  // intentionally not normalized
112      " type INTEGER,"
113      " namespace_url TEXT,"
114      " target_url TEXT,"
115      " is_pattern INTEGER CHECK(is_pattern IN (0, 1)),"
116      " token_expires INTEGER)"},
117 
118     // The |is_pattern| field is obsolete.
119     {kOnlineWhiteListsTable,
120      "(cache_id INTEGER,"
121      " namespace_url TEXT,"
122      " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))"},
123 
124     {kDeletableResponseIdsTable, "(response_id INTEGER NOT NULL)"},
125 };
126 
127 const IndexInfo kIndexes[] = {
128   { "GroupsOriginIndex",
129     kGroupsTable,
130     "(origin)",
131     false },
132 
133   { "GroupsManifestIndex",
134     kGroupsTable,
135     "(manifest_url)",
136     true },
137 
138   { "CachesGroupIndex",
139     kCachesTable,
140     "(group_id)",
141     false },
142 
143   { "EntriesCacheIndex",
144     kEntriesTable,
145     "(cache_id)",
146     false },
147 
148   { "EntriesCacheAndUrlIndex",
149     kEntriesTable,
150     "(cache_id, url)",
151     true },
152 
153   { "EntriesResponseIdIndex",
154     kEntriesTable,
155     "(response_id)",
156     true },
157 
158   { "NamespacesCacheIndex",
159     kNamespacesTable,
160     "(cache_id)",
161     false },
162 
163   { "NamespacesOriginIndex",
164     kNamespacesTable,
165     "(origin)",
166     false },
167 
168   { "NamespacesCacheAndUrlIndex",
169     kNamespacesTable,
170     "(cache_id, namespace_url)",
171     true },
172 
173   { "OnlineWhiteListCacheIndex",
174     kOnlineWhiteListsTable,
175     "(cache_id)",
176     false },
177 
178   { "DeletableResponsesIdIndex",
179     kDeletableResponseIdsTable,
180     "(response_id)",
181     true },
182 };
183 
CreateTable(sql::Database * db,const TableInfo & info)184 bool CreateTable(sql::Database* db, const TableInfo& info) {
185   std::string sql("CREATE TABLE ");
186   sql += info.table_name;
187   sql += info.columns;
188   return db->Execute(sql.c_str());
189 }
190 
CreateIndex(sql::Database * db,const IndexInfo & info)191 bool CreateIndex(sql::Database* db, const IndexInfo& info) {
192   std::string sql;
193   if (info.unique)
194     sql += "CREATE UNIQUE INDEX ";
195   else
196     sql += "CREATE INDEX ";
197   sql += info.index_name;
198   sql += " ON ";
199   sql += info.table_name;
200   sql += info.columns;
201   return db->Execute(sql.c_str());
202 }
203 
GetActiveExperimentFlags()204 std::string GetActiveExperimentFlags() {
205   return std::string();
206 }
207 
208 // GetURL().spec() is used instead of Serialize() to ensure
209 // backwards compatibility with older data.
SerializeOrigin(const url::Origin & origin)210 std::string SerializeOrigin(const url::Origin& origin) {
211   return origin.GetURL().spec();
212 }
213 
214 }  // anon namespace
215 
216 // AppCacheDatabase ----------------------------------------------------------
217 
GroupRecord()218 AppCacheDatabase::GroupRecord::GroupRecord()
219     : group_id(0) {
220 }
221 
222 AppCacheDatabase::GroupRecord::GroupRecord(const GroupRecord& other) = default;
223 
224 AppCacheDatabase::GroupRecord::~GroupRecord() = default;
225 
226 AppCacheDatabase::CacheRecord::CacheRecord() = default;
227 
228 AppCacheDatabase::CacheRecord::CacheRecord(const CacheRecord& other) = default;
229 
230 AppCacheDatabase::CacheRecord::~CacheRecord() = default;
231 
NamespaceRecord()232 AppCacheDatabase::NamespaceRecord::NamespaceRecord()
233     : cache_id(0) {
234 }
235 
236 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() = default;
237 
AppCacheDatabase(const base::FilePath & path)238 AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
239     : db_file_path_(path),
240       is_disabled_(false),
241       is_recreating_(false),
242       was_corruption_detected_(false) {
243 }
244 
~AppCacheDatabase()245 AppCacheDatabase::~AppCacheDatabase() {
246   CommitLazyLastAccessTimes();
247 }
248 
Disable()249 void AppCacheDatabase::Disable() {
250   VLOG(1) << "Disabling appcache database.";
251   is_disabled_ = true;
252   ResetConnectionAndTables();
253 }
254 
GetOriginUsage(const url::Origin & origin)255 int64_t AppCacheDatabase::GetOriginUsage(const url::Origin& origin) {
256   std::vector<CacheRecord> caches;
257   if (!FindCachesForOrigin(origin, &caches))
258     return 0;
259 
260   int64_t origin_usage = 0;
261   for (const auto& cache : caches)
262     origin_usage += cache.cache_size + cache.padding_size;
263   return origin_usage;
264 }
265 
GetAllOriginUsage(std::map<url::Origin,int64_t> * usage_map)266 bool AppCacheDatabase::GetAllOriginUsage(
267     std::map<url::Origin, int64_t>* usage_map) {
268   std::set<url::Origin> origins;
269   if (!FindOriginsWithGroups(&origins))
270     return false;
271   for (const auto& origin : origins)
272     (*usage_map)[origin] = GetOriginUsage(origin);
273   return true;
274 }
275 
FindOriginsWithGroups(std::set<url::Origin> * origins)276 bool AppCacheDatabase::FindOriginsWithGroups(std::set<url::Origin>* origins) {
277   DCHECK(origins && origins->empty());
278   if (!LazyOpen(kDontCreate))
279     return false;
280 
281   static const char kSql[] = "SELECT DISTINCT(origin) FROM Groups";
282 
283   sql::Statement statement(db_->GetUniqueStatement(kSql));
284 
285   while (statement.Step())
286     origins->insert(url::Origin::Create(GURL(statement.ColumnString(0))));
287 
288   return statement.Succeeded();
289 }
290 
FindLastStorageIds(int64_t * last_group_id,int64_t * last_cache_id,int64_t * last_response_id,int64_t * last_deletable_response_rowid)291 bool AppCacheDatabase::FindLastStorageIds(
292     int64_t* last_group_id,
293     int64_t* last_cache_id,
294     int64_t* last_response_id,
295     int64_t* last_deletable_response_rowid) {
296   DCHECK(last_group_id && last_cache_id && last_response_id &&
297          last_deletable_response_rowid);
298 
299   *last_group_id = 0;
300   *last_cache_id = 0;
301   *last_response_id = 0;
302   *last_deletable_response_rowid = 0;
303 
304   if (!LazyOpen(kDontCreate))
305     return false;
306 
307   static const char kMaxGroupIdSql[] = "SELECT MAX(group_id) FROM Groups";
308   static const char kMaxCacheIdSql[] = "SELECT MAX(cache_id) FROM Caches";
309   static const char kMaxResponseIdFromEntriesSql[] =
310       "SELECT MAX(response_id) FROM Entries";
311   static const char kMaxResponseIdFromDeletablesSql[] =
312       "SELECT MAX(response_id) FROM DeletableResponseIds";
313   static const char kMaxDeletableResponseRowIdSql[] =
314       "SELECT MAX(rowid) FROM DeletableResponseIds";
315   int64_t max_group_id;
316   int64_t max_cache_id;
317   int64_t max_response_id_from_entries;
318   int64_t max_response_id_from_deletables;
319   int64_t max_deletable_response_rowid;
320   if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
321       !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
322       !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
323                                          &max_response_id_from_entries) ||
324       !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
325                                          &max_response_id_from_deletables) ||
326       !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
327                                          &max_deletable_response_rowid)) {
328     return false;
329   }
330 
331   *last_group_id = max_group_id;
332   *last_cache_id = max_cache_id;
333   *last_response_id = std::max(max_response_id_from_entries,
334                                max_response_id_from_deletables);
335   *last_deletable_response_rowid = max_deletable_response_rowid;
336   return true;
337 }
338 
FindGroup(int64_t group_id,GroupRecord * record)339 bool AppCacheDatabase::FindGroup(int64_t group_id, GroupRecord* record) {
340   DCHECK(record);
341   if (!LazyOpen(kDontCreate))
342     return false;
343 
344   static const char kSql[] =
345       "SELECT group_id, origin, manifest_url,"
346       "       creation_time, last_access_time,"
347       "       last_full_update_check_time,"
348       "       first_evictable_error_time,"
349       "       token_expires"
350       "  FROM Groups WHERE group_id = ?";
351 
352   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
353 
354   statement.BindInt64(0, group_id);
355   if (!statement.Step())
356     return false;
357 
358   ReadGroupRecord(statement, record);
359   DCHECK(record->group_id == group_id);
360   return true;
361 }
362 
FindGroupForManifestUrl(const GURL & manifest_url,GroupRecord * record)363 bool AppCacheDatabase::FindGroupForManifestUrl(
364     const GURL& manifest_url, GroupRecord* record) {
365   DCHECK(record);
366   if (!LazyOpen(kDontCreate))
367     return false;
368 
369   static const char kSql[] =
370       "SELECT group_id, origin, manifest_url,"
371       "       creation_time, last_access_time,"
372       "       last_full_update_check_time,"
373       "       first_evictable_error_time,"
374       "       token_expires"
375       "  FROM Groups WHERE manifest_url = ?";
376 
377   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
378   statement.BindString(0, manifest_url.spec());
379 
380   if (!statement.Step())
381     return false;
382 
383   ReadGroupRecord(statement, record);
384   DCHECK(record->manifest_url == manifest_url);
385   return true;
386 }
387 
FindGroupsForOrigin(const url::Origin & origin,std::vector<GroupRecord> * records)388 bool AppCacheDatabase::FindGroupsForOrigin(const url::Origin& origin,
389                                            std::vector<GroupRecord>* records) {
390   DCHECK(records && records->empty());
391   if (!LazyOpen(kDontCreate))
392     return false;
393 
394   static const char kSql[] =
395       "SELECT group_id, origin, manifest_url,"
396       "       creation_time, last_access_time,"
397       "       last_full_update_check_time,"
398       "       first_evictable_error_time,"
399       "       token_expires"
400       "   FROM Groups WHERE origin = ?";
401 
402   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
403   statement.BindString(0, SerializeOrigin(origin));
404 
405   while (statement.Step()) {
406     records->push_back(GroupRecord());
407     ReadGroupRecord(statement, &records->back());
408     DCHECK(records->back().origin == origin);
409   }
410 
411   return statement.Succeeded();
412 }
413 
FindGroupForCache(int64_t cache_id,GroupRecord * record)414 bool AppCacheDatabase::FindGroupForCache(int64_t cache_id,
415                                          GroupRecord* record) {
416   DCHECK(record);
417   if (!LazyOpen(kDontCreate))
418     return false;
419 
420   static const char kSql[] =
421       "SELECT g.group_id, g.origin, g.manifest_url,"
422       "       g.creation_time, g.last_access_time,"
423       "       g.last_full_update_check_time,"
424       "       g.first_evictable_error_time,"
425       "       g.token_expires"
426       "  FROM Groups g, Caches c"
427       "  WHERE c.cache_id = ? AND c.group_id = g.group_id";
428 
429   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
430   statement.BindInt64(0, cache_id);
431 
432   if (!statement.Step())
433     return false;
434 
435   ReadGroupRecord(statement, record);
436   return true;
437 }
438 
InsertGroup(const GroupRecord * record)439 bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
440   if (!LazyOpen(kCreateIfNeeded))
441     return false;
442 
443   static const char kSql[] =
444       "INSERT INTO Groups"
445       "  (group_id, origin, manifest_url, creation_time, last_access_time,"
446       "   last_full_update_check_time, first_evictable_error_time,"
447       "   token_expires)"
448       "  VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
449   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
450   statement.BindInt64(0, record->group_id);
451   statement.BindString(1, SerializeOrigin(record->origin));
452   statement.BindString(2, record->manifest_url.spec());
453   statement.BindInt64(3, record->creation_time.ToInternalValue());
454   statement.BindInt64(4, record->last_access_time.ToInternalValue());
455   statement.BindInt64(5, record->last_full_update_check_time.ToInternalValue());
456   statement.BindInt64(6, record->first_evictable_error_time.ToInternalValue());
457   statement.BindInt64(7, record->token_expires.ToInternalValue());
458   return statement.Run();
459 }
460 
DeleteGroup(int64_t group_id)461 bool AppCacheDatabase::DeleteGroup(int64_t group_id) {
462   if (!LazyOpen(kDontCreate))
463     return false;
464 
465   static const char kSql[] = "DELETE FROM Groups WHERE group_id = ?";
466   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
467   statement.BindInt64(0, group_id);
468   return statement.Run();
469 }
470 
UpdateLastAccessTime(int64_t group_id,base::Time time)471 bool AppCacheDatabase::UpdateLastAccessTime(int64_t group_id, base::Time time) {
472   if (!LazyUpdateLastAccessTime(group_id, time))
473     return false;
474   return CommitLazyLastAccessTimes();
475 }
476 
LazyUpdateLastAccessTime(int64_t group_id,base::Time time)477 bool AppCacheDatabase::LazyUpdateLastAccessTime(int64_t group_id,
478                                                 base::Time time) {
479   if (!LazyOpen(kCreateIfNeeded))
480     return false;
481   lazy_last_access_times_[group_id] = time;
482   return true;
483 }
484 
CommitLazyLastAccessTimes()485 bool AppCacheDatabase::CommitLazyLastAccessTimes() {
486   if (lazy_last_access_times_.empty())
487     return true;
488   if (!LazyOpen(kDontCreate))
489     return false;
490 
491   sql::Transaction transaction(db_.get());
492   if (!transaction.Begin())
493     return false;
494   for (const auto& pair : lazy_last_access_times_) {
495     static const char kSql[] =
496         "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
497     sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
498     statement.BindInt64(0, pair.second.ToInternalValue());  // time
499     statement.BindInt64(1, pair.first);  // group_id
500     statement.Run();
501   }
502   lazy_last_access_times_.clear();
503   return transaction.Commit();
504 }
505 
UpdateEvictionTimesAndTokenExpires(int64_t group_id,base::Time last_full_update_check_time,base::Time first_evictable_error_time,base::Time token_expires)506 bool AppCacheDatabase::UpdateEvictionTimesAndTokenExpires(
507     int64_t group_id,
508     base::Time last_full_update_check_time,
509     base::Time first_evictable_error_time,
510     base::Time token_expires) {
511   if (!LazyOpen(kCreateIfNeeded))
512     return false;
513 
514   static const char kSql[] =
515       "UPDATE Groups"
516       " SET last_full_update_check_time = ?,"
517       "     first_evictable_error_time = ?,"
518       "     token_expires = ?"
519       " WHERE group_id = ?";
520   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
521   statement.BindInt64(0, last_full_update_check_time.ToInternalValue());
522   statement.BindInt64(1, first_evictable_error_time.ToInternalValue());
523   statement.BindInt64(2, token_expires.ToInternalValue());
524   statement.BindInt64(3, group_id);
525   return statement.Run();  // Will succeed even if group_id is invalid.
526 }
527 
FindCache(int64_t cache_id,CacheRecord * record)528 bool AppCacheDatabase::FindCache(int64_t cache_id, CacheRecord* record) {
529   DCHECK(record);
530   if (!LazyOpen(kDontCreate))
531     return false;
532 
533   static const char kSql[] =
534       "SELECT cache_id, group_id, online_wildcard, update_time, cache_size, "
535       "padding_size, manifest_parser_version, manifest_scope, token_expires"
536       " FROM Caches WHERE cache_id = ?";
537 
538   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
539   statement.BindInt64(0, cache_id);
540 
541   if (!statement.Step())
542     return false;
543 
544   ReadCacheRecord(statement, record);
545   return true;
546 }
547 
FindCacheForGroup(int64_t group_id,CacheRecord * record)548 bool AppCacheDatabase::FindCacheForGroup(int64_t group_id,
549                                          CacheRecord* record) {
550   DCHECK(record);
551   if (!LazyOpen(kDontCreate))
552     return false;
553 
554   static const char kSql[] =
555       "SELECT cache_id, group_id, online_wildcard, update_time, cache_size, "
556       "padding_size, manifest_parser_version, manifest_scope, token_expires"
557       "  FROM Caches WHERE group_id = ?";
558 
559   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
560   statement.BindInt64(0, group_id);
561 
562   if (!statement.Step())
563     return false;
564 
565   ReadCacheRecord(statement, record);
566   return true;
567 }
568 
FindCachesForOrigin(const url::Origin & origin,std::vector<CacheRecord> * records)569 bool AppCacheDatabase::FindCachesForOrigin(const url::Origin& origin,
570                                            std::vector<CacheRecord>* records) {
571   DCHECK(records);
572   std::vector<GroupRecord> group_records;
573   if (!FindGroupsForOrigin(origin, &group_records))
574     return false;
575 
576   CacheRecord cache_record;
577   for (const auto& record : group_records) {
578     if (FindCacheForGroup(record.group_id, &cache_record))
579       records->push_back(cache_record);
580   }
581   return true;
582 }
583 
InsertCache(const CacheRecord * record)584 bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
585   if (!LazyOpen(kCreateIfNeeded))
586     return false;
587 
588   static const char kSql[] =
589       "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
590       "                    update_time, cache_size, padding_size,"
591       "                    manifest_parser_version, manifest_scope,"
592       "                    token_expires)"
593       "  VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)";
594 
595   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
596   statement.BindInt64(0, record->cache_id);
597   statement.BindInt64(1, record->group_id);
598   statement.BindBool(2, record->online_wildcard);
599   statement.BindInt64(3, record->update_time.ToInternalValue());
600   DCHECK_GE(record->cache_size, 0);
601   statement.BindInt64(4, record->cache_size);
602   DCHECK_GE(record->padding_size, 0);
603   statement.BindInt64(5, record->padding_size);
604   statement.BindInt64(6, record->manifest_parser_version);
605   DCHECK_NE(record->manifest_parser_version, -1);
606   statement.BindString(7, record->manifest_scope);
607   DCHECK_NE(record->manifest_scope, "");
608   statement.BindInt64(8, record->token_expires.ToInternalValue());
609 
610   return statement.Run();
611 }
612 
DeleteCache(int64_t cache_id)613 bool AppCacheDatabase::DeleteCache(int64_t cache_id) {
614   if (!LazyOpen(kDontCreate))
615     return false;
616 
617   static const char kSql[] = "DELETE FROM Caches WHERE cache_id = ?";
618 
619   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
620   statement.BindInt64(0, cache_id);
621 
622   return statement.Run();
623 }
624 
FindEntriesForCache(int64_t cache_id,std::vector<EntryRecord> * records)625 bool AppCacheDatabase::FindEntriesForCache(int64_t cache_id,
626                                            std::vector<EntryRecord>* records) {
627   DCHECK(records && records->empty());
628   if (!LazyOpen(kDontCreate))
629     return false;
630 
631   static const char kSql[] =
632       "SELECT cache_id, url, flags, response_id, response_size, padding_size, "
633       "  token_expires "
634       "FROM Entries"
635       "  WHERE cache_id = ?";
636 
637   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
638   statement.BindInt64(0, cache_id);
639 
640   while (statement.Step()) {
641     records->push_back(EntryRecord());
642     ReadEntryRecord(statement, &records->back());
643     DCHECK(records->back().cache_id == cache_id);
644   }
645 
646   return statement.Succeeded();
647 }
648 
FindEntriesForUrl(const GURL & url,std::vector<EntryRecord> * records)649 bool AppCacheDatabase::FindEntriesForUrl(
650     const GURL& url, std::vector<EntryRecord>* records) {
651   DCHECK(records && records->empty());
652   if (!LazyOpen(kDontCreate))
653     return false;
654 
655   static const char kSql[] =
656       "SELECT cache_id, url, flags, response_id, response_size, padding_size, "
657       "  token_expires "
658       "FROM Entries"
659       "  WHERE url = ?";
660 
661   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
662   statement.BindString(0, url.spec());
663 
664   while (statement.Step()) {
665     records->push_back(EntryRecord());
666     ReadEntryRecord(statement, &records->back());
667     DCHECK(records->back().url == url);
668   }
669 
670   return statement.Succeeded();
671 }
672 
FindEntry(int64_t cache_id,const GURL & url,EntryRecord * record)673 bool AppCacheDatabase::FindEntry(int64_t cache_id,
674                                  const GURL& url,
675                                  EntryRecord* record) {
676   DCHECK(record);
677   if (!LazyOpen(kDontCreate))
678     return false;
679 
680   static const char kSql[] =
681       "SELECT cache_id, url, flags, response_id, response_size, padding_size, "
682       "  token_expires "
683       "FROM Entries"
684       "  WHERE cache_id = ? AND url = ?";
685 
686   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
687   statement.BindInt64(0, cache_id);
688   statement.BindString(1, url.spec());
689 
690   if (!statement.Step())
691     return false;
692 
693   ReadEntryRecord(statement, record);
694   DCHECK(record->cache_id == cache_id);
695   DCHECK(record->url == url);
696   return true;
697 }
698 
InsertEntry(const EntryRecord * record)699 bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
700   if (!LazyOpen(kCreateIfNeeded))
701     return false;
702 
703   static const char kSql[] =
704       "INSERT INTO Entries (cache_id, url, flags, response_id, response_size, "
705       "padding_size, token_expires)"
706       "  VALUES(?, ?, ?, ?, ?, ?, ?)";
707 
708   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
709   statement.BindInt64(0, record->cache_id);
710   statement.BindString(1, record->url.spec());
711   statement.BindInt(2, record->flags);
712   statement.BindInt64(3, record->response_id);
713   DCHECK_GE(record->response_size, 0);
714   statement.BindInt64(4, record->response_size);
715   DCHECK_GE(record->padding_size, 0);
716   statement.BindInt64(5, record->padding_size);
717   statement.BindInt64(6, record->token_expires.ToInternalValue());
718 
719   return statement.Run();
720 }
721 
InsertEntryRecords(const std::vector<EntryRecord> & records)722 bool AppCacheDatabase::InsertEntryRecords(
723     const std::vector<EntryRecord>& records) {
724   if (records.empty())
725     return true;
726   sql::Transaction transaction(db_.get());
727   if (!transaction.Begin())
728     return false;
729   for (const auto& record : records) {
730     if (!InsertEntry(&record))
731       return false;
732   }
733   return transaction.Commit();
734 }
735 
DeleteEntriesForCache(int64_t cache_id)736 bool AppCacheDatabase::DeleteEntriesForCache(int64_t cache_id) {
737   if (!LazyOpen(kDontCreate))
738     return false;
739 
740   static const char kSql[] = "DELETE FROM Entries WHERE cache_id = ?";
741 
742   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
743   statement.BindInt64(0, cache_id);
744 
745   return statement.Run();
746 }
747 
AddEntryFlags(const GURL & entry_url,int64_t cache_id,int additional_flags)748 bool AppCacheDatabase::AddEntryFlags(const GURL& entry_url,
749                                      int64_t cache_id,
750                                      int additional_flags) {
751   if (!LazyOpen(kDontCreate))
752     return false;
753 
754   static const char kSql[] =
755       "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
756 
757   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
758   statement.BindInt(0, additional_flags);
759   statement.BindInt64(1, cache_id);
760   statement.BindString(2, entry_url.spec());
761 
762   return statement.Run() && db_->GetLastChangeCount();
763 }
764 
FindNamespacesForOrigin(const url::Origin & origin,std::vector<NamespaceRecord> * intercepts,std::vector<NamespaceRecord> * fallbacks)765 bool AppCacheDatabase::FindNamespacesForOrigin(
766     const url::Origin& origin,
767     std::vector<NamespaceRecord>* intercepts,
768     std::vector<NamespaceRecord>* fallbacks) {
769   DCHECK(intercepts && intercepts->empty());
770   DCHECK(fallbacks && fallbacks->empty());
771   if (!LazyOpen(kDontCreate))
772     return false;
773 
774   static const char kSql[] =
775       "SELECT cache_id, origin, type, namespace_url, target_url, token_expires"
776       "  FROM Namespaces WHERE origin = ?";
777 
778   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
779   statement.BindString(0, SerializeOrigin(origin));
780 
781   ReadNamespaceRecords(&statement, intercepts, fallbacks);
782 
783   return statement.Succeeded();
784 }
785 
FindNamespacesForCache(int64_t cache_id,std::vector<NamespaceRecord> * intercepts,std::vector<NamespaceRecord> * fallbacks)786 bool AppCacheDatabase::FindNamespacesForCache(
787     int64_t cache_id,
788     std::vector<NamespaceRecord>* intercepts,
789     std::vector<NamespaceRecord>* fallbacks) {
790   DCHECK(intercepts && intercepts->empty());
791   DCHECK(fallbacks && fallbacks->empty());
792   if (!LazyOpen(kDontCreate))
793     return false;
794 
795   static const char kSql[] =
796       "SELECT cache_id, origin, type, namespace_url, target_url, token_expires"
797       "  FROM Namespaces WHERE cache_id = ?";
798 
799   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
800   statement.BindInt64(0, cache_id);
801 
802   ReadNamespaceRecords(&statement, intercepts, fallbacks);
803 
804   return statement.Succeeded();
805 }
806 
InsertNamespace(const NamespaceRecord * record)807 bool AppCacheDatabase::InsertNamespace(
808     const NamespaceRecord* record) {
809   if (!LazyOpen(kCreateIfNeeded))
810     return false;
811 
812   static const char kSql[] =
813       "INSERT INTO Namespaces"
814       "  (cache_id, origin, type, namespace_url, target_url, is_pattern,"
815       "   token_expires)"
816       "  VALUES (?, ?, ?, ?, ?, ?, ?)";
817 
818   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
819   statement.BindInt64(0, record->cache_id);
820   statement.BindString(1, SerializeOrigin(record->origin));
821   statement.BindInt(2, record->namespace_.type);
822   statement.BindString(3, record->namespace_.namespace_url.spec());
823   statement.BindString(4, record->namespace_.target_url.spec());
824   statement.BindBool(5, /*is_pattern=*/false);
825   statement.BindInt64(6, record->token_expires.ToInternalValue());
826   return statement.Run();
827 }
828 
InsertNamespaceRecords(const std::vector<NamespaceRecord> & records)829 bool AppCacheDatabase::InsertNamespaceRecords(
830     const std::vector<NamespaceRecord>& records) {
831   if (records.empty())
832     return true;
833   sql::Transaction transaction(db_.get());
834   if (!transaction.Begin())
835     return false;
836   for (const auto& record : records) {
837     if (!InsertNamespace(&record))
838       return false;
839   }
840   return transaction.Commit();
841 }
842 
DeleteNamespacesForCache(int64_t cache_id)843 bool AppCacheDatabase::DeleteNamespacesForCache(int64_t cache_id) {
844   if (!LazyOpen(kDontCreate))
845     return false;
846 
847   static const char kSql[] = "DELETE FROM Namespaces WHERE cache_id = ?";
848 
849   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
850   statement.BindInt64(0, cache_id);
851 
852   return statement.Run();
853 }
854 
FindOnlineWhiteListForCache(int64_t cache_id,std::vector<OnlineWhiteListRecord> * records)855 bool AppCacheDatabase::FindOnlineWhiteListForCache(
856     int64_t cache_id,
857     std::vector<OnlineWhiteListRecord>* records) {
858   DCHECK(records && records->empty());
859   if (!LazyOpen(kDontCreate))
860     return false;
861 
862   static const char kSql[] =
863       "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
864       "  WHERE cache_id = ?";
865 
866   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
867   statement.BindInt64(0, cache_id);
868 
869   while (statement.Step()) {
870     records->push_back(OnlineWhiteListRecord());
871     this->ReadOnlineWhiteListRecord(statement, &records->back());
872     DCHECK(records->back().cache_id == cache_id);
873   }
874   return statement.Succeeded();
875 }
876 
InsertOnlineWhiteList(const OnlineWhiteListRecord * record)877 bool AppCacheDatabase::InsertOnlineWhiteList(
878     const OnlineWhiteListRecord* record) {
879   if (!LazyOpen(kCreateIfNeeded))
880     return false;
881 
882   static const char kSql[] =
883       "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
884       "  VALUES (?, ?, ?)";
885 
886   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
887   statement.BindInt64(0, record->cache_id);
888   statement.BindString(1, record->namespace_url.spec());
889   statement.BindBool(2, /*is_pattern=*/false);
890 
891   return statement.Run();
892 }
893 
InsertOnlineWhiteListRecords(const std::vector<OnlineWhiteListRecord> & records)894 bool AppCacheDatabase::InsertOnlineWhiteListRecords(
895     const std::vector<OnlineWhiteListRecord>& records) {
896   if (records.empty())
897     return true;
898   sql::Transaction transaction(db_.get());
899   if (!transaction.Begin())
900     return false;
901   for (const auto& record : records) {
902     if (!InsertOnlineWhiteList(&record))
903       return false;
904   }
905   return transaction.Commit();
906 }
907 
DeleteOnlineWhiteListForCache(int64_t cache_id)908 bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64_t cache_id) {
909   if (!LazyOpen(kDontCreate))
910     return false;
911 
912   static const char kSql[] = "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
913 
914   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
915   statement.BindInt64(0, cache_id);
916 
917   return statement.Run();
918 }
919 
GetDeletableResponseIds(std::vector<int64_t> * response_ids,int64_t max_rowid,int limit)920 bool AppCacheDatabase::GetDeletableResponseIds(
921     std::vector<int64_t>* response_ids,
922     int64_t max_rowid,
923     int limit) {
924   if (!LazyOpen(kDontCreate))
925     return false;
926 
927   static const char kSql[] =
928       "SELECT response_id FROM DeletableResponseIds "
929       "  WHERE rowid <= ?"
930       "  LIMIT ?";
931 
932   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
933   statement.BindInt64(0, max_rowid);
934   statement.BindInt64(1, limit);
935 
936   while (statement.Step())
937     response_ids->push_back(statement.ColumnInt64(0));
938   return statement.Succeeded();
939 }
940 
InsertDeletableResponseIds(const std::vector<int64_t> & response_ids)941 bool AppCacheDatabase::InsertDeletableResponseIds(
942     const std::vector<int64_t>& response_ids) {
943   static const char kSql[] =
944       "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
945   return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
946 }
947 
DeleteDeletableResponseIds(const std::vector<int64_t> & response_ids)948 bool AppCacheDatabase::DeleteDeletableResponseIds(
949     const std::vector<int64_t>& response_ids) {
950   static const char kSql[] =
951       "DELETE FROM DeletableResponseIds WHERE response_id = ?";
952   return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
953 }
954 
RunCachedStatementWithIds(sql::StatementID statement_id,const char * sql,const std::vector<int64_t> & ids)955 bool AppCacheDatabase::RunCachedStatementWithIds(
956     sql::StatementID statement_id,
957     const char* sql,
958     const std::vector<int64_t>& ids) {
959   DCHECK(sql);
960   if (!LazyOpen(kCreateIfNeeded))
961     return false;
962 
963   sql::Transaction transaction(db_.get());
964   if (!transaction.Begin())
965     return false;
966 
967   sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
968 
969   for (const auto& id : ids) {
970     statement.BindInt64(0, id);
971     if (!statement.Run())
972       return false;
973     statement.Reset(true);
974   }
975 
976   return transaction.Commit();
977 }
978 
RunUniqueStatementWithInt64Result(const char * sql,int64_t * result)979 bool AppCacheDatabase::RunUniqueStatementWithInt64Result(const char* sql,
980                                                          int64_t* result) {
981   DCHECK(sql);
982   sql::Statement statement(db_->GetUniqueStatement(sql));
983   if (!statement.Step()) {
984     return false;
985   }
986   *result = statement.ColumnInt64(0);
987   return true;
988 }
989 
FindResponseIdsForCacheHelper(int64_t cache_id,std::vector<int64_t> * ids_vector,std::set<int64_t> * ids_set)990 bool AppCacheDatabase::FindResponseIdsForCacheHelper(
991     int64_t cache_id,
992     std::vector<int64_t>* ids_vector,
993     std::set<int64_t>* ids_set) {
994   DCHECK(ids_vector || ids_set);
995   DCHECK(!(ids_vector && ids_set));
996   if (!LazyOpen(kDontCreate))
997     return false;
998 
999   static const char kSql[] =
1000       "SELECT response_id FROM Entries WHERE cache_id = ?";
1001 
1002   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
1003 
1004   statement.BindInt64(0, cache_id);
1005   while (statement.Step()) {
1006     int64_t id = statement.ColumnInt64(0);
1007     if (ids_set)
1008       ids_set->insert(id);
1009     else
1010       ids_vector->push_back(id);
1011   }
1012 
1013   return statement.Succeeded();
1014 }
1015 
ReadGroupRecord(const sql::Statement & statement,GroupRecord * record)1016 void AppCacheDatabase::ReadGroupRecord(
1017     const sql::Statement& statement, GroupRecord* record) {
1018   record->group_id = statement.ColumnInt64(0);
1019   record->origin = url::Origin::Create(GURL(statement.ColumnString(1)));
1020   record->manifest_url = GURL(statement.ColumnString(2));
1021   record->creation_time =
1022       base::Time::FromInternalValue(statement.ColumnInt64(3));
1023 
1024   const auto found = lazy_last_access_times_.find(record->group_id);
1025   if (found != lazy_last_access_times_.end()) {
1026     record->last_access_time = found->second;
1027   } else {
1028     record->last_access_time =
1029         base::Time::FromInternalValue(statement.ColumnInt64(4));
1030   }
1031 
1032   record->last_full_update_check_time =
1033       base::Time::FromInternalValue(statement.ColumnInt64(5));
1034   record->first_evictable_error_time =
1035       base::Time::FromInternalValue(statement.ColumnInt64(6));
1036   record->token_expires =
1037       base::Time::FromInternalValue(statement.ColumnInt64(7));
1038 }
1039 
ReadCacheRecord(const sql::Statement & statement,CacheRecord * record)1040 void AppCacheDatabase::ReadCacheRecord(
1041     const sql::Statement& statement, CacheRecord* record) {
1042   record->cache_id = statement.ColumnInt64(0);
1043   record->group_id = statement.ColumnInt64(1);
1044   record->online_wildcard = statement.ColumnBool(2);
1045   record->update_time =
1046       base::Time::FromInternalValue(statement.ColumnInt64(3));
1047   record->cache_size = statement.ColumnInt64(4);
1048   record->padding_size = statement.ColumnInt64(5);
1049   record->manifest_parser_version = statement.ColumnInt64(6);
1050   record->manifest_scope = statement.ColumnString(7);
1051   record->token_expires =
1052       base::Time::FromInternalValue(statement.ColumnInt64(8));
1053 }
1054 
ReadEntryRecord(const sql::Statement & statement,EntryRecord * record)1055 void AppCacheDatabase::ReadEntryRecord(
1056     const sql::Statement& statement, EntryRecord* record) {
1057   record->cache_id = statement.ColumnInt64(0);
1058   record->url = GURL(statement.ColumnString(1));
1059   record->flags = statement.ColumnInt(2);
1060   record->response_id = statement.ColumnInt64(3);
1061   record->response_size = statement.ColumnInt64(4);
1062   record->padding_size = statement.ColumnInt64(5);
1063   record->token_expires =
1064       base::Time::FromInternalValue(statement.ColumnInt64(6));
1065 }
1066 
ReadNamespaceRecords(sql::Statement * statement,NamespaceRecordVector * intercepts,NamespaceRecordVector * fallbacks)1067 void AppCacheDatabase::ReadNamespaceRecords(
1068       sql::Statement* statement,
1069       NamespaceRecordVector* intercepts,
1070       NamespaceRecordVector* fallbacks) {
1071   while (statement->Step()) {
1072     AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
1073         statement->ColumnInt(2));
1074     NamespaceRecordVector* records =
1075         (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
1076     records->push_back(NamespaceRecord());
1077     ReadNamespaceRecord(statement, &records->back());
1078   }
1079 }
1080 
ReadNamespaceRecord(const sql::Statement * statement,NamespaceRecord * record)1081 void AppCacheDatabase::ReadNamespaceRecord(
1082     const sql::Statement* statement, NamespaceRecord* record) {
1083   record->cache_id = statement->ColumnInt64(0);
1084   record->origin = url::Origin::Create(GURL(statement->ColumnString(1)));
1085   record->namespace_.type =
1086       static_cast<AppCacheNamespaceType>(statement->ColumnInt(2));
1087   record->namespace_.namespace_url = GURL(statement->ColumnString(3));
1088   record->namespace_.target_url = GURL(statement->ColumnString(4));
1089   DCHECK(record->namespace_.type == APPCACHE_FALLBACK_NAMESPACE ||
1090          record->namespace_.type == APPCACHE_INTERCEPT_NAMESPACE);
1091   // The APPCACHE_NETWORK_NAMESPACE are stored as OnlineWhiteListRecords.
1092   record->token_expires =
1093       base::Time::FromInternalValue(statement->ColumnInt64(5));
1094 }
1095 
ReadOnlineWhiteListRecord(const sql::Statement & statement,OnlineWhiteListRecord * record)1096 void AppCacheDatabase::ReadOnlineWhiteListRecord(
1097     const sql::Statement& statement, OnlineWhiteListRecord* record) {
1098   record->cache_id = statement.ColumnInt64(0);
1099   record->namespace_url = GURL(statement.ColumnString(1));
1100 }
1101 
LazyOpen(bool create_if_needed)1102 bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
1103   if (db_)
1104     return true;
1105 
1106   // If we tried and failed once, don't try again in the same session
1107   // to avoid creating an incoherent mess on disk.
1108   if (is_disabled_)
1109     return false;
1110 
1111   // Avoid creating a database at all if we can.
1112   bool use_in_memory_db = db_file_path_.empty();
1113   if (!create_if_needed &&
1114       (use_in_memory_db || !base::PathExists(db_file_path_))) {
1115     return false;
1116   }
1117 
1118   db_ = std::make_unique<sql::Database>();
1119   meta_table_ = std::make_unique<sql::MetaTable>();
1120 
1121   db_->set_histogram_tag("AppCache");
1122 
1123   bool opened = false;
1124   if (use_in_memory_db) {
1125     opened = db_->OpenInMemory();
1126   } else if (!base::CreateDirectory(db_file_path_.DirName())) {
1127     LOG(ERROR) << "Failed to create appcache directory.";
1128   } else {
1129     opened = db_->Open(db_file_path_);
1130     if (opened)
1131       db_->Preload();
1132   }
1133 
1134   if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1135     // We're unable to open the database. This is a fatal error
1136     // which we can't recover from. We try to handle it by deleting
1137     // the existing appcache data and starting with a clean slate in
1138     // this browser session.
1139     if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1140       return true;
1141 
1142     Disable();
1143     return false;
1144   }
1145 
1146   was_corruption_detected_ = false;
1147   db_->set_error_callback(base::BindRepeating(
1148       &AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
1149   return true;
1150 }
1151 
EnsureDatabaseVersion()1152 bool AppCacheDatabase::EnsureDatabaseVersion() {
1153   if (!sql::MetaTable::DoesTableExist(db_.get()))
1154     return CreateSchema();
1155 
1156   if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1157     return false;
1158 
1159   if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1160     LOG(WARNING) << "AppCache database is too new.";
1161     return false;
1162   }
1163 
1164   std::string stored_flags;
1165   meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1166   if (stored_flags != GetActiveExperimentFlags())
1167     return false;
1168 
1169   if (meta_table_->GetVersionNumber() < kCurrentVersion)
1170     return UpgradeSchema();
1171 
1172 #ifndef NDEBUG
1173   DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1174   for (const TableInfo& table : kTables)
1175     DCHECK(db_->DoesTableExist(table.table_name));
1176   for (const IndexInfo& index : kIndexes)
1177     DCHECK(db_->DoesIndexExist(index.index_name));
1178 #endif
1179 
1180   return true;
1181 }
1182 
CreateSchema()1183 bool AppCacheDatabase::CreateSchema() {
1184   sql::Transaction transaction(db_.get());
1185   if (!transaction.Begin())
1186     return false;
1187 
1188   if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1189     return false;
1190 
1191   if (!meta_table_->SetValue(kExperimentFlagsKey,
1192                              GetActiveExperimentFlags())) {
1193     return false;
1194   }
1195 
1196   for (const TableInfo& table : kTables) {
1197     if (!CreateTable(db_.get(), table))
1198       return false;
1199   }
1200 
1201   for (const IndexInfo& index : kIndexes) {
1202     if (!CreateIndex(db_.get(), index))
1203       return false;
1204   }
1205 
1206   return transaction.Commit();
1207 }
1208 
UpgradeSchema()1209 bool AppCacheDatabase::UpgradeSchema() {
1210   // Start from scratch for versions that would require unsupported migrations.
1211   if (meta_table_->GetVersionNumber() < 7)
1212     return DeleteExistingAndCreateNewDatabase();
1213 
1214   // Version 8 adds padding_size.
1215   if (meta_table_->GetVersionNumber() < 8) {
1216     sql::Transaction transaction(db_.get());
1217     if (!transaction.Begin())
1218       return false;
1219     if (!db_->Execute("ALTER TABLE Caches ADD COLUMN padding_size INTEGER"))
1220       return false;
1221     if (!db_->Execute("ALTER TABLE Entries ADD COLUMN padding_size INTEGER"))
1222       return false;
1223     meta_table_->SetVersionNumber(8);
1224     meta_table_->SetCompatibleVersionNumber(8);
1225     if (!AppCacheBackfillerVersion8(db_.get()).BackfillPaddingSizes())
1226       return false;
1227     if (!transaction.Commit())
1228       return false;
1229   }
1230 
1231   // Version 9 adds manifest_parser_version and manifest_scope.
1232   if (meta_table_->GetVersionNumber() < 9) {
1233     sql::Transaction transaction(db_.get());
1234     if (!transaction.Begin())
1235       return false;
1236     if (!db_->Execute(
1237             "ALTER TABLE Caches ADD COLUMN manifest_parser_version INTEGER"))
1238       return false;
1239     if (!db_->Execute("ALTER TABLE Caches ADD COLUMN manifest_scope TEXT"))
1240       return false;
1241     meta_table_->SetVersionNumber(9);
1242     meta_table_->SetCompatibleVersionNumber(9);
1243     if (!AppCacheBackfillerVersion9(db_.get())
1244              .BackfillManifestParserVersionAndScope()) {
1245       return false;
1246     }
1247     if (!transaction.Commit())
1248       return false;
1249   }
1250 
1251   if (meta_table_->GetVersionNumber() < 10) {
1252     sql::Transaction transaction(db_.get());
1253     if (!transaction.Begin())
1254       return false;
1255 
1256     if (!db_->Execute("ALTER TABLE Groups ADD COLUMN token_expires INTEGER"))
1257       return false;
1258     if (!db_->Execute("ALTER TABLE Caches ADD COLUMN token_expires INTEGER"))
1259       return false;
1260     if (!db_->Execute("ALTER TABLE Entries ADD COLUMN token_expires INTEGER"))
1261       return false;
1262     if (!db_->Execute(
1263             "ALTER TABLE Namespaces ADD COLUMN token_expires INTEGER"))
1264       return false;
1265     meta_table_->SetVersionNumber(10);
1266     meta_table_->SetCompatibleVersionNumber(10);
1267     // No backfilling needed as all of these values default to zero,
1268     // which is desired.
1269     if (!transaction.Commit())
1270       return false;
1271   }
1272 
1273   return true;
1274 }
1275 
ResetConnectionAndTables()1276 void AppCacheDatabase::ResetConnectionAndTables() {
1277   meta_table_.reset();
1278   db_.reset();
1279 }
1280 
DeleteExistingAndCreateNewDatabase()1281 bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1282   DCHECK(!db_file_path_.empty());
1283   DCHECK(base::PathExists(db_file_path_));
1284   VLOG(1) << "Deleting existing appcache data and starting over.";
1285 
1286   ResetConnectionAndTables();
1287 
1288   // This also deletes the disk cache data.
1289   base::FilePath directory = db_file_path_.DirName();
1290   if (!base::DeleteFileRecursively(directory))
1291     return false;
1292 
1293   // Make sure the steps above actually deleted things.
1294   if (base::PathExists(directory))
1295     return false;
1296 
1297   if (!base::CreateDirectory(directory))
1298     return false;
1299 
1300   // So we can't go recursive.
1301   if (is_recreating_)
1302     return false;
1303 
1304   base::AutoReset<bool> auto_reset(&is_recreating_, true);
1305   return LazyOpen(kCreateIfNeeded);
1306 }
1307 
OnDatabaseError(int err,sql::Statement * stmt)1308 void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
1309   was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
1310   if (!db_->IsExpectedSqliteError(err))
1311     DLOG(ERROR) << db_->GetErrorMessage();
1312   // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1313 }
1314 
1315 }  // namespace content
1316