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