Home | History | Annotate | Download | only in appcache
      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