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