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