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