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