gecko-dev/netwerk/cookie/CookiePersistentStorage.cpp
Sandor Molnar 9f1531fdbf Backed out 5 changesets (bug 1923663, bug 1922193) for causing crashes @ mozilla::net::CookieStorage::AddCookie
Backed out changeset d43f5d0fcadf (bug 1923663)
Backed out changeset 39226f4da7cf (bug 1923663)
Backed out changeset 4d455d19f7fb (bug 1922193)
Backed out changeset eb6b645090c2 (bug 1922193)
Backed out changeset 636bc2b1c45b (bug 1922193)
2024-11-05 21:49:45 +02:00

2141 lines
77 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "Cookie.h"
#include "CookieCommons.h"
#include "CookieLogging.h"
#include "CookiePersistentStorage.h"
#include "mozilla/FileUtils.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Telemetry.h"
#include "mozIStorageAsyncStatement.h"
#include "mozIStorageError.h"
#include "mozIStorageFunction.h"
#include "mozIStorageService.h"
#include "mozStorageHelper.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsICookieNotification.h"
#include "nsICookieService.h"
#include "nsIEffectiveTLDService.h"
#include "nsILineInputStream.h"
#include "nsIURIMutator.h"
#include "nsNetUtil.h"
#include "nsVariant.h"
#include "prprf.h"
// XXX_hack. See bug 178993.
// This is a hack to hide HttpOnly cookies from older browsers
#define HTTP_ONLY_PREFIX "#HttpOnly_"
constexpr auto COOKIES_SCHEMA_VERSION = 14;
// parameter indexes; see |Read|
constexpr auto IDX_NAME = 0;
constexpr auto IDX_VALUE = 1;
constexpr auto IDX_HOST = 2;
constexpr auto IDX_PATH = 3;
constexpr auto IDX_EXPIRY = 4;
constexpr auto IDX_LAST_ACCESSED = 5;
constexpr auto IDX_CREATION_TIME = 6;
constexpr auto IDX_SECURE = 7;
constexpr auto IDX_HTTPONLY = 8;
constexpr auto IDX_ORIGIN_ATTRIBUTES = 9;
constexpr auto IDX_SAME_SITE = 10;
constexpr auto IDX_RAW_SAME_SITE = 11;
constexpr auto IDX_SCHEME_MAP = 12;
constexpr auto IDX_PARTITIONED_ATTRIBUTE_SET = 13;
#define COOKIES_FILE "cookies.sqlite"
namespace mozilla {
namespace net {
namespace {
void BindCookieParameters(mozIStorageBindingParamsArray* aParamsArray,
const CookieKey& aKey, const Cookie* aCookie) {
NS_ASSERTION(aParamsArray,
"Null params array passed to BindCookieParameters!");
NS_ASSERTION(aCookie, "Null cookie passed to BindCookieParameters!");
// Use the asynchronous binding methods to ensure that we do not acquire the
// database lock.
nsCOMPtr<mozIStorageBindingParams> params;
DebugOnly<nsresult> rv =
aParamsArray->NewBindingParams(getter_AddRefs(params));
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsAutoCString suffix;
aKey.mOriginAttributes.CreateSuffix(suffix);
rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("value"_ns, aCookie->Value());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt64ByName("expiry"_ns, aCookie->Expiry());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt64ByName("lastAccessed"_ns, aCookie->LastAccessed());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt64ByName("creationTime"_ns, aCookie->CreationTime());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("isSecure"_ns, aCookie->IsSecure());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("isHttpOnly"_ns, aCookie->IsHttpOnly());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("sameSite"_ns, aCookie->SameSite());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("rawSameSite"_ns, aCookie->RawSameSite());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("schemeMap"_ns, aCookie->SchemeMap());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindInt32ByName("isPartitionedAttributeSet"_ns,
aCookie->RawIsPartitioned());
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Bind the params to the array.
rv = aParamsArray->AddParams(params);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction {
~ConvertAppIdToOriginAttrsSQLFunction() = default;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction);
NS_IMETHODIMP
ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
nsresult rv;
OriginAttributes attrs;
nsAutoCString suffix;
attrs.CreateSuffix(suffix);
RefPtr<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsAUTF8String(suffix);
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);
return NS_OK;
}
class SetAppIdFromOriginAttributesSQLFunction final
: public mozIStorageFunction {
~SetAppIdFromOriginAttributesSQLFunction() = default;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction);
NS_IMETHODIMP
SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
nsresult rv;
nsAutoCString suffix;
OriginAttributes attrs;
rv = aFunctionArguments->GetUTF8String(0, suffix);
NS_ENSURE_SUCCESS(rv, rv);
bool success = attrs.PopulateFromSuffix(suffix);
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
RefPtr<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsInt32(0); // deprecated appId!
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);
return NS_OK;
}
class SetInBrowserFromOriginAttributesSQLFunction final
: public mozIStorageFunction {
~SetInBrowserFromOriginAttributesSQLFunction() = default;
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction,
mozIStorageFunction);
NS_IMETHODIMP
SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall(
mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
nsresult rv;
nsAutoCString suffix;
OriginAttributes attrs;
rv = aFunctionArguments->GetUTF8String(0, suffix);
NS_ENSURE_SUCCESS(rv, rv);
bool success = attrs.PopulateFromSuffix(suffix);
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
RefPtr<nsVariant> outVar(new nsVariant());
rv = outVar->SetAsInt32(false);
NS_ENSURE_SUCCESS(rv, rv);
outVar.forget(aResult);
return NS_OK;
}
/******************************************************************************
* DBListenerErrorHandler impl:
* Parent class for our async storage listeners that handles the logging of
* errors.
******************************************************************************/
class DBListenerErrorHandler : public mozIStorageStatementCallback {
protected:
explicit DBListenerErrorHandler(CookiePersistentStorage* dbState)
: mStorage(dbState) {}
RefPtr<CookiePersistentStorage> mStorage;
virtual const char* GetOpType() = 0;
public:
NS_IMETHOD HandleError(mozIStorageError* aError) override {
if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) {
int32_t result = -1;
aError->GetResult(&result);
nsAutoCString message;
aError->GetMessage(message);
COOKIE_LOGSTRING(
LogLevel::Warning,
("DBListenerErrorHandler::HandleError(): Error %d occurred while "
"performing operation '%s' with message '%s'; rebuilding database.",
result, GetOpType(), message.get()));
}
// Rebuild the database.
mStorage->HandleCorruptDB();
return NS_OK;
}
};
/******************************************************************************
* InsertCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous insertion operations.
******************************************************************************/
class InsertCookieDBListener final : public DBListenerErrorHandler {
private:
const char* GetOpType() override { return "INSERT"; }
~InsertCookieDBListener() = default;
public:
NS_DECL_ISUPPORTS
explicit InsertCookieDBListener(CookiePersistentStorage* dbState)
: DBListenerErrorHandler(dbState) {}
NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
MOZ_ASSERT_UNREACHABLE(
"Unexpected call to "
"InsertCookieDBListener::HandleResult");
return NS_OK;
}
NS_IMETHOD HandleCompletion(uint16_t aReason) override {
// If we were rebuilding the db and we succeeded, make our mCorruptFlag say
// so.
if (mStorage->GetCorruptFlag() == CookiePersistentStorage::REBUILDING &&
aReason == mozIStorageStatementCallback::REASON_FINISHED) {
COOKIE_LOGSTRING(
LogLevel::Debug,
("InsertCookieDBListener::HandleCompletion(): rebuild complete"));
mStorage->SetCorruptFlag(CookiePersistentStorage::OK);
}
// This notification is just for testing.
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "cookie-saved-on-disk", nullptr);
}
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback)
/******************************************************************************
* UpdateCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous update operations.
******************************************************************************/
class UpdateCookieDBListener final : public DBListenerErrorHandler {
private:
const char* GetOpType() override { return "UPDATE"; }
~UpdateCookieDBListener() = default;
public:
NS_DECL_ISUPPORTS
explicit UpdateCookieDBListener(CookiePersistentStorage* dbState)
: DBListenerErrorHandler(dbState) {}
NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
MOZ_ASSERT_UNREACHABLE(
"Unexpected call to "
"UpdateCookieDBListener::HandleResult");
return NS_OK;
}
NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
};
NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback)
/******************************************************************************
* RemoveCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous removal operations.
******************************************************************************/
class RemoveCookieDBListener final : public DBListenerErrorHandler {
private:
const char* GetOpType() override { return "REMOVE"; }
~RemoveCookieDBListener() = default;
public:
NS_DECL_ISUPPORTS
explicit RemoveCookieDBListener(CookiePersistentStorage* dbState)
: DBListenerErrorHandler(dbState) {}
NS_IMETHOD HandleResult(mozIStorageResultSet* /*aResultSet*/) override {
MOZ_ASSERT_UNREACHABLE(
"Unexpected call to "
"RemoveCookieDBListener::HandleResult");
return NS_OK;
}
NS_IMETHOD HandleCompletion(uint16_t /*aReason*/) override { return NS_OK; }
};
NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback)
/******************************************************************************
* CloseCookieDBListener imp:
* Static mozIStorageCompletionCallback used to notify when the database is
* successfully closed.
******************************************************************************/
class CloseCookieDBListener final : public mozIStorageCompletionCallback {
~CloseCookieDBListener() = default;
public:
explicit CloseCookieDBListener(CookiePersistentStorage* dbState)
: mStorage(dbState) {}
RefPtr<CookiePersistentStorage> mStorage;
NS_DECL_ISUPPORTS
NS_IMETHOD Complete(nsresult /*status*/, nsISupports* /*value*/) override {
mStorage->HandleDBClosed();
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback)
} // namespace
// static
already_AddRefed<CookiePersistentStorage> CookiePersistentStorage::Create() {
RefPtr<CookiePersistentStorage> storage = new CookiePersistentStorage();
storage->Init();
storage->Activate();
return storage.forget();
}
CookiePersistentStorage::CookiePersistentStorage()
: mMonitor("CookiePersistentStorage"),
mInitialized(false),
mCorruptFlag(OK) {}
void CookiePersistentStorage::NotifyChangedInternal(
nsICookieNotification* aNotification, bool aOldCookieIsSession) {
MOZ_ASSERT(aNotification);
// Notify for topic "session-cookie-changed" to update the copy of session
// cookies in session restore component.
nsICookieNotification::Action action = aNotification->GetAction();
// Filter out notifications for individual non-session cookies.
if (action == nsICookieNotification::COOKIE_CHANGED ||
action == nsICookieNotification::COOKIE_DELETED ||
action == nsICookieNotification::COOKIE_ADDED) {
nsCOMPtr<nsICookie> xpcCookie;
DebugOnly<nsresult> rv =
aNotification->GetCookie(getter_AddRefs(xpcCookie));
MOZ_ASSERT(NS_SUCCEEDED(rv) && xpcCookie);
const Cookie& cookie = xpcCookie->AsCookie();
if (!cookie.IsSession() && !aOldCookieIsSession) {
return;
}
}
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(aNotification, "session-cookie-changed", u"");
}
}
void CookiePersistentStorage::RemoveAllInternal() {
// clear the cookie file
if (mDBConn) {
nsCOMPtr<mozIStorageAsyncStatement> stmt;
nsresult rv = mDBConn->CreateAsyncStatement("DELETE FROM moz_cookies"_ns,
getter_AddRefs(stmt));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = stmt->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
MOZ_ASSERT(NS_SUCCEEDED(rv));
} else {
// Recreate the database.
COOKIE_LOGSTRING(LogLevel::Debug,
("RemoveAll(): corruption detected with rv 0x%" PRIx32,
static_cast<uint32_t>(rv)));
HandleCorruptDB();
}
}
}
void CookiePersistentStorage::HandleCorruptDB() {
COOKIE_LOGSTRING(LogLevel::Debug,
("HandleCorruptDB(): CookieStorage %p has mCorruptFlag %u",
this, mCorruptFlag));
// Mark the database corrupt, so the close listener can begin reconstructing
// it.
switch (mCorruptFlag) {
case OK: {
// Move to 'closing' state.
mCorruptFlag = CLOSING_FOR_REBUILD;
CleanupCachedStatements();
mDBConn->AsyncClose(mCloseListener);
CleanupDBConnection();
break;
}
case CLOSING_FOR_REBUILD: {
// We had an error while waiting for close completion. That's OK, just
// ignore it -- we're rebuilding anyway.
return;
}
case REBUILDING: {
// We had an error while rebuilding the DB. Game over. Close the database
// and let the close handler do nothing; then we'll move it out of the
// way.
CleanupCachedStatements();
if (mDBConn) {
mDBConn->AsyncClose(mCloseListener);
}
CleanupDBConnection();
break;
}
}
}
void CookiePersistentStorage::RemoveCookiesWithOriginAttributes(
const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain) {
mozStorageTransaction transaction(mDBConn, false);
// XXX Handle the error, bug 1696130.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
CookieStorage::RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain);
DebugOnly<nsresult> rv = transaction.Commit();
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
void CookiePersistentStorage::RemoveCookiesFromExactHost(
const nsACString& aHost, const nsACString& aBaseDomain,
const OriginAttributesPattern& aPattern) {
mozStorageTransaction transaction(mDBConn, false);
// XXX Handle the error, bug 1696130.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern);
DebugOnly<nsresult> rv = transaction.Commit();
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
void CookiePersistentStorage::RemoveCookieFromDB(const Cookie& aCookie) {
// if it's a non-session cookie, remove it from the db
if (aCookie.IsSession() || !mDBConn) {
return;
}
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
PrepareCookieRemoval(aCookie, paramsArray);
DebugOnly<nsresult> rv = mStmtDelete->BindParameters(paramsArray);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
void CookiePersistentStorage::PrepareCookieRemoval(
const Cookie& aCookie, mozIStorageBindingParamsArray* aParamsArray) {
// if it's a non-session cookie, remove it from the db
if (aCookie.IsSession() || !mDBConn) {
return;
}
nsCOMPtr<mozIStorageBindingParams> params;
aParamsArray->NewBindingParams(getter_AddRefs(params));
DebugOnly<nsresult> rv =
params->BindUTF8StringByName("name"_ns, aCookie.Name());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("host"_ns, aCookie.Host());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("path"_ns, aCookie.Path());
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsAutoCString suffix;
aCookie.OriginAttributesRef().CreateSuffix(suffix);
rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aParamsArray->AddParams(params);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// Null out the statements.
// This must be done before closing the connection.
void CookiePersistentStorage::CleanupCachedStatements() {
mStmtInsert = nullptr;
mStmtDelete = nullptr;
mStmtUpdate = nullptr;
}
// Null out the listeners, and the database connection itself. This
// will not null out the statements, cancel a pending read or
// asynchronously close the connection -- these must be done
// beforehand if necessary.
void CookiePersistentStorage::CleanupDBConnection() {
MOZ_ASSERT(!mStmtInsert, "mStmtInsert has been cleaned up");
MOZ_ASSERT(!mStmtDelete, "mStmtDelete has been cleaned up");
MOZ_ASSERT(!mStmtUpdate, "mStmtUpdate has been cleaned up");
// Null out the database connections. If 'mDBConn' has not been used for any
// asynchronous operations yet, this will synchronously close it; otherwise,
// it's expected that the caller has performed an AsyncClose prior.
mDBConn = nullptr;
// Manually null out our listeners. This is necessary because they hold a
// strong ref to the CookieStorage itself. They'll stay alive until whatever
// statements are still executing complete.
mInsertListener = nullptr;
mUpdateListener = nullptr;
mRemoveListener = nullptr;
mCloseListener = nullptr;
}
void CookiePersistentStorage::Close() {
if (mThread) {
mThread->Shutdown();
mThread = nullptr;
}
// Cleanup cached statements before we can close anything.
CleanupCachedStatements();
if (mDBConn) {
// Asynchronously close the connection. We will null it below.
mDBConn->AsyncClose(mCloseListener);
}
CleanupDBConnection();
mInitialized = false;
mInitializedDBConn = false;
}
void CookiePersistentStorage::StoreCookie(
const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
Cookie* aCookie) {
// if it's a non-session cookie and hasn't just been read from the db, write
// it out.
if (aCookie->IsSession() || !mDBConn) {
return;
}
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
mStmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
CookieKey key(aBaseDomain, aOriginAttributes);
BindCookieParameters(paramsArray, key, aCookie);
MaybeStoreCookiesToDB(paramsArray);
}
void CookiePersistentStorage::MaybeStoreCookiesToDB(
mozIStorageBindingParamsArray* aParamsArray) {
if (!aParamsArray) {
return;
}
uint32_t length;
aParamsArray->GetLength(&length);
if (!length) {
return;
}
DebugOnly<nsresult> rv = mStmtInsert->BindParameters(aParamsArray);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = mStmtInsert->ExecuteAsync(mInsertListener, getter_AddRefs(handle));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
void CookiePersistentStorage::StaleCookies(
const nsTArray<RefPtr<Cookie>>& aCookieList, int64_t aCurrentTimeInUsec) {
// Create an array of parameters to bind to our update statement. Batching
// is OK here since we're updating cookies with no interleaved operations.
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
mozIStorageAsyncStatement* stmt = mStmtUpdate;
if (mDBConn) {
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
}
int32_t count = aCookieList.Length();
for (int32_t i = 0; i < count; ++i) {
Cookie* cookie = aCookieList.ElementAt(i);
if (cookie->IsStale()) {
UpdateCookieInList(cookie, aCurrentTimeInUsec, paramsArray);
}
}
// Update the database now if necessary.
if (paramsArray) {
uint32_t length;
paramsArray->GetLength(&length);
if (length) {
DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = stmt->ExecuteAsync(mUpdateListener, getter_AddRefs(handle));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
}
void CookiePersistentStorage::UpdateCookieInList(
Cookie* aCookie, int64_t aLastAccessed,
mozIStorageBindingParamsArray* aParamsArray) {
MOZ_ASSERT(aCookie);
// udpate the lastAccessed timestamp
aCookie->SetLastAccessed(aLastAccessed);
// if it's a non-session cookie, update it in the db too
if (!aCookie->IsSession() && aParamsArray) {
// Create our params holder.
nsCOMPtr<mozIStorageBindingParams> params;
aParamsArray->NewBindingParams(getter_AddRefs(params));
// Bind our parameters.
DebugOnly<nsresult> rv =
params->BindInt64ByName("lastAccessed"_ns, aLastAccessed);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("name"_ns, aCookie->Name());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("host"_ns, aCookie->Host());
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = params->BindUTF8StringByName("path"_ns, aCookie->Path());
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsAutoCString suffix;
aCookie->OriginAttributesRef().CreateSuffix(suffix);
rv = params->BindUTF8StringByName("originAttributes"_ns, suffix);
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Add our bound parameters to the array.
rv = aParamsArray->AddParams(params);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
void CookiePersistentStorage::DeleteFromDB(
mozIStorageBindingParamsArray* aParamsArray) {
uint32_t length;
aParamsArray->GetLength(&length);
if (length) {
DebugOnly<nsresult> rv = mStmtDelete->BindParameters(aParamsArray);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = mStmtDelete->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
void CookiePersistentStorage::Activate() {
MOZ_ASSERT(!mThread, "already have a cookie thread");
mStorageService = do_GetService("@mozilla.org/storage/service;1");
MOZ_ASSERT(mStorageService);
mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
MOZ_ASSERT(mTLDService);
// Get our cookie file.
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mCookieFile));
if (NS_FAILED(rv)) {
// We've already set up our CookieStorages appropriately; nothing more to
// do.
COOKIE_LOGSTRING(LogLevel::Warning,
("InitCookieStorages(): couldn't get cookie file"));
mInitializedDBConn = true;
mInitialized = true;
return;
}
mCookieFile->AppendNative(nsLiteralCString(COOKIES_FILE));
NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread)));
RefPtr<CookiePersistentStorage> self = this;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction("CookiePersistentStorage::Activate", [self] {
MonitorAutoLock lock(self->mMonitor);
// Attempt to open and read the database. If TryInitDB() returns
// RESULT_RETRY, do so.
OpenDBResult result = self->TryInitDB(false);
if (result == RESULT_RETRY) {
// Database may be corrupt. Synchronously close the connection, clean
// up the default CookieStorage, and try again.
COOKIE_LOGSTRING(LogLevel::Warning,
("InitCookieStorages(): retrying TryInitDB()"));
self->CleanupCachedStatements();
self->CleanupDBConnection();
result = self->TryInitDB(true);
if (result == RESULT_RETRY) {
// We're done. Change the code to failure so we clean up below.
result = RESULT_FAILURE;
}
}
if (result == RESULT_FAILURE) {
COOKIE_LOGSTRING(
LogLevel::Warning,
("InitCookieStorages(): TryInitDB() failed, closing connection"));
// Connection failure is unrecoverable. Clean up our connection. We
// can run fine without persistent storage -- e.g. if there's no
// profile.
self->CleanupCachedStatements();
self->CleanupDBConnection();
// No need to initialize mDBConn
self->mInitializedDBConn = true;
}
self->mInitialized = true;
NS_DispatchToMainThread(
NS_NewRunnableFunction("CookiePersistentStorage::InitDBConn",
[self] { self->InitDBConn(); }));
self->mMonitor.Notify();
});
mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
/* Attempt to open and read the database. If 'aRecreateDB' is true, try to
* move the existing database file out of the way and create a new one.
*
* @returns RESULT_OK if opening or creating the database succeeded;
* RESULT_RETRY if the database cannot be opened, is corrupt, or some
* other failure occurred that might be resolved by recreating the
* database; or RESULT_FAILED if there was an unrecoverable error and
* we must run without a database.
*
* If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
* cleanup of the default CookieStorage.
*/
CookiePersistentStorage::OpenDBResult CookiePersistentStorage::TryInitDB(
bool aRecreateDB) {
NS_ASSERTION(!mDBConn, "nonnull mDBConn");
NS_ASSERTION(!mStmtInsert, "nonnull mStmtInsert");
NS_ASSERTION(!mInsertListener, "nonnull mInsertListener");
NS_ASSERTION(!mSyncConn, "nonnull mSyncConn");
NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread");
// Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
// want to delete it outright, since it may be useful for debugging purposes,
// so we move it out of the way.
nsresult rv;
if (aRecreateDB) {
nsCOMPtr<nsIFile> backupFile;
mCookieFile->Clone(getter_AddRefs(backupFile));
rv = backupFile->MoveToNative(nullptr,
nsLiteralCString(COOKIES_FILE ".bak"));
NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
}
// This block provides scope for the Telemetry AutoTimer
{
Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS>
telemetry;
ReadAheadFile(mCookieFile);
// open a connection to the cookie database, and only cache our connection
// and statements upon success. The connection is opened unshared to
// eliminate cache contention between the main and background threads.
rv = mStorageService->OpenUnsharedDatabase(
mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
getter_AddRefs(mSyncConn));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
auto guard = MakeScopeExit([&] { mSyncConn = nullptr; });
bool tableExists = false;
mSyncConn->TableExists("moz_cookies"_ns, &tableExists);
if (!tableExists) {
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
} else {
// table already exists; check the schema version before reading
int32_t dbSchemaVersion;
rv = mSyncConn->GetSchemaVersion(&dbSchemaVersion);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Start a transaction for the whole migration block.
mozStorageTransaction transaction(mSyncConn, true);
// XXX Handle the error, bug 1696130.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
switch (dbSchemaVersion) {
// Upgrading.
// Every time you increment the database schema, you need to implement
// the upgrading code from the previous version to the new one. If
// migration fails for any reason, it's a bug -- so we return RESULT_RETRY
// such that the original database will be saved, in the hopes that we
// might one day see it and fix it.
case 1: {
// Add the lastAccessed column to the table.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
[[fallthrough]];
case 2: {
// Add the baseDomain column and index to the table.
rv = mSyncConn->ExecuteSimpleSQL(
"ALTER TABLE moz_cookies ADD baseDomain TEXT"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Compute the baseDomains for the table. This must be done eagerly
// otherwise we won't be able to synchronously read in individual
// domains on demand.
const int64_t SCHEMA2_IDX_ID = 0;
const int64_t SCHEMA2_IDX_HOST = 1;
nsCOMPtr<mozIStorageStatement> select;
rv = mSyncConn->CreateStatement("SELECT id, host FROM moz_cookies"_ns,
getter_AddRefs(select));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCOMPtr<mozIStorageStatement> update;
rv = mSyncConn->CreateStatement(
nsLiteralCString("UPDATE moz_cookies SET baseDomain = "
":baseDomain WHERE id = :id"),
getter_AddRefs(update));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCString baseDomain;
nsCString host;
bool hasResult;
while (true) {
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (!hasResult) {
break;
}
int64_t id = select->AsInt64(SCHEMA2_IDX_ID);
select->GetUTF8String(SCHEMA2_IDX_HOST, host);
rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host,
baseDomain);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
mozStorageStatementScoper scoper(update);
rv = update->BindUTF8StringByName("baseDomain"_ns, baseDomain);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = update->BindInt64ByName("id"_ns, id);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = update->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Create an index on baseDomain.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
[[fallthrough]];
case 3: {
// Add the creationTime column to the table, and create a unique index
// on (name, host, path). Before we do this, we have to purge the table
// of expired cookies such that we know that the (name, host, path)
// index is truly unique -- otherwise we can't create the index. Note
// that we can't just execute a statement to delete all rows where the
// expiry column is in the past -- doing so would rely on the clock
// (both now and when previous cookies were set) being monotonic.
// Select the whole table, and order by the fields we're interested in.
// This means we can simply do a linear traversal of the results and
// check for duplicates as we go.
const int64_t SCHEMA3_IDX_ID = 0;
const int64_t SCHEMA3_IDX_NAME = 1;
const int64_t SCHEMA3_IDX_HOST = 2;
const int64_t SCHEMA3_IDX_PATH = 3;
nsCOMPtr<mozIStorageStatement> select;
rv = mSyncConn->CreateStatement(
nsLiteralCString(
"SELECT id, name, host, path FROM moz_cookies "
"ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
getter_AddRefs(select));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCOMPtr<mozIStorageStatement> deleteExpired;
rv = mSyncConn->CreateStatement(
"DELETE FROM moz_cookies WHERE id = :id"_ns,
getter_AddRefs(deleteExpired));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Read the first row.
bool hasResult;
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (hasResult) {
nsCString name1;
nsCString host1;
nsCString path1;
int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID);
select->GetUTF8String(SCHEMA3_IDX_NAME, name1);
select->GetUTF8String(SCHEMA3_IDX_HOST, host1);
select->GetUTF8String(SCHEMA3_IDX_PATH, path1);
nsCString name2;
nsCString host2;
nsCString path2;
while (true) {
// Read the second row.
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (!hasResult) {
break;
}
int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID);
select->GetUTF8String(SCHEMA3_IDX_NAME, name2);
select->GetUTF8String(SCHEMA3_IDX_HOST, host2);
select->GetUTF8String(SCHEMA3_IDX_PATH, path2);
// If the two rows match in (name, host, path), we know the earlier
// row has an earlier expiry time. Delete it.
if (name1 == name2 && host1 == host2 && path1 == path2) {
mozStorageStatementScoper scoper(deleteExpired);
rv = deleteExpired->BindInt64ByName("id"_ns, id1);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = deleteExpired->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Make the second row the first for the next iteration.
name1 = name2;
host1 = host2;
path1 = path2;
id1 = id2;
}
}
// Add the creationTime column to the table.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD creationTime INTEGER"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Copy the id of each row into the new creationTime column.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("UPDATE moz_cookies SET creationTime = "
"(SELECT id WHERE id = moz_cookies.id)"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create a unique index on (name, host, path) to allow fast lookup.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE UNIQUE INDEX moz_uniqueid "
"ON moz_cookies (name, host, path)"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
[[fallthrough]];
case 4: {
// We need to add appId/inBrowserElement, plus change a constraint on
// the table (unique entries now include appId/inBrowserElement):
// this requires creating a new table and copying the data to it. We
// then rename the new table to the old name.
//
// Why we made this change: appId/inBrowserElement allow "cookie jars"
// for Firefox OS. We create a separate cookie namespace per {appId,
// inBrowserElement}. When upgrading, we convert existing cookies
// (which imply we're on desktop/mobile) to use {0, false}, as that is
// the only namespace used by a non-Firefox-OS implementation.
// Rename existing table
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop existing index (CreateTable will create new one for new table)
rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create new table (with new fields and new unique constraint)
rv = CreateTableForSchemaVersion5();
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Copy data from old table, using appId/inBrowser=0 for existing rows
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"INSERT INTO moz_cookies "
"(baseDomain, appId, inBrowserElement, name, value, host, path, "
"expiry,"
" lastAccessed, creationTime, isSecure, isHttpOnly) "
"SELECT baseDomain, 0, 0, name, value, host, path, expiry,"
" lastAccessed, creationTime, isSecure, isHttpOnly "
"FROM moz_cookies_old"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop old table
rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 5"));
}
// Fall through to the next upgrade.
[[fallthrough]];
case 5: {
// Change in the version: Replace the columns |appId| and
// |inBrowserElement| by a single column |originAttributes|.
//
// Why we made this change: FxOS new security model (NSec) encapsulates
// "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to
// make it easier to modify the contents of this structure in the
// future.
//
// We do the migration in several steps:
// 1. Rename the old table.
// 2. Create a new table.
// 3. Copy data from the old table to the new table; convert appId and
// inBrowserElement to originAttributes in the meantime.
// Rename existing table.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop existing index (CreateTable will create new one for new table).
rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create new table with new fields and new unique constraint.
rv = CreateTableForSchemaVersion6();
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Copy data from old table without the two deprecated columns appId and
// inBrowserElement.
nsCOMPtr<mozIStorageFunction> convertToOriginAttrs(
new ConvertAppIdToOriginAttrsSQLFunction());
NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY);
constexpr auto convertToOriginAttrsName =
"CONVERT_TO_ORIGIN_ATTRIBUTES"_ns;
rv = mSyncConn->CreateFunction(convertToOriginAttrsName, 2,
convertToOriginAttrs);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"INSERT INTO moz_cookies "
"(baseDomain, originAttributes, name, value, host, path, expiry,"
" lastAccessed, creationTime, isSecure, isHttpOnly) "
"SELECT baseDomain, "
" CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement),"
" name, value, host, path, expiry, lastAccessed, creationTime, "
" isSecure, isHttpOnly "
"FROM moz_cookies_old"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->RemoveFunction(convertToOriginAttrsName);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop old table
rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 6"));
}
[[fallthrough]];
case 6: {
// We made a mistake in schema version 6. We cannot remove expected
// columns of any version (checked in the default case) from cookie
// database, because doing this would destroy the possibility of
// downgrading database.
//
// This version simply restores appId and inBrowserElement columns in
// order to fix downgrading issue even though these two columns are no
// longer used in the latest schema.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Compute and populate the values of appId and inBrwoserElement from
// originAttributes.
nsCOMPtr<mozIStorageFunction> setAppId(
new SetAppIdFromOriginAttributesSQLFunction());
NS_ENSURE_TRUE(setAppId, RESULT_RETRY);
constexpr auto setAppIdName = "SET_APP_ID"_ns;
rv = mSyncConn->CreateFunction(setAppIdName, 1, setAppId);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCOMPtr<mozIStorageFunction> setInBrowser(
new SetInBrowserFromOriginAttributesSQLFunction());
NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY);
constexpr auto setInBrowserName = "SET_IN_BROWSER"_ns;
rv = mSyncConn->CreateFunction(setInBrowserName, 1, setInBrowser);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), "
"inBrowserElement = SET_IN_BROWSER(originAttributes);"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->RemoveFunction(setAppIdName);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mSyncConn->RemoveFunction(setInBrowserName);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 7"));
}
[[fallthrough]];
case 7: {
// Remove the appId field from moz_cookies.
//
// Unfortunately sqlite doesn't support dropping columns using ALTER
// TABLE, so we need to go through the procedure documented in
// https://www.sqlite.org/lang_altertable.html.
// Drop existing index
rv = mSyncConn->ExecuteSimpleSQL("DROP INDEX moz_basedomain"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create a new_moz_cookies table without the appId field.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE new_moz_cookies("
"id INTEGER PRIMARY KEY, "
"baseDomain TEXT, "
"originAttributes TEXT NOT NULL DEFAULT '', "
"name TEXT, "
"value TEXT, "
"host TEXT, "
"path TEXT, "
"expiry INTEGER, "
"lastAccessed INTEGER, "
"creationTime INTEGER, "
"isSecure INTEGER, "
"isHttpOnly INTEGER, "
"inBrowserElement INTEGER DEFAULT 0, "
"CONSTRAINT moz_uniqueid UNIQUE (name, host, "
"path, originAttributes)"
")"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Move the data over.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO new_moz_cookies ("
"id, "
"baseDomain, "
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"inBrowserElement "
") SELECT "
"id, "
"baseDomain, "
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"inBrowserElement "
"FROM moz_cookies;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop the old table
rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies;"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Rename new_moz_cookies to moz_cookies.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE new_moz_cookies RENAME TO moz_cookies;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Recreate our index.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE INDEX moz_basedomain ON moz_cookies "
"(baseDomain, originAttributes)"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 8"));
}
[[fallthrough]];
case 8: {
// Add the sameSite column to the table.
rv = mSyncConn->ExecuteSimpleSQL(
"ALTER TABLE moz_cookies ADD sameSite INTEGER"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 9"));
}
[[fallthrough]];
case 9: {
// Add the rawSameSite column to the table.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD rawSameSite INTEGER"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Copy the current sameSite value into rawSameSite.
rv = mSyncConn->ExecuteSimpleSQL(
"UPDATE moz_cookies SET rawSameSite = sameSite"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 10"));
}
[[fallthrough]];
case 10: {
// Rename existing table
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies RENAME TO moz_cookies_old"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create a new moz_cookies table without the baseDomain field.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE moz_cookies("
"id INTEGER PRIMARY KEY, "
"originAttributes TEXT NOT NULL DEFAULT '', "
"name TEXT, "
"value TEXT, "
"host TEXT, "
"path TEXT, "
"expiry INTEGER, "
"lastAccessed INTEGER, "
"creationTime INTEGER, "
"isSecure INTEGER, "
"isHttpOnly INTEGER, "
"inBrowserElement INTEGER DEFAULT 0, "
"sameSite INTEGER DEFAULT 0, "
"rawSameSite INTEGER DEFAULT 0, "
"CONSTRAINT moz_uniqueid UNIQUE (name, host, "
"path, originAttributes)"
")"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Move the data over.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO moz_cookies ("
"id, "
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"inBrowserElement, "
"sameSite, "
"rawSameSite "
") SELECT "
"id, "
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"inBrowserElement, "
"sameSite, "
"rawSameSite "
"FROM moz_cookies_old "
"WHERE baseDomain NOTNULL;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop the old table
rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies_old;"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Drop the moz_basedomain index from the database (if it hasn't been
// removed already by removing the table).
rv = mSyncConn->ExecuteSimpleSQL(
"DROP INDEX IF EXISTS moz_basedomain;"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 11"));
}
[[fallthrough]];
case 11: {
// Add the schemeMap column to the table.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_cookies ADD schemeMap INTEGER DEFAULT 0;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 12"));
}
[[fallthrough]];
case 12: {
// Add the isPartitionedAttributeSet column to the table.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("ALTER TABLE moz_cookies ADD "
"isPartitionedAttributeSet INTEGER DEFAULT 0;"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
COOKIE_LOGSTRING(LogLevel::Debug,
("Upgraded database to schema version 13"));
[[fallthrough]];
}
case 13: {
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("UPDATE moz_cookies SET expiry = unixepoch() + "
"34560000 WHERE expiry > unixepoch() + 34560000"));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// No more upgrades. Update the schema version.
rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
[[fallthrough]];
}
case COOKIES_SCHEMA_VERSION:
break;
case 0: {
NS_WARNING("couldn't get schema version!");
// the table may be usable; someone might've just clobbered the schema
// version. we can treat this case like a downgrade using the codepath
// below, by verifying the columns we care about are all there. for now,
// re-set the schema version in the db, in case the checks succeed (if
// they don't, we're dropping the table anyway).
rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// fall through to downgrade check
[[fallthrough]];
// downgrading.
// if columns have been added to the table, we can still use the ones we
// understand safely. if columns have been deleted or altered, just
// blow away the table and start from scratch! if you change the way
// a column is interpreted, make sure you also change its name so this
// check will catch it.
default: {
// check if all the expected columns exist
nsCOMPtr<mozIStorageStatement> stmt;
rv = mSyncConn->CreateStatement(
nsLiteralCString("SELECT "
"id, "
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"sameSite, "
"rawSameSite, "
"schemeMap, "
"isPartitionedAttributeSet "
"FROM moz_cookies"),
getter_AddRefs(stmt));
if (NS_SUCCEEDED(rv)) {
break;
}
// our columns aren't there - drop the table!
rv = mSyncConn->ExecuteSimpleSQL("DROP TABLE moz_cookies"_ns);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
} break;
}
}
// if we deleted a corrupt db, don't attempt to import - return now
if (aRecreateDB) {
return RESULT_OK;
}
// check whether to import or just read in the db
if (tableExists) {
return Read();
}
return RESULT_OK;
}
void CookiePersistentStorage::RebuildCorruptDB() {
NS_ASSERTION(!mDBConn, "shouldn't have an open db connection");
NS_ASSERTION(mCorruptFlag == CookiePersistentStorage::CLOSING_FOR_REBUILD,
"should be in CLOSING_FOR_REBUILD state");
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
mCorruptFlag = CookiePersistentStorage::REBUILDING;
COOKIE_LOGSTRING(LogLevel::Debug,
("RebuildCorruptDB(): creating new database"));
RefPtr<CookiePersistentStorage> self = this;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [self] {
// The database has been closed, and we're ready to rebuild. Open a
// connection.
OpenDBResult result = self->TryInitDB(true);
nsCOMPtr<nsIRunnable> innerRunnable = NS_NewRunnableFunction(
"RebuildCorruptDB.TryInitDBComplete", [self, result] {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (result != RESULT_OK) {
// We're done. Reset our DB connection and statements, and
// notify of closure.
COOKIE_LOGSTRING(
LogLevel::Warning,
("RebuildCorruptDB(): TryInitDB() failed with result %u",
result));
self->CleanupCachedStatements();
self->CleanupDBConnection();
self->mCorruptFlag = CookiePersistentStorage::OK;
if (os) {
os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
}
return;
}
// Notify observers that we're beginning the rebuild.
if (os) {
os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
}
self->InitDBConnInternal();
// Enumerate the hash, and add cookies to the params array.
mozIStorageAsyncStatement* stmt = self->mStmtInsert;
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
for (auto iter = self->mHostTable.Iter(); !iter.Done();
iter.Next()) {
CookieEntry* entry = iter.Get();
const CookieEntry::ArrayType& cookies = entry->GetCookies();
for (CookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
Cookie* cookie = cookies[i];
if (!cookie->IsSession()) {
BindCookieParameters(paramsArray, CookieKey(entry), cookie);
}
}
}
// Make sure we've got something to write. If we don't, we're
// done.
uint32_t length;
paramsArray->GetLength(&length);
if (length == 0) {
COOKIE_LOGSTRING(
LogLevel::Debug,
("RebuildCorruptDB(): nothing to write, rebuild complete"));
self->mCorruptFlag = CookiePersistentStorage::OK;
return;
}
self->MaybeStoreCookiesToDB(paramsArray);
});
NS_DispatchToMainThread(innerRunnable);
});
mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
void CookiePersistentStorage::HandleDBClosed() {
COOKIE_LOGSTRING(LogLevel::Debug,
("HandleDBClosed(): CookieStorage %p closed", this));
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
switch (mCorruptFlag) {
case CookiePersistentStorage::OK: {
// Database is healthy. Notify of closure.
if (os) {
os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
}
break;
}
case CookiePersistentStorage::CLOSING_FOR_REBUILD: {
// Our close finished. Start the rebuild, and notify of db closure later.
RebuildCorruptDB();
break;
}
case CookiePersistentStorage::REBUILDING: {
// We encountered an error during rebuild, closed the database, and now
// here we are. We already have a 'cookies.sqlite.bak' from the original
// dead database; we don't want to overwrite it, so let's move this one to
// 'cookies.sqlite.bak-rebuild'.
nsCOMPtr<nsIFile> backupFile;
mCookieFile->Clone(getter_AddRefs(backupFile));
nsresult rv = backupFile->MoveToNative(
nullptr, nsLiteralCString(COOKIES_FILE ".bak-rebuild"));
COOKIE_LOGSTRING(LogLevel::Warning,
("HandleDBClosed(): CookieStorage %p encountered error "
"rebuilding db; move to "
"'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32,
this, static_cast<uint32_t>(rv)));
if (os) {
os->NotifyObservers(nullptr, "cookie-db-closed", nullptr);
}
break;
}
}
}
CookiePersistentStorage::OpenDBResult CookiePersistentStorage::Read() {
MOZ_ASSERT(NS_GetCurrentThread() == mThread);
// Read in the data synchronously.
// see IDX_NAME, etc. for parameter indexes
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv =
mSyncConn->CreateStatement(nsLiteralCString("SELECT "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"originAttributes, "
"sameSite, "
"rawSameSite, "
"schemeMap, "
"isPartitionedAttributeSet "
"FROM moz_cookies"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (NS_WARN_IF(!mReadArray.IsEmpty())) {
mReadArray.Clear();
}
mReadArray.SetCapacity(kMaxNumberOfCookies);
nsCString baseDomain;
nsCString name;
nsCString value;
nsCString host;
nsCString path;
bool hasResult;
while (true) {
rv = stmt->ExecuteStep(&hasResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
mReadArray.Clear();
return RESULT_RETRY;
}
if (!hasResult) {
break;
}
stmt->GetUTF8String(IDX_HOST, host);
rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
if (NS_FAILED(rv)) {
COOKIE_LOGSTRING(LogLevel::Debug,
("Read(): Ignoring invalid host '%s'", host.get()));
continue;
}
nsAutoCString suffix;
OriginAttributes attrs;
stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix);
// If PopulateFromSuffix failed we just ignore the OA attributes
// that we don't support
Unused << attrs.PopulateFromSuffix(suffix);
CookieKey key(baseDomain, attrs);
CookieDomainTuple* tuple = mReadArray.AppendElement();
tuple->key = std::move(key);
tuple->originAttributes = attrs;
tuple->cookie = GetCookieFromRow(stmt);
}
COOKIE_LOGSTRING(LogLevel::Debug,
("Read(): %zu cookies read", mReadArray.Length()));
return RESULT_OK;
}
// Extract data from a single result row and create an Cookie.
UniquePtr<CookieStruct> CookiePersistentStorage::GetCookieFromRow(
mozIStorageStatement* aRow) {
nsCString name;
nsCString value;
nsCString host;
nsCString path;
DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aRow->GetUTF8String(IDX_VALUE, value);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aRow->GetUTF8String(IDX_HOST, host);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = aRow->GetUTF8String(IDX_PATH, path);
MOZ_ASSERT(NS_SUCCEEDED(rv));
int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
int32_t rawSameSite = aRow->AsInt32(IDX_RAW_SAME_SITE);
int32_t schemeMap = aRow->AsInt32(IDX_SCHEME_MAP);
bool isPartitionedAttributeSet =
0 != aRow->AsInt32(IDX_PARTITIONED_ATTRIBUTE_SET);
// Create a new constCookie and assign the data.
return MakeUnique<CookieStruct>(
name, value, host, path, expiry, lastAccessed, creationTime, isHttpOnly,
false, isSecure, isPartitionedAttributeSet, sameSite, rawSameSite,
static_cast<nsICookie::schemeType>(schemeMap));
}
void CookiePersistentStorage::EnsureInitialized() {
MOZ_ASSERT(NS_IsMainThread());
bool isAccumulated = false;
if (!mInitialized) {
#ifndef ANDROID
TimeStamp startBlockTime = TimeStamp::Now();
#endif
MonitorAutoLock lock(mMonitor);
while (!mInitialized) {
mMonitor.Wait();
}
#ifndef ANDROID
TimeStamp endBlockTime = TimeStamp::Now();
mozilla::glean::networking::sqlite_cookies_block_main_thread
.AccumulateRawDuration(endBlockTime - startBlockTime);
mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread
.AccumulateRawDuration(TimeDuration::Zero());
#endif
isAccumulated = true;
} else if (!mEndInitDBConn.IsNull()) {
// We didn't block main thread, and here comes the first cookie request.
// Collect how close we're going to block main thread.
#ifndef ANDROID
TimeStamp now = TimeStamp::Now();
mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread
.AccumulateRawDuration(now - mEndInitDBConn);
#endif
// Nullify the timestamp so wo don't accumulate this telemetry probe again.
mEndInitDBConn = TimeStamp();
isAccumulated = true;
} else if (!mInitializedDBConn) {
// A request comes while we finished cookie thread task and InitDBConn is
// on the way from cookie thread to main thread. We're very close to block
// main thread.
#ifndef ANDROID
mozilla::glean::networking::sqlite_cookies_time_to_block_main_thread
.AccumulateRawDuration(TimeDuration::Zero());
#endif
isAccumulated = true;
}
if (!mInitializedDBConn) {
InitDBConn();
if (isAccumulated) {
// Nullify the timestamp so wo don't accumulate this telemetry probe
// again.
mEndInitDBConn = TimeStamp();
}
}
}
void CookiePersistentStorage::InitDBConn() {
MOZ_ASSERT(NS_IsMainThread());
// We should skip InitDBConn if we close profile during initializing
// CookieStorages and then InitDBConn is called after we close the
// CookieStorages.
if (!mInitialized || mInitializedDBConn) {
return;
}
nsCOMPtr<nsIURI> dummyUri;
nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://example.com");
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsTArray<RefPtr<Cookie>> cleanupCookies;
for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
CookieDomainTuple& tuple = mReadArray[i];
MOZ_ASSERT(!tuple.cookie->isSession());
// filter invalid non-ipv4 host ending in number from old db values
nsCOMPtr<nsIURIMutator> outMut;
nsCOMPtr<nsIURIMutator> dummyMut;
rv = dummyUri->Mutate(getter_AddRefs(dummyMut));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = dummyMut->SetHost(tuple.cookie->host(), getter_AddRefs(outMut));
if (NS_FAILED(rv)) {
COOKIE_LOGSTRING(LogLevel::Debug, ("Removing cookie from db with "
"newly invalid hostname: '%s'",
tuple.cookie->host().get()));
RefPtr<Cookie> cookie =
Cookie::Create(*tuple.cookie, tuple.originAttributes);
cleanupCookies.AppendElement(cookie);
continue;
}
// CreateValidated fixes up the creation and lastAccessed times.
// If the DB is corrupted and the timestaps are far away in the future
// we don't want the creation timestamp to update gLastCreationTime
// as that would contaminate all the next creation times.
// We fix up these dates to not be later than the current time.
// The downside is that if the user sets the date far away in the past
// then back to the current date, those cookies will be stale,
// but if we don't fix their dates, those cookies might never be
// evicted.
RefPtr<Cookie> cookie =
Cookie::CreateValidated(*tuple.cookie, tuple.originAttributes);
AddCookieToList(tuple.key.mBaseDomain, tuple.key.mOriginAttributes, cookie);
}
if (NS_FAILED(InitDBConnInternal())) {
COOKIE_LOGSTRING(LogLevel::Warning,
("InitDBConn(): retrying InitDBConnInternal()"));
CleanupCachedStatements();
CleanupDBConnection();
if (NS_FAILED(InitDBConnInternal())) {
COOKIE_LOGSTRING(
LogLevel::Warning,
("InitDBConn(): InitDBConnInternal() failed, closing connection"));
// Game over, clean the connections.
CleanupCachedStatements();
CleanupDBConnection();
}
}
mInitializedDBConn = true;
COOKIE_LOGSTRING(LogLevel::Debug,
("InitDBConn(): mInitializedDBConn = true"));
mEndInitDBConn = TimeStamp::Now();
for (const auto& cookie : cleanupCookies) {
RemoveCookieFromDB(*cookie);
}
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(nullptr, "cookie-db-read", nullptr);
mReadArray.Clear();
}
}
nsresult CookiePersistentStorage::InitDBConnInternal() {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mStorageService->OpenUnsharedDatabase(
mCookieFile, mozIStorageService::CONNECTION_DEFAULT,
getter_AddRefs(mDBConn));
NS_ENSURE_SUCCESS(rv, rv);
// Set up our listeners.
mInsertListener = new InsertCookieDBListener(this);
mUpdateListener = new UpdateCookieDBListener(this);
mRemoveListener = new RemoveCookieDBListener(this);
mCloseListener = new CloseCookieDBListener(this);
// Grow cookie db in 512KB increments
mDBConn->SetGrowthIncrement(512 * 1024, ""_ns);
// make operations on the table asynchronous, for performance
mDBConn->ExecuteSimpleSQL("PRAGMA synchronous = OFF"_ns);
// Use write-ahead-logging for performance. We cap the autocheckpoint limit at
// 16 pages (around 500KB).
mDBConn->ExecuteSimpleSQL(nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR
"PRAGMA journal_mode = WAL"));
mDBConn->ExecuteSimpleSQL("PRAGMA wal_autocheckpoint = 16"_ns);
// cache frequently used statements (for insertion, deletion, and updating)
rv = mDBConn->CreateAsyncStatement(
nsLiteralCString("INSERT INTO moz_cookies ("
"originAttributes, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"creationTime, "
"isSecure, "
"isHttpOnly, "
"sameSite, "
"rawSameSite, "
"schemeMap, "
"isPartitionedAttributeSet "
") VALUES ("
":originAttributes, "
":name, "
":value, "
":host, "
":path, "
":expiry, "
":lastAccessed, "
":creationTime, "
":isSecure, "
":isHttpOnly, "
":sameSite, "
":rawSameSite, "
":schemeMap, "
":isPartitionedAttributeSet "
")"),
getter_AddRefs(mStmtInsert));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->CreateAsyncStatement(
nsLiteralCString("DELETE FROM moz_cookies "
"WHERE name = :name AND host = :host AND path = :path "
"AND originAttributes = :originAttributes"),
getter_AddRefs(mStmtDelete));
NS_ENSURE_SUCCESS(rv, rv);
rv = mDBConn->CreateAsyncStatement(
nsLiteralCString("UPDATE moz_cookies SET lastAccessed = :lastAccessed "
"WHERE name = :name AND host = :host AND path = :path "
"AND originAttributes = :originAttributes"),
getter_AddRefs(mStmtUpdate));
return rv;
}
// Sets the schema version and creates the moz_cookies table.
nsresult CookiePersistentStorage::CreateTableWorker(const char* aName) {
// Create the table.
// We default originAttributes to empty string: this is so if users revert to
// an older Firefox version that doesn't know about this field, any cookies
// set will still work once they upgrade back.
nsAutoCString command("CREATE TABLE ");
command.Append(aName);
command.AppendLiteral(
" ("
"id INTEGER PRIMARY KEY, "
"originAttributes TEXT NOT NULL DEFAULT '', "
"name TEXT, "
"value TEXT, "
"host TEXT, "
"path TEXT, "
"expiry INTEGER, "
"lastAccessed INTEGER, "
"creationTime INTEGER, "
"isSecure INTEGER, "
"isHttpOnly INTEGER, "
"inBrowserElement INTEGER DEFAULT 0, "
"sameSite INTEGER DEFAULT 0, "
"rawSameSite INTEGER DEFAULT 0, "
"schemeMap INTEGER DEFAULT 0, "
"isPartitionedAttributeSet INTEGER DEFAULT 0, "
"CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
")");
return mSyncConn->ExecuteSimpleSQL(command);
}
// Sets the schema version and creates the moz_cookies table.
nsresult CookiePersistentStorage::CreateTable() {
// Set the schema version, before creating the table.
nsresult rv = mSyncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
if (NS_FAILED(rv)) {
return rv;
}
rv = CreateTableWorker("moz_cookies");
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
// Sets the schema version and creates the moz_cookies table.
nsresult CookiePersistentStorage::CreateTableForSchemaVersion6() {
// Set the schema version, before creating the table.
nsresult rv = mSyncConn->SetSchemaVersion(6);
if (NS_FAILED(rv)) {
return rv;
}
// Create the table.
// We default originAttributes to empty string: this is so if users revert to
// an older Firefox version that doesn't know about this field, any cookies
// set will still work once they upgrade back.
rv = mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"CREATE TABLE moz_cookies ("
"id INTEGER PRIMARY KEY, "
"baseDomain TEXT, "
"originAttributes TEXT NOT NULL DEFAULT '', "
"name TEXT, "
"value TEXT, "
"host TEXT, "
"path TEXT, "
"expiry INTEGER, "
"lastAccessed INTEGER, "
"creationTime INTEGER, "
"isSecure INTEGER, "
"isHttpOnly INTEGER, "
"CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
")"));
if (NS_FAILED(rv)) {
return rv;
}
// Create an index on baseDomain.
return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
"originAttributes)"));
}
// Sets the schema version and creates the moz_cookies table.
nsresult CookiePersistentStorage::CreateTableForSchemaVersion5() {
// Set the schema version, before creating the table.
nsresult rv = mSyncConn->SetSchemaVersion(5);
if (NS_FAILED(rv)) {
return rv;
}
// Create the table. We default appId/inBrowserElement to 0: this is so if
// users revert to an older Firefox version that doesn't know about these
// fields, any cookies set will still work once they upgrade back.
rv = mSyncConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE moz_cookies ("
"id INTEGER PRIMARY KEY, "
"baseDomain TEXT, "
"appId INTEGER DEFAULT 0, "
"inBrowserElement INTEGER DEFAULT 0, "
"name TEXT, "
"value TEXT, "
"host TEXT, "
"path TEXT, "
"expiry INTEGER, "
"lastAccessed INTEGER, "
"creationTime INTEGER, "
"isSecure INTEGER, "
"isHttpOnly INTEGER, "
"CONSTRAINT moz_uniqueid UNIQUE (name, host, path, "
"appId, inBrowserElement)"
")"));
if (NS_FAILED(rv)) {
return rv;
}
// Create an index on baseDomain.
return mSyncConn->ExecuteSimpleSQL(nsLiteralCString(
"CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, "
"appId, "
"inBrowserElement)"));
}
nsresult CookiePersistentStorage::RunInTransaction(
nsICookieTransactionCallback* aCallback) {
if (NS_WARN_IF(!mDBConn)) {
return NS_ERROR_NOT_AVAILABLE;
}
mozStorageTransaction transaction(mDBConn, true);
// XXX Handle the error, bug 1696130.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
if (NS_FAILED(aCallback->Callback())) {
Unused << transaction.Rollback();
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// purges expired and old cookies in a batch operation.
already_AddRefed<nsIArray> CookiePersistentStorage::PurgeCookies(
int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
int64_t aCookiePurgeAge) {
// Create a params array to batch the removals. This is OK here because
// all the removals are in order, and there are no interleaved additions.
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
if (mDBConn) {
mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
}
RefPtr<CookiePersistentStorage> self = this;
return PurgeCookiesWithCallbacks(
aCurrentTimeInUsec, aMaxNumberOfCookies, aCookiePurgeAge,
[paramsArray, self](const CookieListIter& aIter) {
self->PrepareCookieRemoval(*aIter.Cookie(), paramsArray);
self->RemoveCookieFromListInternal(aIter);
},
[paramsArray, self]() {
if (paramsArray) {
self->DeleteFromDB(paramsArray);
}
});
}
void CookiePersistentStorage::CollectCookieJarSizeData() {
COOKIE_LOGSTRING(LogLevel::Debug,
("CookiePersistentStorage::CollectCookieJarSizeData"));
uint32_t sumPartitioned = 0;
uint32_t sumUnpartitioned = 0;
for (const auto& cookieEntry : mHostTable) {
if (cookieEntry.IsPartitioned()) {
uint16_t cePartitioned = cookieEntry.GetCookies().Length();
sumPartitioned += cePartitioned;
mozilla::glean::networking::cookie_count_part_by_key
.AccumulateSingleSample(cePartitioned);
} else {
uint16_t ceUnpartitioned = cookieEntry.GetCookies().Length();
sumUnpartitioned += ceUnpartitioned;
mozilla::glean::networking::cookie_count_unpart_by_key
.AccumulateSingleSample(ceUnpartitioned);
}
}
mozilla::glean::networking::cookie_count_total.AccumulateSingleSample(
mCookieCount);
mozilla::glean::networking::cookie_count_partitioned.AccumulateSingleSample(
sumPartitioned);
mozilla::glean::networking::cookie_count_unpartitioned.AccumulateSingleSample(
sumUnpartitioned);
}
} // namespace net
} // namespace mozilla