summaryrefslogtreecommitdiffstats
path: root/dom/security/ReferrerInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/security/ReferrerInfo.cpp1729
1 files changed, 1729 insertions, 0 deletions
diff --git a/dom/security/ReferrerInfo.cpp b/dom/security/ReferrerInfo.cpp
new file mode 100644
index 0000000000..70cdddb8ab
--- /dev/null
+++ b/dom/security/ReferrerInfo.cpp
@@ -0,0 +1,1729 @@
+/* -*- 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 "mozilla/RefPtr.h"
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIHttpChannel.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsIURL.h"
+
+#include "nsWhitespaceTokenizer.h"
+#include "nsAlgorithm.h"
+#include "nsContentUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsScriptSecurityManager.h"
+#include "nsStreamUtils.h"
+#include "ReferrerInfo.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/Telemetry.h"
+#include "nsIWebProgressListener.h"
+
+static mozilla::LazyLogModule gReferrerInfoLog("ReferrerInfo");
+#define LOG(msg) MOZ_LOG(gReferrerInfoLog, mozilla::LogLevel::Debug, msg)
+#define LOG_ENABLED() MOZ_LOG_TEST(gReferrerInfoLog, mozilla::LogLevel::Debug)
+
+using namespace mozilla::net;
+
+namespace mozilla::dom {
+
+// Implementation of ClassInfo is required to serialize/deserialize
+NS_IMPL_CLASSINFO(ReferrerInfo, nullptr, nsIClassInfo::THREADSAFE,
+ REFERRERINFO_CID)
+
+NS_IMPL_ISUPPORTS_CI(ReferrerInfo, nsIReferrerInfo, nsISerializable)
+
+#define MAX_REFERRER_SENDING_POLICY 2
+#define MAX_CROSS_ORIGIN_SENDING_POLICY 2
+#define MAX_TRIMMING_POLICY 2
+
+#define MIN_REFERRER_SENDING_POLICY 0
+#define MIN_CROSS_ORIGIN_SENDING_POLICY 0
+#define MIN_TRIMMING_POLICY 0
+
+/*
+ * Default referrer policy to use
+ */
+enum DefaultReferrerPolicy : uint32_t {
+ eDefaultPolicyNoReferrer = 0,
+ eDefaultPolicySameOrgin = 1,
+ eDefaultPolicyStrictWhenXorigin = 2,
+ eDefaultPolicyNoReferrerWhenDownGrade = 3,
+};
+
+static uint32_t GetDefaultFirstPartyReferrerPolicyPref(bool aPrivateBrowsing) {
+ return aPrivateBrowsing
+ ? StaticPrefs::network_http_referer_defaultPolicy_pbmode()
+ : StaticPrefs::network_http_referer_defaultPolicy();
+}
+
+static uint32_t GetDefaultThirdPartyReferrerPolicyPref(bool aPrivateBrowsing) {
+ return aPrivateBrowsing
+ ? StaticPrefs::network_http_referer_defaultPolicy_trackers_pbmode()
+ : StaticPrefs::network_http_referer_defaultPolicy_trackers();
+}
+
+static ReferrerPolicy DefaultReferrerPolicyToReferrerPolicy(
+ uint32_t aDefaultToUse) {
+ switch (aDefaultToUse) {
+ case DefaultReferrerPolicy::eDefaultPolicyNoReferrer:
+ return ReferrerPolicy::No_referrer;
+ case DefaultReferrerPolicy::eDefaultPolicySameOrgin:
+ return ReferrerPolicy::Same_origin;
+ case DefaultReferrerPolicy::eDefaultPolicyStrictWhenXorigin:
+ return ReferrerPolicy::Strict_origin_when_cross_origin;
+ }
+
+ return ReferrerPolicy::No_referrer_when_downgrade;
+}
+
+struct LegacyReferrerPolicyTokenMap {
+ const char* mToken;
+ ReferrerPolicy mPolicy;
+};
+
+/*
+ * Parse ReferrerPolicy from token.
+ * The supported tokens are defined in ReferrerPolicy.webidl.
+ * The legacy tokens are "never", "default", "always" and
+ * "origin-when-crossorigin". The legacy tokens are only supported in meta
+ * referrer content
+ *
+ * @param aContent content string to be transformed into
+ * ReferrerPolicyEnum, e.g. "origin".
+ */
+ReferrerPolicy ReferrerPolicyFromToken(const nsAString& aContent,
+ bool allowedLegacyToken) {
+ nsString lowerContent(aContent);
+ ToLowerCase(lowerContent);
+
+ if (allowedLegacyToken) {
+ static const LegacyReferrerPolicyTokenMap sLegacyReferrerPolicyToken[] = {
+ {"never", ReferrerPolicy::No_referrer},
+ {"default", ReferrerPolicy::No_referrer_when_downgrade},
+ {"always", ReferrerPolicy::Unsafe_url},
+ {"origin-when-crossorigin", ReferrerPolicy::Origin_when_cross_origin},
+ };
+
+ uint8_t numStr = (sizeof(sLegacyReferrerPolicyToken) /
+ sizeof(sLegacyReferrerPolicyToken[0]));
+ for (uint8_t i = 0; i < numStr; i++) {
+ if (lowerContent.EqualsASCII(sLegacyReferrerPolicyToken[i].mToken)) {
+ return sLegacyReferrerPolicyToken[i].mPolicy;
+ }
+ }
+ }
+
+ // Supported tokes - ReferrerPolicyValues, are generated from
+ // ReferrerPolicy.webidl
+ for (uint8_t i = 0; ReferrerPolicyValues::strings[i].value; i++) {
+ if (lowerContent.EqualsASCII(ReferrerPolicyValues::strings[i].value)) {
+ return static_cast<enum ReferrerPolicy>(i);
+ }
+ }
+
+ // Return no referrer policy (empty string) if none of the previous match
+ return ReferrerPolicy::_empty;
+}
+
+// static
+ReferrerPolicy ReferrerInfo::ReferrerPolicyFromMetaString(
+ const nsAString& aContent) {
+ // This is implemented as described in
+ // https://html.spec.whatwg.org/multipage/semantics.html#meta-referrer
+ // Meta referrer accepts both supported tokens in ReferrerPolicy.webidl and
+ // legacy tokens.
+ return ReferrerPolicyFromToken(aContent, true);
+}
+
+// static
+ReferrerPolicy ReferrerInfo::ReferrerPolicyAttributeFromString(
+ const nsAString& aContent) {
+ // This is implemented as described in
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute
+ // referrerpolicy attribute only accepts supported tokens in
+ // ReferrerPolicy.webidl
+ return ReferrerPolicyFromToken(aContent, false);
+}
+
+// static
+ReferrerPolicy ReferrerInfo::ReferrerPolicyFromHeaderString(
+ const nsAString& aContent) {
+ // Multiple headers could be concatenated into one comma-separated
+ // list of policies. Need to tokenize the multiple headers.
+ ReferrerPolicyEnum referrerPolicy = ReferrerPolicy::_empty;
+ for (const auto& token : nsCharSeparatedTokenizer(aContent, ',').ToRange()) {
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ // Referrer-Policy header only accepts supported tokens in
+ // ReferrerPolicy.webidl
+ ReferrerPolicyEnum policy = ReferrerPolicyFromToken(token, false);
+ // If there are multiple policies available, the last valid policy should be
+ // used.
+ // https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
+ if (policy != ReferrerPolicy::_empty) {
+ referrerPolicy = policy;
+ }
+ }
+ return referrerPolicy;
+}
+
+// static
+const char* ReferrerInfo::ReferrerPolicyToString(ReferrerPolicyEnum aPolicy) {
+ uint8_t index = static_cast<uint8_t>(aPolicy);
+ uint8_t referrerPolicyCount = ArrayLength(ReferrerPolicyValues::strings);
+ MOZ_ASSERT(index < referrerPolicyCount);
+ if (index >= referrerPolicyCount) {
+ return "";
+ }
+
+ return ReferrerPolicyValues::strings[index].value;
+}
+
+/* static */
+uint32_t ReferrerInfo::GetUserReferrerSendingPolicy() {
+ return clamped<uint32_t>(
+ StaticPrefs::network_http_sendRefererHeader_DoNotUseDirectly(),
+ MIN_REFERRER_SENDING_POLICY, MAX_REFERRER_SENDING_POLICY);
+}
+
+/* static */
+uint32_t ReferrerInfo::GetUserXOriginSendingPolicy() {
+ return clamped<uint32_t>(
+ StaticPrefs::network_http_referer_XOriginPolicy_DoNotUseDirectly(),
+ MIN_CROSS_ORIGIN_SENDING_POLICY, MAX_CROSS_ORIGIN_SENDING_POLICY);
+}
+
+/* static */
+uint32_t ReferrerInfo::GetUserTrimmingPolicy() {
+ return clamped<uint32_t>(
+ StaticPrefs::network_http_referer_trimmingPolicy_DoNotUseDirectly(),
+ MIN_TRIMMING_POLICY, MAX_TRIMMING_POLICY);
+}
+
+/* static */
+uint32_t ReferrerInfo::GetUserXOriginTrimmingPolicy() {
+ return clamped<uint32_t>(
+ StaticPrefs::
+ network_http_referer_XOriginTrimmingPolicy_DoNotUseDirectly(),
+ MIN_TRIMMING_POLICY, MAX_TRIMMING_POLICY);
+}
+
+/* static */
+ReferrerPolicy ReferrerInfo::GetDefaultReferrerPolicy(nsIHttpChannel* aChannel,
+ nsIURI* aURI,
+ bool aPrivateBrowsing) {
+ bool thirdPartyTrackerIsolated = false;
+ if (aChannel && aURI) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsICookieJarSettings> cjs;
+ Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
+ if (!cjs) {
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting(
+ aChannel, RFPTarget::IsAlwaysEnabledForPrecompute);
+ cjs = aPrivateBrowsing
+ ? net::CookieJarSettings::Create(CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : net::CookieJarSettings::Create(CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ // We only check if the channel is isolated if it's in the parent process
+ // with the rejection of third party contexts is enabled. We don't need to
+ // check this in content processes since the tracking state of the channel
+ // is unknown here and the referrer policy would be updated when the channel
+ // starts connecting in the parent process.
+ if (XRE_IsParentProcess() && cjs->GetRejectThirdPartyContexts()) {
+ uint32_t rejectedReason = 0;
+ thirdPartyTrackerIsolated =
+ !ShouldAllowAccessFor(aChannel, aURI, &rejectedReason) &&
+ rejectedReason !=
+ static_cast<uint32_t>(
+ nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN);
+ // Here we intentionally do not notify about the rejection reason, if any
+ // in order to avoid this check to have any visible side-effects (e.g. a
+ // web console report.)
+ }
+ }
+
+ // Select the appropriate pref starting with
+ // "network.http.referer.defaultPolicy" to use based on private-browsing
+ // ("pbmode") AND third-party trackers ("trackers").
+ return DefaultReferrerPolicyToReferrerPolicy(
+ thirdPartyTrackerIsolated
+ ? GetDefaultThirdPartyReferrerPolicyPref(aPrivateBrowsing)
+ : GetDefaultFirstPartyReferrerPolicyPref(aPrivateBrowsing));
+}
+
+/* static */
+bool ReferrerInfo::IsReferrerSchemeAllowed(nsIURI* aReferrer) {
+ NS_ENSURE_TRUE(aReferrer, false);
+
+ nsAutoCString scheme;
+ nsresult rv = aReferrer->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return scheme.EqualsIgnoreCase("https") || scheme.EqualsIgnoreCase("http");
+}
+
+/* static */
+bool ReferrerInfo::ShouldResponseInheritReferrerInfo(nsIChannel* aChannel) {
+ if (!aChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> channelURI;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isAbout = channelURI->SchemeIs("about");
+ if (!isAbout) {
+ return false;
+ }
+
+ nsAutoCString aboutSpec;
+ rv = channelURI->GetSpec(aboutSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return aboutSpec.EqualsLiteral("about:srcdoc");
+}
+
+/* static */
+nsresult ReferrerInfo::HandleSecureToInsecureReferral(
+ nsIURI* aOriginalURI, nsIURI* aURI, ReferrerPolicyEnum aPolicy,
+ bool& aAllowed) {
+ NS_ENSURE_ARG(aOriginalURI);
+ NS_ENSURE_ARG(aURI);
+
+ aAllowed = false;
+
+ bool referrerIsHttpsScheme = aOriginalURI->SchemeIs("https");
+ if (!referrerIsHttpsScheme) {
+ aAllowed = true;
+ return NS_OK;
+ }
+
+ // It's ok to send referrer for https-to-http scenarios if the referrer
+ // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
+ // in other referrer policies, https->http is not allowed...
+ bool uriIsHttpsScheme = aURI->SchemeIs("https");
+ if (aPolicy != ReferrerPolicy::Unsafe_url &&
+ aPolicy != ReferrerPolicy::Origin_when_cross_origin &&
+ aPolicy != ReferrerPolicy::Origin && !uriIsHttpsScheme) {
+ return NS_OK;
+ }
+
+ aAllowed = true;
+ return NS_OK;
+}
+
+nsresult ReferrerInfo::HandleUserXOriginSendingPolicy(nsIURI* aURI,
+ nsIURI* aReferrer,
+ bool& aAllowed) const {
+ NS_ENSURE_ARG(aURI);
+ aAllowed = false;
+
+ nsAutoCString uriHost;
+ nsAutoCString referrerHost;
+
+ nsresult rv = aURI->GetAsciiHost(uriHost);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aReferrer->GetAsciiHost(referrerHost);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Send an empty referrer if xorigin and leaving a .onion domain.
+ if (StaticPrefs::network_http_referer_hideOnionSource() &&
+ !uriHost.Equals(referrerHost) &&
+ StringEndsWith(referrerHost, ".onion"_ns)) {
+ return NS_OK;
+ }
+
+ switch (GetUserXOriginSendingPolicy()) {
+ // Check policy for sending referrer only when hosts match
+ case XOriginSendingPolicy::ePolicySendWhenSameHost: {
+ if (!uriHost.Equals(referrerHost)) {
+ return NS_OK;
+ }
+ break;
+ }
+
+ case XOriginSendingPolicy::ePolicySendWhenSameDomain: {
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!eTLDService) {
+ // check policy for sending only when effective top level domain
+ // matches. this falls back on using host if eTLDService does not work
+ if (!uriHost.Equals(referrerHost)) {
+ return NS_OK;
+ }
+ break;
+ }
+
+ nsAutoCString uriDomain;
+ nsAutoCString referrerDomain;
+ uint32_t extraDomains = 0;
+
+ rv = eTLDService->GetBaseDomain(aURI, extraDomains, uriDomain);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // uri is either an IP address, an alias such as 'localhost', an eTLD
+ // such as 'co.uk', or the empty string. Uses the normalized host in
+ // such cases.
+ rv = aURI->GetAsciiHost(uriDomain);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = eTLDService->GetBaseDomain(aReferrer, extraDomains, referrerDomain);
+ if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+ rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ // referrer is either an IP address, an alias such as 'localhost', an
+ // eTLD such as 'co.uk', or the empty string. Uses the normalized host
+ // in such cases.
+ rv = aReferrer->GetAsciiHost(referrerDomain);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!uriDomain.Equals(referrerDomain)) {
+ return NS_OK;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ aAllowed = true;
+ return NS_OK;
+}
+
+// This roughly implements Step 3.1. of
+// https://fetch.spec.whatwg.org/#append-a-request-origin-header
+/* static */
+bool ReferrerInfo::ShouldSetNullOriginHeader(net::HttpBaseChannel* aChannel,
+ nsIURI* aOriginURI) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(aOriginURI);
+
+ // If request’s mode is not "cors", then switch on request’s referrer policy:
+ RequestMode requestMode = RequestMode::No_cors;
+ MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestMode(&requestMode));
+ if (requestMode == RequestMode::Cors) {
+ return false;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ NS_ENSURE_SUCCESS(aChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)),
+ false);
+ if (!referrerInfo) {
+ return false;
+ }
+
+ // "no-referrer":
+ enum ReferrerPolicy policy = referrerInfo->ReferrerPolicy();
+ if (policy == ReferrerPolicy::No_referrer) {
+ // Set serializedOrigin to `null`.
+ // Note: Returning true is the same as setting the serializedOrigin to null
+ // in this method.
+ return true;
+ }
+
+ // "no-referrer-when-downgrade":
+ // "strict-origin":
+ // "strict-origin-when-cross-origin":
+ // If request’s origin is a tuple origin, its scheme is "https", and
+ // request’s current URL’s scheme is not "https", then set serializedOrigin
+ // to `null`.
+ bool allowed = false;
+ nsCOMPtr<nsIURI> uri;
+ NS_ENSURE_SUCCESS(aChannel->GetURI(getter_AddRefs(uri)), false);
+ if (NS_SUCCEEDED(ReferrerInfo::HandleSecureToInsecureReferral(
+ aOriginURI, uri, policy, allowed)) &&
+ !allowed) {
+ return true;
+ }
+
+ // "same-origin":
+ if (policy == ReferrerPolicy::Same_origin) {
+ // If request’s origin is not same origin with request’s current URL’s
+ // origin, then set serializedOrigin to `null`.
+ return ReferrerInfo::IsCrossOriginRequest(aChannel);
+ }
+
+ // Otherwise:
+ // Do Nothing.
+ return false;
+}
+
+nsresult ReferrerInfo::HandleUserReferrerSendingPolicy(nsIHttpChannel* aChannel,
+ bool& aAllowed) const {
+ aAllowed = false;
+ uint32_t referrerSendingPolicy;
+ uint32_t loadFlags;
+ nsresult rv = aChannel->GetLoadFlags(&loadFlags);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (loadFlags & nsIHttpChannel::LOAD_INITIAL_DOCUMENT_URI) {
+ referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendWhenUserTrigger;
+ } else {
+ referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendInlineContent;
+ }
+ if (GetUserReferrerSendingPolicy() < referrerSendingPolicy) {
+ return NS_OK;
+ }
+
+ aAllowed = true;
+ return NS_OK;
+}
+
+/* static */
+bool ReferrerInfo::IsCrossOriginRequest(nsIHttpChannel* aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) {
+ LOG(("no triggering URI via loadInfo, assuming load is cross-origin"));
+ return true;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString triggeringURISpec;
+ loadInfo->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec);
+ LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ return !loadInfo->TriggeringPrincipal()->IsSameOrigin(uri);
+}
+
+/* static */
+bool ReferrerInfo::IsReferrerCrossOrigin(nsIHttpChannel* aChannel,
+ nsIURI* aReferrer) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) {
+ LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ return !nsScriptSecurityManager::SecurityCompareURIs(uri, aReferrer);
+}
+
+/* static */
+bool ReferrerInfo::IsCrossSiteRequest(nsIHttpChannel* aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) {
+ LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
+ return true;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString triggeringURISpec;
+ loadInfo->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec);
+ LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ bool isCrossSite = true;
+ rv = loadInfo->TriggeringPrincipal()->IsThirdPartyURI(uri, &isCrossSite);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isCrossSite;
+}
+
+ReferrerInfo::TrimmingPolicy ReferrerInfo::ComputeTrimmingPolicy(
+ nsIHttpChannel* aChannel, nsIURI* aReferrer) const {
+ uint32_t trimmingPolicy = GetUserTrimmingPolicy();
+
+ switch (mPolicy) {
+ case ReferrerPolicy::Origin:
+ case ReferrerPolicy::Strict_origin:
+ trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort;
+ break;
+
+ case ReferrerPolicy::Origin_when_cross_origin:
+ case ReferrerPolicy::Strict_origin_when_cross_origin:
+ if (trimmingPolicy != TrimmingPolicy::ePolicySchemeHostPort &&
+ IsReferrerCrossOrigin(aChannel, aReferrer)) {
+ // Ignore set trimmingPolicy if it is already the strictest
+ // policy.
+ trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort;
+ }
+ break;
+
+ // This function is called when a nonempty referrer value is allowed to
+ // send. For the next 3 policies: same-origin, no-referrer-when-downgrade,
+ // unsafe-url, without trimming we should have a full uri. And the trimming
+ // policy only depends on user prefs.
+ case ReferrerPolicy::Same_origin:
+ case ReferrerPolicy::No_referrer_when_downgrade:
+ case ReferrerPolicy::Unsafe_url:
+ if (trimmingPolicy != TrimmingPolicy::ePolicySchemeHostPort) {
+ // Ignore set trimmingPolicy if it is already the strictest
+ // policy. Apply the user cross-origin trimming policy if it's more
+ // restrictive than the general one.
+ if (GetUserXOriginTrimmingPolicy() != TrimmingPolicy::ePolicyFullURI &&
+ IsCrossOriginRequest(aChannel)) {
+ trimmingPolicy =
+ std::max(trimmingPolicy, GetUserXOriginTrimmingPolicy());
+ }
+ }
+ break;
+
+ case ReferrerPolicy::No_referrer:
+ case ReferrerPolicy::_empty:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected value");
+ break;
+ }
+
+ return static_cast<TrimmingPolicy>(trimmingPolicy);
+}
+
+nsresult ReferrerInfo::LimitReferrerLength(
+ nsIHttpChannel* aChannel, nsIURI* aReferrer, TrimmingPolicy aTrimmingPolicy,
+ nsACString& aInAndOutTrimmedReferrer) const {
+ if (!StaticPrefs::network_http_referer_referrerLengthLimit()) {
+ return NS_OK;
+ }
+
+ if (aInAndOutTrimmedReferrer.Length() <=
+ StaticPrefs::network_http_referer_referrerLengthLimit()) {
+ return NS_OK;
+ }
+
+ nsAutoString referrerLengthLimit;
+ referrerLengthLimit.AppendInt(
+ StaticPrefs::network_http_referer_referrerLengthLimit());
+ if (aTrimmingPolicy == ePolicyFullURI ||
+ aTrimmingPolicy == ePolicySchemeHostPortPath) {
+ // If referrer header is over max Length, down to origin
+ nsresult rv = GetOriginFromReferrerURI(aReferrer, aInAndOutTrimmedReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
+ // states that the trailing "/" does not need to get stripped. However,
+ // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
+ // add it back here.
+ aInAndOutTrimmedReferrer.AppendLiteral("/");
+ if (aInAndOutTrimmedReferrer.Length() <=
+ StaticPrefs::network_http_referer_referrerLengthLimit()) {
+ AutoTArray<nsString, 2> params = {
+ referrerLengthLimit, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer)};
+ LogMessageToConsole(aChannel, "ReferrerLengthOverLimitation", params);
+ return NS_OK;
+ }
+ }
+
+ // If we end up here either the trimmingPolicy is equal to
+ // 'ePolicySchemeHostPort' or the 'origin' of any other policy is still over
+ // the length limit. If so, truncate the referrer entirely.
+ AutoTArray<nsString, 2> params = {
+ referrerLengthLimit, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer)};
+ LogMessageToConsole(aChannel, "ReferrerOriginLengthOverLimitation", params);
+ aInAndOutTrimmedReferrer.Truncate();
+
+ return NS_OK;
+}
+
+nsresult ReferrerInfo::GetOriginFromReferrerURI(nsIURI* aReferrer,
+ nsACString& aResult) const {
+ MOZ_ASSERT(aReferrer);
+ aResult.Truncate();
+ // We want the IDN-normalized PrePath. That's not something currently
+ // available and there doesn't yet seem to be justification for adding it to
+ // the interfaces, so just build it up from scheme+AsciiHostPort
+ nsAutoCString scheme, asciiHostPort;
+ nsresult rv = aReferrer->GetScheme(scheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aResult = scheme;
+ aResult.AppendLiteral("://");
+ // Note we explicitly cleared UserPass above, so do not need to build it.
+ rv = aReferrer->GetAsciiHostPort(asciiHostPort);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aResult.Append(asciiHostPort);
+ return NS_OK;
+}
+
+nsresult ReferrerInfo::TrimReferrerWithPolicy(nsIURI* aReferrer,
+ TrimmingPolicy aTrimmingPolicy,
+ nsACString& aResult) const {
+ MOZ_ASSERT(aReferrer);
+
+ if (aTrimmingPolicy == TrimmingPolicy::ePolicyFullURI) {
+ return aReferrer->GetAsciiSpec(aResult);
+ }
+
+ nsresult rv = GetOriginFromReferrerURI(aReferrer, aResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aTrimmingPolicy == TrimmingPolicy::ePolicySchemeHostPortPath) {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aReferrer));
+ if (url) {
+ nsAutoCString path;
+ rv = url->GetFilePath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aResult.Append(path);
+ return NS_OK;
+ }
+ }
+
+ // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
+ // states that the trailing "/" does not need to get stripped. However,
+ // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
+ // add it back here.
+ aResult.AppendLiteral("/");
+ return NS_OK;
+}
+
+bool ReferrerInfo::ShouldIgnoreLessRestrictedPolicies(
+ nsIHttpChannel* aChannel, const ReferrerPolicyEnum aPolicy) const {
+ MOZ_ASSERT(aChannel);
+
+ // We only care about the less restricted policies.
+ if (aPolicy != ReferrerPolicy::Unsafe_url &&
+ aPolicy != ReferrerPolicy::No_referrer_when_downgrade &&
+ aPolicy != ReferrerPolicy::Origin_when_cross_origin) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ bool isPrivate = NS_UsePrivateBrowsing(aChannel);
+
+ // Return early if we don't want to ignore less restricted policies for the
+ // top navigation.
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ bool isEnabledForTopNavigation =
+ isPrivate
+ ? StaticPrefs::
+ network_http_referer_disallowCrossSiteRelaxingDefault_pbmode_top_navigation()
+ : StaticPrefs::
+ network_http_referer_disallowCrossSiteRelaxingDefault_top_navigation();
+ if (!isEnabledForTopNavigation) {
+ return false;
+ }
+
+ // We have to get the value of the contentBlockingAllowList earlier because
+ // the channel hasn't been opened yet here. Note that we only need to do
+ // this for first-party navigation. For third-party loads, the value is
+ // inherited from the parent.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ Unused << loadInfo->GetCookieJarSettings(
+ getter_AddRefs(cookieJarSettings));
+
+ net::CookieJarSettings::Cast(cookieJarSettings)
+ ->UpdateIsOnContentBlockingAllowList(aChannel);
+ }
+ }
+
+ // We don't ignore less restricted referrer policies if ETP is toggled off.
+ // This would affect iframe loads and top navigation. For iframes, it will
+ // stop ignoring if the first-party site toggled ETP off. For top navigation,
+ // it depends on the ETP toggle for the destination site.
+ if (ContentBlockingAllowList::Check(aChannel)) {
+ return false;
+ }
+
+ bool isCrossSite = IsCrossSiteRequest(aChannel);
+ bool isEnabled =
+ isPrivate
+ ? StaticPrefs::
+ network_http_referer_disallowCrossSiteRelaxingDefault_pbmode()
+ : StaticPrefs::
+ network_http_referer_disallowCrossSiteRelaxingDefault();
+
+ if (!isEnabled) {
+ // Log the warning message to console to inform that we will ignore
+ // less restricted policies for cross-site requests in the future.
+ if (isCrossSite) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ AutoTArray<nsString, 1> params = {
+ NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())};
+ LogMessageToConsole(aChannel, "ReferrerPolicyDisallowRelaxingWarning",
+ params);
+ }
+ return false;
+ }
+
+ // Check if the channel is triggered by the system or the extension.
+ auto* triggerBasePrincipal =
+ BasePrincipal::Cast(loadInfo->TriggeringPrincipal());
+ if (triggerBasePrincipal->IsSystemPrincipal() ||
+ triggerBasePrincipal->AddonPolicy()) {
+ return false;
+ }
+
+ if (isCrossSite) {
+ // Log the console message to say that the less restricted policy was
+ // ignored.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, true);
+
+ uint32_t idx = static_cast<uint32_t>(aPolicy);
+
+ AutoTArray<nsString, 2> params = {
+ NS_ConvertUTF8toUTF16(
+ nsDependentCString(ReferrerPolicyValues::strings[idx].value)),
+ NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())};
+ LogMessageToConsole(aChannel, "ReferrerPolicyDisallowRelaxingMessage",
+ params);
+ }
+
+ return isCrossSite;
+}
+
+void ReferrerInfo::LogMessageToConsole(
+ nsIHttpChannel* aChannel, const char* aMsg,
+ const nsTArray<nsString>& aParams) const {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uint64_t windowID = 0;
+
+ rv = aChannel->GetTopLevelContentWindowId(&windowID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (!windowID) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ if (loadGroup) {
+ windowID = nsContentUtils::GetInnerWindowID(loadGroup);
+ }
+ }
+
+ nsAutoString localizedMsg;
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES, aMsg, aParams, localizedMsg);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ rv = nsContentUtils::ReportToConsoleByWindowID(
+ localizedMsg, nsIScriptError::infoFlag, "Security"_ns, windowID, uri);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+ReferrerPolicy ReferrerPolicyIDLToReferrerPolicy(
+ nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy) {
+ switch (aReferrerPolicy) {
+ case nsIReferrerInfo::EMPTY:
+ return ReferrerPolicy::_empty;
+ break;
+ case nsIReferrerInfo::NO_REFERRER:
+ return ReferrerPolicy::No_referrer;
+ break;
+ case nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE:
+ return ReferrerPolicy::No_referrer_when_downgrade;
+ break;
+ case nsIReferrerInfo::ORIGIN:
+ return ReferrerPolicy::Origin;
+ break;
+ case nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN:
+ return ReferrerPolicy::Origin_when_cross_origin;
+ break;
+ case nsIReferrerInfo::UNSAFE_URL:
+ return ReferrerPolicy::Unsafe_url;
+ break;
+ case nsIReferrerInfo::SAME_ORIGIN:
+ return ReferrerPolicy::Same_origin;
+ break;
+ case nsIReferrerInfo::STRICT_ORIGIN:
+ return ReferrerPolicy::Strict_origin;
+ break;
+ case nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN:
+ return ReferrerPolicy::Strict_origin_when_cross_origin;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
+ break;
+ }
+
+ return ReferrerPolicy::_empty;
+}
+
+nsIReferrerInfo::ReferrerPolicyIDL ReferrerPolicyToReferrerPolicyIDL(
+ ReferrerPolicy aReferrerPolicy) {
+ switch (aReferrerPolicy) {
+ case ReferrerPolicy::_empty:
+ return nsIReferrerInfo::EMPTY;
+ break;
+ case ReferrerPolicy::No_referrer:
+ return nsIReferrerInfo::NO_REFERRER;
+ break;
+ case ReferrerPolicy::No_referrer_when_downgrade:
+ return nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE;
+ break;
+ case ReferrerPolicy::Origin:
+ return nsIReferrerInfo::ORIGIN;
+ break;
+ case ReferrerPolicy::Origin_when_cross_origin:
+ return nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN;
+ break;
+ case ReferrerPolicy::Unsafe_url:
+ return nsIReferrerInfo::UNSAFE_URL;
+ break;
+ case ReferrerPolicy::Same_origin:
+ return nsIReferrerInfo::SAME_ORIGIN;
+ break;
+ case ReferrerPolicy::Strict_origin:
+ return nsIReferrerInfo::STRICT_ORIGIN;
+ break;
+ case ReferrerPolicy::Strict_origin_when_cross_origin:
+ return nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
+ break;
+ }
+
+ return nsIReferrerInfo::EMPTY;
+}
+
+ReferrerInfo::ReferrerInfo()
+ : mOriginalReferrer(nullptr),
+ mPolicy(ReferrerPolicy::_empty),
+ mOriginalPolicy(ReferrerPolicy::_empty),
+ mSendReferrer(true),
+ mInitialized(false),
+ mOverridePolicyByDefault(false) {}
+
+ReferrerInfo::ReferrerInfo(const Document& aDoc) : ReferrerInfo() {
+ InitWithDocument(&aDoc);
+}
+
+ReferrerInfo::ReferrerInfo(const Element& aElement) : ReferrerInfo() {
+ InitWithElement(&aElement);
+}
+
+ReferrerInfo::ReferrerInfo(const Element& aElement,
+ ReferrerPolicyEnum aOverridePolicy)
+ : ReferrerInfo(aElement) {
+ // Override referrer policy if not empty
+ if (aOverridePolicy != ReferrerPolicyEnum::_empty) {
+ mPolicy = aOverridePolicy;
+ mOriginalPolicy = aOverridePolicy;
+ }
+}
+
+ReferrerInfo::ReferrerInfo(nsIURI* aOriginalReferrer,
+ ReferrerPolicyEnum aPolicy, bool aSendReferrer,
+ const Maybe<nsCString>& aComputedReferrer)
+ : mOriginalReferrer(aOriginalReferrer),
+ mPolicy(aPolicy),
+ mOriginalPolicy(aPolicy),
+ mSendReferrer(aSendReferrer),
+ mInitialized(true),
+ mOverridePolicyByDefault(false),
+ mComputedReferrer(aComputedReferrer) {}
+
+ReferrerInfo::ReferrerInfo(const ReferrerInfo& rhs)
+ : mOriginalReferrer(rhs.mOriginalReferrer),
+ mPolicy(rhs.mPolicy),
+ mOriginalPolicy(rhs.mOriginalPolicy),
+ mSendReferrer(rhs.mSendReferrer),
+ mInitialized(rhs.mInitialized),
+ mOverridePolicyByDefault(rhs.mOverridePolicyByDefault),
+ mComputedReferrer(rhs.mComputedReferrer) {}
+
+already_AddRefed<ReferrerInfo> ReferrerInfo::Clone() const {
+ RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
+ return copy.forget();
+}
+
+already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewPolicy(
+ ReferrerPolicyEnum aPolicy) const {
+ RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
+ copy->mPolicy = aPolicy;
+ copy->mOriginalPolicy = aPolicy;
+ return copy.forget();
+}
+
+already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewSendReferrer(
+ bool aSendReferrer) const {
+ RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
+ copy->mSendReferrer = aSendReferrer;
+ return copy.forget();
+}
+
+already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewOriginalReferrer(
+ nsIURI* aOriginalReferrer) const {
+ RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
+ copy->mOriginalReferrer = aOriginalReferrer;
+ return copy.forget();
+}
+
+NS_IMETHODIMP
+ReferrerInfo::GetOriginalReferrer(nsIURI** aOriginalReferrer) {
+ *aOriginalReferrer = mOriginalReferrer;
+ NS_IF_ADDREF(*aOriginalReferrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::GetReferrerPolicy(
+ JSContext* aCx, nsIReferrerInfo::ReferrerPolicyIDL* aReferrerPolicy) {
+ *aReferrerPolicy = ReferrerPolicyToReferrerPolicyIDL(mPolicy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::GetReferrerPolicyString(nsACString& aResult) {
+ aResult.AssignASCII(ReferrerPolicyToString(mPolicy));
+ return NS_OK;
+}
+
+ReferrerPolicy ReferrerInfo::ReferrerPolicy() { return mPolicy; }
+
+NS_IMETHODIMP
+ReferrerInfo::GetSendReferrer(bool* aSendReferrer) {
+ *aSendReferrer = mSendReferrer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::Equals(nsIReferrerInfo* aOther, bool* aResult) {
+ NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
+ MOZ_ASSERT(mInitialized);
+ if (aOther == this) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ *aResult = false;
+ ReferrerInfo* other = static_cast<ReferrerInfo*>(aOther);
+ MOZ_ASSERT(other->mInitialized);
+
+ if (mPolicy != other->mPolicy || mSendReferrer != other->mSendReferrer ||
+ mOverridePolicyByDefault != other->mOverridePolicyByDefault ||
+ mComputedReferrer != other->mComputedReferrer) {
+ return NS_OK;
+ }
+
+ if (!mOriginalReferrer != !other->mOriginalReferrer) {
+ // One or the other has mOriginalReferrer, but not both... not equal
+ return NS_OK;
+ }
+
+ bool originalReferrerEquals;
+ if (mOriginalReferrer &&
+ (NS_FAILED(mOriginalReferrer->Equals(other->mOriginalReferrer,
+ &originalReferrerEquals)) ||
+ !originalReferrerEquals)) {
+ return NS_OK;
+ }
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::GetComputedReferrerSpec(nsAString& aComputedReferrerSpec) {
+ aComputedReferrerSpec.Assign(
+ mComputedReferrer.isSome()
+ ? NS_ConvertUTF8toUTF16(mComputedReferrer.value())
+ : EmptyString());
+ return NS_OK;
+}
+
+already_AddRefed<nsIURI> ReferrerInfo::GetComputedReferrer() {
+ if (!mComputedReferrer.isSome() || mComputedReferrer.value().IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> result;
+ nsresult rv = NS_NewURI(getter_AddRefs(result), mComputedReferrer.value());
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+HashNumber ReferrerInfo::Hash() const {
+ MOZ_ASSERT(mInitialized);
+ nsAutoCString originalReferrerSpec;
+ if (mOriginalReferrer) {
+ Unused << mOriginalReferrer->GetSpec(originalReferrerSpec);
+ }
+
+ return mozilla::AddToHash(
+ static_cast<uint32_t>(mPolicy), mSendReferrer, mOverridePolicyByDefault,
+ mozilla::HashString(originalReferrerSpec),
+ mozilla::HashString(mComputedReferrer.isSome() ? mComputedReferrer.value()
+ : ""_ns));
+}
+
+NS_IMETHODIMP
+ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy,
+ bool aSendReferrer, nsIURI* aOriginalReferrer) {
+ MOZ_ASSERT(!mInitialized);
+ if (mInitialized) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ };
+
+ mPolicy = ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy);
+ mOriginalPolicy = mPolicy;
+ mSendReferrer = aSendReferrer;
+ mOriginalReferrer = aOriginalReferrer;
+ mInitialized = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::InitWithDocument(const Document* aDocument) {
+ MOZ_ASSERT(!mInitialized);
+ if (mInitialized) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ };
+
+ mPolicy = aDocument->GetReferrerPolicy();
+ mOriginalPolicy = mPolicy;
+ mSendReferrer = true;
+ mOriginalReferrer = aDocument->GetDocumentURIAsReferrer();
+ mInitialized = true;
+ return NS_OK;
+}
+
+/**
+ * Check whether the given node has referrerpolicy attribute and parse
+ * referrer policy from the attribute.
+ * Currently, referrerpolicy attribute is supported in a, area, img, iframe,
+ * script, or link element.
+ */
+static ReferrerPolicy ReferrerPolicyFromAttribute(const Element& aElement) {
+ if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
+ nsGkAtoms::script, nsGkAtoms::iframe,
+ nsGkAtoms::link, nsGkAtoms::img)) {
+ return ReferrerPolicy::_empty;
+ }
+ return aElement.GetReferrerPolicyAsEnum();
+}
+
+static bool HasRelNoReferrer(const Element& aElement) {
+ // rel=noreferrer is only supported in <a>, <area>, and <form>
+ if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
+ nsGkAtoms::form) &&
+ !aElement.IsSVGElement(nsGkAtoms::a)) {
+ return false;
+ }
+
+ nsAutoString rel;
+ aElement.GetAttr(nsGkAtoms::rel, rel);
+ nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(rel);
+
+ while (tok.hasMoreTokens()) {
+ const nsAString& token = tok.nextToken();
+ if (token.LowerCaseEqualsLiteral("noreferrer")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::InitWithElement(const Element* aElement) {
+ MOZ_ASSERT(!mInitialized);
+ if (mInitialized) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ };
+
+ // Referrer policy from referrerpolicy attribute will have a higher priority
+ // than referrer policy from <meta> tag and Referrer-Policy header.
+ mPolicy = ReferrerPolicyFromAttribute(*aElement);
+ if (mPolicy == ReferrerPolicy::_empty) {
+ // Fallback to use document's referrer poicy if we don't have referrer
+ // policy from attribute.
+ mPolicy = aElement->OwnerDoc()->GetReferrerPolicy();
+ }
+
+ mOriginalPolicy = mPolicy;
+ mSendReferrer = !HasRelNoReferrer(*aElement);
+ mOriginalReferrer = aElement->OwnerDoc()->GetDocumentURIAsReferrer();
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+/* static */
+already_AddRefed<nsIReferrerInfo>
+ReferrerInfo::CreateFromDocumentAndPolicyOverride(
+ Document* aDoc, ReferrerPolicyEnum aPolicyOverride) {
+ MOZ_ASSERT(aDoc);
+ ReferrerPolicyEnum policy = aPolicyOverride != ReferrerPolicy::_empty
+ ? aPolicyOverride
+ : aDoc->GetReferrerPolicy();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new ReferrerInfo(aDoc->GetDocumentURIAsReferrer(), policy);
+ return referrerInfo.forget();
+}
+
+/* static */
+already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForFetch(
+ nsIPrincipal* aPrincipal, Document* aDoc) {
+ MOZ_ASSERT(aPrincipal);
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (!aPrincipal || aPrincipal->IsSystemPrincipal()) {
+ referrerInfo = new ReferrerInfo(nullptr);
+ return referrerInfo.forget();
+ }
+
+ if (!aDoc) {
+ aPrincipal->CreateReferrerInfo(ReferrerPolicy::_empty,
+ getter_AddRefs(referrerInfo));
+ return referrerInfo.forget();
+ }
+
+ // If it weren't for history.push/replaceState, we could just use the
+ // principal's URI here. But since we want changes to the URI effected
+ // by push/replaceState to be reflected in the XHR referrer, we have to
+ // be more clever.
+ //
+ // If the document's original URI (before any push/replaceStates) matches
+ // our principal, then we use the document's current URI (after
+ // push/replaceStates). Otherwise (if the document is, say, a data:
+ // URI), we just use the principal's URI.
+ nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
+
+ if (docCurURI && docOrigURI) {
+ bool equal = false;
+ aPrincipal->EqualsURI(docOrigURI, &equal);
+ if (equal) {
+ referrerInfo = new ReferrerInfo(docCurURI, aDoc->GetReferrerPolicy());
+ return referrerInfo.forget();
+ }
+ }
+ aPrincipal->CreateReferrerInfo(aDoc->GetReferrerPolicy(),
+ getter_AddRefs(referrerInfo));
+ return referrerInfo.forget();
+}
+
+/* static */
+already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForExternalCSSResources(
+ mozilla::StyleSheet* aExternalSheet, ReferrerPolicyEnum aPolicy) {
+ MOZ_ASSERT(aExternalSheet && !aExternalSheet->IsInline());
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+
+ // Step 2
+ // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css
+ // Use empty policy at the beginning and update it later from Referrer-Policy
+ // header.
+ referrerInfo = new ReferrerInfo(aExternalSheet->GetSheetURI(), aPolicy);
+ return referrerInfo.forget();
+}
+
+/* static */
+already_AddRefed<nsIReferrerInfo>
+ReferrerInfo::CreateForInternalCSSAndSVGResources(Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+ return do_AddRef(new ReferrerInfo(aDocument->GetDocumentURI(),
+ aDocument->GetReferrerPolicy()));
+}
+
+nsresult ReferrerInfo::ComputeReferrer(nsIHttpChannel* aChannel) {
+ NS_ENSURE_ARG(aChannel);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If the referrerInfo is passed around when redirect, just use the last
+ // computedReferrer to recompute
+ nsCOMPtr<nsIURI> referrer;
+ nsresult rv = NS_OK;
+ mOverridePolicyByDefault = false;
+
+ if (mComputedReferrer.isSome()) {
+ if (mComputedReferrer.value().IsEmpty()) {
+ return NS_OK;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(referrer), mComputedReferrer.value());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mComputedReferrer.reset();
+ // Emplace mComputedReferrer with an empty string, which means we have
+ // computed the referrer and the result referrer value is empty (not send
+ // referrer). So any early return later than this line will use that empty
+ // referrer.
+ mComputedReferrer.emplace(""_ns);
+
+ if (!mSendReferrer || !mOriginalReferrer ||
+ mPolicy == ReferrerPolicy::No_referrer) {
+ return NS_OK;
+ }
+
+ if (mPolicy == ReferrerPolicy::_empty ||
+ ShouldIgnoreLessRestrictedPolicies(aChannel, mOriginalPolicy)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ bool isPrivate = attrs.mPrivateBrowsingId > 0;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mPolicy = GetDefaultReferrerPolicy(aChannel, uri, isPrivate);
+ mOverridePolicyByDefault = true;
+ }
+
+ // This is for the case where the ETP toggle is off. In this case, we need to
+ // reset the referrer and the policy if the original policy is different from
+ // the current policy in order to recompute the referrer policy with the
+ // original policy.
+ if (!mOverridePolicyByDefault && mOriginalPolicy != ReferrerPolicy::_empty &&
+ mPolicy != mOriginalPolicy) {
+ referrer = nullptr;
+ mPolicy = mOriginalPolicy;
+ }
+
+ if (mPolicy == ReferrerPolicy::No_referrer) {
+ return NS_OK;
+ }
+
+ bool isUserReferrerSendingAllowed = false;
+ rv = HandleUserReferrerSendingPolicy(aChannel, isUserReferrerSendingAllowed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isUserReferrerSendingAllowed) {
+ return NS_OK;
+ }
+
+ // Enforce Referrer allowlist, only http, https scheme are allowed
+ if (!IsReferrerSchemeAllowed(mOriginalReferrer)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isSecureToInsecureAllowed = false;
+ rv = HandleSecureToInsecureReferral(mOriginalReferrer, uri, mPolicy,
+ isSecureToInsecureAllowed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isSecureToInsecureAllowed) {
+ return NS_OK;
+ }
+
+ // Strip away any fragment per RFC 2616 section 14.36
+ // and Referrer Policy section 6.3.5.
+ if (!referrer) {
+ rv = NS_GetURIWithoutRef(mOriginalReferrer, getter_AddRefs(referrer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ bool isUserXOriginAllowed = false;
+ rv = HandleUserXOriginSendingPolicy(uri, referrer, isUserXOriginAllowed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!isUserXOriginAllowed) {
+ return NS_OK;
+ }
+
+ // Handle user pref network.http.referer.spoofSource, send spoofed referrer if
+ // desired
+ if (StaticPrefs::network_http_referer_spoofSource()) {
+ nsCOMPtr<nsIURI> userSpoofReferrer;
+ rv = NS_GetURIWithoutRef(uri, getter_AddRefs(userSpoofReferrer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ referrer = userSpoofReferrer;
+ }
+
+ // strip away any userpass; we don't want to be giving out passwords ;-)
+ // This is required by Referrer Policy stripping algorithm.
+ nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(referrer);
+ referrer = exposableURI;
+
+ // Don't send referrer when the request is cross-origin and policy is
+ // "same-origin".
+ if (mPolicy == ReferrerPolicy::Same_origin &&
+ IsReferrerCrossOrigin(aChannel, referrer)) {
+ return NS_OK;
+ }
+
+ TrimmingPolicy trimmingPolicy = ComputeTrimmingPolicy(aChannel, referrer);
+
+ nsAutoCString trimmedReferrer;
+ // We first trim the referrer according to the policy by calling
+ // 'TrimReferrerWithPolicy' and right after we have to call
+ // 'LimitReferrerLength' (using the same arguments) because the trimmed
+ // referrer might exceed the allowed max referrer length.
+ rv = TrimReferrerWithPolicy(referrer, trimmingPolicy, trimmedReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = LimitReferrerLength(aChannel, referrer, trimmingPolicy, trimmedReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // finally, remember the referrer spec.
+ mComputedReferrer.reset();
+ mComputedReferrer.emplace(trimmedReferrer);
+
+ return NS_OK;
+}
+
+/* ===== nsISerializable implementation ====== */
+
+nsresult ReferrerInfo::ReadTailDataBeforeGecko100(
+ const uint32_t& aData, nsIObjectInputStream* aInputStream) {
+ MOZ_ASSERT(aInputStream);
+
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ // We need to create a new pipe in order to read the aData and the rest of
+ // the input stream together in the old format. This would also help us with
+ // handling big endian correctly.
+ NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer));
+
+ nsCOMPtr<nsIBinaryOutputStream> binaryPipeWriter =
+ NS_NewObjectOutputStream(writer);
+
+ // Write back the aData so that we can read bytes from it and handle big
+ // endian correctly.
+ nsresult rv = binaryPipeWriter->Write32(aData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIBinaryInputStream> binaryPipeReader =
+ NS_NewObjectInputStream(reader);
+
+ rv = binaryPipeReader->ReadBoolean(&mSendReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isComputed;
+ rv = binaryPipeReader->ReadBoolean(&isComputed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We need to handle the following string if isComputed is true.
+ if (isComputed) {
+ // Comsume the following 2 bytes from the input stream. They are the half
+ // part of the length prefix of the following string.
+ uint16_t data;
+ rv = aInputStream->Read16(&data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Write the bytes to the pipe so that we can read the length of the string.
+ rv = binaryPipeWriter->Write16(data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t length;
+ rv = binaryPipeReader->Read32(&length);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Consume the string body from the input stream.
+ nsAutoCString computedReferrer;
+ rv = NS_ConsumeStream(aInputStream, length, computedReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mComputedReferrer.emplace(computedReferrer);
+
+ // Read the remaining two bytes and write to the pipe.
+ uint16_t remain;
+ rv = aInputStream->Read16(&remain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = binaryPipeWriter->Write16(remain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = binaryPipeReader->ReadBoolean(&mInitialized);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = binaryPipeReader->ReadBoolean(&mOverridePolicyByDefault);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::Read(nsIObjectInputStream* aStream) {
+ bool nonNull;
+ nsresult rv = aStream->ReadBoolean(&nonNull);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (nonNull) {
+ nsAutoCString spec;
+ nsresult rv = aStream->ReadCString(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(mOriginalReferrer), spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ mOriginalReferrer = nullptr;
+ }
+
+ // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store
+ // to disk using the order of ReferrerPolicyIDL, so we convert to
+ // ReferrerPolicyIDL to make it be compatible to the old format.
+ uint32_t policy;
+ rv = aStream->Read32(&policy);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mPolicy = ReferrerPolicyIDLToReferrerPolicy(
+ static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(policy));
+
+ uint32_t originalPolicy;
+ rv = aStream->Read32(&originalPolicy);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more
+ // details.
+ //
+ // We need to differentiate the old format and the new format here in order
+ // to be able to read both formats. The check here helps us with verifying
+ // which format it is.
+ if (MOZ_UNLIKELY(originalPolicy > 0xFF)) {
+ mOriginalPolicy = mPolicy;
+
+ return ReadTailDataBeforeGecko100(originalPolicy, aStream);
+ }
+
+ mOriginalPolicy = ReferrerPolicyIDLToReferrerPolicy(
+ static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(originalPolicy));
+
+ rv = aStream->ReadBoolean(&mSendReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isComputed;
+ rv = aStream->ReadBoolean(&isComputed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isComputed) {
+ nsAutoCString computedReferrer;
+ rv = aStream->ReadCString(computedReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ mComputedReferrer.emplace(computedReferrer);
+ }
+
+ rv = aStream->ReadBoolean(&mInitialized);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->ReadBoolean(&mOverridePolicyByDefault);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReferrerInfo::Write(nsIObjectOutputStream* aStream) {
+ bool nonNull = (mOriginalReferrer != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonNull);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (nonNull) {
+ nsAutoCString spec;
+ nsresult rv = mOriginalReferrer->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteStringZ(spec.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mSendReferrer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isComputed = mComputedReferrer.isSome();
+ rv = aStream->WriteBoolean(isComputed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (isComputed) {
+ rv = aStream->WriteStringZ(mComputedReferrer.value().get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aStream->WriteBoolean(mInitialized);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aStream->WriteBoolean(mOverridePolicyByDefault);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void ReferrerInfo::RecordTelemetry(nsIHttpChannel* aChannel) {
+#ifdef DEBUG
+ MOZ_ASSERT(!mTelemetryRecorded);
+ mTelemetryRecorded = true;
+#endif // DEBUG
+
+ // The telemetry probe has 18 buckets. The first 9 buckets are for same-site
+ // requests and the rest 9 buckets are for cross-site requests.
+ uint32_t telemetryOffset =
+ IsCrossSiteRequest(aChannel)
+ ? static_cast<uint32_t>(ReferrerPolicy::EndGuard_)
+ : 0;
+
+ Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT,
+ static_cast<uint32_t>(mPolicy) + telemetryOffset);
+}
+
+} // namespace mozilla::dom