/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CookieCommons.h" #include "CookieLogging.h" #include "CookieParser.h" #include "CookieService.h" #include "mozilla/AppShutdown.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" #include "mozilla/ConsoleReportCollector.h" #include "mozilla/ContentBlockingNotifier.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/nsMixedContentBlocker.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/CookiePersistentStorage.h" #include "mozilla/net/CookiePrivateStorage.h" #include "mozilla/net/CookieService.h" #include "mozilla/net/CookieServiceChild.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/StoragePrincipalHelper.h" #include "mozIThirdPartyUtil.h" #include "nsICookiePermission.h" #include "nsIConsoleReportCollector.h" #include "nsIEffectiveTLDService.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsIURI.h" #include "nsIWebProgressListener.h" #include "nsNetUtil.h" #include "ThirdPartyUtil.h" using namespace mozilla::dom; namespace { uint32_t MakeCookieBehavior(uint32_t aCookieBehavior) { bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled(); if (isFirstPartyIsolated && aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) { return nsICookieService::BEHAVIOR_REJECT_TRACKER; } return aCookieBehavior; } /* Enables sanitizeOnShutdown cleaning prefs and disables the network.cookie.lifetimePolicy */ void MigrateCookieLifetimePrefs() { // Former network.cookie.lifetimePolicy values ACCEPT_SESSION/ACCEPT_NORMALLY // are not available anymore 2 = ACCEPT_SESSION if (mozilla::Preferences::GetInt("network.cookie.lifetimePolicy") != 2) { return; } if (!mozilla::Preferences::GetBool("privacy.sanitize.sanitizeOnShutdown")) { mozilla::Preferences::SetBool("privacy.sanitize.sanitizeOnShutdown", true); // To avoid clearing categories that the user did not intend to clear mozilla::Preferences::SetBool("privacy.clearOnShutdown.history", false); mozilla::Preferences::SetBool("privacy.clearOnShutdown.formdata", false); mozilla::Preferences::SetBool("privacy.clearOnShutdown.downloads", false); mozilla::Preferences::SetBool("privacy.clearOnShutdown.sessions", false); mozilla::Preferences::SetBool("privacy.clearOnShutdown.siteSettings", false); // We will migrate the new clear on shutdown prefs to align both sets of // prefs incase the user has not migrated yet. We don't have a new sessions // prefs, as it was merged into cookiesAndStorage as part of the effort for // the clear data revamp Bug 1853996 mozilla::Preferences::SetBool( "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads", false); mozilla::Preferences::SetBool("privacy.clearOnShutdown_v2.siteSettings", false); } mozilla::Preferences::SetBool("privacy.clearOnShutdown.cookies", true); mozilla::Preferences::SetBool("privacy.clearOnShutdown.cache", true); mozilla::Preferences::SetBool("privacy.clearOnShutdown.offlineApps", true); // Migrate the new clear on shutdown prefs mozilla::Preferences::SetBool("privacy.clearOnShutdown_v2.cookiesAndStorage", true); mozilla::Preferences::SetBool("privacy.clearOnShutdown_v2.cache", true); mozilla::Preferences::ClearUser("network.cookie.lifetimePolicy"); } } // anonymous namespace // static uint32_t nsICookieManager::GetCookieBehavior(bool aIsPrivate) { if (aIsPrivate) { // To sync the cookieBehavior pref between regular and private mode in ETP // custom mode, we will return the regular cookieBehavior pref for private // mode when // 1. The regular cookieBehavior pref has a non-default value. // 2. And the private cookieBehavior pref has a default value. // Also, this can cover the migration case where the user has a non-default // cookieBehavior before the private cookieBehavior was introduced. The // getter here will directly return the regular cookieBehavior, so that the // cookieBehavior for private mode is consistent. if (mozilla::Preferences::HasUserValue( "network.cookie.cookieBehavior.pbmode")) { return MakeCookieBehavior( mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode()); } if (mozilla::Preferences::HasUserValue("network.cookie.cookieBehavior")) { return MakeCookieBehavior( mozilla::StaticPrefs::network_cookie_cookieBehavior()); } return MakeCookieBehavior( mozilla::StaticPrefs::network_cookie_cookieBehavior_pbmode()); } return MakeCookieBehavior( mozilla::StaticPrefs::network_cookie_cookieBehavior()); } namespace mozilla { namespace net { /****************************************************************************** * CookieService impl: * useful types & constants ******************************************************************************/ static StaticRefPtr gCookieService; constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns; namespace { // Return false if the cookie should be ignored for the current channel. bool ProcessSameSiteCookieForForeignRequest(nsIChannel* aChannel, Cookie* aCookie, bool aIsSafeTopLevelNav, bool aHadCrossSiteRedirects, bool aLaxByDefault) { // If it's a cross-site request and the cookie is same site only (strict) // don't send it. if (aCookie->SameSite() == nsICookie::SAMESITE_STRICT) { return false; } // Explicit SameSite=None cookies are always processed. When laxByDefault // is OFF then so are default cookies. if (aCookie->SameSite() == nsICookie::SAMESITE_NONE || (!aLaxByDefault && aCookie->SameSite() == nsICookie::SAMESITE_UNSET)) { return true; } // Lax-by-default cookies are processed even with an intermediate // cross-site redirect (they are treated like aIsSameSiteForeign = false). if (aLaxByDefault && aCookie->SameSite() == nsICookie::SAMESITE_UNSET && aHadCrossSiteRedirects && StaticPrefs:: network_cookie_sameSite_laxByDefault_allowBoomerangRedirect()) { return true; } int64_t currentTimeInUsec = PR_Now(); // 2 minutes of tolerance for 'SameSite=Lax by default' for cookies set // without a SameSite value when used for unsafe http methods. if (aLaxByDefault && aCookie->SameSite() == nsICookie::SAMESITE_UNSET && StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 && currentTimeInUsec - aCookie->CreationTime() <= (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() * PR_USEC_PER_SEC) && !NS_IsSafeMethodNav(aChannel)) { return true; } MOZ_ASSERT( (aLaxByDefault && aCookie->SameSite() == nsICookie::SAMESITE_UNSET) || aCookie->SameSite() == nsICookie::SAMESITE_LAX); // We only have SameSite=Lax or lax-by-default cookies at this point. These // are processed only if it's a top-level navigation return aIsSafeTopLevelNav; } } // namespace /****************************************************************************** * CookieService impl: * singleton instance ctor/dtor methods ******************************************************************************/ already_AddRefed CookieService::GetXPCOMSingleton() { if (IsNeckoChild()) { return CookieServiceChild::GetSingleton(); } return GetSingleton(); } already_AddRefed CookieService::GetSingleton() { NS_ASSERTION(!IsNeckoChild(), "not a parent process"); if (gCookieService) { return do_AddRef(gCookieService); } // Create a new singleton CookieService. // We AddRef only once since XPCOM has rules about the ordering of module // teardowns - by the time our module destructor is called, it's too late to // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC // cycles have already been completed and would result in serious leaks. // See bug 209571. // TODO: Verify what is the earliest point in time during shutdown where // we can deny the creation of the CookieService as a whole. gCookieService = new CookieService(); if (gCookieService) { if (NS_SUCCEEDED(gCookieService->Init())) { ClearOnShutdown(&gCookieService); } else { gCookieService = nullptr; } } return do_AddRef(gCookieService); } /****************************************************************************** * CookieService impl: * public methods ******************************************************************************/ NS_IMPL_ISUPPORTS(CookieService, nsICookieService, nsICookieManager, nsIObserver, nsISupportsWeakReference, nsIMemoryReporter) CookieService::CookieService() = default; nsresult CookieService::Init() { nsresult rv; mTLDService = mozilla::components::EffectiveTLD::Service(&rv); NS_ENSURE_SUCCESS(rv, rv); mThirdPartyUtil = mozilla::components::ThirdPartyUtil::Service(); NS_ENSURE_SUCCESS(rv, rv); // Init our default, and possibly private CookieStorages. InitCookieStorages(); // Migrate network.cookie.lifetimePolicy pref to sanitizeOnShutdown prefs MigrateCookieLifetimePrefs(); RegisterWeakMemoryReporter(this); nsCOMPtr os = services::GetObserverService(); NS_ENSURE_STATE(os); os->AddObserver(this, "profile-before-change", true); os->AddObserver(this, "profile-do-change", true); os->AddObserver(this, "last-pb-context-exited", true); os->AddObserver(this, "browser-delayed-startup-finished", true); return NS_OK; } void CookieService::InitCookieStorages() { NS_ASSERTION(!mPersistentStorage, "already have a default CookieStorage"); NS_ASSERTION(!mPrivateStorage, "already have a private CookieStorage"); // Create two new CookieStorages. If we are in or beyond our observed // shutdown phase, just be non-persistent. if (MOZ_UNLIKELY(StaticPrefs::network_cookie_noPersistentStorage() || AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown))) { mPersistentStorage = CookiePrivateStorage::Create(); } else { mPersistentStorage = CookiePersistentStorage::Create(); } mPrivateStorage = CookiePrivateStorage::Create(); } void CookieService::CloseCookieStorages() { // return if we already closed if (!mPersistentStorage) { return; } // Let's nullify both storages before calling Close(). RefPtr privateStorage; privateStorage.swap(mPrivateStorage); RefPtr persistentStorage; persistentStorage.swap(mPersistentStorage); privateStorage->Close(); persistentStorage->Close(); } CookieService::~CookieService() { CloseCookieStorages(); UnregisterWeakMemoryReporter(this); gCookieService = nullptr; } NS_IMETHODIMP CookieService::Observe(nsISupports* /*aSubject*/, const char* aTopic, const char16_t* /*aData*/) { // check the topic if (!strcmp(aTopic, "profile-before-change")) { // The profile is about to change, // or is going away because the application is shutting down. // Close the default DB connection and null out our CookieStorages before // changing. CloseCookieStorages(); } else if (!strcmp(aTopic, "profile-do-change")) { NS_ASSERTION(!mPersistentStorage, "shouldn't have a default CookieStorage"); NS_ASSERTION(!mPrivateStorage, "shouldn't have a private CookieStorage"); // the profile has already changed; init the db from the new location. // if we are in the private browsing state, however, we do not want to read // data into it - we should instead put it into the default state, so it's // ready for us if and when we switch back to it. InitCookieStorages(); } else if (!strcmp(aTopic, "browser-delayed-startup-finished")) { mThirdPartyCookieBlockingExceptions.Initialize(); RunOnShutdown([self = RefPtr{this}] { self->mThirdPartyCookieBlockingExceptions.Shutdown(); }); } else if (!strcmp(aTopic, "last-pb-context-exited")) { // Flush all the cookies stored by private browsing contexts OriginAttributesPattern pattern; pattern.mPrivateBrowsingId.Construct(1); RemoveCookiesWithOriginAttributes(pattern, ""_ns); mPrivateStorage = CookiePrivateStorage::Create(); } return NS_OK; } NS_IMETHODIMP CookieService::GetCookieBehavior(bool aIsPrivate, uint32_t* aCookieBehavior) { NS_ENSURE_ARG_POINTER(aCookieBehavior); *aCookieBehavior = nsICookieManager::GetCookieBehavior(aIsPrivate); return NS_OK; } NS_IMETHODIMP CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel, nsACString& aCookieString) { NS_ENSURE_ARG(aHostURI); NS_ENSURE_ARG(aChannel); aCookieString.Truncate(); if (!CookieCommons::IsSchemeSupported(aHostURI)) { return NS_OK; } uint32_t rejectedReason = 0; ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel( aChannel, false, aHostURI, nullptr, &rejectedReason); bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel); bool hadCrossSiteRedirects = false; bool isSameSiteForeign = CookieCommons::IsSameSiteForeign( aChannel, aHostURI, &hadCrossSiteRedirects); OriginAttributes storageOriginAttributes; StoragePrincipalHelper::GetOriginAttributes( aChannel, storageOriginAttributes, StoragePrincipalHelper::eStorageAccessPrincipal); nsTArray originAttributesList; originAttributesList.AppendElement(storageOriginAttributes); // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available // (and therefore the partitioned OriginAttributes), the unpartitioned cookie // jar is only available in first-party or third-party with storageAccess // contexts. nsCOMPtr cookieJarSettings = CookieCommons::GetCookieJarSettings(aChannel); bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() && !cookieJarSettings->GetBlockingAllContexts(); bool isUnpartitioned = !result.contains(ThirdPartyAnalysis::IsForeign) || result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted); if (isCHIPS && isUnpartitioned) { // Assert that the storage originAttributes is empty. In other words, // it's unpartitioned. MOZ_ASSERT(storageOriginAttributes.mPartitionKey.IsEmpty()); // Add the partitioned principal to principals OriginAttributes partitionedOriginAttributes; StoragePrincipalHelper::GetOriginAttributes( aChannel, partitionedOriginAttributes, StoragePrincipalHelper::ePartitionedPrincipal); // Only append the partitioned originAttributes if the partitionKey is set. // The partitionKey could be empty for partitionKey in partitioned // originAttributes if the channel is for privilege request, such as // extension's requests. if (!partitionedOriginAttributes.mPartitionKey.IsEmpty()) { originAttributesList.AppendElement(partitionedOriginAttributes); } } AutoTArray, 8> foundCookieList; GetCookiesForURI( aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign), result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), rejectedReason, isSafeTopLevelNav, isSameSiteForeign, hadCrossSiteRedirects, true, false, originAttributesList, foundCookieList); CookieCommons::ComposeCookieString(foundCookieList, aCookieString); if (!aCookieString.IsEmpty()) { COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false); } return NS_OK; } NS_IMETHODIMP CookieService::SetCookieStringFromHttp(nsIURI* aHostURI, const nsACString& aCookieHeader, nsIChannel* aChannel) { NS_ENSURE_ARG(aHostURI); NS_ENSURE_ARG(aChannel); if (!IsInitialized()) { return NS_OK; } if (!CookieCommons::IsSchemeSupported(aHostURI)) { return NS_OK; } uint32_t rejectedReason = 0; ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel( aChannel, false, aHostURI, nullptr, &rejectedReason); OriginAttributes storagePrincipalOriginAttributes; StoragePrincipalHelper::GetOriginAttributes( aChannel, storagePrincipalOriginAttributes, StoragePrincipalHelper::eStorageAccessPrincipal); // get the base domain for the host URI. // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". // file:// URI's (i.e. with an empty host) are allowed, but any other // scheme must have a non-empty host. A trailing dot in the host // is acceptable. bool requireHostMatch; nsAutoCString baseDomain; nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch); if (NS_FAILED(rv)) { COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "couldn't get base domain from URI"); return NS_OK; } nsCOMPtr cookieJarSettings = CookieCommons::GetCookieJarSettings(aChannel); nsAutoCString hostFromURI; nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI); nsAutoCString baseDomainFromURI; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, hostFromURI, baseDomainFromURI); NS_ENSURE_SUCCESS(rv, NS_OK); CookieStorage* storage = PickStorage(storagePrincipalOriginAttributes); // check default prefs uint32_t priorCookieCount = storage->CountCookiesFromHost( baseDomainFromURI, storagePrincipalOriginAttributes.mPrivateBrowsingId); nsCOMPtr crc = do_QueryInterface(aChannel); CookieStatus cookieStatus = CheckPrefs( crc, cookieJarSettings, aHostURI, result.contains(ThirdPartyAnalysis::IsForeign), result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), aCookieHeader, priorCookieCount, storagePrincipalOriginAttributes, &rejectedReason); MOZ_ASSERT_IF( rejectedReason && rejectedReason != nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER, cookieStatus == STATUS_REJECTED); // fire a notification if third party or if cookie was rejected // (but not if there was an error) switch (cookieStatus) { case STATUS_REJECTED: CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason, OPERATION_WRITE); return NS_OK; // Stop here case STATUS_REJECTED_WITH_ERROR: CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason, OPERATION_WRITE); return NS_OK; case STATUS_ACCEPTED: // Fallthrough case STATUS_ACCEPT_SESSION: NotifyAccepted(aChannel); // Notify the content blocking event if tracker cookies are partitioned. if (rejectedReason == nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER) { ContentBlockingNotifier::OnDecision( aChannel, ContentBlockingNotifier::BlockingDecision::eBlock, rejectedReason); } break; default: break; } bool addonAllowsLoad = false; nsCOMPtr channelURI; NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); nsCOMPtr loadInfo = aChannel->LoadInfo(); addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal()) ->AddonAllowsLoad(channelURI); bool isForeignAndNotAddon = false; if (!addonAllowsLoad) { mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeignAndNotAddon); // include sub-document navigations from cross-site to same-site // wrt top-level in our check for thirdparty-ness if (StaticPrefs::network_cookie_sameSite_crossSiteIframeSetCheck() && !isForeignAndNotAddon && loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_SUBDOCUMENT) { bool triggeringPrincipalIsThirdParty = false; BasePrincipal::Cast(loadInfo->TriggeringPrincipal()) ->IsThirdPartyURI(channelURI, &triggeringPrincipalIsThirdParty); isForeignAndNotAddon |= triggeringPrincipalIsThirdParty; } } bool mustBePartitioned = isForeignAndNotAddon && cookieJarSettings->GetCookieBehavior() == nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN && !result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted); nsCString cookieHeader(aCookieHeader); // CHIPS - The partitioned cookie jar is always available and it is always // possible to store cookies in it using the "Partitioned" attribute. // Prepare the partitioned principals OAs to enable possible partitioned // cookie storing from first-party or with StorageAccess. // Similar behavior to CookieServiceChild::SetCookieStringFromHttp(). OriginAttributes partitionedPrincipalOriginAttributes; bool isPartitionedPrincipal = !storagePrincipalOriginAttributes.mPartitionKey.IsEmpty(); bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() && !cookieJarSettings->GetBlockingAllContexts(); // Only need to get OAs if we don't already use the partitioned principal. if (isCHIPS && !isPartitionedPrincipal) { StoragePrincipalHelper::GetOriginAttributes( aChannel, partitionedPrincipalOriginAttributes, StoragePrincipalHelper::ePartitionedPrincipal); } nsAutoCString dateHeader; CookieCommons::GetServerDateHeader(aChannel, dateHeader); // process each cookie in the header CookieParser cookieParser(crc, aHostURI); cookieParser.Parse(baseDomain, requireHostMatch, cookieStatus, cookieHeader, dateHeader, true, isForeignAndNotAddon, mustBePartitioned, storagePrincipalOriginAttributes.IsPrivateBrowsing(), loadInfo->GetIsOn3PCBExceptionList()); if (!cookieParser.ContainsCookie()) { return NS_OK; } // check permissions from site permission list. if (!CookieCommons::CheckCookiePermission(aChannel, cookieParser.CookieData())) { COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie rejected by permission manager"); CookieCommons::NotifyRejected( aHostURI, aChannel, nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION, OPERATION_WRITE); cookieParser.RejectCookie(CookieParser::RejectedByPermissionManager); return NS_OK; } // CHIPS - If the partitioned attribute is set, store cookie in partitioned // cookie jar independent of context. If the cookies are stored in the // partitioned cookie jar anyway no special treatment of CHIPS cookies // necessary. bool needPartitioned = isCHIPS && cookieParser.CookieData().isPartitioned() && !isPartitionedPrincipal; OriginAttributes& cookieOriginAttributes = needPartitioned ? partitionedPrincipalOriginAttributes : storagePrincipalOriginAttributes; // Assert that partitionedPrincipalOriginAttributes are initialized if used. MOZ_ASSERT_IF(needPartitioned, !partitionedPrincipalOriginAttributes.mPartitionKey.IsEmpty()); // create a new Cookie RefPtr cookie = Cookie::Create(cookieParser.CookieData(), cookieOriginAttributes); MOZ_ASSERT(cookie); int64_t currentTimeInUsec = PR_Now(); cookie->SetLastAccessed(currentTimeInUsec); cookie->SetCreationTime( Cookie::GenerateUniqueCreationTime(currentTimeInUsec)); // Use TargetBrowsingContext to also take frame loads into account. RefPtr bc = loadInfo->GetTargetBrowsingContext(); // add the cookie to the list. AddCookie() takes care of logging. storage->AddCookie(&cookieParser, baseDomain, cookieOriginAttributes, cookie, currentTimeInUsec, aHostURI, aCookieHeader, true, isForeignAndNotAddon, bc); return NS_OK; } void CookieService::NotifyAccepted(nsIChannel* aChannel) { ContentBlockingNotifier::OnDecision( aChannel, ContentBlockingNotifier::BlockingDecision::eAllow, 0); } /****************************************************************************** * CookieService: * public transaction helper impl ******************************************************************************/ NS_IMETHODIMP CookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) { NS_ENSURE_ARG(aCallback); if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } mPersistentStorage->EnsureInitialized(); return mPersistentStorage->RunInTransaction(aCallback); } /****************************************************************************** * nsICookieManager impl: * nsICookieManager ******************************************************************************/ NS_IMETHODIMP CookieService::RemoveAll() { if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } mPersistentStorage->EnsureInitialized(); mPersistentStorage->RemoveAll(); return NS_OK; } NS_IMETHODIMP CookieService::GetCookies(nsTArray>& aCookies) { if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } mPersistentStorage->EnsureInitialized(); // We expose only non-private cookies. mPersistentStorage->GetCookies(aCookies); return NS_OK; } NS_IMETHODIMP CookieService::GetSessionCookies(nsTArray>& aCookies) { if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } mPersistentStorage->EnsureInitialized(); // We expose only non-private cookies. mPersistentStorage->GetSessionCookies(aCookies); return NS_OK; } NS_IMETHODIMP CookieService::Add(const nsACString& aHost, const nsACString& aPath, const nsACString& aName, const nsACString& aValue, bool aIsSecure, bool aIsHttpOnly, bool aIsSession, int64_t aExpiry, JS::Handle aOriginAttributes, int32_t aSameSite, nsICookie::schemeType aSchemeMap, bool aIsPartitioned, JSContext* aCx) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } return AddNative(nullptr, aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly, aIsSession, aExpiry, &attrs, aSameSite, aSchemeMap, aIsPartitioned, /* from-http: */ true, nullptr, [](CookieStruct&) -> bool { return true; }); } NS_IMETHODIMP_(nsresult) CookieService::AddNative(nsIURI* aCookieURI, const nsACString& aHost, const nsACString& aPath, const nsACString& aName, const nsACString& aValue, bool aIsSecure, bool aIsHttpOnly, bool aIsSession, int64_t aExpiry, OriginAttributes* aOriginAttributes, int32_t aSameSite, nsICookie::schemeType aSchemeMap, bool aIsPartitioned, bool aFromHttp, const nsID* aOperationID, const std::function& aCheck) { if (NS_WARN_IF(!aOriginAttributes)) { return NS_ERROR_FAILURE; } if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } // first, normalize the hostname, and fail if it contains illegal characters. nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); // get the base domain for the host URI. // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); int64_t currentTimeInUsec = PR_Now(); CookieStruct cookieData(nsCString(aName), nsCString(aValue), host, nsCString(aPath), aExpiry, currentTimeInUsec, Cookie::GenerateUniqueCreationTime(currentTimeInUsec), aIsHttpOnly, aIsSession, aIsSecure, aIsPartitioned, aSameSite, aSchemeMap); if (!aCheck(cookieData)) { return NS_ERROR_FAILURE; } RefPtr cookie = Cookie::Create(cookieData, *aOriginAttributes); MOZ_ASSERT(cookie); CookieStorage* storage = PickStorage(*aOriginAttributes); storage->AddCookie(nullptr, baseDomain, *aOriginAttributes, cookie, currentTimeInUsec, aCookieURI, VoidCString(), aFromHttp, !aOriginAttributes->mPartitionKey.IsEmpty(), nullptr, aOperationID); return NS_OK; } nsresult CookieService::Remove(const nsACString& aHost, const OriginAttributes& aAttrs, const nsACString& aName, const nsACString& aPath, bool aFromHttp, const nsID* aOperationID) { // first, normalize the hostname, and fail if it contains illegal characters. nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; if (!host.IsEmpty()) { rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); } if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } CookieStorage* storage = PickStorage(aAttrs); storage->RemoveCookie(baseDomain, aAttrs, host, PromiseFlatCString(aName), PromiseFlatCString(aPath), aFromHttp, aOperationID); return NS_OK; } NS_IMETHODIMP CookieService::Remove(const nsACString& aHost, const nsACString& aName, const nsACString& aPath, JS::Handle aOriginAttributes, JSContext* aCx) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } return RemoveNative(aHost, aName, aPath, &attrs, /* from http: */ true, nullptr); } NS_IMETHODIMP_(nsresult) CookieService::RemoveNative(const nsACString& aHost, const nsACString& aName, const nsACString& aPath, OriginAttributes* aOriginAttributes, bool aFromHttp, const nsID* aOperationID) { if (NS_WARN_IF(!aOriginAttributes)) { return NS_ERROR_FAILURE; } nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aFromHttp, aOperationID); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } void CookieService::GetCookiesForURI( nsIURI* aHostURI, nsIChannel* aChannel, bool aIsForeign, bool aIsThirdPartyTrackingResource, bool aIsThirdPartySocialTrackingResource, bool aStorageAccessPermissionGranted, uint32_t aRejectedReason, bool aIsSafeTopLevelNav, bool aIsSameSiteForeign, bool aHadCrossSiteRedirects, bool aHttpBound, bool aAllowSecureCookiesToInsecureOrigin, const nsTArray& aOriginAttrsList, nsTArray>& aCookieList) { NS_ASSERTION(aHostURI, "null host!"); if (!CookieCommons::IsSchemeSupported(aHostURI)) { return; } if (!IsInitialized()) { return; } nsCOMPtr cookieJarSettings = CookieCommons::GetCookieJarSettings(aChannel); nsCOMPtr crc = do_QueryInterface(aChannel); nsCOMPtr loadInfo = aChannel ? aChannel->LoadInfo() : nullptr; const bool on3pcdException = loadInfo && loadInfo->GetIsOn3PCBExceptionList(); for (const auto& attrs : aOriginAttrsList) { CookieStorage* storage = PickStorage(attrs); // get the base domain, host, and path from the URI. // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk". // file:// URI's (i.e. with an empty host) are allowed, but any other // scheme must have a non-empty host. A trailing dot in the host // is acceptable. bool requireHostMatch; nsAutoCString baseDomain; nsAutoCString hostFromURI; nsAutoCString pathFromURI; nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch); if (NS_SUCCEEDED(rv)) { rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI); } if (NS_SUCCEEDED(rv)) { rv = aHostURI->GetFilePath(pathFromURI); } if (NS_FAILED(rv)) { COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(), "invalid host/path from URI"); return; } nsAutoCString normalizedHostFromURI(hostFromURI); rv = NormalizeHost(normalizedHostFromURI); NS_ENSURE_SUCCESS_VOID(rv); nsAutoCString baseDomainFromURI; rv = CookieCommons::GetBaseDomainFromHost( mTLDService, normalizedHostFromURI, baseDomainFromURI); NS_ENSURE_SUCCESS_VOID(rv); // check default prefs uint32_t rejectedReason = aRejectedReason; uint32_t priorCookieCount = storage->CountCookiesFromHost( baseDomainFromURI, attrs.mPrivateBrowsingId); CookieStatus cookieStatus = CheckPrefs( crc, cookieJarSettings, aHostURI, aIsForeign, aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource, aStorageAccessPermissionGranted, VoidCString(), priorCookieCount, attrs, &rejectedReason); MOZ_ASSERT_IF( rejectedReason && rejectedReason != nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER, cookieStatus == STATUS_REJECTED); // for GetCookie(), we only fire acceptance/rejection notifications // (but not if there was an error) switch (cookieStatus) { case STATUS_REJECTED: // If we don't have any cookies from this host, fail silently. if (priorCookieCount) { CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason, OPERATION_READ); } return; default: break; } // Note: The following permissions logic is mirrored in // extensions::MatchPattern::MatchesCookie. // If it changes, please update that function, or file a bug for someone // else to do so. // check if aHostURI is using an https secure protocol. // if it isn't, then we can't send a secure cookie over the connection. // if SchemeIs fails, assume an insecure connection, to be on the safe side bool potentiallyTrustworthy = nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI); int64_t currentTimeInUsec = PR_Now(); int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; bool stale = false; nsTArray> cookies; storage->GetCookiesFromHost(baseDomain, attrs, cookies); if (cookies.IsEmpty()) { continue; } bool laxByDefault = StaticPrefs::network_cookie_sameSite_laxByDefault() && !nsContentUtils::IsURIInPrefList( aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts"); // iterate the cookies! for (Cookie* cookie : cookies) { // check the host, since the base domain lookup is conservative. if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { continue; } // if the cookie is secure and the host scheme isn't, we avoid sending // cookie if possible. But for process synchronization purposes, we may // want the content process to know about the cookie (without it's value). // In which case we will wipe the value before sending if (cookie->IsSecure() && !potentiallyTrustworthy && !aAllowSecureCookiesToInsecureOrigin) { continue; } // if the cookie is httpOnly and it's not going directly to the HTTP // connection, don't send it if (cookie->IsHttpOnly() && !aHttpBound) { continue; } // if the nsIURI path doesn't match the cookie path, don't send it back if (!CookieCommons::PathMatches(cookie, pathFromURI)) { continue; } // check if the cookie has expired if (cookie->Expiry() <= currentTime) { continue; } // Check if we need to block the cookie because of opt-in partitioning. // We will only allow partitioned cookies with "partitioned" attribution // if opt-in partitioning is enabled. // // Note: If the cookie is on the 3pcd exception list, we will include // the cookie. if (aIsForeign && cookieJarSettings->GetPartitionForeign() && (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() || (attrs.IsPrivateBrowsing() && StaticPrefs:: network_cookie_cookieBehavior_optInPartitioning_pbmode())) && !(cookie->IsPartitioned() && cookie->RawIsPartitioned()) && !aStorageAccessPermissionGranted && !on3pcdException) { continue; } if (aHttpBound && aIsSameSiteForeign) { bool blockCookie = !ProcessSameSiteCookieForForeignRequest( aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects, laxByDefault); if (blockCookie) { if (aHadCrossSiteRedirects) { CookieLogging::LogMessageToConsole( crc, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns, AutoTArray{ NS_ConvertUTF8toUTF16(cookie->Name()), }); } continue; } } // all checks passed - add to list and check if lastAccessed stamp needs // updating aCookieList.AppendElement(cookie); if (cookie->IsStale()) { stale = true; } } if (aCookieList.IsEmpty()) { continue; } // update lastAccessed timestamps. we only do this if the timestamp is stale // by a certain amount, to avoid thrashing the db during pageload. if (stale) { storage->StaleCookies(aCookieList, currentTimeInUsec); } } if (aCookieList.IsEmpty()) { return; } // Send a notification about the acceptance of the cookies now that we found // some. NotifyAccepted(aChannel); // return cookies in order of path length; longest to shortest. // this is required per RFC2109. if cookies match in length, // then sort by creation time (see bug 236772). aCookieList.Sort(CompareCookiesForSending()); } /****************************************************************************** * CookieService impl: * private domain & permission compliance enforcement functions ******************************************************************************/ nsresult CookieService::NormalizeHost(nsCString& aHost) { nsAutoCString host; if (!CookieCommons::IsIPv6BaseDomain(aHost)) { nsresult rv = NS_DomainToASCII(aHost, host); if (NS_FAILED(rv)) { return rv; } aHost = host; } return NS_OK; } CookieStatus CookieService::CheckPrefs( nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings, nsIURI* aHostURI, bool aIsForeign, bool aIsThirdPartyTrackingResource, bool aIsThirdPartySocialTrackingResource, bool aStorageAccessPermissionGranted, const nsACString& aCookieHeader, const int aNumOfCookies, const OriginAttributes& aOriginAttrs, uint32_t* aRejectedReason) { nsresult rv; MOZ_ASSERT(aRejectedReason); *aRejectedReason = 0; // don't let unsupported scheme sites get/set cookies (could be a security // issue) if (!CookieCommons::IsSchemeSupported(aHostURI)) { COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "non http/https sites cannot read cookies"); return STATUS_REJECTED_WITH_ERROR; } nsCOMPtr principal = BasePrincipal::CreateContentPrincipal(aHostURI, aOriginAttrs); if (!principal) { COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "non-content principals cannot get/set cookies"); return STATUS_REJECTED_WITH_ERROR; } // check the permission list first; if we find an entry, it overrides // default prefs. see bug 184059. uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT; rv = aCookieJarSettings->CookiePermission(principal, &cookiePermission); if (NS_SUCCEEDED(rv)) { switch (cookiePermission) { case nsICookiePermission::ACCESS_DENY: COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "cookies are blocked for this site"); CookieLogging::LogMessageToConsole( aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns, AutoTArray{ NS_ConvertUTF8toUTF16(aCookieHeader), }); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; return STATUS_REJECTED; case nsICookiePermission::ACCESS_ALLOW: return STATUS_ACCEPTED; default: break; } } // No cookies allowed if this request comes from a resource in a 3rd party // context, when anti-tracking protection is enabled and when we don't have // access to the first-party cookie jar. if (aIsForeign && aIsThirdPartyTrackingResource && !aStorageAccessPermissionGranted && aCookieJarSettings->GetRejectThirdPartyContexts()) { // Set the reject reason to partitioned tracker if we are not blocking // tracker cookie. uint32_t rejectReason = aCookieJarSettings->GetPartitionForeign() && !StaticPrefs:: network_cookie_cookieBehavior_trackerCookieBlocking() ? nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN : nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; if (StoragePartitioningEnabled(rejectReason, aCookieJarSettings)) { MOZ_ASSERT(!aOriginAttrs.mPartitionKey.IsEmpty(), "We must have a StoragePrincipal here!"); // Set the reject reason to partitioned tracker if the resource to reflect // that we are partitioning tracker cookies. *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER; return STATUS_ACCEPTED; } COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "cookies are disabled in trackers"); if (aIsThirdPartySocialTrackingResource) { *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; } else { *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; } return STATUS_REJECTED; } // check default prefs. // Check aStorageAccessPermissionGranted when checking aCookieBehavior // so that we take things such as the content blocking allow list into // account. if (aCookieJarSettings->GetCookieBehavior() == nsICookieService::BEHAVIOR_REJECT && !aStorageAccessPermissionGranted) { COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "cookies are disabled"); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; return STATUS_REJECTED; } // check if cookie is foreign if (aIsForeign) { if (aCookieJarSettings->GetCookieBehavior() == nsICookieService::BEHAVIOR_REJECT_FOREIGN && !aStorageAccessPermissionGranted) { COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "context is third party"); CookieLogging::LogMessageToConsole( aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns, AutoTArray{ NS_ConvertUTF8toUTF16(aCookieHeader), }); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; return STATUS_REJECTED; } if (aCookieJarSettings->GetLimitForeignContexts() && !aStorageAccessPermissionGranted && aNumOfCookies == 0) { COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader, "context is third party"); CookieLogging::LogMessageToConsole( aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns, AutoTArray{ NS_ConvertUTF8toUTF16(aCookieHeader), }); *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; return STATUS_REJECTED; } } // if nothing has complained, accept cookie return STATUS_ACCEPTED; } /****************************************************************************** * CookieService impl: * private cookielist management functions ******************************************************************************/ // find whether a given cookie has been previously set. this is provided by the // nsICookieManager interface. NS_IMETHODIMP CookieService::CookieExists(const nsACString& aHost, const nsACString& aPath, const nsACString& aName, JS::Handle aOriginAttributes, JSContext* aCx, bool* aFoundCookie) { NS_ENSURE_ARG_POINTER(aCx); NS_ENSURE_ARG_POINTER(aFoundCookie); OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie); } NS_IMETHODIMP_(nsresult) CookieService::CookieExistsNative(const nsACString& aHost, const nsACString& aPath, const nsACString& aName, OriginAttributes* aOriginAttributes, bool* aFoundCookie) { nsCOMPtr cookie; nsresult rv = GetCookieNative(aHost, aPath, aName, aOriginAttributes, getter_AddRefs(cookie)); NS_ENSURE_SUCCESS(rv, rv); *aFoundCookie = cookie != nullptr; return NS_OK; } NS_IMETHODIMP_(nsresult) CookieService::GetCookieNative(const nsACString& aHost, const nsACString& aPath, const nsACString& aName, OriginAttributes* aOriginAttributes, nsICookie** aCookie) { NS_ENSURE_ARG_POINTER(aOriginAttributes); NS_ENSURE_ARG_POINTER(aCookie); if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } nsAutoCString baseDomain; nsresult rv = CookieCommons::GetBaseDomainFromHost(mTLDService, aHost, baseDomain); NS_ENSURE_SUCCESS(rv, rv); CookieListIter iter{}; CookieStorage* storage = PickStorage(*aOriginAttributes); bool foundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost, aName, aPath, iter); if (foundCookie) { RefPtr cookie = iter.Cookie(); NS_ENSURE_TRUE(cookie, NS_ERROR_NULL_POINTER); cookie.forget(aCookie); } return NS_OK; } // count the number of cookies stored by a particular host. this is provided by // the nsICookieManager interface. NS_IMETHODIMP CookieService::CountCookiesFromHost(const nsACString& aHost, uint32_t* aCountFromHost) { // first, normalize the hostname, and fail if it contains illegal characters. nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } mPersistentStorage->EnsureInitialized(); *aCountFromHost = mPersistentStorage->CountCookiesFromHost(baseDomain, 0); return NS_OK; } // get an enumerator of cookies stored by a particular host. this is provided by // the nsICookieManager interface. NS_IMETHODIMP CookieService::GetCookiesFromHost(const nsACString& aHost, JS::Handle aOriginAttributes, bool aSorted, JSContext* aCx, nsTArray>& aResult) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } return GetCookiesFromHostNative(aHost, &attrs, aSorted, aResult); } NS_IMETHODIMP CookieService::GetCookiesFromHostNative(const nsACString& aHost, OriginAttributes* aAttrs, bool aSorted, nsTArray>& aResult) { // first, normalize the hostname, and fail if it contains illegal characters. nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } CookieStorage* storage = PickStorage(*aAttrs); nsTArray> cookies; storage->GetCookiesFromHost(baseDomain, *aAttrs, cookies); if (cookies.IsEmpty()) { return NS_OK; } aResult.SetCapacity(cookies.Length()); for (Cookie* cookie : cookies) { aResult.AppendElement(cookie); } if (aSorted) { aResult.Sort(CompareCookiesForSending()); } return NS_OK; } NS_IMETHODIMP CookieService::GetCookiesWithOriginAttributes( const nsAString& aPattern, const nsACString& aHost, const bool aSorted, nsTArray>& aResult) { OriginAttributesPattern pattern; if (!pattern.Init(aPattern)) { return NS_ERROR_INVALID_ARG; } nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); return GetCookiesWithOriginAttributes(pattern, baseDomain, aSorted, aResult); } nsresult CookieService::GetCookiesWithOriginAttributes( const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, bool aSorted, nsTArray>& aResult) { CookieStorage* storage = PickStorage(aPattern); storage->GetCookiesWithOriginAttributes(aPattern, aBaseDomain, aSorted, aResult); return NS_OK; } NS_IMETHODIMP CookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern, const nsACString& aHost) { MOZ_ASSERT(XRE_IsParentProcess()); OriginAttributesPattern pattern; if (!pattern.Init(aPattern)) { return NS_ERROR_INVALID_ARG; } nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); return RemoveCookiesWithOriginAttributes(pattern, baseDomain); } nsresult CookieService::RemoveCookiesWithOriginAttributes( const OriginAttributesPattern& aPattern, const nsCString& aBaseDomain) { if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } CookieStorage* storage = PickStorage(aPattern); storage->RemoveCookiesWithOriginAttributes(aPattern, aBaseDomain); return NS_OK; } NS_IMETHODIMP CookieService::RemoveCookiesFromExactHost(const nsACString& aHost, const nsAString& aPattern) { MOZ_ASSERT(XRE_IsParentProcess()); OriginAttributesPattern pattern; if (!pattern.Init(aPattern)) { return NS_ERROR_INVALID_ARG; } return RemoveCookiesFromExactHost(aHost, pattern); } nsresult CookieService::RemoveCookiesFromExactHost( const nsACString& aHost, const OriginAttributesPattern& aPattern) { nsAutoCString host(aHost); nsresult rv = NormalizeHost(host); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString baseDomain; rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain); NS_ENSURE_SUCCESS(rv, rv); if (!IsInitialized()) { return NS_ERROR_NOT_AVAILABLE; } CookieStorage* storage = PickStorage(aPattern); storage->RemoveCookiesFromExactHost(aHost, baseDomain, aPattern); return NS_OK; } namespace { class RemoveAllSinceRunnable : public Runnable { public: using CookieArray = nsTArray>; RemoveAllSinceRunnable(Promise* aPromise, CookieService* aSelf, CookieArray&& aCookieArray, int64_t aSinceWhen) : Runnable("RemoveAllSinceRunnable"), mPromise(aPromise), mSelf(aSelf), mList(std::move(aCookieArray)), mIndex(0), mSinceWhen(aSinceWhen) {} NS_IMETHODIMP Run() override { RemoveSome(); if (mIndex < mList.Length()) { return NS_DispatchToCurrentThread(this); } mPromise->MaybeResolveWithUndefined(); return NS_OK; } private: void RemoveSome() { for (CookieArray::size_type iter = 0; iter < kYieldPeriod && mIndex < mList.Length(); ++mIndex, ++iter) { auto* cookie = static_cast(mList[mIndex].get()); if (cookie->CreationTime() > mSinceWhen && NS_FAILED(mSelf->Remove(cookie->Host(), cookie->OriginAttributesRef(), cookie->Name(), cookie->Path(), /* from http: */ true, nullptr))) { continue; } } } private: RefPtr mPromise; RefPtr mSelf; CookieArray mList; CookieArray::size_type mIndex; int64_t mSinceWhen; static const CookieArray::size_type kYieldPeriod = 10; }; } // namespace NS_IMETHODIMP CookieService::RemoveAllSince(int64_t aSinceWhen, JSContext* aCx, Promise** aRetVal) { nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); if (NS_WARN_IF(!globalObject)) { return NS_ERROR_UNEXPECTED; } ErrorResult result; RefPtr promise = Promise::Create(globalObject, result); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } mPersistentStorage->EnsureInitialized(); nsTArray> cookieList; // We delete only non-private cookies. mPersistentStorage->GetAll(cookieList); RefPtr runMe = new RemoveAllSinceRunnable( promise, this, std::move(cookieList), aSinceWhen); promise.forget(aRetVal); return runMe->Run(); } namespace { class CompareCookiesCreationTime { public: static bool Equals(const nsICookie* aCookie1, const nsICookie* aCookie2) { return static_cast(aCookie1)->CreationTime() == static_cast(aCookie2)->CreationTime(); } static bool LessThan(const nsICookie* aCookie1, const nsICookie* aCookie2) { return static_cast(aCookie1)->CreationTime() < static_cast(aCookie2)->CreationTime(); } }; } // namespace NS_IMETHODIMP CookieService::GetCookiesSince(int64_t aSinceWhen, nsTArray>& aResult) { if (!IsInitialized()) { return NS_OK; } mPersistentStorage->EnsureInitialized(); // We expose only non-private cookies. nsTArray> cookieList; mPersistentStorage->GetAll(cookieList); for (RefPtr& cookie : cookieList) { if (static_cast(cookie.get())->CreationTime() >= aSinceWhen) { aResult.AppendElement(cookie); } } aResult.Sort(CompareCookiesCreationTime()); return NS_OK; } size_t CookieService::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = aMallocSizeOf(this); if (mPersistentStorage) { n += mPersistentStorage->SizeOfIncludingThis(aMallocSizeOf); } if (mPrivateStorage) { n += mPrivateStorage->SizeOfIncludingThis(aMallocSizeOf); } return n; } MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf) NS_IMETHODIMP CookieService::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool /*aAnonymize*/) { MOZ_COLLECT_REPORT("explicit/cookie-service", KIND_HEAP, UNITS_BYTES, SizeOfIncludingThis(CookieServiceMallocSizeOf), "Memory used by the cookie service."); return NS_OK; } bool CookieService::IsInitialized() const { if (!mPersistentStorage) { NS_WARNING("No CookieStorage! Profile already close?"); return false; } MOZ_ASSERT(mPrivateStorage); return true; } CookieStorage* CookieService::PickStorage(const OriginAttributes& aAttrs) { MOZ_ASSERT(IsInitialized()); if (aAttrs.IsPrivateBrowsing()) { return mPrivateStorage; } mPersistentStorage->EnsureInitialized(); return mPersistentStorage; } CookieStorage* CookieService::PickStorage( const OriginAttributesPattern& aAttrs) { MOZ_ASSERT(IsInitialized()); if (aAttrs.mPrivateBrowsingId.WasPassed() && aAttrs.mPrivateBrowsingId.Value() > 0) { return mPrivateStorage; } mPersistentStorage->EnsureInitialized(); return mPersistentStorage; } bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain, const OriginAttributes& aAttrs, nsIURI* aHostURI, bool aFromHttp, bool aIsThirdParty, const nsTArray& aCookies, BrowsingContext* aBrowsingContext) { if (!IsInitialized()) { // If we are probably shutting down, we can ignore this cookie. return true; } CookieStorage* storage = PickStorage(aAttrs); int64_t currentTimeInUsec = PR_Now(); for (const CookieStruct& cookieData : aCookies) { if (!CookieCommons::CheckPathSize(cookieData)) { return false; } // reject cookie if it's over the size limit, per RFC2109 if (!CookieCommons::CheckNameAndValueSize(cookieData)) { return false; } if (!CookieCommons::CheckName(cookieData)) { return false; } if (!CookieCommons::CheckValue(cookieData)) { return false; } // create a new Cookie and copy attributes RefPtr cookie = Cookie::Create(cookieData, aAttrs); if (!cookie) { continue; } cookie->SetLastAccessed(currentTimeInUsec); cookie->SetCreationTime( Cookie::GenerateUniqueCreationTime(currentTimeInUsec)); storage->AddCookie(nullptr, aBaseDomain, aAttrs, cookie, currentTimeInUsec, aHostURI, ""_ns, aFromHttp, aIsThirdParty, aBrowsingContext); } return true; } void CookieService::GetCookiesFromHost( const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, nsTArray>& aCookies) { if (!IsInitialized()) { return; } CookieStorage* storage = PickStorage(aOriginAttributes); storage->GetCookiesFromHost(aBaseDomain, aOriginAttributes, aCookies); } void CookieService::StaleCookies(const nsTArray>& aCookies, int64_t aCurrentTimeInUsec) { if (!IsInitialized()) { return; } if (aCookies.IsEmpty()) { return; } OriginAttributes originAttributes = aCookies[0]->OriginAttributesRef(); #ifdef MOZ_DEBUG for (Cookie* cookie : aCookies) { MOZ_ASSERT(originAttributes == cookie->OriginAttributesRef()); } #endif CookieStorage* storage = PickStorage(originAttributes); storage->StaleCookies(aCookies, aCurrentTimeInUsec); } bool CookieService::HasExistingCookies( const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes) { if (!IsInitialized()) { return false; } CookieStorage* storage = PickStorage(aOriginAttributes); return !!storage->CountCookiesFromHost(aBaseDomain, aOriginAttributes.mPrivateBrowsingId); } void CookieService::AddCookieFromDocument( CookieParser& aCookieParser, const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, Cookie& aCookie, int64_t aCurrentTimeInUsec, nsIURI* aDocumentURI, bool aThirdParty, Document* aDocument) { MOZ_ASSERT(aDocumentURI); MOZ_ASSERT(aDocument); if (!IsInitialized()) { return; } nsAutoCString cookieString; aCookieParser.GetCookieString(cookieString); PickStorage(aOriginAttributes) ->AddCookie(&aCookieParser, aBaseDomain, aOriginAttributes, &aCookie, aCurrentTimeInUsec, aDocumentURI, cookieString, false, aThirdParty, aDocument->GetBrowsingContext()); } /* static */ void CookieService::Update3PCBExceptionInfo(nsIChannel* aChannel) { MOZ_ASSERT(aChannel); MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr loadInfo = aChannel->LoadInfo(); RefPtr csSingleton = CookieService::GetSingleton(); // Bail out if the channel is a top-level loading. The exception is only // applicable to third-party loading. if (loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_DOCUMENT) { return; } // Bail out earlier if the 3PCB exception service is not initialized. if (!csSingleton->mThirdPartyCookieBlockingExceptions.IsInitialized()) { return; } // If the channel is triggered by a system principal, we don't need to do // anything because the channel is for loading system resources. if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { return; } bool isInExceptionList = csSingleton->mThirdPartyCookieBlockingExceptions.CheckExceptionForChannel( aChannel); Unused << loadInfo->SetIsOn3PCBExceptionList(isInExceptionList); } NS_IMETHODIMP CookieService::AddThirdPartyCookieBlockingExceptions( const nsTArray>& aExceptions) { for (const auto& ex : aExceptions) { nsAutoCString exception; MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception)); mThirdPartyCookieBlockingExceptions.Insert(exception); } return NS_OK; } NS_IMETHODIMP CookieService::RemoveThirdPartyCookieBlockingExceptions( const nsTArray>& aExceptions) { for (const auto& ex : aExceptions) { nsAutoCString exception; MOZ_ALWAYS_SUCCEEDS(ex->Serialize(exception)); mThirdPartyCookieBlockingExceptions.Remove(exception); } return NS_OK; } NS_IMETHODIMP CookieService::TestGet3PCBExceptions(nsTArray& aExceptions) { aExceptions.Clear(); mThirdPartyCookieBlockingExceptions.GetExceptions(aExceptions); return NS_OK; } } // namespace net } // namespace mozilla