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