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