summaryrefslogtreecommitdiffstats
path: root/netwerk/cookie/CookiePersistentStorage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cookie/CookiePersistentStorage.cpp')
-rw-r--r--netwerk/cookie/CookiePersistentStorage.cpp2041
1 files changed, 2041 insertions, 0 deletions
diff --git a/netwerk/cookie/CookiePersistentStorage.cpp b/netwerk/cookie/CookiePersistentStorage.cpp
new file mode 100644
index 0000000000..76bb2018f2
--- /dev/null
+++ b/netwerk/cookie/CookiePersistentStorage.cpp
@@ -0,0 +1,2041 @@
+/* -*- 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/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 "nsICookieService.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsILineInputStream.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 = 12;
+
+// 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;
+
+#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));
+
+ // 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;
+ int32_t inIsolatedMozBrowser;
+
+ rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create an originAttributes object by inIsolatedMozBrowser.
+ // Then create the originSuffix string from this object.
+ OriginAttributes attrs(inIsolatedMozBrowser != 0);
+ 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(attrs.mInIsolatedMozBrowser);
+ 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(nsISupports* aSubject,
+ const char16_t* aData,
+ bool aOldCookieIsSession) {
+ // Notify for topic "session-cookie-changed" to update the copy of session
+ // cookies in session restore component.
+
+ // Filter out notifications for individual non-session cookies.
+ if (u"changed"_ns.Equals(aData) || u"deleted"_ns.Equals(aData) ||
+ u"added"_ns.Equals(aData)) {
+ nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
+ MOZ_ASSERT(xpcCookie);
+ auto* cookie = static_cast<Cookie*>(xpcCookie.get());
+ if (!cookie->IsSession() && !aOldCookieIsSession) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aSubject, "session-cookie-changed", aData);
+ }
+}
+
+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 CookieListIter& aIter) {
+ // if it's a non-session cookie, remove it from the db
+ if (aIter.Cookie()->IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
+
+ PrepareCookieRemoval(aIter, 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 CookieListIter& aIter, mozIStorageBindingParamsArray* aParamsArray) {
+ // if it's a non-session cookie, remove it from the db
+ if (aIter.Cookie()->IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParams> params;
+ aParamsArray->NewBindingParams(getter_AddRefs(params));
+
+ DebugOnly<nsresult> rv =
+ params->BindUTF8StringByName("name"_ns, aIter.Cookie()->Name());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("host"_ns, aIter.Cookie()->Host());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("path"_ns, aIter.Cookie()->Path());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString suffix;
+ aIter.Cookie()->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<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"));
+
+ // 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 "
+ "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 "
+ "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);
+
+ // Create a new constCookie and assign the data.
+ return MakeUnique<CookieStruct>(
+ name, value, host, path, expiry, lastAccessed, creationTime, isHttpOnly,
+ false, isSecure, sameSite, rawSameSite,
+ static_cast<nsICookie::schemeType>(schemeMap));
+}
+
+void CookiePersistentStorage::EnsureInitialized() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool isAccumulated = false;
+
+ if (!mInitialized) {
+ TimeStamp startBlockTime = TimeStamp::Now();
+ MonitorAutoLock lock(mMonitor);
+
+ while (!mInitialized) {
+ mMonitor.Wait();
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, startBlockTime);
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
+ 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.
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS,
+ (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds());
+ // 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.
+ Telemetry::Accumulate(
+ Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0);
+ 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;
+ }
+
+ for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
+ CookieDomainTuple& tuple = mReadArray[i];
+ MOZ_ASSERT(!tuple.cookie->isSession());
+
+ // 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();
+
+ 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 "
+ ") VALUES ("
+ ":originAttributes, "
+ ":name, "
+ ":value, "
+ ":host, "
+ ":path, "
+ ":expiry, "
+ ":lastAccessed, "
+ ":creationTime, "
+ ":isSecure, "
+ ":isHttpOnly, "
+ ":sameSite, "
+ ":rawSameSite, "
+ ":schemeMap "
+ ")"),
+ 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, "
+ "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, paramsArray);
+ self->RemoveCookieFromListInternal(aIter);
+ },
+ [paramsArray, self]() {
+ if (paramsArray) {
+ self->DeleteFromDB(paramsArray);
+ }
+ });
+}
+
+} // namespace net
+} // namespace mozilla