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