Remove more unnecessary #if PLATFORM(IOS)s in ApplicationCacheStorage.cpp
[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     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
570     ASSERT(m_database.isOpen());
571     
572     bool result = m_database.executeCommand(sql);
573     if (!result)
574         LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 
575                   sql.utf8().data(), m_database.lastErrorMsg());
576
577     return result;
578 }
579
580 // Update the schemaVersion when the schema of any the Application Cache
581 // SQLite tables changes. This allows the database to be rebuilt when
582 // a new, incompatible change has been introduced to the database schema.
583 static const int schemaVersion = 7;
584     
585 void ApplicationCacheStorage::verifySchemaVersion()
586 {
587     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
588
589     int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0);
590     if (version == schemaVersion)
591         return;
592
593     deleteTables();
594
595     // Update user version.
596     SQLiteTransaction setDatabaseVersion(m_database);
597     setDatabaseVersion.begin();
598
599     char userVersionSQL[32];
600     int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion);
601     ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes);
602
603     SQLiteStatement statement(m_database, userVersionSQL);
604     if (statement.prepare() != SQLResultOk)
605         return;
606     
607     executeStatement(statement);
608     setDatabaseVersion.commit();
609 }
610     
611 void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
612 {
613     SQLiteTransactionInProgressAutoCounter transactionCounter;
614
615     if (m_database.isOpen())
616         return;
617
618     // The cache directory should never be null, but if it for some weird reason is we bail out.
619     if (m_cacheDirectory.isNull())
620         return;
621
622     m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db");
623     if (!createIfDoesNotExist && !fileExists(m_cacheFile))
624         return;
625
626     makeAllDirectories(m_cacheDirectory);
627     m_database.open(m_cacheFile);
628     
629     if (!m_database.isOpen())
630         return;
631     
632     verifySchemaVersion();
633     
634     // Create tables
635     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, "
636                       "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER, origin TEXT)");
637     executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)");
638     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
639     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
640     executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "
641                       "cache INTEGER NOT NULL ON CONFLICT FAIL)");
642     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)");
643     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, "
644                       "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)");
645     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB, path TEXT)");
646     executeSQLCommand("CREATE TABLE IF NOT EXISTS DeletedCacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT)");
647     executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)");
648
649     // When a cache is deleted, all its entries and its whitelist should be deleted.
650     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches"
651                       " FOR EACH ROW BEGIN"
652                       "  DELETE FROM CacheEntries WHERE cache = OLD.id;"
653                       "  DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;"
654                       "  DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;"
655                       "  DELETE FROM FallbackURLs WHERE cache = OLD.id;"
656                       " END");
657
658     // When a cache entry is deleted, its resource should also be deleted.
659     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries"
660                       " FOR EACH ROW BEGIN"
661                       "  DELETE FROM CacheResources WHERE id = OLD.resource;"
662                       " END");
663
664     // When a cache resource is deleted, its data blob should also be deleted.
665     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources"
666                       " FOR EACH ROW BEGIN"
667                       "  DELETE FROM CacheResourceData WHERE id = OLD.data;"
668                       " END");
669     
670     // When a cache resource is deleted, if it contains a non-empty path, that path should
671     // be added to the DeletedCacheResources table so the flat file at that path can
672     // be deleted at a later time.
673     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDataDeleted AFTER DELETE ON CacheResourceData"
674                       " FOR EACH ROW"
675                       " WHEN OLD.path NOT NULL BEGIN"
676                       "  INSERT INTO DeletedCacheResources (path) values (OLD.path);"
677                       " END");
678 }
679
680 bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
681 {
682     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
683     bool result = statement.executeCommand();
684     if (!result)
685         LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", 
686                   statement.query().utf8().data(), m_database.lastErrorMsg());
687     
688     return result;
689 }    
690
691 bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal)
692 {
693     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
694     ASSERT(group->storageID() == 0);
695     ASSERT(journal);
696
697     SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL, origin) VALUES (?, ?, ?)");
698     if (statement.prepare() != SQLResultOk)
699         return false;
700
701     statement.bindInt64(1, urlHostHash(group->manifestURL()));
702     statement.bindText(2, group->manifestURL());
703     statement.bindText(3, group->origin()->databaseIdentifier());
704
705     if (!executeStatement(statement))
706         return false;
707
708     unsigned groupStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
709
710     if (!ensureOriginRecord(group->origin()))
711         return false;
712
713     group->setStorageID(groupStorageID);
714     journal->add(group, 0);
715     return true;
716 }    
717
718 bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal)
719 {
720     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
721     ASSERT(cache->storageID() == 0);
722     ASSERT(cache->group()->storageID() != 0);
723     ASSERT(storageIDJournal);
724     
725     SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)");
726     if (statement.prepare() != SQLResultOk)
727         return false;
728
729     statement.bindInt64(1, cache->group()->storageID());
730     statement.bindInt64(2, cache->estimatedSizeInStorage());
731
732     if (!executeStatement(statement))
733         return false;
734     
735     unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
736
737     // Store all resources
738     {
739         ApplicationCache::ResourceMap::const_iterator end = cache->end();
740         for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
741             unsigned oldStorageID = it->value->storageID();
742             if (!store(it->value.get(), cacheStorageID))
743                 return false;
744
745             // Storing the resource succeeded. Log its old storageID in case
746             // it needs to be restored later.
747             storageIDJournal->add(it->value.get(), oldStorageID);
748         }
749     }
750     
751     // Store the online whitelist
752     const Vector<URL>& onlineWhitelist = cache->onlineWhitelist();
753     {
754         size_t whitelistSize = onlineWhitelist.size();
755         for (size_t i = 0; i < whitelistSize; ++i) {
756             SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)");
757             statement.prepare();
758
759             statement.bindText(1, onlineWhitelist[i]);
760             statement.bindInt64(2, cacheStorageID);
761
762             if (!executeStatement(statement))
763                 return false;
764         }
765     }
766
767     // Store online whitelist wildcard flag.
768     {
769         SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)");
770         statement.prepare();
771
772         statement.bindInt64(1, cache->allowsAllNetworkRequests());
773         statement.bindInt64(2, cacheStorageID);
774
775         if (!executeStatement(statement))
776             return false;
777     }
778     
779     // Store fallback URLs.
780     const FallbackURLVector& fallbackURLs = cache->fallbackURLs();
781     {
782         size_t fallbackCount = fallbackURLs.size();
783         for (size_t i = 0; i < fallbackCount; ++i) {
784             SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)");
785             statement.prepare();
786
787             statement.bindText(1, fallbackURLs[i].first);
788             statement.bindText(2, fallbackURLs[i].second);
789             statement.bindInt64(3, cacheStorageID);
790
791             if (!executeStatement(statement))
792                 return false;
793         }
794     }
795
796     cache->setStorageID(cacheStorageID);
797     return true;
798 }
799
800 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID)
801 {
802     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
803     ASSERT(cacheStorageID);
804     ASSERT(!resource->storageID());
805     
806     openDatabase(true);
807
808     // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available.
809     if (!m_database.isOpen())
810         return false;
811
812     // First, insert the data
813     SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data, path) VALUES (?, ?)");
814     if (dataStatement.prepare() != SQLResultOk)
815         return false;
816     
817
818     String fullPath;
819     if (!resource->path().isEmpty())
820         dataStatement.bindText(2, pathGetFileName(resource->path()));
821     else if (shouldStoreResourceAsFlatFile(resource)) {
822         // First, check to see if creating the flat file would violate the maximum total quota. We don't need
823         // to check the per-origin quota here, as it was already checked in storeNewestCache().
824         if (m_database.totalSize() + flatFileAreaSize() + resource->data()->size() > m_maximumSize) {
825             m_isMaximumSizeReached = true;
826             return false;
827         }
828         
829         String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
830         makeAllDirectories(flatFileDirectory);
831
832         String extension;
833         
834         String fileName = resource->response().suggestedFilename();
835         size_t dotIndex = fileName.reverseFind('.');
836         if (dotIndex != notFound && dotIndex < (fileName.length() - 1))
837             extension = fileName.substring(dotIndex);
838
839         String path;
840         if (!writeDataToUniqueFileInDirectory(resource->data(), flatFileDirectory, path, extension))
841             return false;
842         
843         fullPath = pathByAppendingComponent(flatFileDirectory, path);
844         resource->setPath(fullPath);
845         dataStatement.bindText(2, path);
846     } else {
847         if (resource->data()->size())
848             dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
849     }
850     
851     if (!dataStatement.executeCommand()) {
852         // Clean up the file which we may have written to:
853         if (!fullPath.isEmpty())
854             deleteFile(fullPath);
855
856         return false;
857     }
858
859     unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
860
861     // Then, insert the resource
862     
863     // Serialize the headers
864     StringBuilder stringBuilder;
865     
866     HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end();
867     for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) {
868         stringBuilder.append(it->key);
869         stringBuilder.append(':');
870         stringBuilder.append(it->value);
871         stringBuilder.append('\n');
872     }
873     
874     String headers = stringBuilder.toString();
875     
876     SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)");
877     if (resourceStatement.prepare() != SQLResultOk)
878         return false;
879     
880     // The same ApplicationCacheResource are used in ApplicationCacheResource::size()
881     // to calculate the approximate size of an ApplicationCacheResource object. If
882     // you change the code below, please also change ApplicationCacheResource::size().
883     resourceStatement.bindText(1, resource->url());
884     resourceStatement.bindInt64(2, resource->response().httpStatusCode());
885     resourceStatement.bindText(3, resource->response().url());
886     resourceStatement.bindText(4, headers);
887     resourceStatement.bindInt64(5, dataId);
888     resourceStatement.bindText(6, resource->response().mimeType());
889     resourceStatement.bindText(7, resource->response().textEncodingName());
890
891     if (!executeStatement(resourceStatement))
892         return false;
893
894     unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID());
895     
896     // Finally, insert the cache entry
897     SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)");
898     if (entryStatement.prepare() != SQLResultOk)
899         return false;
900     
901     entryStatement.bindInt64(1, cacheStorageID);
902     entryStatement.bindInt64(2, resource->type());
903     entryStatement.bindInt64(3, resourceId);
904     
905     if (!executeStatement(entryStatement))
906         return false;
907     
908     // Did we successfully write the resource data to a file? If so,
909     // release the resource's data and free up a potentially large amount
910     // of memory:
911     if (!fullPath.isEmpty())
912         resource->data()->clear();
913
914     resource->setStorageID(resourceId);
915     return true;
916 }
917
918 bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache)
919 {
920     SQLiteTransactionInProgressAutoCounter transactionCounter;
921
922     ASSERT_UNUSED(cache, cache->storageID());
923     ASSERT(resource->storageID());
924
925     // First, insert the data
926     SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?");
927     if (entryStatement.prepare() != SQLResultOk)
928         return false;
929
930     entryStatement.bindInt64(1, resource->type());
931     entryStatement.bindInt64(2, resource->storageID());
932
933     return executeStatement(entryStatement);
934 }
935
936 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)
937 {
938     SQLiteTransactionInProgressAutoCounter transactionCounter;
939
940     ASSERT(cache->storageID());
941     
942     openDatabase(true);
943
944     if (!m_database.isOpen())
945         return false;
946  
947     m_isMaximumSizeReached = false;
948     m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
949
950     SQLiteTransaction storeResourceTransaction(m_database);
951     storeResourceTransaction.begin();
952     
953     if (!store(resource, cache->storageID())) {
954         checkForMaxSizeReached();
955         return false;
956     }
957
958     // A resource was added to the cache. Update the total data size for the cache.
959     SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?");
960     if (sizeUpdateStatement.prepare() != SQLResultOk)
961         return false;
962
963     sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage());
964     sizeUpdateStatement.bindInt64(2, cache->storageID());
965
966     if (!executeStatement(sizeUpdateStatement))
967         return false;
968     
969     storeResourceTransaction.commit();
970     return true;
971 }
972
973 bool ApplicationCacheStorage::ensureOriginRecord(const SecurityOrigin* origin)
974 {
975     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
976     SQLiteStatement insertOriginStatement(m_database, "INSERT INTO Origins (origin, quota) VALUES (?, ?)");
977     if (insertOriginStatement.prepare() != SQLResultOk)
978         return false;
979
980     insertOriginStatement.bindText(1, origin->databaseIdentifier());
981     insertOriginStatement.bindInt64(2, m_defaultOriginQuota);
982     if (!executeStatement(insertOriginStatement))
983         return false;
984
985     return true;
986 }
987
988 bool ApplicationCacheStorage::checkOriginQuota(ApplicationCacheGroup* group, ApplicationCache* oldCache, ApplicationCache* newCache, int64_t& totalSpaceNeeded)
989 {
990     // Check if the oldCache with the newCache would reach the per-origin quota.
991     int64_t remainingSpaceInOrigin;
992     const SecurityOrigin* origin = group->origin();
993     if (calculateRemainingSizeForOriginExcludingCache(origin, oldCache, remainingSpaceInOrigin)) {
994         if (remainingSpaceInOrigin < newCache->estimatedSizeInStorage()) {
995             int64_t quota;
996             if (calculateQuotaForOrigin(origin, quota)) {
997                 totalSpaceNeeded = quota - remainingSpaceInOrigin + newCache->estimatedSizeInStorage();
998                 return false;
999             }
1000
1001             ASSERT_NOT_REACHED();
1002             totalSpaceNeeded = 0;
1003             return false;
1004         }
1005     }
1006
1007     return true;
1008 }
1009
1010 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, ApplicationCache* oldCache, FailureReason& failureReason)
1011 {
1012     openDatabase(true);
1013
1014     if (!m_database.isOpen())
1015         return false;
1016
1017     m_isMaximumSizeReached = false;
1018     m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
1019
1020     SQLiteTransaction storeCacheTransaction(m_database);
1021     
1022     storeCacheTransaction.begin();
1023
1024     // Check if this would reach the per-origin quota.
1025     int64_t totalSpaceNeededIgnored;
1026     if (!checkOriginQuota(group, oldCache, group->newestCache(), totalSpaceNeededIgnored)) {
1027         failureReason = OriginQuotaReached;
1028         return false;
1029     }
1030
1031     GroupStorageIDJournal groupStorageIDJournal;
1032     if (!group->storageID()) {
1033         // Store the group
1034         if (!store(group, &groupStorageIDJournal)) {
1035             checkForMaxSizeReached();
1036             failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
1037             return false;
1038         }
1039     }
1040     
1041     ASSERT(group->newestCache());
1042     ASSERT(!group->isObsolete());
1043     ASSERT(!group->newestCache()->storageID());
1044     
1045     // Log the storageID changes to the in-memory resource objects. The journal
1046     // object will roll them back automatically in case a database operation
1047     // fails and this method returns early.
1048     ResourceStorageIDJournal resourceStorageIDJournal;
1049
1050     // Store the newest cache
1051     if (!store(group->newestCache(), &resourceStorageIDJournal)) {
1052         checkForMaxSizeReached();
1053         failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
1054         return false;
1055     }
1056     
1057     // Update the newest cache in the group.
1058     
1059     SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?");
1060     if (statement.prepare() != SQLResultOk) {
1061         failureReason = DiskOrOperationFailure;
1062         return false;
1063     }
1064     
1065     statement.bindInt64(1, group->newestCache()->storageID());
1066     statement.bindInt64(2, group->storageID());
1067     
1068     if (!executeStatement(statement)) {
1069         failureReason = DiskOrOperationFailure;
1070         return false;
1071     }
1072     
1073     groupStorageIDJournal.commit();
1074     resourceStorageIDJournal.commit();
1075     storeCacheTransaction.commit();
1076     return true;
1077 }
1078
1079 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
1080 {
1081     // Ignore the reason for failing, just attempt the store.
1082     FailureReason ignoredFailureReason;
1083     return storeNewestCache(group, 0, ignoredFailureReason);
1084 }
1085
1086 template <typename CharacterType>
1087 static inline void parseHeader(const CharacterType* header, size_t headerLength, ResourceResponse& response)
1088 {
1089     size_t pos = find(header, headerLength, ':');
1090     ASSERT(pos != notFound);
1091     
1092     AtomicString headerName = AtomicString(header, pos);
1093     String headerValue = String(header + pos + 1, headerLength - pos - 1);
1094     
1095     response.setHTTPHeaderField(headerName, headerValue);
1096 }
1097
1098 static inline void parseHeaders(const String& headers, ResourceResponse& response)
1099 {
1100     unsigned startPos = 0;
1101     size_t endPos;
1102     while ((endPos = headers.find('\n', startPos)) != notFound) {
1103         ASSERT(startPos != endPos);
1104
1105         if (headers.is8Bit())
1106             parseHeader(headers.characters8() + startPos, endPos - startPos, response);
1107         else
1108             parseHeader(headers.characters16() + startPos, endPos - startPos, response);
1109         
1110         startPos = endPos + 1;
1111     }
1112     
1113     if (startPos != headers.length()) {
1114         if (headers.is8Bit())
1115             parseHeader(headers.characters8(), headers.length(), response);
1116         else
1117             parseHeader(headers.characters16(), headers.length(), response);
1118     }
1119 }
1120     
1121 PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID)
1122 {
1123     ASSERT(SQLiteDatabaseTracker::hasTransactionInProgress());
1124     SQLiteStatement cacheStatement(m_database,
1125                                    "SELECT url, statusCode, type, mimeType, textEncodingName, headers, CacheResourceData.data, CacheResourceData.path FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id "
1126                                    "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?");
1127     if (cacheStatement.prepare() != SQLResultOk) {
1128         LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg());
1129         return 0;
1130     }
1131     
1132     cacheStatement.bindInt64(1, storageID);
1133
1134     RefPtr<ApplicationCache> cache = ApplicationCache::create();
1135
1136     String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1137
1138     int result;
1139     while ((result = cacheStatement.step()) == SQLResultRow) {
1140         URL url(ParsedURLString, cacheStatement.getColumnText(0));
1141         
1142         int httpStatusCode = cacheStatement.getColumnInt(1);
1143
1144         unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(2));
1145
1146         Vector<char> blob;
1147         cacheStatement.getColumnBlobAsVector(6, blob);
1148         
1149         RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
1150         
1151         String path = cacheStatement.getColumnText(7);
1152         long long size = 0;
1153         if (path.isEmpty())
1154             size = data->size();
1155         else {
1156             path = pathByAppendingComponent(flatFileDirectory, path);
1157             getFileSize(path, size);
1158         }
1159         
1160         String mimeType = cacheStatement.getColumnText(3);
1161         String textEncodingName = cacheStatement.getColumnText(4);
1162         
1163         ResourceResponse response(url, mimeType, size, textEncodingName, "");
1164         response.setHTTPStatusCode(httpStatusCode);
1165
1166         String headers = cacheStatement.getColumnText(5);
1167         parseHeaders(headers, response);
1168         
1169         RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release(), path);
1170
1171         if (type & ApplicationCacheResource::Manifest)
1172             cache->setManifestResource(resource.release());
1173         else
1174             cache->addResource(resource.release());
1175     }
1176
1177     if (result != SQLResultDone)
1178         LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg());
1179     
1180     // Load the online whitelist
1181     SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?");
1182     if (whitelistStatement.prepare() != SQLResultOk)
1183         return 0;
1184     whitelistStatement.bindInt64(1, storageID);
1185     
1186     Vector<URL> whitelist;
1187     while ((result = whitelistStatement.step()) == SQLResultRow) 
1188         whitelist.append(URL(ParsedURLString, whitelistStatement.getColumnText(0)));
1189
1190     if (result != SQLResultDone)
1191         LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg());
1192
1193     cache->setOnlineWhitelist(whitelist);
1194
1195     // Load online whitelist wildcard flag.
1196     SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?");
1197     if (whitelistWildcardStatement.prepare() != SQLResultOk)
1198         return 0;
1199     whitelistWildcardStatement.bindInt64(1, storageID);
1200     
1201     result = whitelistWildcardStatement.step();
1202     if (result != SQLResultRow)
1203         LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg());
1204
1205     cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0));
1206
1207     if (whitelistWildcardStatement.step() != SQLResultDone)
1208         LOG_ERROR("Too many rows for online whitelist wildcard flag");
1209
1210     // Load fallback URLs.
1211     SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?");
1212     if (fallbackStatement.prepare() != SQLResultOk)
1213         return 0;
1214     fallbackStatement.bindInt64(1, storageID);
1215     
1216     FallbackURLVector fallbackURLs;
1217     while ((result = fallbackStatement.step()) == SQLResultRow) 
1218         fallbackURLs.append(std::make_pair(URL(ParsedURLString, fallbackStatement.getColumnText(0)), URL(ParsedURLString, fallbackStatement.getColumnText(1))));
1219
1220     if (result != SQLResultDone)
1221         LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg());
1222
1223     cache->setFallbackURLs(fallbackURLs);
1224     
1225     cache->setStorageID(storageID);
1226
1227     return cache.release();
1228 }    
1229     
1230 void ApplicationCacheStorage::remove(ApplicationCache* cache)
1231 {
1232     SQLiteTransactionInProgressAutoCounter transactionCounter;
1233
1234     if (!cache->storageID())
1235         return;
1236     
1237     openDatabase(false);
1238     if (!m_database.isOpen())
1239         return;
1240
1241     ASSERT(cache->group());
1242     ASSERT(cache->group()->storageID());
1243
1244     // All associated data will be deleted by database triggers.
1245     SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?");
1246     if (statement.prepare() != SQLResultOk)
1247         return;
1248     
1249     statement.bindInt64(1, cache->storageID());
1250     executeStatement(statement);
1251
1252     cache->clearStorageID();
1253
1254     if (cache->group()->newestCache() == cache) {
1255         // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above.
1256         SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
1257         if (groupStatement.prepare() != SQLResultOk)
1258             return;
1259         
1260         groupStatement.bindInt64(1, cache->group()->storageID());
1261         executeStatement(groupStatement);
1262
1263         cache->group()->clearStorageID();
1264     }
1265     
1266     checkForDeletedResources();
1267 }    
1268
1269 void ApplicationCacheStorage::empty()
1270 {
1271     SQLiteTransactionInProgressAutoCounter transactionCounter;
1272
1273     openDatabase(false);
1274     
1275     if (!m_database.isOpen())
1276         return;
1277     
1278     // Clear cache groups, caches, cache resources, and origins.
1279     executeSQLCommand("DELETE FROM CacheGroups");
1280     executeSQLCommand("DELETE FROM Caches");
1281     executeSQLCommand("DELETE FROM Origins");
1282     
1283     // Clear the storage IDs for the caches in memory.
1284     // The caches will still work, but cached resources will not be saved to disk 
1285     // until a cache update process has been initiated.
1286     CacheGroupMap::const_iterator end = m_cachesInMemory.end();
1287     for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
1288         it->value->clearStorageID();
1289     
1290     checkForDeletedResources();
1291 }
1292     
1293 void ApplicationCacheStorage::deleteTables()
1294 {
1295     empty();
1296     m_database.clearAllTables();
1297 }
1298     
1299 bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource)
1300 {
1301     return resource->response().mimeType().startsWith("audio/", false) 
1302         || resource->response().mimeType().startsWith("video/", false);
1303 }
1304     
1305 bool ApplicationCacheStorage::writeDataToUniqueFileInDirectory(SharedBuffer* data, const String& directory, String& path, const String& fileExtension)
1306 {
1307     String fullPath;
1308     
1309     do {
1310         path = encodeForFileName(createCanonicalUUIDString()) + fileExtension;
1311         // Guard against the above function being called on a platform which does not implement
1312         // createCanonicalUUIDString().
1313         ASSERT(!path.isEmpty());
1314         if (path.isEmpty())
1315             return false;
1316         
1317         fullPath = pathByAppendingComponent(directory, path);
1318     } while (directoryName(fullPath) != directory || fileExists(fullPath));
1319     
1320     PlatformFileHandle handle = openFile(fullPath, OpenForWrite);
1321     if (!handle)
1322         return false;
1323     
1324     int64_t writtenBytes = writeToFile(handle, data->data(), data->size());
1325     closeFile(handle);
1326     
1327     if (writtenBytes != static_cast<int64_t>(data->size())) {
1328         deleteFile(fullPath);
1329         return false;
1330     }
1331     
1332     return true;
1333 }
1334
1335 bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost)
1336 {
1337     SQLiteTransactionInProgressAutoCounter transactionCounter;
1338
1339     ApplicationCache* cache = cacheHost->applicationCache();
1340     if (!cache)
1341         return true;
1342
1343     // Create a new cache.
1344     RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create();
1345
1346     cacheCopy->setOnlineWhitelist(cache->onlineWhitelist());
1347     cacheCopy->setFallbackURLs(cache->fallbackURLs());
1348
1349     // Traverse the cache and add copies of all resources.
1350     ApplicationCache::ResourceMap::const_iterator end = cache->end();
1351     for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
1352         ApplicationCacheResource* resource = it->value.get();
1353         
1354         RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data(), resource->path());
1355         
1356         cacheCopy->addResource(resourceCopy.release());
1357     }
1358     
1359     // Now create a new cache group.
1360     OwnPtr<ApplicationCacheGroup> groupCopy(adoptPtr(new ApplicationCacheGroup(cache->group()->manifestURL(), true)));
1361     
1362     groupCopy->setNewestCache(cacheCopy);
1363     
1364     ApplicationCacheStorage copyStorage;
1365     copyStorage.setCacheDirectory(cacheDirectory);
1366     
1367     // Empty the cache in case something was there before.
1368     copyStorage.empty();
1369     
1370     return copyStorage.storeNewestCache(groupCopy.get());
1371 }
1372
1373 bool ApplicationCacheStorage::manifestURLs(Vector<URL>* urls)
1374 {
1375     SQLiteTransactionInProgressAutoCounter transactionCounter;
1376
1377     ASSERT(urls);
1378     openDatabase(false);
1379     if (!m_database.isOpen())
1380         return false;
1381
1382     SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups");
1383
1384     if (selectURLs.prepare() != SQLResultOk)
1385         return false;
1386
1387     while (selectURLs.step() == SQLResultRow)
1388         urls->append(URL(ParsedURLString, selectURLs.getColumnText(0)));
1389
1390     return true;
1391 }
1392
1393 bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size)
1394 {
1395     SQLiteTransactionInProgressAutoCounter transactionCounter;
1396
1397     ASSERT(size);
1398     openDatabase(false);
1399     if (!m_database.isOpen())
1400         return false;
1401
1402     SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?");
1403     if (statement.prepare() != SQLResultOk)
1404         return false;
1405
1406     statement.bindText(1, manifestURL);
1407
1408     int result = statement.step();
1409     if (result == SQLResultDone)
1410         return false;
1411
1412     if (result != SQLResultRow) {
1413         LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg());
1414         return false;
1415     }
1416
1417     *size = statement.getColumnInt64(0);
1418     return true;
1419 }
1420
1421 bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL)
1422 {
1423     SQLiteTransactionInProgressAutoCounter transactionCounter;
1424
1425     SQLiteTransaction deleteTransaction(m_database);
1426     // Check to see if the group is in memory.
1427     ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL);
1428     if (group)
1429         cacheGroupMadeObsolete(group);
1430     else {
1431         // The cache group is not in memory, so remove it from the disk.
1432         openDatabase(false);
1433         if (!m_database.isOpen())
1434             return false;
1435
1436         SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?");
1437         if (idStatement.prepare() != SQLResultOk)
1438             return false;
1439
1440         idStatement.bindText(1, manifestURL);
1441
1442         int result = idStatement.step();
1443         if (result == SQLResultDone)
1444             return false;
1445
1446         if (result != SQLResultRow) {
1447             LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg());
1448             return false;
1449         }
1450
1451         int64_t groupId = idStatement.getColumnInt64(0);
1452
1453         SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?");
1454         if (cacheStatement.prepare() != SQLResultOk)
1455             return false;
1456
1457         SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
1458         if (groupStatement.prepare() != SQLResultOk)
1459             return false;
1460
1461         cacheStatement.bindInt64(1, groupId);
1462         executeStatement(cacheStatement);
1463         groupStatement.bindInt64(1, groupId);
1464         executeStatement(groupStatement);
1465     }
1466
1467     deleteTransaction.commit();
1468     
1469     checkForDeletedResources();
1470     
1471     return true;
1472 }
1473
1474 void ApplicationCacheStorage::vacuumDatabaseFile()
1475 {
1476     SQLiteTransactionInProgressAutoCounter transactionCounter;
1477
1478     openDatabase(false);
1479     if (!m_database.isOpen())
1480         return;
1481
1482     m_database.runVacuumCommand();
1483 }
1484
1485 void ApplicationCacheStorage::checkForMaxSizeReached()
1486 {
1487     if (m_database.lastError() == SQLResultFull)
1488         m_isMaximumSizeReached = true;
1489 }
1490     
1491 void ApplicationCacheStorage::checkForDeletedResources()
1492 {
1493     openDatabase(false);
1494     if (!m_database.isOpen())
1495         return;
1496
1497     // Select only the paths in DeletedCacheResources that do not also appear in CacheResourceData:
1498     SQLiteStatement selectPaths(m_database, "SELECT DeletedCacheResources.path "
1499         "FROM DeletedCacheResources "
1500         "LEFT JOIN CacheResourceData "
1501         "ON DeletedCacheResources.path = CacheResourceData.path "
1502         "WHERE (SELECT DeletedCacheResources.path == CacheResourceData.path) IS NULL");
1503     
1504     if (selectPaths.prepare() != SQLResultOk)
1505         return;
1506     
1507     if (selectPaths.step() != SQLResultRow)
1508         return;
1509     
1510     do {
1511         String path = selectPaths.getColumnText(0);
1512         if (path.isEmpty())
1513             continue;
1514         
1515         String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1516         String fullPath = pathByAppendingComponent(flatFileDirectory, path);
1517         
1518         // Don't exit the flatFileDirectory! This should only happen if the "path" entry contains a directory 
1519         // component, but protect against it regardless.
1520         if (directoryName(fullPath) != flatFileDirectory)
1521             continue;
1522         
1523         deleteFile(fullPath);
1524     } while (selectPaths.step() == SQLResultRow);
1525     
1526     executeSQLCommand("DELETE FROM DeletedCacheResources");
1527 }
1528     
1529 long long ApplicationCacheStorage::flatFileAreaSize()
1530 {
1531     openDatabase(false);
1532     if (!m_database.isOpen())
1533         return 0;
1534     
1535     SQLiteStatement selectPaths(m_database, "SELECT path FROM CacheResourceData WHERE path NOT NULL");
1536
1537     if (selectPaths.prepare() != SQLResultOk) {
1538         LOG_ERROR("Could not load flat file cache resource data, error \"%s\"", m_database.lastErrorMsg());
1539         return 0;
1540     }
1541
1542     long long totalSize = 0;
1543     String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1544     while (selectPaths.step() == SQLResultRow) {
1545         String path = selectPaths.getColumnText(0);
1546         String fullPath = pathByAppendingComponent(flatFileDirectory, path);
1547         long long pathSize = 0;
1548         if (!getFileSize(fullPath, pathSize))
1549             continue;
1550         totalSize += pathSize;
1551     }
1552     
1553     return totalSize;
1554 }
1555
1556 void ApplicationCacheStorage::getOriginsWithCache(HashSet<RefPtr<SecurityOrigin>>& origins)
1557 {
1558     Vector<URL> urls;
1559     if (!manifestURLs(&urls)) {
1560         LOG_ERROR("Failed to retrieve ApplicationCache manifest URLs");
1561         return;
1562     }
1563
1564     // Multiple manifest URLs might share the same SecurityOrigin, so we might be creating extra, wasted origins here.
1565     // The current schema doesn't allow for a more efficient way of building this list.
1566     size_t count = urls.size();
1567     for (size_t i = 0; i < count; ++i) {
1568         RefPtr<SecurityOrigin> origin = SecurityOrigin::create(urls[i]);
1569         origins.add(origin);
1570     }
1571 }
1572
1573 void ApplicationCacheStorage::deleteAllEntries()
1574 {
1575     empty();
1576     vacuumDatabaseFile();
1577 }
1578
1579 ApplicationCacheStorage::ApplicationCacheStorage() 
1580     : m_maximumSize(ApplicationCacheStorage::noQuota())
1581     , m_isMaximumSizeReached(false)
1582     , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
1583 {
1584 }
1585
1586 ApplicationCacheStorage& cacheStorage()
1587 {
1588     DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ());
1589     
1590     return storage;
1591 }
1592
1593 } // namespace WebCore