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 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 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 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 189 AppCacheDatabase::GroupRecord::GroupRecord() 190 : group_id(0) { 191 } 192 193 AppCacheDatabase::GroupRecord::~GroupRecord() { 194 } 195 196 AppCacheDatabase::NamespaceRecord::NamespaceRecord() 197 : cache_id(0) { 198 } 199 200 AppCacheDatabase::NamespaceRecord::~NamespaceRecord() { 201 } 202 203 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 211 AppCacheDatabase::~AppCacheDatabase() { 212 } 213 214 void AppCacheDatabase::Disable() { 215 VLOG(1) << "Disabling appcache database."; 216 is_disabled_ = true; 217 ResetConnectionAndTables(); 218 } 219 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 1187 void AppCacheDatabase::ResetConnectionAndTables() { 1188 meta_table_.reset(); 1189 db_.reset(); 1190 } 1191 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 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