summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp')
-rw-r--r--Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp225
1 files changed, 192 insertions, 33 deletions
diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
index 5c6937e..a126f8a 100644
--- a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
+++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp
@@ -38,6 +38,7 @@
#include "SQLiteStatement.h"
#include "SQLiteTransaction.h"
#include "SecurityOrigin.h"
+#include "UUID.h"
#include <wtf/text/CString.h>
#include <wtf/StdLibExtras.h>
#include <wtf/StringExtras.h>
@@ -46,6 +47,8 @@ using namespace std;
namespace WebCore {
+static const char flatFileSubdirectory[] = "ApplicationCache";
+
template <class T>
class StorageIDJournal {
public:
@@ -90,7 +93,7 @@ static unsigned urlHostHash(const KURL& url)
unsigned hostStart = url.hostStart();
unsigned hostEnd = url.hostEnd();
- return AlreadyHashed::avoidDeletedValue(WTF::StringHasher::createHash(url.string().characters() + hostStart, hostEnd - hostStart));
+ return AlreadyHashed::avoidDeletedValue(StringHasher::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));
}
ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL)
@@ -153,6 +156,11 @@ ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KUR
return group;
}
+ApplicationCacheGroup* ApplicationCacheStorage::findInMemoryCacheGroup(const KURL& manifestURL) const
+{
+ return m_cachesInMemory.get(manifestURL);
+}
+
void ApplicationCacheStorage::loadManifestHostHashes()
{
static bool hasLoadedHashes = false;
@@ -389,7 +397,7 @@ int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave)
if (!getFileSize(m_cacheFile, fileSize))
return 0;
- int64_t currentSize = fileSize;
+ int64_t currentSize = fileSize + flatFileAreaSize();
// Determine the amount of free space we have available.
int64_t totalAvailableSize = 0;
@@ -555,7 +563,7 @@ bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
// Update the schemaVersion when the schema of any the Application Cache
// SQLite tables changes. This allows the database to be rebuilt when
// a new, incompatible change has been introduced to the database schema.
-static const int schemaVersion = 6;
+static const int schemaVersion = 7;
void ApplicationCacheStorage::verifySchemaVersion()
{
@@ -563,7 +571,7 @@ void ApplicationCacheStorage::verifySchemaVersion()
if (version == schemaVersion)
return;
- m_database.clearAllTables();
+ deleteTables();
// Update user version.
SQLiteTransaction setDatabaseVersion(m_database);
@@ -613,7 +621,8 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
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)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB, path TEXT)");
+ executeSQLCommand("CREATE TABLE IF NOT EXISTS DeletedCacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT)");
executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)");
// When a cache is deleted, all its entries and its whitelist should be deleted.
@@ -636,6 +645,15 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
" FOR EACH ROW BEGIN"
" DELETE FROM CacheResourceData WHERE id = OLD.data;"
" END");
+
+ // When a cache resource is deleted, if it contains a non-empty path, that path should
+ // be added to the DeletedCacheResources table so the flat file at that path can
+ // be deleted at a later time.
+ executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDataDeleted AFTER DELETE ON CacheResourceData"
+ " FOR EACH ROW"
+ " WHEN OLD.path NOT NULL BEGIN"
+ " INSERT INTO DeletedCacheResources (path) values (OLD.path);"
+ " END");
}
bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
@@ -767,15 +785,43 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned
return false;
// First, insert the data
- SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)");
+ SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data, path) VALUES (?, ?)");
if (dataStatement.prepare() != SQLResultOk)
return false;
- if (resource->data()->size())
- dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
+
+ String fullPath;
+ if (!resource->path().isEmpty())
+ dataStatement.bindText(2, pathGetFileName(resource->path()));
+ else if (shouldStoreResourceAsFlatFile(resource)) {
+ // First, check to see if creating the flat file would violate the maximum total quota. We don't need
+ // to check the per-origin quota here, as it was already checked in storeNewestCache().
+ if (m_database.totalSize() + flatFileAreaSize() + resource->data()->size() > m_maximumSize) {
+ m_isMaximumSizeReached = true;
+ return false;
+ }
+
+ String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
+ makeAllDirectories(flatFileDirectory);
+ String path;
+ if (!writeDataToUniqueFileInDirectory(resource->data(), flatFileDirectory, path))
+ return false;
+
+ fullPath = pathByAppendingComponent(flatFileDirectory, path);
+ resource->setPath(fullPath);
+ dataStatement.bindText(2, path);
+ } else {
+ if (resource->data()->size())
+ dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
+ }
- if (!dataStatement.executeCommand())
+ if (!dataStatement.executeCommand()) {
+ // Clean up the file which we may have written to:
+ if (!fullPath.isEmpty())
+ deleteFile(fullPath);
+
return false;
+ }
unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
@@ -826,6 +872,12 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned
if (!executeStatement(entryStatement))
return false;
+ // Did we successfully write the resource data to a file? If so,
+ // release the resource's data and free up a potentially large amount
+ // of memory:
+ if (!fullPath.isEmpty())
+ resource->data()->clear();
+
resource->setStorageID(resourceId);
return true;
}
@@ -856,7 +908,7 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, Applicat
return false;
m_isMaximumSizeReached = false;
- m_database.setMaximumSize(m_maximumSize);
+ m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
SQLiteTransaction storeResourceTransaction(m_database);
storeResourceTransaction.begin();
@@ -903,7 +955,7 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, App
return false;
m_isMaximumSizeReached = false;
- m_database.setMaximumSize(m_maximumSize);
+ m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
SQLiteTransaction storeCacheTransaction(m_database);
@@ -1003,7 +1055,7 @@ static inline void parseHeaders(const String& headers, ResourceResponse& respons
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 "
+ "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data, CacheResourceData.path 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());
@@ -1013,7 +1065,9 @@ PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storage
cacheStatement.bindInt64(1, storageID);
RefPtr<ApplicationCache> cache = ApplicationCache::create();
-
+
+ String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
+
int result;
while ((result = cacheStatement.step()) == SQLResultRow) {
KURL url(ParsedURLString, cacheStatement.getColumnText(0));
@@ -1025,15 +1079,24 @@ PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storage
RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
+ String path = cacheStatement.getColumnText(6);
+ long long size = 0;
+ if (path.isEmpty())
+ size = data->size();
+ else {
+ path = pathByAppendingComponent(flatFileDirectory, path);
+ getFileSize(path, size);
+ }
+
String mimeType = cacheStatement.getColumnText(2);
String textEncodingName = cacheStatement.getColumnText(3);
- ResourceResponse response(url, mimeType, data->size(), textEncodingName, "");
+ ResourceResponse response(url, mimeType, size, textEncodingName, "");
String headers = cacheStatement.getColumnText(4);
parseHeaders(headers, response);
- RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release());
+ RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release(), path);
if (type & ApplicationCacheResource::Manifest)
cache->setManifestResource(resource.release());
@@ -1127,6 +1190,8 @@ void ApplicationCacheStorage::remove(ApplicationCache* cache)
cache->group()->clearStorageID();
}
+
+ checkForDeletedResources();
}
void ApplicationCacheStorage::empty()
@@ -1147,7 +1212,51 @@ void ApplicationCacheStorage::empty()
CacheGroupMap::const_iterator end = m_cachesInMemory.end();
for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
it->second->clearStorageID();
-}
+
+ checkForDeletedResources();
+}
+
+void ApplicationCacheStorage::deleteTables()
+{
+ empty();
+ m_database.clearAllTables();
+}
+
+bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource)
+{
+ return resource->response().mimeType().startsWith("audio/", false)
+ || resource->response().mimeType().startsWith("video/", false);
+}
+
+bool ApplicationCacheStorage::writeDataToUniqueFileInDirectory(SharedBuffer* data, const String& directory, String& path)
+{
+ String fullPath;
+
+ do {
+ path = encodeForFileName(createCanonicalUUIDString());
+ // Guard against the above function being called on a platform which does not implement
+ // createCanonicalUUIDString().
+ ASSERT(!path.isEmpty());
+ if (path.isEmpty())
+ return false;
+
+ fullPath = pathByAppendingComponent(directory, path);
+ } while (directoryName(fullPath) != directory || fileExists(fullPath));
+
+ PlatformFileHandle handle = openFile(fullPath, OpenForWrite);
+ if (!handle)
+ return false;
+
+ int64_t writtenBytes = writeToFile(handle, data->data(), data->size());
+ closeFile(handle);
+
+ if (writtenBytes != static_cast<int64_t>(data->size())) {
+ deleteFile(fullPath);
+ return false;
+ }
+
+ return true;
+}
bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost)
{
@@ -1166,7 +1275,7 @@ bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, App
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());
+ RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data(), resource->path());
cacheCopy->addResource(resourceCopy.release());
}
@@ -1274,6 +1383,9 @@ bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL)
}
deleteTransaction.commit();
+
+ checkForDeletedResources();
+
return true;
}
@@ -1291,25 +1403,73 @@ void ApplicationCacheStorage::checkForMaxSizeReached()
if (m_database.lastError() == SQLResultFull)
m_isMaximumSizeReached = true;
}
-
-void ApplicationCacheStorage::getOriginsWithCache(HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash>& origins)
+
+void ApplicationCacheStorage::checkForDeletedResources()
{
- Vector<KURL> urls;
- if (!manifestURLs(&urls)) {
- LOG_ERROR("Failed to retrieve ApplicationCache manifest URLs");
+ openDatabase(false);
+ if (!m_database.isOpen())
return;
+
+ // Select only the paths in DeletedCacheResources that do not also appear in CacheResourceData:
+ SQLiteStatement selectPaths(m_database, "SELECT DeletedCacheResources.path "
+ "FROM DeletedCacheResources "
+ "LEFT JOIN CacheResourceData "
+ "ON DeletedCacheResources.path = CacheResourceData.path "
+ "WHERE (SELECT DeletedCacheResources.path == CacheResourceData.path) IS NULL");
+
+ if (selectPaths.prepare() != SQLResultOk)
+ return;
+
+ if (selectPaths.step() != SQLResultRow)
+ return;
+
+ do {
+ String path = selectPaths.getColumnText(0);
+ if (path.isEmpty())
+ continue;
+
+ String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
+ String fullPath = pathByAppendingComponent(flatFileDirectory, path);
+
+ // Don't exit the flatFileDirectory! This should only happen if the "path" entry contains a directory
+ // component, but protect against it regardless.
+ if (directoryName(fullPath) != flatFileDirectory)
+ continue;
+
+ deleteFile(fullPath);
+ } while (selectPaths.step() == SQLResultRow);
+
+ executeSQLCommand("DELETE FROM DeletedCacheResources");
+}
+
+long long ApplicationCacheStorage::flatFileAreaSize()
+{
+ openDatabase(false);
+ if (!m_database.isOpen())
+ return 0;
+
+ SQLiteStatement selectPaths(m_database, "SELECT path FROM CacheResourceData WHERE path NOT NULL");
+
+ if (selectPaths.prepare() != SQLResultOk) {
+ LOG_ERROR("Could not load flat file cache resource data, error \"%s\"", m_database.lastErrorMsg());
+ return 0;
}
- // Multiple manifest URLs might share the same SecurityOrigin, so we might be creating extra, wasted origins here.
- // The current schema doesn't allow for a more efficient way of building this list.
- size_t count = urls.size();
- for (size_t i = 0; i < count; ++i) {
- RefPtr<SecurityOrigin> origin = SecurityOrigin::create(urls[i]);
- origins.add(origin);
+ long long totalSize = 0;
+ String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
+ while (selectPaths.step() == SQLResultRow) {
+ String path = selectPaths.getColumnText(0);
+ String fullPath = pathByAppendingComponent(flatFileDirectory, path);
+ long long pathSize = 0;
+ if (!getFileSize(fullPath, pathSize))
+ continue;
+ totalSize += pathSize;
}
+
+ return totalSize;
}
-void ApplicationCacheStorage::deleteEntriesForOrigin(SecurityOrigin* origin)
+void ApplicationCacheStorage::getOriginsWithCache(HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash>& origins)
{
Vector<KURL> urls;
if (!manifestURLs(&urls)) {
@@ -1318,12 +1478,11 @@ void ApplicationCacheStorage::deleteEntriesForOrigin(SecurityOrigin* origin)
}
// Multiple manifest URLs might share the same SecurityOrigin, so we might be creating extra, wasted origins here.
- // The current schema doesn't allow for a more efficient way of deleting by origin.
+ // The current schema doesn't allow for a more efficient way of building this list.
size_t count = urls.size();
for (size_t i = 0; i < count; ++i) {
- RefPtr<SecurityOrigin> manifestOrigin = SecurityOrigin::create(urls[i]);
- if (manifestOrigin->isSameSchemeHostPort(origin))
- deleteCacheGroup(urls[i]);
+ RefPtr<SecurityOrigin> origin = SecurityOrigin::create(urls[i]);
+ origins.add(origin);
}
}