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