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