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