Remove ResourceResponse::m_suggestedFilename
[WebKit-https.git] / Source / WebCore / loader / appcache / ApplicationCacheStorage.cpp
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
46 namespace WebCore {
47
48 static const char flatFileSubdirectory[] = "ApplicationCache";
49
50 template <class T>
51 class StorageIDJournal {
52 public:  
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
70 private:
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
89 static 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
102 ApplicationCacheGroup* 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
139 ApplicationCacheGroup* 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
164 ApplicationCacheGroup* ApplicationCacheStorage::findInMemoryCacheGroup(const URL& manifestURL) const
165 {
166     return m_cachesInMemory.get(manifestURL);
167 }
168
169 void 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
195 ApplicationCacheGroup* 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
271 ApplicationCacheGroup* 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
341 void 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
358 void 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
370 void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
371 {
372     ASSERT(m_cacheDirectory.isNull());
373     ASSERT(!cacheDirectory.isNull());
374     
375     m_cacheDirectory = cacheDirectory;
376 }
377
378 const String& ApplicationCacheStorage::cacheDirectory() const
379 {
380     return m_cacheDirectory;
381 }
382
383 void ApplicationCacheStorage::setMaximumSize(int64_t size)
384 {
385     m_maximumSize = size;
386 }
387
388 int64_t ApplicationCacheStorage::maximumSize() const
389 {
390     return m_maximumSize;
391 }
392
393 bool ApplicationCacheStorage::isMaximumSizeReached() const
394 {
395     return m_isMaximumSizeReached;
396 }
397
398 int64_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
436 void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota)
437 {
438     m_defaultOriginQuota = quota;
439 }
440
441 bool 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
466 bool 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
492 bool 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
543 bool 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
564 bool 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.
580 static const int schemaVersion = 7;
581     
582 void 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     
608 void 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
677 bool 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
688 bool 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
721 bool 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
800 bool 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
917 bool 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
935 bool 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
972 bool 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
987 bool 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
1009 bool 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
1078 bool 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
1085 template <typename CharacterType>
1086 static 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
1097 static 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     
1120 PassRefPtr<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     
1234 void 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
1273 void 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     
1296 void ApplicationCacheStorage::deleteTables()
1297 {
1298     empty();
1299     m_database.clearAllTables();
1300 }
1301     
1302 bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource)
1303 {
1304     return resource->response().mimeType().startsWith("audio/", false) 
1305         || resource->response().mimeType().startsWith("video/", false);
1306 }
1307     
1308 bool 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
1338 bool 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
1373 bool 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
1393 bool 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
1421 bool 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
1451 bool 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
1478 void ApplicationCacheStorage::vacuumDatabaseFile()
1479 {
1480     SQLiteTransactionInProgressAutoCounter transactionCounter;
1481
1482     openDatabase(false);
1483     if (!m_database.isOpen())
1484         return;
1485
1486     m_database.runVacuumCommand();
1487 }
1488
1489 void ApplicationCacheStorage::checkForMaxSizeReached()
1490 {
1491     if (m_database.lastError() == SQLResultFull)
1492         m_isMaximumSizeReached = true;
1493 }
1494     
1495 void 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     
1533 long 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
1560 void 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
1577 void ApplicationCacheStorage::deleteAllEntries()
1578 {
1579     empty();
1580     vacuumDatabaseFile();
1581 }
1582
1583 ApplicationCacheStorage::ApplicationCacheStorage() 
1584     : m_maximumSize(ApplicationCacheStorage::noQuota())
1585     , m_isMaximumSizeReached(false)
1586     , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
1587 {
1588 }
1589
1590 ApplicationCacheStorage& cacheStorage()
1591 {
1592     static NeverDestroyed<ApplicationCacheStorage> storage;
1593     return storage;
1594 }
1595
1596 } // namespace WebCore