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