Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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"
// 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) {