gecko/dom/cache/DBAction.cpp
Ehsan Akhgari db9a1f6dab Bug 1143959 - Set the journal mode and foreign key pragmas for all DBActions; r=bkelly
Before this patch, we would only set these pragmas as part of CreateSchema
which runs in SetupAction.  This meant that the connection used to perform
other DBActions would not have had these pragmas applied.  As a result,
sqlite would not honor foreign keys on such connections, so the cascade
delete rules responsible for deleting rows from request_headers and
response_headers would not get executed when DBSchema::CachePut deleted the
old entry before adding a new one.

The test in the patch demonstrates how this could result in an observable
breakage.  Before this patch, the response headers stored in the cache for
the overwritten entry would reflect both `Mirrored: `foo' and `Mirrored: bar'
headers, which means that attempting to get this header on the cached
response would return the first entry, `foo'.
2015-03-17 08:18:28 -04:00

204 lines
5.6 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/cache/DBAction.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/net/nsFileProtocolHandler.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "nsIFile.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "DBSchema.h"
#include "FileUtils.h"
namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
DBAction::DBAction(Mode aMode)
: mMode(aMode)
{
}
DBAction::~DBAction()
{
}
void
DBAction::RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aResolver);
MOZ_ASSERT(aQuotaInfo.mDir);
if (IsCanceled()) {
aResolver->Resolve(NS_ERROR_ABORT);
return;
}
nsCOMPtr<nsIFile> dbDir;
nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver->Resolve(rv);
return;
}
rv = dbDir->Append(NS_LITERAL_STRING("cache"));
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver->Resolve(rv);
return;
}
nsCOMPtr<mozIStorageConnection> conn;
rv = OpenConnection(aQuotaInfo, dbDir, getter_AddRefs(conn));
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver->Resolve(rv);
return;
}
MOZ_ASSERT(conn);
RunWithDBOnTarget(aResolver, aQuotaInfo, dbDir, conn);
}
nsresult
DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection** aConnOut)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aDBDir);
MOZ_ASSERT(aConnOut);
nsCOMPtr<mozIStorageConnection> conn;
bool exists;
nsresult rv = aDBDir->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (!exists) {
if (NS_WARN_IF(mMode != Create)) { return NS_ERROR_FILE_NOT_FOUND; }
rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
nsCOMPtr<nsIFile> dbFile;
rv = aDBDir->Clone(getter_AddRefs(dbFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = dbFile->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Use our default file:// protocol handler directly to construct the database
// URL. This avoids any problems if a plugin registers a custom file://
// handler. If such a custom handler used javascript, then we would have a
// bad time running off the main thread here.
nsRefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
rv = handler->Init();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIURI> uri;
rv = handler->NewFileURI(dbFile, getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFileURL> dbFileUrl = do_QueryInterface(uri);
if (NS_WARN_IF(!dbFileUrl)) { return NS_ERROR_UNEXPECTED; }
nsAutoCString type;
PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);
rv = dbFileUrl->SetQuery(
NS_LITERAL_CSTRING("persistenceType=") + type +
NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<mozIStorageService> ss =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
if (rv == NS_ERROR_FILE_CORRUPTED) {
NS_WARNING("Cache database corrupted. Recreating empty database.");
conn = nullptr;
// There is nothing else we can do to recover. Also, this data can
// be deleted by QuotaManager at any time anyways.
rv = WipeDatabase(dbFile, aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
}
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Check the schema to make sure it is not too old.
int32_t schemaVersion = 0;
rv = conn->GetSchemaVersion(&schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (schemaVersion > 0 && schemaVersion < DBSchema::kMaxWipeSchemaVersion) {
conn = nullptr;
rv = WipeDatabase(dbFile, aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
}
rv = DBSchema::InitializeConnection(conn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
conn.forget(aConnOut);
return rv;
}
nsresult
DBAction::WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir)
{
nsresult rv = aDBFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Delete the morgue as well.
rv = FileUtils::BodyDeleteDir(aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
SyncDBAction::SyncDBAction(Mode aMode)
: DBAction(aMode)
{
}
SyncDBAction::~SyncDBAction()
{
}
void
SyncDBAction::RunWithDBOnTarget(Resolver* aResolver,
const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aResolver);
MOZ_ASSERT(aDBDir);
MOZ_ASSERT(aConn);
nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn);
aResolver->Resolve(rv);
}
} // namespace cache
} // namespace dom
} // namespace mozilla