1/* 2 * Copyright (C) 2008, 2009, 2010, 2011 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 "ApplicationCacheGroup.h" 33#include "ApplicationCacheHost.h" 34#include "ApplicationCacheResource.h" 35#include "FileSystem.h" 36#include "KURL.h" 37#include "NotImplemented.h" 38#include "SQLiteStatement.h" 39#include "SQLiteTransaction.h" 40#include "SecurityOrigin.h" 41#include "UUID.h" 42#include <wtf/text/CString.h> 43#include <wtf/StdLibExtras.h> 44#include <wtf/StringExtras.h> 45 46using namespace std; 47 48namespace WebCore { 49 50static const char flatFileSubdirectory[] = "ApplicationCache"; 51 52template <class T> 53class StorageIDJournal { 54public: 55 ~StorageIDJournal() 56 { 57 size_t size = m_records.size(); 58 for (size_t i = 0; i < size; ++i) 59 m_records[i].restore(); 60 } 61 62 void add(T* resource, unsigned storageID) 63 { 64 m_records.append(Record(resource, storageID)); 65 } 66 67 void commit() 68 { 69 m_records.clear(); 70 } 71 72private: 73 class Record { 74 public: 75 Record() : m_resource(0), m_storageID(0) { } 76 Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } 77 78 void restore() 79 { 80 m_resource->setStorageID(m_storageID); 81 } 82 83 private: 84 T* m_resource; 85 unsigned m_storageID; 86 }; 87 88 Vector<Record> m_records; 89}; 90 91static unsigned urlHostHash(const KURL& url) 92{ 93 unsigned hostStart = url.hostStart(); 94 unsigned hostEnd = url.hostEnd(); 95 96 return AlreadyHashed::avoidDeletedValue(StringHasher::computeHash(url.string().characters() + hostStart, hostEnd - hostStart)); 97} 98 99ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL) 100{ 101 openDatabase(false); 102 if (!m_database.isOpen()) 103 return 0; 104 105 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?"); 106 if (statement.prepare() != SQLResultOk) 107 return 0; 108 109 statement.bindText(1, manifestURL); 110 111 int result = statement.step(); 112 if (result == SQLResultDone) 113 return 0; 114 115 if (result != SQLResultRow) { 116 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 117 return 0; 118 } 119 120 unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2)); 121 122 RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); 123 if (!cache) 124 return 0; 125 126 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 127 128 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 129 group->setNewestCache(cache.release()); 130 131 return group; 132} 133 134ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL) 135{ 136 ASSERT(!manifestURL.hasFragmentIdentifier()); 137 138 std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0); 139 140 if (!result.second) { 141 ASSERT(result.first->second); 142 return result.first->second; 143 } 144 145 // Look up the group in the database 146 ApplicationCacheGroup* group = loadCacheGroup(manifestURL); 147 148 // If the group was not found we need to create it 149 if (!group) { 150 group = new ApplicationCacheGroup(manifestURL); 151 m_cacheHostSet.add(urlHostHash(manifestURL)); 152 } 153 154 result.first->second = group; 155 156 return group; 157} 158 159ApplicationCacheGroup* ApplicationCacheStorage::findInMemoryCacheGroup(const KURL& manifestURL) const 160{ 161 return m_cachesInMemory.get(manifestURL); 162} 163 164void ApplicationCacheStorage::loadManifestHostHashes() 165{ 166 static bool hasLoadedHashes = false; 167 168 if (hasLoadedHashes) 169 return; 170 171 // We set this flag to true before the database has been opened 172 // to avoid trying to open the database over and over if it doesn't exist. 173 hasLoadedHashes = true; 174 175 openDatabase(false); 176 if (!m_database.isOpen()) 177 return; 178 179 // Fetch the host hashes. 180 SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups"); 181 if (statement.prepare() != SQLResultOk) 182 return; 183 184 while (statement.step() == SQLResultRow) 185 m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0))); 186} 187 188ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url) 189{ 190 ASSERT(!url.hasFragmentIdentifier()); 191 192 loadManifestHostHashes(); 193 194 // Hash the host name and see if there's a manifest with the same host. 195 if (!m_cacheHostSet.contains(urlHostHash(url))) 196 return 0; 197 198 // Check if a cache already exists in memory. 199 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 200 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { 201 ApplicationCacheGroup* group = it->second; 202 203 ASSERT(!group->isObsolete()); 204 205 if (!protocolHostAndPortAreEqual(url, group->manifestURL())) 206 continue; 207 208 if (ApplicationCache* cache = group->newestCache()) { 209 ApplicationCacheResource* resource = cache->resourceForURL(url); 210 if (!resource) 211 continue; 212 if (resource->type() & ApplicationCacheResource::Foreign) 213 continue; 214 return group; 215 } 216 } 217 218 if (!m_database.isOpen()) 219 return 0; 220 221 // Check the database. Look for all cache groups with a newest cache. 222 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); 223 if (statement.prepare() != SQLResultOk) 224 return 0; 225 226 int result; 227 while ((result = statement.step()) == SQLResultRow) { 228 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1)); 229 230 if (m_cachesInMemory.contains(manifestURL)) 231 continue; 232 233 if (!protocolHostAndPortAreEqual(url, manifestURL)) 234 continue; 235 236 // We found a cache group that matches. Now check if the newest cache has a resource with 237 // a matching URL. 238 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); 239 RefPtr<ApplicationCache> cache = loadCache(newestCacheID); 240 if (!cache) 241 continue; 242 243 ApplicationCacheResource* resource = cache->resourceForURL(url); 244 if (!resource) 245 continue; 246 if (resource->type() & ApplicationCacheResource::Foreign) 247 continue; 248 249 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 250 251 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 252 group->setNewestCache(cache.release()); 253 254 m_cachesInMemory.set(group->manifestURL(), group); 255 256 return group; 257 } 258 259 if (result != SQLResultDone) 260 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 261 262 return 0; 263} 264 265ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url) 266{ 267 ASSERT(!url.hasFragmentIdentifier()); 268 269 // Check if an appropriate cache already exists in memory. 270 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 271 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { 272 ApplicationCacheGroup* group = it->second; 273 274 ASSERT(!group->isObsolete()); 275 276 if (ApplicationCache* cache = group->newestCache()) { 277 KURL fallbackURL; 278 if (cache->isURLInOnlineWhitelist(url)) 279 continue; 280 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) 281 continue; 282 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) 283 continue; 284 return group; 285 } 286 } 287 288 if (!m_database.isOpen()) 289 return 0; 290 291 // Check the database. Look for all cache groups with a newest cache. 292 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); 293 if (statement.prepare() != SQLResultOk) 294 return 0; 295 296 int result; 297 while ((result = statement.step()) == SQLResultRow) { 298 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1)); 299 300 if (m_cachesInMemory.contains(manifestURL)) 301 continue; 302 303 // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match. 304 if (!protocolHostAndPortAreEqual(url, manifestURL)) 305 continue; 306 307 // We found a cache group that matches. Now check if the newest cache has a resource with 308 // a matching fallback namespace. 309 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); 310 RefPtr<ApplicationCache> cache = loadCache(newestCacheID); 311 312 KURL fallbackURL; 313 if (cache->isURLInOnlineWhitelist(url)) 314 continue; 315 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) 316 continue; 317 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) 318 continue; 319 320 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); 321 322 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); 323 group->setNewestCache(cache.release()); 324 325 m_cachesInMemory.set(group->manifestURL(), group); 326 327 return group; 328 } 329 330 if (result != SQLResultDone) 331 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); 332 333 return 0; 334} 335 336void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group) 337{ 338 if (group->isObsolete()) { 339 ASSERT(!group->storageID()); 340 ASSERT(m_cachesInMemory.get(group->manifestURL()) != group); 341 return; 342 } 343 344 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); 345 346 m_cachesInMemory.remove(group->manifestURL()); 347 348 // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database). 349 if (!group->storageID()) 350 m_cacheHostSet.remove(urlHostHash(group->manifestURL())); 351} 352 353void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group) 354{ 355 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); 356 ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL()))); 357 358 if (ApplicationCache* newestCache = group->newestCache()) 359 remove(newestCache); 360 361 m_cachesInMemory.remove(group->manifestURL()); 362 m_cacheHostSet.remove(urlHostHash(group->manifestURL())); 363} 364 365void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory) 366{ 367 ASSERT(m_cacheDirectory.isNull()); 368 ASSERT(!cacheDirectory.isNull()); 369 370 m_cacheDirectory = cacheDirectory; 371} 372 373const String& ApplicationCacheStorage::cacheDirectory() const 374{ 375 return m_cacheDirectory; 376} 377 378void ApplicationCacheStorage::setMaximumSize(int64_t size) 379{ 380 m_maximumSize = size; 381} 382 383int64_t ApplicationCacheStorage::maximumSize() const 384{ 385 return m_maximumSize; 386} 387 388bool ApplicationCacheStorage::isMaximumSizeReached() const 389{ 390 return m_isMaximumSizeReached; 391} 392 393int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave) 394{ 395 int64_t spaceNeeded = 0; 396 long long fileSize = 0; 397 if (!getFileSize(m_cacheFile, fileSize)) 398 return 0; 399 400 int64_t currentSize = fileSize + flatFileAreaSize(); 401 402 // Determine the amount of free space we have available. 403 int64_t totalAvailableSize = 0; 404 if (m_maximumSize < currentSize) { 405 // The max size is smaller than the actual size of the app cache file. 406 // This can happen if the client previously imposed a larger max size 407 // value and the app cache file has already grown beyond the current 408 // max size value. 409 // The amount of free space is just the amount of free space inside 410 // the database file. Note that this is always 0 if SQLite is compiled 411 // with AUTO_VACUUM = 1. 412 totalAvailableSize = m_database.freeSpaceSize(); 413 } else { 414 // The max size is the same or larger than the current size. 415 // The amount of free space available is the amount of free space 416 // inside the database file plus the amount we can grow until we hit 417 // the max size. 418 totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize(); 419 } 420 421 // The space needed to be freed in order to accommodate the failed cache is 422 // the size of the failed cache minus any already available free space. 423 spaceNeeded = cacheToSave - totalAvailableSize; 424 // The space needed value must be positive (or else the total already 425 // available free space would be larger than the size of the failed cache and 426 // saving of the cache should have never failed). 427 ASSERT(spaceNeeded); 428 return spaceNeeded; 429} 430 431void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota) 432{ 433 m_defaultOriginQuota = quota; 434} 435 436bool ApplicationCacheStorage::quotaForOrigin(const SecurityOrigin* origin, int64_t& quota) 437{ 438 // If an Origin record doesn't exist, then the COUNT will be 0 and quota will be 0. 439 // Using the count to determine if a record existed or not is a safe way to determine 440 // if a quota of 0 is real, from the record, or from null. 441 SQLiteStatement statement(m_database, "SELECT COUNT(quota), quota FROM Origins WHERE origin=?"); 442 if (statement.prepare() != SQLResultOk) 443 return false; 444 445 statement.bindText(1, origin->databaseIdentifier()); 446 int result = statement.step(); 447 448 // Return the quota, or if it was null the default. 449 if (result == SQLResultRow) { 450 bool wasNoRecord = statement.getColumnInt64(0) == 0; 451 quota = wasNoRecord ? m_defaultOriginQuota : statement.getColumnInt64(1); 452 return true; 453 } 454 455 LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg()); 456 return false; 457} 458 459bool ApplicationCacheStorage::usageForOrigin(const SecurityOrigin* origin, int64_t& usage) 460{ 461 // If an Origins record doesn't exist, then the SUM will be null, 462 // which will become 0, as expected, when converting to a number. 463 SQLiteStatement statement(m_database, "SELECT SUM(Caches.size)" 464 " FROM CacheGroups" 465 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" 466 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" 467 " WHERE Origins.origin=?"); 468 if (statement.prepare() != SQLResultOk) 469 return false; 470 471 statement.bindText(1, origin->databaseIdentifier()); 472 int result = statement.step(); 473 474 if (result == SQLResultRow) { 475 usage = statement.getColumnInt64(0); 476 return true; 477 } 478 479 LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg()); 480 return false; 481} 482 483bool ApplicationCacheStorage::remainingSizeForOriginExcludingCache(const SecurityOrigin* origin, ApplicationCache* cache, int64_t& remainingSize) 484{ 485 openDatabase(false); 486 if (!m_database.isOpen()) 487 return false; 488 489 // Remaining size = total origin quota - size of all caches with origin excluding the provided cache. 490 // Keep track of the number of caches so we can tell if the result was a calculation or not. 491 const char* query; 492 int64_t excludingCacheIdentifier = cache ? cache->storageID() : 0; 493 if (excludingCacheIdentifier != 0) { 494 query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)" 495 " FROM CacheGroups" 496 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" 497 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" 498 " WHERE Origins.origin=?" 499 " AND Caches.id!=?"; 500 } else { 501 query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)" 502 " FROM CacheGroups" 503 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" 504 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" 505 " WHERE Origins.origin=?"; 506 } 507 508 SQLiteStatement statement(m_database, query); 509 if (statement.prepare() != SQLResultOk) 510 return false; 511 512 statement.bindText(1, origin->databaseIdentifier()); 513 if (excludingCacheIdentifier != 0) 514 statement.bindInt64(2, excludingCacheIdentifier); 515 int result = statement.step(); 516 517 // If the count was 0 that then we have to query the origin table directly 518 // for its quota. Otherwise we can use the calculated value. 519 if (result == SQLResultRow) { 520 int64_t numberOfCaches = statement.getColumnInt64(0); 521 if (numberOfCaches == 0) 522 quotaForOrigin(origin, remainingSize); 523 else 524 remainingSize = statement.getColumnInt64(1); 525 return true; 526 } 527 528 LOG_ERROR("Could not get the remaining size of an origin's quota, error \"%s\"", m_database.lastErrorMsg()); 529 return false; 530} 531 532bool ApplicationCacheStorage::storeUpdatedQuotaForOrigin(const SecurityOrigin* origin, int64_t quota) 533{ 534 openDatabase(true); 535 if (!m_database.isOpen()) 536 return false; 537 538 if (!ensureOriginRecord(origin)) 539 return false; 540 541 SQLiteStatement updateStatement(m_database, "UPDATE Origins SET quota=? WHERE origin=?"); 542 if (updateStatement.prepare() != SQLResultOk) 543 return false; 544 545 updateStatement.bindInt64(1, quota); 546 updateStatement.bindText(2, origin->databaseIdentifier()); 547 548 return executeStatement(updateStatement); 549} 550 551bool ApplicationCacheStorage::executeSQLCommand(const String& sql) 552{ 553 ASSERT(m_database.isOpen()); 554 555 bool result = m_database.executeCommand(sql); 556 if (!result) 557 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 558 sql.utf8().data(), m_database.lastErrorMsg()); 559 560 return result; 561} 562 563// Update the schemaVersion when the schema of any the Application Cache 564// SQLite tables changes. This allows the database to be rebuilt when 565// a new, incompatible change has been introduced to the database schema. 566static const int schemaVersion = 7; 567 568void ApplicationCacheStorage::verifySchemaVersion() 569{ 570 int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0); 571 if (version == schemaVersion) 572 return; 573 574 deleteTables(); 575 576 // Update user version. 577 SQLiteTransaction setDatabaseVersion(m_database); 578 setDatabaseVersion.begin(); 579 580 char userVersionSQL[32]; 581 int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion); 582 ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes); 583 584 SQLiteStatement statement(m_database, userVersionSQL); 585 if (statement.prepare() != SQLResultOk) 586 return; 587 588 executeStatement(statement); 589 setDatabaseVersion.commit(); 590} 591 592void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) 593{ 594 if (m_database.isOpen()) 595 return; 596 597 // The cache directory should never be null, but if it for some weird reason is we bail out. 598 if (m_cacheDirectory.isNull()) 599 return; 600 601 m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); 602 if (!createIfDoesNotExist && !fileExists(m_cacheFile)) 603 return; 604 605 makeAllDirectories(m_cacheDirectory); 606 m_database.open(m_cacheFile); 607 608 if (!m_database.isOpen()) 609 return; 610 611 verifySchemaVersion(); 612 613 // Create tables 614 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, " 615 "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER, origin TEXT)"); 616 executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)"); 617 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); 618 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); 619 executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, " 620 "cache INTEGER NOT NULL ON CONFLICT FAIL)"); 621 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)"); 622 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, " 623 "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)"); 624 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB, path TEXT)"); 625 executeSQLCommand("CREATE TABLE IF NOT EXISTS DeletedCacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT)"); 626 executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)"); 627 628 // When a cache is deleted, all its entries and its whitelist should be deleted. 629 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches" 630 " FOR EACH ROW BEGIN" 631 " DELETE FROM CacheEntries WHERE cache = OLD.id;" 632 " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;" 633 " DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;" 634 " DELETE FROM FallbackURLs WHERE cache = OLD.id;" 635 " END"); 636 637 // When a cache entry is deleted, its resource should also be deleted. 638 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries" 639 " FOR EACH ROW BEGIN" 640 " DELETE FROM CacheResources WHERE id = OLD.resource;" 641 " END"); 642 643 // When a cache resource is deleted, its data blob should also be deleted. 644 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources" 645 " FOR EACH ROW BEGIN" 646 " DELETE FROM CacheResourceData WHERE id = OLD.data;" 647 " END"); 648 649 // When a cache resource is deleted, if it contains a non-empty path, that path should 650 // be added to the DeletedCacheResources table so the flat file at that path can 651 // be deleted at a later time. 652 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDataDeleted AFTER DELETE ON CacheResourceData" 653 " FOR EACH ROW" 654 " WHEN OLD.path NOT NULL BEGIN" 655 " INSERT INTO DeletedCacheResources (path) values (OLD.path);" 656 " END"); 657} 658 659bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement) 660{ 661 bool result = statement.executeCommand(); 662 if (!result) 663 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 664 statement.query().utf8().data(), m_database.lastErrorMsg()); 665 666 return result; 667} 668 669bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal) 670{ 671 ASSERT(group->storageID() == 0); 672 ASSERT(journal); 673 674 SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL, origin) VALUES (?, ?, ?)"); 675 if (statement.prepare() != SQLResultOk) 676 return false; 677 678 statement.bindInt64(1, urlHostHash(group->manifestURL())); 679 statement.bindText(2, group->manifestURL()); 680 statement.bindText(3, group->origin()->databaseIdentifier()); 681 682 if (!executeStatement(statement)) 683 return false; 684 685 unsigned groupStorageID = static_cast<unsigned>(m_database.lastInsertRowID()); 686 687 if (!ensureOriginRecord(group->origin())) 688 return false; 689 690 group->setStorageID(groupStorageID); 691 journal->add(group, 0); 692 return true; 693} 694 695bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal) 696{ 697 ASSERT(cache->storageID() == 0); 698 ASSERT(cache->group()->storageID() != 0); 699 ASSERT(storageIDJournal); 700 701 SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)"); 702 if (statement.prepare() != SQLResultOk) 703 return false; 704 705 statement.bindInt64(1, cache->group()->storageID()); 706 statement.bindInt64(2, cache->estimatedSizeInStorage()); 707 708 if (!executeStatement(statement)) 709 return false; 710 711 unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID()); 712 713 // Store all resources 714 { 715 ApplicationCache::ResourceMap::const_iterator end = cache->end(); 716 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { 717 unsigned oldStorageID = it->second->storageID(); 718 if (!store(it->second.get(), cacheStorageID)) 719 return false; 720 721 // Storing the resource succeeded. Log its old storageID in case 722 // it needs to be restored later. 723 storageIDJournal->add(it->second.get(), oldStorageID); 724 } 725 } 726 727 // Store the online whitelist 728 const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist(); 729 { 730 size_t whitelistSize = onlineWhitelist.size(); 731 for (size_t i = 0; i < whitelistSize; ++i) { 732 SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)"); 733 statement.prepare(); 734 735 statement.bindText(1, onlineWhitelist[i]); 736 statement.bindInt64(2, cacheStorageID); 737 738 if (!executeStatement(statement)) 739 return false; 740 } 741 } 742 743 // Store online whitelist wildcard flag. 744 { 745 SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)"); 746 statement.prepare(); 747 748 statement.bindInt64(1, cache->allowsAllNetworkRequests()); 749 statement.bindInt64(2, cacheStorageID); 750 751 if (!executeStatement(statement)) 752 return false; 753 } 754 755 // Store fallback URLs. 756 const FallbackURLVector& fallbackURLs = cache->fallbackURLs(); 757 { 758 size_t fallbackCount = fallbackURLs.size(); 759 for (size_t i = 0; i < fallbackCount; ++i) { 760 SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)"); 761 statement.prepare(); 762 763 statement.bindText(1, fallbackURLs[i].first); 764 statement.bindText(2, fallbackURLs[i].second); 765 statement.bindInt64(3, cacheStorageID); 766 767 if (!executeStatement(statement)) 768 return false; 769 } 770 } 771 772 cache->setStorageID(cacheStorageID); 773 return true; 774} 775 776bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID) 777{ 778 ASSERT(cacheStorageID); 779 ASSERT(!resource->storageID()); 780 781 openDatabase(true); 782 783 // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available. 784 if (!m_database.isOpen()) 785 return false; 786 787 // First, insert the data 788 SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data, path) VALUES (?, ?)"); 789 if (dataStatement.prepare() != SQLResultOk) 790 return false; 791 792 793 String fullPath; 794 if (!resource->path().isEmpty()) 795 dataStatement.bindText(2, pathGetFileName(resource->path())); 796 else if (shouldStoreResourceAsFlatFile(resource)) { 797 // First, check to see if creating the flat file would violate the maximum total quota. We don't need 798 // to check the per-origin quota here, as it was already checked in storeNewestCache(). 799 if (m_database.totalSize() + flatFileAreaSize() + resource->data()->size() > m_maximumSize) { 800 m_isMaximumSizeReached = true; 801 return false; 802 } 803 804 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory); 805 makeAllDirectories(flatFileDirectory); 806 String path; 807 if (!writeDataToUniqueFileInDirectory(resource->data(), flatFileDirectory, path)) 808 return false; 809 810 fullPath = pathByAppendingComponent(flatFileDirectory, path); 811 resource->setPath(fullPath); 812 dataStatement.bindText(2, path); 813 } else { 814 if (resource->data()->size()) 815 dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size()); 816 } 817 818 if (!dataStatement.executeCommand()) { 819 // Clean up the file which we may have written to: 820 if (!fullPath.isEmpty()) 821 deleteFile(fullPath); 822 823 return false; 824 } 825 826 unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID()); 827 828 // Then, insert the resource 829 830 // Serialize the headers 831 Vector<UChar> stringBuilder; 832 833 HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end(); 834 for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) { 835 stringBuilder.append(it->first.characters(), it->first.length()); 836 stringBuilder.append((UChar)':'); 837 stringBuilder.append(it->second.characters(), it->second.length()); 838 stringBuilder.append((UChar)'\n'); 839 } 840 841 String headers = String::adopt(stringBuilder); 842 843 SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)"); 844 if (resourceStatement.prepare() != SQLResultOk) 845 return false; 846 847 // The same ApplicationCacheResource are used in ApplicationCacheResource::size() 848 // to calculate the approximate size of an ApplicationCacheResource object. If 849 // you change the code below, please also change ApplicationCacheResource::size(). 850 resourceStatement.bindText(1, resource->url()); 851 resourceStatement.bindInt64(2, resource->response().httpStatusCode()); 852 resourceStatement.bindText(3, resource->response().url()); 853 resourceStatement.bindText(4, headers); 854 resourceStatement.bindInt64(5, dataId); 855 resourceStatement.bindText(6, resource->response().mimeType()); 856 resourceStatement.bindText(7, resource->response().textEncodingName()); 857 858 if (!executeStatement(resourceStatement)) 859 return false; 860 861 unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID()); 862 863 // Finally, insert the cache entry 864 SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)"); 865 if (entryStatement.prepare() != SQLResultOk) 866 return false; 867 868 entryStatement.bindInt64(1, cacheStorageID); 869 entryStatement.bindInt64(2, resource->type()); 870 entryStatement.bindInt64(3, resourceId); 871 872 if (!executeStatement(entryStatement)) 873 return false; 874 875 // Did we successfully write the resource data to a file? If so, 876 // release the resource's data and free up a potentially large amount 877 // of memory: 878 if (!fullPath.isEmpty()) 879 resource->data()->clear(); 880 881 resource->setStorageID(resourceId); 882 return true; 883} 884 885bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache) 886{ 887 ASSERT_UNUSED(cache, cache->storageID()); 888 ASSERT(resource->storageID()); 889 890 // First, insert the data 891 SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?"); 892 if (entryStatement.prepare() != SQLResultOk) 893 return false; 894 895 entryStatement.bindInt64(1, resource->type()); 896 entryStatement.bindInt64(2, resource->storageID()); 897 898 return executeStatement(entryStatement); 899} 900 901bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) 902{ 903 ASSERT(cache->storageID()); 904 905 openDatabase(true); 906 907 if (!m_database.isOpen()) 908 return false; 909 910 m_isMaximumSizeReached = false; 911 m_database.setMaximumSize(m_maximumSize - flatFileAreaSize()); 912 913 SQLiteTransaction storeResourceTransaction(m_database); 914 storeResourceTransaction.begin(); 915 916 if (!store(resource, cache->storageID())) { 917 checkForMaxSizeReached(); 918 return false; 919 } 920 921 // A resource was added to the cache. Update the total data size for the cache. 922 SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?"); 923 if (sizeUpdateStatement.prepare() != SQLResultOk) 924 return false; 925 926 sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage()); 927 sizeUpdateStatement.bindInt64(2, cache->storageID()); 928 929 if (!executeStatement(sizeUpdateStatement)) 930 return false; 931 932 storeResourceTransaction.commit(); 933 return true; 934} 935 936bool ApplicationCacheStorage::ensureOriginRecord(const SecurityOrigin* origin) 937{ 938 SQLiteStatement insertOriginStatement(m_database, "INSERT INTO Origins (origin, quota) VALUES (?, ?)"); 939 if (insertOriginStatement.prepare() != SQLResultOk) 940 return false; 941 942 insertOriginStatement.bindText(1, origin->databaseIdentifier()); 943 insertOriginStatement.bindInt64(2, m_defaultOriginQuota); 944 if (!executeStatement(insertOriginStatement)) 945 return false; 946 947 return true; 948} 949 950bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, ApplicationCache* oldCache, FailureReason& failureReason) 951{ 952 openDatabase(true); 953 954 if (!m_database.isOpen()) 955 return false; 956 957 m_isMaximumSizeReached = false; 958 m_database.setMaximumSize(m_maximumSize - flatFileAreaSize()); 959 960 SQLiteTransaction storeCacheTransaction(m_database); 961 962 storeCacheTransaction.begin(); 963 964 // Check if this would reach the per-origin quota. 965 int64_t remainingSpaceInOrigin; 966 if (remainingSizeForOriginExcludingCache(group->origin(), oldCache, remainingSpaceInOrigin)) { 967 if (remainingSpaceInOrigin < group->newestCache()->estimatedSizeInStorage()) { 968 failureReason = OriginQuotaReached; 969 return false; 970 } 971 } 972 973 GroupStorageIDJournal groupStorageIDJournal; 974 if (!group->storageID()) { 975 // Store the group 976 if (!store(group, &groupStorageIDJournal)) { 977 checkForMaxSizeReached(); 978 failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure; 979 return false; 980 } 981 } 982 983 ASSERT(group->newestCache()); 984 ASSERT(!group->isObsolete()); 985 ASSERT(!group->newestCache()->storageID()); 986 987 // Log the storageID changes to the in-memory resource objects. The journal 988 // object will roll them back automatically in case a database operation 989 // fails and this method returns early. 990 ResourceStorageIDJournal resourceStorageIDJournal; 991 992 // Store the newest cache 993 if (!store(group->newestCache(), &resourceStorageIDJournal)) { 994 checkForMaxSizeReached(); 995 failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure; 996 return false; 997 } 998 999 // Update the newest cache in the group. 1000 1001 SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?"); 1002 if (statement.prepare() != SQLResultOk) { 1003 failureReason = DiskOrOperationFailure; 1004 return false; 1005 } 1006 1007 statement.bindInt64(1, group->newestCache()->storageID()); 1008 statement.bindInt64(2, group->storageID()); 1009 1010 if (!executeStatement(statement)) { 1011 failureReason = DiskOrOperationFailure; 1012 return false; 1013 } 1014 1015 groupStorageIDJournal.commit(); 1016 resourceStorageIDJournal.commit(); 1017 storeCacheTransaction.commit(); 1018 return true; 1019} 1020 1021bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) 1022{ 1023 // Ignore the reason for failing, just attempt the store. 1024 FailureReason ignoredFailureReason; 1025 return storeNewestCache(group, 0, ignoredFailureReason); 1026} 1027 1028static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response) 1029{ 1030 size_t pos = find(header, headerLength, ':'); 1031 ASSERT(pos != notFound); 1032 1033 AtomicString headerName = AtomicString(header, pos); 1034 String headerValue = String(header + pos + 1, headerLength - pos - 1); 1035 1036 response.setHTTPHeaderField(headerName, headerValue); 1037} 1038 1039static inline void parseHeaders(const String& headers, ResourceResponse& response) 1040{ 1041 unsigned startPos = 0; 1042 size_t endPos; 1043 while ((endPos = headers.find('\n', startPos)) != notFound) { 1044 ASSERT(startPos != endPos); 1045 1046 parseHeader(headers.characters() + startPos, endPos - startPos, response); 1047 1048 startPos = endPos + 1; 1049 } 1050 1051 if (startPos != headers.length()) 1052 parseHeader(headers.characters(), headers.length(), response); 1053} 1054 1055PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID) 1056{ 1057 SQLiteStatement cacheStatement(m_database, 1058 "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data, CacheResourceData.path FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id " 1059 "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?"); 1060 if (cacheStatement.prepare() != SQLResultOk) { 1061 LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg()); 1062 return 0; 1063 } 1064 1065 cacheStatement.bindInt64(1, storageID); 1066 1067 RefPtr<ApplicationCache> cache = ApplicationCache::create(); 1068 1069 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory); 1070 1071 int result; 1072 while ((result = cacheStatement.step()) == SQLResultRow) { 1073 KURL url(ParsedURLString, cacheStatement.getColumnText(0)); 1074 1075 unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(1)); 1076 1077 Vector<char> blob; 1078 cacheStatement.getColumnBlobAsVector(5, blob); 1079 1080 RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob); 1081 1082 String path = cacheStatement.getColumnText(6); 1083 long long size = 0; 1084 if (path.isEmpty()) 1085 size = data->size(); 1086 else { 1087 path = pathByAppendingComponent(flatFileDirectory, path); 1088 getFileSize(path, size); 1089 } 1090 1091 String mimeType = cacheStatement.getColumnText(2); 1092 String textEncodingName = cacheStatement.getColumnText(3); 1093 1094 ResourceResponse response(url, mimeType, size, textEncodingName, ""); 1095 1096 String headers = cacheStatement.getColumnText(4); 1097 parseHeaders(headers, response); 1098 1099 RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release(), path); 1100 1101 if (type & ApplicationCacheResource::Manifest) 1102 cache->setManifestResource(resource.release()); 1103 else 1104 cache->addResource(resource.release()); 1105 } 1106 1107 if (result != SQLResultDone) 1108 LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg()); 1109 1110 // Load the online whitelist 1111 SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?"); 1112 if (whitelistStatement.prepare() != SQLResultOk) 1113 return 0; 1114 whitelistStatement.bindInt64(1, storageID); 1115 1116 Vector<KURL> whitelist; 1117 while ((result = whitelistStatement.step()) == SQLResultRow) 1118 whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0))); 1119 1120 if (result != SQLResultDone) 1121 LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg()); 1122 1123 cache->setOnlineWhitelist(whitelist); 1124 1125 // Load online whitelist wildcard flag. 1126 SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?"); 1127 if (whitelistWildcardStatement.prepare() != SQLResultOk) 1128 return 0; 1129 whitelistWildcardStatement.bindInt64(1, storageID); 1130 1131 result = whitelistWildcardStatement.step(); 1132 if (result != SQLResultRow) 1133 LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg()); 1134 1135 cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0)); 1136 1137 if (whitelistWildcardStatement.step() != SQLResultDone) 1138 LOG_ERROR("Too many rows for online whitelist wildcard flag"); 1139 1140 // Load fallback URLs. 1141 SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?"); 1142 if (fallbackStatement.prepare() != SQLResultOk) 1143 return 0; 1144 fallbackStatement.bindInt64(1, storageID); 1145 1146 FallbackURLVector fallbackURLs; 1147 while ((result = fallbackStatement.step()) == SQLResultRow) 1148 fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1)))); 1149 1150 if (result != SQLResultDone) 1151 LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg()); 1152 1153 cache->setFallbackURLs(fallbackURLs); 1154 1155 cache->setStorageID(storageID); 1156 1157 return cache.release(); 1158} 1159 1160void ApplicationCacheStorage::remove(ApplicationCache* cache) 1161{ 1162 if (!cache->storageID()) 1163 return; 1164 1165 openDatabase(false); 1166 if (!m_database.isOpen()) 1167 return; 1168 1169 ASSERT(cache->group()); 1170 ASSERT(cache->group()->storageID()); 1171 1172 // All associated data will be deleted by database triggers. 1173 SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?"); 1174 if (statement.prepare() != SQLResultOk) 1175 return; 1176 1177 statement.bindInt64(1, cache->storageID()); 1178 executeStatement(statement); 1179 1180 cache->clearStorageID(); 1181 1182 if (cache->group()->newestCache() == cache) { 1183 // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above. 1184 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); 1185 if (groupStatement.prepare() != SQLResultOk) 1186 return; 1187 1188 groupStatement.bindInt64(1, cache->group()->storageID()); 1189 executeStatement(groupStatement); 1190 1191 cache->group()->clearStorageID(); 1192 } 1193 1194 checkForDeletedResources(); 1195} 1196 1197void ApplicationCacheStorage::empty() 1198{ 1199 openDatabase(false); 1200 1201 if (!m_database.isOpen()) 1202 return; 1203 1204 // Clear cache groups, caches, cache resources, and origins. 1205 executeSQLCommand("DELETE FROM CacheGroups"); 1206 executeSQLCommand("DELETE FROM Caches"); 1207 executeSQLCommand("DELETE FROM Origins"); 1208 1209 // Clear the storage IDs for the caches in memory. 1210 // The caches will still work, but cached resources will not be saved to disk 1211 // until a cache update process has been initiated. 1212 CacheGroupMap::const_iterator end = m_cachesInMemory.end(); 1213 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) 1214 it->second->clearStorageID(); 1215 1216 checkForDeletedResources(); 1217} 1218 1219void ApplicationCacheStorage::deleteTables() 1220{ 1221 empty(); 1222 m_database.clearAllTables(); 1223} 1224 1225bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource) 1226{ 1227 return resource->response().mimeType().startsWith("audio/", false) 1228 || resource->response().mimeType().startsWith("video/", false); 1229} 1230 1231bool ApplicationCacheStorage::writeDataToUniqueFileInDirectory(SharedBuffer* data, const String& directory, String& path) 1232{ 1233 String fullPath; 1234 1235 do { 1236 path = encodeForFileName(createCanonicalUUIDString()); 1237 // Guard against the above function being called on a platform which does not implement 1238 // createCanonicalUUIDString(). 1239 ASSERT(!path.isEmpty()); 1240 if (path.isEmpty()) 1241 return false; 1242 1243 fullPath = pathByAppendingComponent(directory, path); 1244 } while (directoryName(fullPath) != directory || fileExists(fullPath)); 1245 1246 PlatformFileHandle handle = openFile(fullPath, OpenForWrite); 1247 if (!handle) 1248 return false; 1249 1250 int64_t writtenBytes = writeToFile(handle, data->data(), data->size()); 1251 closeFile(handle); 1252 1253 if (writtenBytes != static_cast<int64_t>(data->size())) { 1254 deleteFile(fullPath); 1255 return false; 1256 } 1257 1258 return true; 1259} 1260 1261bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost) 1262{ 1263 ApplicationCache* cache = cacheHost->applicationCache(); 1264 if (!cache) 1265 return true; 1266 1267 // Create a new cache. 1268 RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create(); 1269 1270 cacheCopy->setOnlineWhitelist(cache->onlineWhitelist()); 1271 cacheCopy->setFallbackURLs(cache->fallbackURLs()); 1272 1273 // Traverse the cache and add copies of all resources. 1274 ApplicationCache::ResourceMap::const_iterator end = cache->end(); 1275 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { 1276 ApplicationCacheResource* resource = it->second.get(); 1277 1278 RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data(), resource->path()); 1279 1280 cacheCopy->addResource(resourceCopy.release()); 1281 } 1282 1283 // Now create a new cache group. 1284 OwnPtr<ApplicationCacheGroup> groupCopy(adoptPtr(new ApplicationCacheGroup(cache->group()->manifestURL(), true))); 1285 1286 groupCopy->setNewestCache(cacheCopy); 1287 1288 ApplicationCacheStorage copyStorage; 1289 copyStorage.setCacheDirectory(cacheDirectory); 1290 1291 // Empty the cache in case something was there before. 1292 copyStorage.empty(); 1293 1294 return copyStorage.storeNewestCache(groupCopy.get()); 1295} 1296 1297bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls) 1298{ 1299 ASSERT(urls); 1300 openDatabase(false); 1301 if (!m_database.isOpen()) 1302 return false; 1303 1304 SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups"); 1305 1306 if (selectURLs.prepare() != SQLResultOk) 1307 return false; 1308 1309 while (selectURLs.step() == SQLResultRow) 1310 urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0))); 1311 1312 return true; 1313} 1314 1315bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size) 1316{ 1317 ASSERT(size); 1318 openDatabase(false); 1319 if (!m_database.isOpen()) 1320 return false; 1321 1322 SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?"); 1323 if (statement.prepare() != SQLResultOk) 1324 return false; 1325 1326 statement.bindText(1, manifestURL); 1327 1328 int result = statement.step(); 1329 if (result == SQLResultDone) 1330 return false; 1331 1332 if (result != SQLResultRow) { 1333 LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg()); 1334 return false; 1335 } 1336 1337 *size = statement.getColumnInt64(0); 1338 return true; 1339} 1340 1341bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL) 1342{ 1343 SQLiteTransaction deleteTransaction(m_database); 1344 // Check to see if the group is in memory. 1345 ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL); 1346 if (group) 1347 cacheGroupMadeObsolete(group); 1348 else { 1349 // The cache group is not in memory, so remove it from the disk. 1350 openDatabase(false); 1351 if (!m_database.isOpen()) 1352 return false; 1353 1354 SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?"); 1355 if (idStatement.prepare() != SQLResultOk) 1356 return false; 1357 1358 idStatement.bindText(1, manifestURL); 1359 1360 int result = idStatement.step(); 1361 if (result == SQLResultDone) 1362 return false; 1363 1364 if (result != SQLResultRow) { 1365 LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg()); 1366 return false; 1367 } 1368 1369 int64_t groupId = idStatement.getColumnInt64(0); 1370 1371 SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?"); 1372 if (cacheStatement.prepare() != SQLResultOk) 1373 return false; 1374 1375 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); 1376 if (groupStatement.prepare() != SQLResultOk) 1377 return false; 1378 1379 cacheStatement.bindInt64(1, groupId); 1380 executeStatement(cacheStatement); 1381 groupStatement.bindInt64(1, groupId); 1382 executeStatement(groupStatement); 1383 } 1384 1385 deleteTransaction.commit(); 1386 1387 checkForDeletedResources(); 1388 1389 return true; 1390} 1391 1392void ApplicationCacheStorage::vacuumDatabaseFile() 1393{ 1394 openDatabase(false); 1395 if (!m_database.isOpen()) 1396 return; 1397 1398 m_database.runVacuumCommand(); 1399} 1400 1401void ApplicationCacheStorage::checkForMaxSizeReached() 1402{ 1403 if (m_database.lastError() == SQLResultFull) 1404 m_isMaximumSizeReached = true; 1405} 1406 1407void ApplicationCacheStorage::checkForDeletedResources() 1408{ 1409 openDatabase(false); 1410 if (!m_database.isOpen()) 1411 return; 1412 1413 // Select only the paths in DeletedCacheResources that do not also appear in CacheResourceData: 1414 SQLiteStatement selectPaths(m_database, "SELECT DeletedCacheResources.path " 1415 "FROM DeletedCacheResources " 1416 "LEFT JOIN CacheResourceData " 1417 "ON DeletedCacheResources.path = CacheResourceData.path " 1418 "WHERE (SELECT DeletedCacheResources.path == CacheResourceData.path) IS NULL"); 1419 1420 if (selectPaths.prepare() != SQLResultOk) 1421 return; 1422 1423 if (selectPaths.step() != SQLResultRow) 1424 return; 1425 1426 do { 1427 String path = selectPaths.getColumnText(0); 1428 if (path.isEmpty()) 1429 continue; 1430 1431 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory); 1432 String fullPath = pathByAppendingComponent(flatFileDirectory, path); 1433 1434 // Don't exit the flatFileDirectory! This should only happen if the "path" entry contains a directory 1435 // component, but protect against it regardless. 1436 if (directoryName(fullPath) != flatFileDirectory) 1437 continue; 1438 1439 deleteFile(fullPath); 1440 } while (selectPaths.step() == SQLResultRow); 1441 1442 executeSQLCommand("DELETE FROM DeletedCacheResources"); 1443} 1444 1445long long ApplicationCacheStorage::flatFileAreaSize() 1446{ 1447 openDatabase(false); 1448 if (!m_database.isOpen()) 1449 return 0; 1450 1451 SQLiteStatement selectPaths(m_database, "SELECT path FROM CacheResourceData WHERE path NOT NULL"); 1452 1453 if (selectPaths.prepare() != SQLResultOk) { 1454 LOG_ERROR("Could not load flat file cache resource data, error \"%s\"", m_database.lastErrorMsg()); 1455 return 0; 1456 } 1457 1458 long long totalSize = 0; 1459 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory); 1460 while (selectPaths.step() == SQLResultRow) { 1461 String path = selectPaths.getColumnText(0); 1462 String fullPath = pathByAppendingComponent(flatFileDirectory, path); 1463 long long pathSize = 0; 1464 if (!getFileSize(fullPath, pathSize)) 1465 continue; 1466 totalSize += pathSize; 1467 } 1468 1469 return totalSize; 1470} 1471 1472void ApplicationCacheStorage::getOriginsWithCache(HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash>& origins) 1473{ 1474 Vector<KURL> urls; 1475 if (!manifestURLs(&urls)) { 1476 LOG_ERROR("Failed to retrieve ApplicationCache manifest URLs"); 1477 return; 1478 } 1479 1480 // Multiple manifest URLs might share the same SecurityOrigin, so we might be creating extra, wasted origins here. 1481 // The current schema doesn't allow for a more efficient way of building this list. 1482 size_t count = urls.size(); 1483 for (size_t i = 0; i < count; ++i) { 1484 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(urls[i]); 1485 origins.add(origin); 1486 } 1487} 1488 1489void ApplicationCacheStorage::deleteAllEntries() 1490{ 1491 empty(); 1492 vacuumDatabaseFile(); 1493} 1494 1495ApplicationCacheStorage::ApplicationCacheStorage() 1496 : m_maximumSize(ApplicationCacheStorage::noQuota()) 1497 , m_isMaximumSizeReached(false) 1498 , m_defaultOriginQuota(ApplicationCacheStorage::noQuota()) 1499{ 1500} 1501 1502ApplicationCacheStorage& cacheStorage() 1503{ 1504 DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ()); 1505 1506 return storage; 1507} 1508 1509} // namespace WebCore 1510 1511#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) 1512