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