summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp')
-rw-r--r--src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp796
1 files changed, 796 insertions, 0 deletions
diff --git a/src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp
new file mode 100644
index 0000000..97a6d5a
--- /dev/null
+++ b/src/3rdparty/webkit/WebCore/loader/appcache/ApplicationCacheStorage.cpp
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ApplicationCacheStorage.h"
+
+#if ENABLE(OFFLINE_WEB_APPLICATIONS)
+
+#include "ApplicationCache.h"
+#include "ApplicationCacheGroup.h"
+#include "ApplicationCacheResource.h"
+#include "CString.h"
+#include "FileSystem.h"
+#include "KURL.h"
+#include "SQLiteStatement.h"
+#include "SQLiteTransaction.h"
+#include <wtf/StdLibExtras.h>
+#include <wtf/StringExtras.h>
+
+using namespace std;
+
+namespace WebCore {
+
+static unsigned urlHostHash(const KURL& url)
+{
+ unsigned hostStart = url.hostStart();
+ unsigned hostEnd = url.hostEnd();
+
+ return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL)
+{
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return 0;
+
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ statement.bindText(1, manifestURL);
+
+ int result = statement.step();
+ if (result == SQLResultDone)
+ return 0;
+
+ if (result != SQLResultRow) {
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+ return 0;
+ }
+
+ unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2));
+
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID);
+ if (!cache)
+ return 0;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ return group;
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL)
+{
+ std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0);
+
+ if (!result.second) {
+ ASSERT(result.first->second);
+
+ return result.first->second;
+ }
+
+ // Look up the group in the database
+ ApplicationCacheGroup* group = loadCacheGroup(manifestURL);
+
+ // If the group was not found we need to create it
+ if (!group) {
+ group = new ApplicationCacheGroup(manifestURL);
+ m_cacheHostSet.add(urlHostHash(manifestURL));
+ }
+
+ result.first->second = group;
+
+ return group;
+}
+
+void ApplicationCacheStorage::loadManifestHostHashes()
+{
+ static bool hasLoadedHashes = false;
+
+ if (hasLoadedHashes)
+ return;
+
+ // We set this flag to true before the database has been opened
+ // to avoid trying to open the database over and over if it doesn't exist.
+ hasLoadedHashes = true;
+
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return;
+
+ // Fetch the host hashes.
+ SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups");
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ int result;
+ while ((result = statement.step()) == SQLResultRow)
+ m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0)));
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url)
+{
+ loadManifestHostHashes();
+
+ // Hash the host name and see if there's a manifest with the same host.
+ if (!m_cacheHostSet.contains(urlHostHash(url)))
+ return 0;
+
+ // Check if a cache already exists in memory.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
+ ApplicationCacheGroup* group = it->second;
+
+ if (!protocolHostAndPortAreEqual(url, group->manifestURL()))
+ continue;
+
+ if (ApplicationCache* cache = group->newestCache()) {
+ ApplicationCacheResource* resource = cache->resourceForURL(url);
+ if (!resource)
+ continue;
+ if (resource->type() & ApplicationCacheResource::Foreign)
+ continue;
+ return group;
+ }
+ }
+
+ if (!m_database.isOpen())
+ return 0;
+
+ // Check the database. Look for all cache groups with a newest cache.
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ int result;
+ while ((result = statement.step()) == SQLResultRow) {
+ KURL manifestURL = KURL(statement.getColumnText(1));
+
+ if (m_cachesInMemory.contains(manifestURL))
+ continue;
+
+ if (!protocolHostAndPortAreEqual(url, manifestURL))
+ continue;
+
+ // We found a cache group that matches. Now check if the newest cache has a resource with
+ // a matching URL.
+ unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
+
+ ApplicationCacheResource* resource = cache->resourceForURL(url);
+ if (!resource)
+ continue;
+ if (resource->type() & ApplicationCacheResource::Foreign)
+ continue;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ m_cachesInMemory.set(group->manifestURL(), group);
+
+ return group;
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+
+ return 0;
+}
+
+ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url)
+{
+ // Check if an appropriate cache already exists in memory.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
+ ApplicationCacheGroup* group = it->second;
+
+ if (ApplicationCache* cache = group->newestCache()) {
+ KURL fallbackURL;
+ if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
+ continue;
+ if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
+ continue;
+ return group;
+ }
+ }
+
+ if (!m_database.isOpen())
+ return 0;
+
+ // Check the database. Look for all cache groups with a newest cache.
+ SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
+ if (statement.prepare() != SQLResultOk)
+ return 0;
+
+ int result;
+ while ((result = statement.step()) == SQLResultRow) {
+ KURL manifestURL = KURL(statement.getColumnText(1));
+
+ if (m_cachesInMemory.contains(manifestURL))
+ continue;
+
+ // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match.
+ if (!protocolHostAndPortAreEqual(url, manifestURL))
+ continue;
+
+ // We found a cache group that matches. Now check if the newest cache has a resource with
+ // a matching fallback namespace.
+ unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
+ RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
+
+ KURL fallbackURL;
+ if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
+ continue;
+ if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
+ continue;
+
+ ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
+
+ group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
+ group->setNewestCache(cache.release());
+
+ m_cachesInMemory.set(group->manifestURL(), group);
+
+ return group;
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
+
+ return 0;
+}
+
+void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group)
+{
+ ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
+
+ m_cachesInMemory.remove(group->manifestURL());
+
+ // If the cache is half-created, we don't want it in the saved set.
+ if (!group->savedNewestCachePointer())
+ m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
+}
+
+void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
+{
+ ASSERT(m_cacheDirectory.isNull());
+ ASSERT(!cacheDirectory.isNull());
+
+ m_cacheDirectory = cacheDirectory;
+}
+
+const String& ApplicationCacheStorage::cacheDirectory() const
+{
+ return m_cacheDirectory;
+}
+
+
+bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
+{
+ ASSERT(m_database.isOpen());
+
+ bool result = m_database.executeCommand(sql);
+ if (!result)
+ LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
+ sql.utf8().data(), m_database.lastErrorMsg());
+
+ return result;
+}
+
+static const int schemaVersion = 3;
+
+void ApplicationCacheStorage::verifySchemaVersion()
+{
+ int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0);
+ if (version == schemaVersion)
+ return;
+
+ m_database.clearAllTables();
+
+ // Update user version.
+ SQLiteTransaction setDatabaseVersion(m_database);
+ setDatabaseVersion.begin();
+
+ char userVersionSQL[32];
+ int numBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion);
+ ASSERT_UNUSED(numBytes, static_cast<int>(sizeof(userVersionSQL)) >= numBytes);
+
+ SQLiteStatement statement(m_database, userVersionSQL);
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ executeStatement(statement);
+ setDatabaseVersion.commit();
+}
+
+void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
+{
+ if (m_database.isOpen())
+ return;
+
+ // The cache directory should never be null, but if it for some weird reason is we bail out.
+ if (m_cacheDirectory.isNull())
+ return;
+
+ String applicationCachePath = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db");
+ if (!createIfDoesNotExist && !fileExists(applicationCachePath))
+ return;
+
+ makeAllDirectories(m_cacheDirectory);
+ m_database.open(applicationCachePath);
+
+ if (!m_database.isOpen())
+ return;
+
+ verifySchemaVersion();
+
+ // Create tables
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "
+ "cache INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, "
+ "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)");
+
+ // When a cache is deleted, all its entries and its whitelist should be deleted.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches"
+ " FOR EACH ROW BEGIN"
+ " DELETE FROM CacheEntries WHERE cache = OLD.id;"
+ " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;"
+ " DELETE FROM FallbackURLs WHERE cache = OLD.id;"
+ " END");
+
+ // When a cache resource is deleted, its data blob should also be deleted.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources"
+ " FOR EACH ROW BEGIN"
+ " DELETE FROM CacheResourceData WHERE id = OLD.data;"
+ " END");
+}
+
+bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
+{
+ bool result = statement.executeCommand();
+ if (!result)
+ LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
+ statement.query().utf8().data(), m_database.lastErrorMsg());
+
+ return result;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCacheGroup* group)
+{
+ ASSERT(group->storageID() == 0);
+
+ SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindInt64(1, urlHostHash(group->manifestURL()));
+ statement.bindText(2, group->manifestURL());
+
+ if (!executeStatement(statement))
+ return false;
+
+ group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID()));
+ return true;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCache* cache)
+{
+ ASSERT(cache->storageID() == 0);
+ ASSERT(cache->group()->storageID() != 0);
+
+ SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup) VALUES (?)");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindInt64(1, cache->group()->storageID());
+
+ if (!executeStatement(statement))
+ return false;
+
+ unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Store all resources
+ {
+ ApplicationCache::ResourceMap::const_iterator end = cache->end();
+ for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
+ if (!store(it->second.get(), cacheStorageID))
+ return false;
+ }
+ }
+
+ // Store the online whitelist
+ const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist();
+ {
+ size_t whitelistSize = onlineWhitelist.size();
+ for (size_t i = 0; i < whitelistSize; ++i) {
+ SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)");
+ statement.prepare();
+
+ statement.bindText(1, onlineWhitelist[i]);
+ statement.bindInt64(2, cacheStorageID);
+
+ if (!executeStatement(statement))
+ return false;
+ }
+ }
+
+ // Store fallback URLs.
+ const FallbackURLVector& fallbackURLs = cache->fallbackURLs();
+ {
+ size_t fallbackCount = fallbackURLs.size();
+ for (size_t i = 0; i < fallbackCount; ++i) {
+ SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)");
+ statement.prepare();
+
+ statement.bindText(1, fallbackURLs[i].first);
+ statement.bindText(2, fallbackURLs[i].second);
+ statement.bindInt64(3, cacheStorageID);
+
+ if (!executeStatement(statement))
+ return false;
+ }
+ }
+
+ cache->setStorageID(cacheStorageID);
+ return true;
+}
+
+bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID)
+{
+ ASSERT(cacheStorageID);
+ ASSERT(!resource->storageID());
+
+ openDatabase(true);
+
+ // First, insert the data
+ SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)");
+ if (dataStatement.prepare() != SQLResultOk)
+ return false;
+
+ if (resource->data()->size())
+ dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
+
+ if (!dataStatement.executeCommand())
+ return false;
+
+ unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Then, insert the resource
+
+ // Serialize the headers
+ Vector<UChar> stringBuilder;
+
+ HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end();
+ for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) {
+ stringBuilder.append(it->first.characters(), it->first.length());
+ stringBuilder.append((UChar)':');
+ stringBuilder.append(it->second.characters(), it->second.length());
+ stringBuilder.append((UChar)'\n');
+ }
+
+ String headers = String::adopt(stringBuilder);
+
+ SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)");
+ if (resourceStatement.prepare() != SQLResultOk)
+ return false;
+
+ resourceStatement.bindText(1, resource->url());
+ resourceStatement.bindInt64(2, resource->response().httpStatusCode());
+ resourceStatement.bindText(3, resource->response().url());
+ resourceStatement.bindText(4, headers);
+ resourceStatement.bindInt64(5, dataId);
+ resourceStatement.bindText(6, resource->response().mimeType());
+ resourceStatement.bindText(7, resource->response().textEncodingName());
+
+ if (!executeStatement(resourceStatement))
+ return false;
+
+ unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID());
+
+ // Finally, insert the cache entry
+ SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)");
+ if (entryStatement.prepare() != SQLResultOk)
+ return false;
+
+ entryStatement.bindInt64(1, cacheStorageID);
+ entryStatement.bindInt64(2, resource->type());
+ entryStatement.bindInt64(3, resourceId);
+
+ if (!executeStatement(entryStatement))
+ return false;
+
+ resource->setStorageID(resourceId);
+ return true;
+}
+
+bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache)
+{
+ ASSERT_UNUSED(cache, cache->storageID());
+ ASSERT(resource->storageID());
+
+ // FIXME: If the resource gained a Dynamic bit, it should be re-inserted at the end for correct order.
+ ASSERT(!(resource->type() & ApplicationCacheResource::Dynamic));
+
+ // First, insert the data
+ SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?");
+ if (entryStatement.prepare() != SQLResultOk)
+ return false;
+
+ entryStatement.bindInt64(1, resource->type());
+ entryStatement.bindInt64(2, resource->storageID());
+
+ return executeStatement(entryStatement);
+}
+
+void ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)
+{
+ ASSERT(cache->storageID());
+
+ openDatabase(true);
+
+ SQLiteTransaction storeResourceTransaction(m_database);
+ storeResourceTransaction.begin();
+
+ if (!store(resource, cache->storageID()))
+ return;
+
+ storeResourceTransaction.commit();
+}
+
+bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
+{
+ openDatabase(true);
+
+ SQLiteTransaction storeCacheTransaction(m_database);
+
+ storeCacheTransaction.begin();
+
+ if (!group->storageID()) {
+ // Store the group
+ if (!store(group))
+ return false;
+ }
+
+ ASSERT(group->newestCache());
+ ASSERT(!group->newestCache()->storageID());
+
+ // Store the newest cache
+ if (!store(group->newestCache()))
+ return false;
+
+ // Update the newest cache in the group.
+
+ SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?");
+ if (statement.prepare() != SQLResultOk)
+ return false;
+
+ statement.bindInt64(1, group->newestCache()->storageID());
+ statement.bindInt64(2, group->storageID());
+
+ if (!executeStatement(statement))
+ return false;
+
+ storeCacheTransaction.commit();
+ return true;
+}
+
+static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response)
+{
+ int pos = find(header, headerLength, ':');
+ ASSERT(pos != -1);
+
+ AtomicString headerName = AtomicString(header, pos);
+ String headerValue = String(header + pos + 1, headerLength - pos - 1);
+
+ response.setHTTPHeaderField(headerName, headerValue);
+}
+
+static inline void parseHeaders(const String& headers, ResourceResponse& response)
+{
+ int startPos = 0;
+ int endPos;
+ while ((endPos = headers.find('\n', startPos)) != -1) {
+ ASSERT(startPos != endPos);
+
+ parseHeader(headers.characters() + startPos, endPos - startPos, response);
+
+ startPos = endPos + 1;
+ }
+
+ if (startPos != static_cast<int>(headers.length()))
+ parseHeader(headers.characters(), headers.length(), response);
+}
+
+PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID)
+{
+ SQLiteStatement cacheStatement(m_database,
+ "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id "
+ "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?");
+ if (cacheStatement.prepare() != SQLResultOk) {
+ LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg());
+ return 0;
+ }
+
+ cacheStatement.bindInt64(1, storageID);
+
+ RefPtr<ApplicationCache> cache = ApplicationCache::create();
+
+ int result;
+ while ((result = cacheStatement.step()) == SQLResultRow) {
+ KURL url(cacheStatement.getColumnText(0));
+
+ unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(1));
+
+ Vector<char> blob;
+ cacheStatement.getColumnBlobAsVector(5, blob);
+
+ RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
+
+ String mimeType = cacheStatement.getColumnText(2);
+ String textEncodingName = cacheStatement.getColumnText(3);
+
+ ResourceResponse response(url, mimeType, data->size(), textEncodingName, "");
+
+ String headers = cacheStatement.getColumnText(4);
+ parseHeaders(headers, response);
+
+ RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release());
+
+ if (type & ApplicationCacheResource::Manifest)
+ cache->setManifestResource(resource.release());
+ else
+ cache->addResource(resource.release());
+ }
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg());
+
+ // Load the online whitelist
+ SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?");
+ if (whitelistStatement.prepare() != SQLResultOk)
+ return 0;
+ whitelistStatement.bindInt64(1, storageID);
+
+ Vector<KURL> whitelist;
+ while ((result = whitelistStatement.step()) == SQLResultRow)
+ whitelist.append(whitelistStatement.getColumnText(0));
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg());
+
+ cache->setOnlineWhitelist(whitelist);
+
+ // Load fallback URLs.
+ SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?");
+ if (fallbackStatement.prepare() != SQLResultOk)
+ return 0;
+ fallbackStatement.bindInt64(1, storageID);
+
+ FallbackURLVector fallbackURLs;
+ while ((result = fallbackStatement.step()) == SQLResultRow)
+ fallbackURLs.append(make_pair(fallbackStatement.getColumnText(0), fallbackStatement.getColumnText(1)));
+
+ if (result != SQLResultDone)
+ LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg());
+
+ cache->setFallbackURLs(fallbackURLs);
+
+ cache->setStorageID(storageID);
+
+ return cache.release();
+}
+
+void ApplicationCacheStorage::remove(ApplicationCache* cache)
+{
+ if (!cache->storageID())
+ return;
+
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return;
+
+ SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?");
+ if (statement.prepare() != SQLResultOk)
+ return;
+
+ statement.bindInt64(1, cache->storageID());
+ executeStatement(statement);
+}
+
+void ApplicationCacheStorage::empty()
+{
+ openDatabase(false);
+
+ if (!m_database.isOpen())
+ return;
+
+ // Clear cache groups, caches and cache resources.
+ executeSQLCommand("DELETE FROM CacheGroups");
+ executeSQLCommand("DELETE FROM Caches");
+ executeSQLCommand("DELETE FROM CacheResources");
+
+ // Clear the storage IDs for the caches in memory.
+ // The caches will still work, but cached resources will not be saved to disk
+ // until a cache update process has been initiated.
+ CacheGroupMap::const_iterator end = m_cachesInMemory.end();
+ for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
+ it->second->clearStorageID();
+}
+
+bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCache* cache)
+{
+ // Create a new cache.
+ RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create();
+
+ cacheCopy->setOnlineWhitelist(cache->onlineWhitelist());
+ cacheCopy->setFallbackURLs(cache->fallbackURLs());
+
+ // Traverse the cache and add copies of all resources.
+ ApplicationCache::ResourceMap::const_iterator end = cache->end();
+ for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
+ ApplicationCacheResource* resource = it->second.get();
+
+ RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data());
+
+ cacheCopy->addResource(resourceCopy.release());
+ }
+
+ // Now create a new cache group.
+ OwnPtr<ApplicationCacheGroup> groupCopy(new ApplicationCacheGroup(cache->group()->manifestURL(), true));
+
+ groupCopy->setNewestCache(cacheCopy);
+
+ ApplicationCacheStorage copyStorage;
+ copyStorage.setCacheDirectory(cacheDirectory);
+
+ // Empty the cache in case something was there before.
+ copyStorage.empty();
+
+ return copyStorage.storeNewestCache(groupCopy.get());
+}
+
+ApplicationCacheStorage& cacheStorage()
+{
+ DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ());
+
+ return storage;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(OFFLINE_WEB_APPLICATIONS)