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