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
46using namespace std;
47
48namespace WebCore {
49
50static const char flatFileSubdirectory[] = "ApplicationCache";
51
52template <class T>
53class StorageIDJournal {
54public:
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
72private:
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
91static 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
99ApplicationCacheGroup* 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
134ApplicationCacheGroup* 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
159ApplicationCacheGroup* ApplicationCacheStorage::findInMemoryCacheGroup(const KURL& manifestURL) const
160{
161    return m_cachesInMemory.get(manifestURL);
162}
163
164void 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
188ApplicationCacheGroup* 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
265ApplicationCacheGroup* 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
336void 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
353void 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
365void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
366{
367    ASSERT(m_cacheDirectory.isNull());
368    ASSERT(!cacheDirectory.isNull());
369
370    m_cacheDirectory = cacheDirectory;
371}
372
373const String& ApplicationCacheStorage::cacheDirectory() const
374{
375    return m_cacheDirectory;
376}
377
378void ApplicationCacheStorage::setMaximumSize(int64_t size)
379{
380    m_maximumSize = size;
381}
382
383int64_t ApplicationCacheStorage::maximumSize() const
384{
385    return m_maximumSize;
386}
387
388bool ApplicationCacheStorage::isMaximumSizeReached() const
389{
390    return m_isMaximumSizeReached;
391}
392
393int64_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
431void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota)
432{
433    m_defaultOriginQuota = quota;
434}
435
436bool 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
459bool 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
483bool 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
532bool 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
551bool 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.
566static const int schemaVersion = 7;
567
568void 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
592void 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
659bool 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
669bool 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
695bool 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
776bool 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
885bool 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
901bool 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
936bool 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
950bool 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
1021bool 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
1028static 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
1039static 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
1055PassRefPtr<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
1160void 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
1197void 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
1219void ApplicationCacheStorage::deleteTables()
1220{
1221    empty();
1222    m_database.clearAllTables();
1223}
1224
1225bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource)
1226{
1227    return resource->response().mimeType().startsWith("audio/", false)
1228        || resource->response().mimeType().startsWith("video/", false);
1229}
1230
1231bool 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
1261bool 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
1297bool 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
1315bool 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
1341bool 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
1392void ApplicationCacheStorage::vacuumDatabaseFile()
1393{
1394    openDatabase(false);
1395    if (!m_database.isOpen())
1396        return;
1397
1398    m_database.runVacuumCommand();
1399}
1400
1401void ApplicationCacheStorage::checkForMaxSizeReached()
1402{
1403    if (m_database.lastError() == SQLResultFull)
1404        m_isMaximumSizeReached = true;
1405}
1406
1407void 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
1445long 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
1472void 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
1489void ApplicationCacheStorage::deleteAllEntries()
1490{
1491    empty();
1492    vacuumDatabaseFile();
1493}
1494
1495ApplicationCacheStorage::ApplicationCacheStorage()
1496    : m_maximumSize(ApplicationCacheStorage::noQuota())
1497    , m_isMaximumSizeReached(false)
1498    , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
1499{
1500}
1501
1502ApplicationCacheStorage& 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