/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PartitioningExceptionList.h" #include "AntiTrackingLog.h" #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" namespace mozilla { namespace { static constexpr std::array kSupportedSchemes = { {"https://"_ns, "http://"_ns}}; StaticRefPtr gPartitioningExceptionList; } // namespace NS_IMPL_ISUPPORTS(PartitioningExceptionList, nsIPartitioningExceptionListObserver) bool PartitioningExceptionList::Check(const nsACString& aFirstPartyOrigin, const nsACString& aThirdPartyOrigin) { if (!StaticPrefs::privacy_antitracking_enableWebcompat()) { LOG(("Partition exception list disabled via pref")); return false; } if (aFirstPartyOrigin.IsEmpty() || aFirstPartyOrigin == "null" || aThirdPartyOrigin.IsEmpty() || aThirdPartyOrigin == "null") { return false; } LOG(("Check partitioning exception list for url %s and %s", PromiseFlatCString(aFirstPartyOrigin).get(), PromiseFlatCString(aThirdPartyOrigin).get())); for (PartitionExceptionListEntry& entry : GetOrCreate()->mExceptionList) { if (OriginMatchesPattern(aFirstPartyOrigin, entry.mFirstParty) && OriginMatchesPattern(aThirdPartyOrigin, entry.mThirdParty)) { LOG(("Found partitioning exception list entry for %s and %s", PromiseFlatCString(aFirstPartyOrigin).get(), PromiseFlatCString(aThirdPartyOrigin).get())); return true; } } return false; } PartitioningExceptionList* PartitioningExceptionList::GetOrCreate() { if (!gPartitioningExceptionList) { gPartitioningExceptionList = new PartitioningExceptionList(); gPartitioningExceptionList->Init(); RunOnShutdown([&] { gPartitioningExceptionList->Shutdown(); gPartitioningExceptionList = nullptr; }); } return gPartitioningExceptionList; } nsresult PartitioningExceptionList::Init() { mService = do_GetService("@mozilla.org/partitioning/exception-list-service;1"); if (NS_WARN_IF(!mService)) { return NS_ERROR_FAILURE; } mService->RegisterAndRunExceptionListObserver(this); return NS_OK; } void PartitioningExceptionList::Shutdown() { if (mService) { mService->UnregisterExceptionListObserver(this); mService = nullptr; } mExceptionList.Clear(); } NS_IMETHODIMP PartitioningExceptionList::OnExceptionListUpdate(const nsACString& aList) { mExceptionList.Clear(); nsresult rv; for (const nsACString& item : aList.Split(';')) { auto origins = item.Split(','); auto originsIt = origins.begin(); if (originsIt == origins.end()) { LOG(("Ignoring empty exception entry")); continue; } PartitionExceptionListEntry entry; rv = GetExceptionListPattern(*originsIt, entry.mFirstParty); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } ++originsIt; if (originsIt == origins.end()) { LOG(("Ignoring incomplete exception entry")); continue; } rv = GetExceptionListPattern(*originsIt, entry.mThirdParty); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } if (entry.mFirstParty.mSuffix == "*" && entry.mThirdParty.mSuffix == "*") { LOG(("Ignoring *,* exception entry")); continue; } LOG(("onExceptionListUpdate: %s%s - %s%s", entry.mFirstParty.mScheme.get(), entry.mFirstParty.mSuffix.get(), entry.mThirdParty.mScheme.get(), entry.mThirdParty.mSuffix.get())); mExceptionList.AppendElement(entry); } return NS_OK; } nsresult PartitioningExceptionList::GetSchemeFromOrigin( const nsACString& aOrigin, nsACString& aScheme, nsACString& aOriginNoScheme) { NS_ENSURE_FALSE(aOrigin.IsEmpty(), NS_ERROR_INVALID_ARG); for (const auto& scheme : kSupportedSchemes) { if (aOrigin.Length() <= scheme.Length() || !StringBeginsWith(aOrigin, scheme)) { continue; } aScheme = Substring(aOrigin, 0, scheme.Length()); aOriginNoScheme = Substring(aOrigin, scheme.Length()); return NS_OK; } return NS_ERROR_FAILURE; } bool PartitioningExceptionList::OriginMatchesPattern( const nsACString& aOrigin, const PartitionExceptionListPattern& aPattern) { if (NS_WARN_IF(aOrigin.IsEmpty())) { return false; } if (aPattern.mSuffix == "*") { return true; } nsAutoCString scheme, originNoScheme; nsresult rv = GetSchemeFromOrigin(aOrigin, scheme, originNoScheme); NS_ENSURE_SUCCESS(rv, false); // Always strict match scheme. if (scheme != aPattern.mScheme) { return false; } if (!aPattern.mIsWildCard) { // aPattern is not a wildcard, match strict. return originNoScheme == aPattern.mSuffix; } // For wildcard patterns, check if origin suffix matches pattern suffix. return StringEndsWith(originNoScheme, aPattern.mSuffix); } // Parses a string with an origin or an origin-pattern into a // PartitionExceptionListPattern. nsresult PartitioningExceptionList::GetExceptionListPattern( const nsACString& aOriginPattern, PartitionExceptionListPattern& aPattern) { NS_ENSURE_FALSE(aOriginPattern.IsEmpty(), NS_ERROR_INVALID_ARG); if (aOriginPattern == "*") { aPattern.mIsWildCard = true; aPattern.mSuffix = "*"; return NS_OK; } nsAutoCString originPatternNoScheme; nsresult rv = GetSchemeFromOrigin(aOriginPattern, aPattern.mScheme, originPatternNoScheme); NS_ENSURE_SUCCESS(rv, rv); if (StringBeginsWith(originPatternNoScheme, "*"_ns)) { NS_ENSURE_TRUE(originPatternNoScheme.Length() > 2, NS_ERROR_INVALID_ARG); aPattern.mIsWildCard = true; aPattern.mSuffix = Substring(originPatternNoScheme, 1); return NS_OK; } aPattern.mIsWildCard = false; aPattern.mSuffix = originPatternNoScheme; return NS_OK; } } // namespace mozilla