1 /* 2 * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "ApplicationCacheStorage.h" 28 29 #if ENABLE(OFFLINE_WEB_APPLICATIONS) 30 31 #include "ApplicationCache.h" 32 #include "ApplicationCacheHost.h" 33 #include "ApplicationCacheGroup.h" 34 #include "ApplicationCacheResource.h" 35 #include "CString.h" 36 #include "FileSystem.h" 37 #include "KURL.h" 38 #include "SQLiteStatement.h" 39 #include "SQLiteTransaction.h" 40 #include <wtf/StdLibExtras.h> 41 #include <wtf/StringExtras.h> 42 43 using namespace std; 44 45 namespace WebCore { 46 47 template <class T> 48 class StorageIDJournal { 49 public: 50 ~StorageIDJournal() 51 { 52 size_t size = m_records.size(); 53 for (size_t i = 0; i < size; ++i) 54 m_records[i].restore(); 55 } 56 57 void add(T* resource, unsigned storageID) 58 { 59 m_records.append(Record(resource, storageID)); 60 } 61 62 void commit() 63 { 64 m_records.clear(); 65 } 66 67 private: 68 class Record { 69 public: 70 Record() : m_resource(0), m_storageID(0) { } 71 Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } 72 73 void restore() 74 { 75 m_resource->setStorageID(m_storageID); 76 } 77 78 private: 79 T* m_resource; 80 unsigned m_storageID; 81 }; 82 83 Vector<Record> m_records; 84 }; 85 86 static unsigned urlHostHash(const KURL& url) 87 { 88 unsigned hostStart = url.hostStart(); 89 unsigned hostEnd = url.hostEnd(); 90 91 return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart)); 92 } 93 94 ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL) 95 { 96 openDatabase(false); 97 if (!m_database.isOpen()) 98 return 0; 99 100 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?"); 101 if (statement.prepare() != SQLResultOk) 102 return 0; 103 104 statement.bindText(1, manifestURL); 105 106 int result = statement.step(); 107 if (result == SQLResultDone) 108 return 0; 109 110 if (result != SQLResultRow) { 111 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 112 return 0; 113 } 114 115 unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2)); 116 117 RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); 118 if (!cache) 119 return 0; 120 121 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 122 123 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 124 group->setNewestCache(cache.release()); 125 126 return group; 127 } 128 129 ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL) 130 { 131 ASSERT(!manifestURL.hasFragmentIdentifier()); 132 133 std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0); 134 135 if (!result.second) { 136 ASSERT(result.first->second); 137 return result.first->second; 138 } 139 140 // Look up the group in the database 141 ApplicationCacheGroup* group = loadCacheGroup(manifestURL); 142 143 // If the group was not found we need to create it 144 if (!group) { 145 group = new ApplicationCacheGroup(manifestURL); 146 m_cacheHostSet.add(urlHostHash(manifestURL)); 147 } 148 149 result.first->second = group; 150 151 return group; 152 } 153 154 void ApplicationCacheStorage::loadManifestHostHashes() 155 { 156 static bool hasLoadedHashes = false; 157 158 if (hasLoadedHashes) 159 return; 160 161 // We set this flag to true before the database has been opened 162 // to avoid trying to open the database over and over if it doesn't exist. 163 hasLoadedHashes = true; 164 165 openDatabase(false); 166 if (!m_database.isOpen()) 167 return; 168 169 // Fetch the host hashes. 170 SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups"); 171 if (statement.prepare() != SQLResultOk) 172 return; 173 174 int result; 175 while ((result = statement.step()) == SQLResultRow) 176 m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0))); 177 } 178 179 ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url) 180 { 181 ASSERT(!url.hasFragmentIdentifier()); 182 183 loadManifestHostHashes(); 184 185 // Hash the host name and see if there's a manifest with the same host. 186 if (!m_cacheHostSet.contains(urlHostHash(url))) 187 return 0; 188 189 // Check if a cache already exists in memory. 190 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 191 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { 192 ApplicationCacheGroup* group = it->second; 193 194 ASSERT(!group->isObsolete()); 195 196 if (!protocolHostAndPortAreEqual(url, group->manifestURL())) 197 continue; 198 199 if (ApplicationCache* cache = group->newestCache()) { 200 ApplicationCacheResource* resource = cache->resourceForURL(url); 201 if (!resource) 202 continue; 203 if (resource->type() & ApplicationCacheResource::Foreign) 204 continue; 205 return group; 206 } 207 } 208 209 if (!m_database.isOpen()) 210 return 0; 211 212 // Check the database. Look for all cache groups with a newest cache. 213 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); 214 if (statement.prepare() != SQLResultOk) 215 return 0; 216 217 int result; 218 while ((result = statement.step()) == SQLResultRow) { 219 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1)); 220 221 if (m_cachesInMemory.contains(manifestURL)) 222 continue; 223 224 if (!protocolHostAndPortAreEqual(url, manifestURL)) 225 continue; 226 227 // We found a cache group that matches. Now check if the newest cache has a resource with 228 // a matching URL. 229 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); 230 RefPtr<ApplicationCache> cache = loadCache(newestCacheID); 231 if (!cache) 232 continue; 233 234 ApplicationCacheResource* resource = cache->resourceForURL(url); 235 if (!resource) 236 continue; 237 if (resource->type() & ApplicationCacheResource::Foreign) 238 continue; 239 240 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 241 242 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 243 group->setNewestCache(cache.release()); 244 245 m_cachesInMemory.set(group->manifestURL(), group); 246 247 return group; 248 } 249 250 if (result != SQLResultDone) 251 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 252 253 return 0; 254 } 255 256 ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url) 257 { 258 ASSERT(!url.hasFragmentIdentifier()); 259 260 // Check if an appropriate cache already exists in memory. 261 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 262 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { 263 ApplicationCacheGroup* group = it->second; 264 265 ASSERT(!group->isObsolete()); 266 267 if (ApplicationCache* cache = group->newestCache()) { 268 KURL fallbackURL; 269 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) 270 continue; 271 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) 272 continue; 273 return group; 274 } 275 } 276 277 if (!m_database.isOpen()) 278 return 0; 279 280 // Check the database. Look for all cache groups with a newest cache. 281 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); 282 if (statement.prepare() != SQLResultOk) 283 return 0; 284 285 int result; 286 while ((result = statement.step()) == SQLResultRow) { 287 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1)); 288 289 if (m_cachesInMemory.contains(manifestURL)) 290 continue; 291 292 // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match. 293 if (!protocolHostAndPortAreEqual(url, manifestURL)) 294 continue; 295 296 // We found a cache group that matches. Now check if the newest cache has a resource with 297 // a matching fallback namespace. 298 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); 299 RefPtr<ApplicationCache> cache = loadCache(newestCacheID); 300 301 KURL fallbackURL; 302 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) 303 continue; 304 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) 305 continue; 306 307 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 308 309 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 310 group->setNewestCache(cache.release()); 311 312 m_cachesInMemory.set(group->manifestURL(), group); 313 314 return group; 315 } 316 317 if (result != SQLResultDone) 318 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 319 320 return 0; 321 } 322 323 void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group) 324 { 325 if (group->isObsolete()) { 326 ASSERT(!group->storageID()); 327 ASSERT(m_cachesInMemory.get(group->manifestURL()) != group); 328 return; 329 } 330 331 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); 332 333 m_cachesInMemory.remove(group->manifestURL()); 334 335 // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database). 336 if (!group->storageID()) 337 m_cacheHostSet.remove(urlHostHash(group->manifestURL())); 338 } 339 340 void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group) 341 { 342 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); 343 ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL()))); 344 345 if (ApplicationCache* newestCache = group->newestCache()) 346 remove(newestCache); 347 348 m_cachesInMemory.remove(group->manifestURL()); 349 m_cacheHostSet.remove(urlHostHash(group->manifestURL())); 350 } 351 352 void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory) 353 { 354 ASSERT(m_cacheDirectory.isNull()); 355 ASSERT(!cacheDirectory.isNull()); 356 357 m_cacheDirectory = cacheDirectory; 358 } 359 360 const String& ApplicationCacheStorage::cacheDirectory() const 361 { 362 return m_cacheDirectory; 363 } 364 365 void ApplicationCacheStorage::setMaximumSize(int64_t size) 366 { 367 m_maximumSize = size; 368 } 369 370 int64_t ApplicationCacheStorage::maximumSize() const 371 { 372 return m_maximumSize; 373 } 374 375 bool ApplicationCacheStorage::isMaximumSizeReached() const 376 { 377 return m_isMaximumSizeReached; 378 } 379 380 int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave) 381 { 382 int64_t spaceNeeded = 0; 383 long long fileSize = 0; 384 if (!getFileSize(m_cacheFile, fileSize)) 385 return 0; 386 387 int64_t currentSize = fileSize; 388 389 // Determine the amount of free space we have available. 390 int64_t totalAvailableSize = 0; 391 if (m_maximumSize < currentSize) { 392 // The max size is smaller than the actual size of the app cache file. 393 // This can happen if the client previously imposed a larger max size 394 // value and the app cache file has already grown beyond the current 395 // max size value. 396 // The amount of free space is just the amount of free space inside 397 // the database file. Note that this is always 0 if SQLite is compiled 398 // with AUTO_VACUUM = 1. 399 totalAvailableSize = m_database.freeSpaceSize(); 400 } else { 401 // The max size is the same or larger than the current size. 402 // The amount of free space available is the amount of free space 403 // inside the database file plus the amount we can grow until we hit 404 // the max size. 405 totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize(); 406 } 407 408 // The space needed to be freed in order to accommodate the failed cache is 409 // the size of the failed cache minus any already available free space. 410 spaceNeeded = cacheToSave - totalAvailableSize; 411 // The space needed value must be positive (or else the total already 412 // available free space would be larger than the size of the failed cache and 413 // saving of the cache should have never failed). 414 ASSERT(spaceNeeded); 415 return spaceNeeded; 416 } 417 418 bool ApplicationCacheStorage::executeSQLCommand(const String& sql) 419 { 420 ASSERT(m_database.isOpen()); 421 422 bool result = m_database.executeCommand(sql); 423 if (!result) 424 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 425 sql.utf8().data(), m_database.lastErrorMsg()); 426 427 return result; 428 } 429 430 static const int schemaVersion = 5; 431 432 void ApplicationCacheStorage::verifySchemaVersion() 433 { 434 int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0); 435 if (version == schemaVersion) 436 return; 437 438 m_database.clearAllTables(); 439 440 // Update user version. 441 SQLiteTransaction setDatabaseVersion(m_database); 442 setDatabaseVersion.begin(); 443 444 char userVersionSQL[32]; 445 int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion); 446 ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes); 447 448 SQLiteStatement statement(m_database, userVersionSQL); 449 if (statement.prepare() != SQLResultOk) 450 return; 451 452 executeStatement(statement); 453 setDatabaseVersion.commit(); 454 } 455 456 void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) 457 { 458 if (m_database.isOpen()) 459 return; 460 461 // The cache directory should never be null, but if it for some weird reason is we bail out. 462 if (m_cacheDirectory.isNull()) 463 return; 464 465 m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); 466 if (!createIfDoesNotExist && !fileExists(m_cacheFile)) 467 return; 468 469 makeAllDirectories(m_cacheDirectory); 470 m_database.open(m_cacheFile); 471 472 if (!m_database.isOpen()) 473 return; 474 475 verifySchemaVersion(); 476 477 // Create tables 478 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, " 479 "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)"); 480 executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)"); 481 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); 482 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); 483 executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, " 484 "cache INTEGER NOT NULL ON CONFLICT FAIL)"); 485 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)"); 486 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, " 487 "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)"); 488 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)"); 489 490 // When a cache is deleted, all its entries and its whitelist should be deleted. 491 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches" 492 " FOR EACH ROW BEGIN" 493 " DELETE FROM CacheEntries WHERE cache = OLD.id;" 494 " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;" 495 " DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;" 496 " DELETE FROM FallbackURLs WHERE cache = OLD.id;" 497 " END"); 498 499 // When a cache entry is deleted, its resource should also be deleted. 500 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries" 501 " FOR EACH ROW BEGIN" 502 " DELETE FROM CacheResources WHERE id = OLD.resource;" 503 " END"); 504 505 // When a cache resource is deleted, its data blob should also be deleted. 506 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources" 507 " FOR EACH ROW BEGIN" 508 " DELETE FROM CacheResourceData WHERE id = OLD.data;" 509 " END"); 510 } 511 512 bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement) 513 { 514 bool result = statement.executeCommand(); 515 if (!result) 516 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 517 statement.query().utf8().data(), m_database.lastErrorMsg()); 518 519 return result; 520 } 521 522 bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal) 523 { 524 ASSERT(group->storageID() == 0); 525 ASSERT(journal); 526 527 SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)"); 528 if (statement.prepare() != SQLResultOk) 529 return false; 530 531 statement.bindInt64(1, urlHostHash(group->manifestURL())); 532 statement.bindText(2, group->manifestURL()); 533 534 if (!executeStatement(statement)) 535 return false; 536 537 group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID())); 538 journal->add(group, 0); 539 return true; 540 } 541 542 bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal) 543 { 544 ASSERT(cache->storageID() == 0); 545 ASSERT(cache->group()->storageID() != 0); 546 ASSERT(storageIDJournal); 547 548 SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)"); 549 if (statement.prepare() != SQLResultOk) 550 return false; 551 552 statement.bindInt64(1, cache->group()->storageID()); 553 statement.bindInt64(2, cache->estimatedSizeInStorage()); 554 555 if (!executeStatement(statement)) 556 return false; 557 558 unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID()); 559 560 // Store all resources 561 { 562 ApplicationCache::ResourceMap::const_iterator end = cache->end(); 563 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { 564 unsigned oldStorageID = it->second->storageID(); 565 if (!store(it->second.get(), cacheStorageID)) 566 return false; 567 568 // Storing the resource succeeded. Log its old storageID in case 569 // it needs to be restored later. 570 storageIDJournal->add(it->second.get(), oldStorageID); 571 } 572 } 573 574 // Store the online whitelist 575 const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist(); 576 { 577 size_t whitelistSize = onlineWhitelist.size(); 578 for (size_t i = 0; i < whitelistSize; ++i) { 579 SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)"); 580 statement.prepare(); 581 582 statement.bindText(1, onlineWhitelist[i]); 583 statement.bindInt64(2, cacheStorageID); 584 585 if (!executeStatement(statement)) 586 return false; 587 } 588 } 589 590 // Store online whitelist wildcard flag. 591 { 592 SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)"); 593 statement.prepare(); 594 595 statement.bindInt64(1, cache->allowsAllNetworkRequests()); 596 statement.bindInt64(2, cacheStorageID); 597 598 if (!executeStatement(statement)) 599 return false; 600 } 601 602 // Store fallback URLs. 603 const FallbackURLVector& fallbackURLs = cache->fallbackURLs(); 604 { 605 size_t fallbackCount = fallbackURLs.size(); 606 for (size_t i = 0; i < fallbackCount; ++i) { 607 SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)"); 608 statement.prepare(); 609 610 statement.bindText(1, fallbackURLs[i].first); 611 statement.bindText(2, fallbackURLs[i].second); 612 statement.bindInt64(3, cacheStorageID); 613 614 if (!executeStatement(statement)) 615 return false; 616 } 617 } 618 619 cache->setStorageID(cacheStorageID); 620 return true; 621 } 622 623 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID) 624 { 625 ASSERT(cacheStorageID); 626 ASSERT(!resource->storageID()); 627 628 openDatabase(true); 629 630 // First, insert the data 631 SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)"); 632 if (dataStatement.prepare() != SQLResultOk) 633 return false; 634 635 if (resource->data()->size()) 636 dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size()); 637 638 if (!dataStatement.executeCommand()) 639 return false; 640 641 unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID()); 642 643 // Then, insert the resource 644 645 // Serialize the headers 646 Vector<UChar> stringBuilder; 647 648 HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end(); 649 for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) { 650 stringBuilder.append(it->first.characters(), it->first.length()); 651 stringBuilder.append((UChar)':'); 652 stringBuilder.append(it->second.characters(), it->second.length()); 653 stringBuilder.append((UChar)'\n'); 654 } 655 656 String headers = String::adopt(stringBuilder); 657 658 SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)"); 659 if (resourceStatement.prepare() != SQLResultOk) 660 return false; 661 662 // The same ApplicationCacheResource are used in ApplicationCacheResource::size() 663 // to calculate the approximate size of an ApplicationCacheResource object. If 664 // you change the code below, please also change ApplicationCacheResource::size(). 665 resourceStatement.bindText(1, resource->url()); 666 resourceStatement.bindInt64(2, resource->response().httpStatusCode()); 667 resourceStatement.bindText(3, resource->response().url()); 668 resourceStatement.bindText(4, headers); 669 resourceStatement.bindInt64(5, dataId); 670 resourceStatement.bindText(6, resource->response().mimeType()); 671 resourceStatement.bindText(7, resource->response().textEncodingName()); 672 673 if (!executeStatement(resourceStatement)) 674 return false; 675 676 unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID()); 677 678 // Finally, insert the cache entry 679 SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)"); 680 if (entryStatement.prepare() != SQLResultOk) 681 return false; 682 683 entryStatement.bindInt64(1, cacheStorageID); 684 entryStatement.bindInt64(2, resource->type()); 685 entryStatement.bindInt64(3, resourceId); 686 687 if (!executeStatement(entryStatement)) 688 return false; 689 690 resource->setStorageID(resourceId); 691 return true; 692 } 693 694 bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache) 695 { 696 ASSERT_UNUSED(cache, cache->storageID()); 697 ASSERT(resource->storageID()); 698 699 // First, insert the data 700 SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?"); 701 if (entryStatement.prepare() != SQLResultOk) 702 return false; 703 704 entryStatement.bindInt64(1, resource->type()); 705 entryStatement.bindInt64(2, resource->storageID()); 706 707 return executeStatement(entryStatement); 708 } 709 710 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) 711 { 712 ASSERT(cache->storageID()); 713 714 openDatabase(true); 715 716 m_isMaximumSizeReached = false; 717 m_database.setMaximumSize(m_maximumSize); 718 719 SQLiteTransaction storeResourceTransaction(m_database); 720 storeResourceTransaction.begin(); 721 722 if (!store(resource, cache->storageID())) { 723 checkForMaxSizeReached(); 724 return false; 725 } 726 727 // A resource was added to the cache. Update the total data size for the cache. 728 SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?"); 729 if (sizeUpdateStatement.prepare() != SQLResultOk) 730 return false; 731 732 sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage()); 733 sizeUpdateStatement.bindInt64(2, cache->storageID()); 734 735 if (!executeStatement(sizeUpdateStatement)) 736 return false; 737 738 storeResourceTransaction.commit(); 739 return true; 740 } 741 742 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) 743 { 744 openDatabase(true); 745 746 m_isMaximumSizeReached = false; 747 m_database.setMaximumSize(m_maximumSize); 748 749 SQLiteTransaction storeCacheTransaction(m_database); 750 751 storeCacheTransaction.begin(); 752 753 GroupStorageIDJournal groupStorageIDJournal; 754 if (!group->storageID()) { 755 // Store the group 756 if (!store(group, &groupStorageIDJournal)) { 757 checkForMaxSizeReached(); 758 return false; 759 } 760 } 761 762 ASSERT(group->newestCache()); 763 ASSERT(!group->isObsolete()); 764 ASSERT(!group->newestCache()->storageID()); 765 766 // Log the storageID changes to the in-memory resource objects. The journal 767 // object will roll them back automatically in case a database operation 768 // fails and this method returns early. 769 ResourceStorageIDJournal resourceStorageIDJournal; 770 771 // Store the newest cache 772 if (!store(group->newestCache(), &resourceStorageIDJournal)) { 773 checkForMaxSizeReached(); 774 return false; 775 } 776 777 // Update the newest cache in the group. 778 779 SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?"); 780 if (statement.prepare() != SQLResultOk) 781 return false; 782 783 statement.bindInt64(1, group->newestCache()->storageID()); 784 statement.bindInt64(2, group->storageID()); 785 786 if (!executeStatement(statement)) 787 return false; 788 789 groupStorageIDJournal.commit(); 790 resourceStorageIDJournal.commit(); 791 storeCacheTransaction.commit(); 792 return true; 793 } 794 795 static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response) 796 { 797 int pos = find(header, headerLength, ':'); 798 ASSERT(pos != -1); 799 800 AtomicString headerName = AtomicString(header, pos); 801 String headerValue = String(header + pos + 1, headerLength - pos - 1); 802 803 response.setHTTPHeaderField(headerName, headerValue); 804 } 805 806 static inline void parseHeaders(const String& headers, ResourceResponse& response) 807 { 808 int startPos = 0; 809 int endPos; 810 while ((endPos = headers.find('\n', startPos)) != -1) { 811 ASSERT(startPos != endPos); 812 813 parseHeader(headers.characters() + startPos, endPos - startPos, response); 814 815 startPos = endPos + 1; 816 } 817 818 if (startPos != static_cast<int>(headers.length())) 819 parseHeader(headers.characters(), headers.length(), response); 820 } 821 822 PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID) 823 { 824 SQLiteStatement cacheStatement(m_database, 825 "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id " 826 "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?"); 827 if (cacheStatement.prepare() != SQLResultOk) { 828 LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg()); 829 return 0; 830 } 831 832 cacheStatement.bindInt64(1, storageID); 833 834 RefPtr<ApplicationCache> cache = ApplicationCache::create(); 835 836 int result; 837 while ((result = cacheStatement.step()) == SQLResultRow) { 838 KURL url(ParsedURLString, cacheStatement.getColumnText(0)); 839 840 unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(1)); 841 842 Vector<char> blob; 843 cacheStatement.getColumnBlobAsVector(5, blob); 844 845 RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob); 846 847 String mimeType = cacheStatement.getColumnText(2); 848 String textEncodingName = cacheStatement.getColumnText(3); 849 850 ResourceResponse response(url, mimeType, data->size(), textEncodingName, ""); 851 852 String headers = cacheStatement.getColumnText(4); 853 parseHeaders(headers, response); 854 855 RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release()); 856 857 if (type & ApplicationCacheResource::Manifest) 858 cache->setManifestResource(resource.release()); 859 else 860 cache->addResource(resource.release()); 861 } 862 863 if (result != SQLResultDone) 864 LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg()); 865 866 // Load the online whitelist 867 SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?"); 868 if (whitelistStatement.prepare() != SQLResultOk) 869 return 0; 870 whitelistStatement.bindInt64(1, storageID); 871 872 Vector<KURL> whitelist; 873 while ((result = whitelistStatement.step()) == SQLResultRow) 874 whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0))); 875 876 if (result != SQLResultDone) 877 LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg()); 878 879 cache->setOnlineWhitelist(whitelist); 880 881 // Load online whitelist wildcard flag. 882 SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?"); 883 if (whitelistWildcardStatement.prepare() != SQLResultOk) 884 return 0; 885 whitelistWildcardStatement.bindInt64(1, storageID); 886 887 result = whitelistWildcardStatement.step(); 888 if (result != SQLResultRow) 889 LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg()); 890 891 cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0)); 892 893 if (whitelistWildcardStatement.step() != SQLResultDone) 894 LOG_ERROR("Too many rows for online whitelist wildcard flag"); 895 896 // Load fallback URLs. 897 SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?"); 898 if (fallbackStatement.prepare() != SQLResultOk) 899 return 0; 900 fallbackStatement.bindInt64(1, storageID); 901 902 FallbackURLVector fallbackURLs; 903 while ((result = fallbackStatement.step()) == SQLResultRow) 904 fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1)))); 905 906 if (result != SQLResultDone) 907 LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg()); 908 909 cache->setFallbackURLs(fallbackURLs); 910 911 cache->setStorageID(storageID); 912 913 return cache.release(); 914 } 915 916 void ApplicationCacheStorage::remove(ApplicationCache* cache) 917 { 918 if (!cache->storageID()) 919 return; 920 921 openDatabase(false); 922 if (!m_database.isOpen()) 923 return; 924 925 ASSERT(cache->group()); 926 ASSERT(cache->group()->storageID()); 927 928 // All associated data will be deleted by database triggers. 929 SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?"); 930 if (statement.prepare() != SQLResultOk) 931 return; 932 933 statement.bindInt64(1, cache->storageID()); 934 executeStatement(statement); 935 936 cache->clearStorageID(); 937 938 if (cache->group()->newestCache() == cache) { 939 // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above. 940 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); 941 if (groupStatement.prepare() != SQLResultOk) 942 return; 943 944 groupStatement.bindInt64(1, cache->group()->storageID()); 945 executeStatement(groupStatement); 946 947 cache->group()->clearStorageID(); 948 } 949 } 950 951 void ApplicationCacheStorage::empty() 952 { 953 openDatabase(false); 954 955 if (!m_database.isOpen()) 956 return; 957 958 // Clear cache groups, caches and cache resources. 959 executeSQLCommand("DELETE FROM CacheGroups"); 960 executeSQLCommand("DELETE FROM Caches"); 961 962 // Clear the storage IDs for the caches in memory. 963 // The caches will still work, but cached resources will not be saved to disk 964 // until a cache update process has been initiated. 965 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 966 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) 967 it->second->clearStorageID(); 968 } 969 970 bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost) 971 { 972 ApplicationCache* cache = cacheHost->applicationCache(); 973 if (!cache) 974 return true; 975 976 // Create a new cache. 977 RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create(); 978 979 cacheCopy->setOnlineWhitelist(cache->onlineWhitelist()); 980 cacheCopy->setFallbackURLs(cache->fallbackURLs()); 981 982 // Traverse the cache and add copies of all resources. 983 ApplicationCache::ResourceMap::const_iterator end = cache->end(); 984 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { 985 ApplicationCacheResource* resource = it->second.get(); 986 987 RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data()); 988 989 cacheCopy->addResource(resourceCopy.release()); 990 } 991 992 // Now create a new cache group. 993 OwnPtr<ApplicationCacheGroup> groupCopy(new ApplicationCacheGroup(cache->group()->manifestURL(), true)); 994 995 groupCopy->setNewestCache(cacheCopy); 996 997 ApplicationCacheStorage copyStorage; 998 copyStorage.setCacheDirectory(cacheDirectory); 999 1000 // Empty the cache in case something was there before. 1001 copyStorage.empty(); 1002 1003 return copyStorage.storeNewestCache(groupCopy.get()); 1004 } 1005 1006 bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls) 1007 { 1008 ASSERT(urls); 1009 openDatabase(false); 1010 if (!m_database.isOpen()) 1011 return false; 1012 1013 SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups"); 1014 1015 if (selectURLs.prepare() != SQLResultOk) 1016 return false; 1017 1018 while (selectURLs.step() == SQLResultRow) 1019 urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0))); 1020 1021 return true; 1022 } 1023 1024 bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size) 1025 { 1026 ASSERT(size); 1027 openDatabase(false); 1028 if (!m_database.isOpen()) 1029 return false; 1030 1031 SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?"); 1032 if (statement.prepare() != SQLResultOk) 1033 return false; 1034 1035 statement.bindText(1, manifestURL); 1036 1037 int result = statement.step(); 1038 if (result == SQLResultDone) 1039 return false; 1040 1041 if (result != SQLResultRow) { 1042 LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg()); 1043 return false; 1044 } 1045 1046 *size = statement.getColumnInt64(0); 1047 return true; 1048 } 1049 1050 bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL) 1051 { 1052 SQLiteTransaction deleteTransaction(m_database); 1053 // Check to see if the group is in memory. 1054 ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL); 1055 if (group) 1056 cacheGroupMadeObsolete(group); 1057 else { 1058 // The cache group is not in memory, so remove it from the disk. 1059 openDatabase(false); 1060 if (!m_database.isOpen()) 1061 return false; 1062 1063 SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?"); 1064 if (idStatement.prepare() != SQLResultOk) 1065 return false; 1066 1067 idStatement.bindText(1, manifestURL); 1068 1069 int result = idStatement.step(); 1070 if (result == SQLResultDone) 1071 return false; 1072 1073 if (result != SQLResultRow) { 1074 LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg()); 1075 return false; 1076 } 1077 1078 int64_t groupId = idStatement.getColumnInt64(0); 1079 1080 SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?"); 1081 if (cacheStatement.prepare() != SQLResultOk) 1082 return false; 1083 1084 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); 1085 if (groupStatement.prepare() != SQLResultOk) 1086 return false; 1087 1088 cacheStatement.bindInt64(1, groupId); 1089 executeStatement(cacheStatement); 1090 groupStatement.bindInt64(1, groupId); 1091 executeStatement(groupStatement); 1092 } 1093 1094 deleteTransaction.commit(); 1095 return true; 1096 } 1097 1098 void ApplicationCacheStorage::vacuumDatabaseFile() 1099 { 1100 openDatabase(false); 1101 if (!m_database.isOpen()) 1102 return; 1103 1104 m_database.runVacuumCommand(); 1105 } 1106 1107 void ApplicationCacheStorage::checkForMaxSizeReached() 1108 { 1109 if (m_database.lastError() == SQLResultFull) 1110 m_isMaximumSizeReached = true; 1111 } 1112 1113 ApplicationCacheStorage::ApplicationCacheStorage() 1114 : m_maximumSize(INT_MAX) 1115 , m_isMaximumSizeReached(false) 1116 { 1117 } 1118 1119 ApplicationCacheStorage& cacheStorage() 1120 { 1121 DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ()); 1122 1123 return storage; 1124 } 1125 1126 } // namespace WebCore 1127 1128 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS) 1129