mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
198c331213
Use it liberally across the tree. This could be cleaned up even more in the future. Differential Revision: https://phabricator.services.mozilla.com/D218114
1709 lines
53 KiB
C++
1709 lines
53 KiB
C++
/* -*- 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Return no referrer policy (empty string) if it's not a valid enum value.
|
||
return StringToEnum<ReferrerPolicy>(lowerContent)
|
||
.valueOr(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 */
|
||
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);
|
||
|
||
AutoTArray<nsString, 2> params = {
|
||
NS_ConvertUTF8toUTF16(GetEnumString(aPolicy)),
|
||
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,
|
||
SourceLocation(std::move(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(GetEnumString(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(nsACString& aComputedReferrerSpec) {
|
||
aComputedReferrerSpec.Assign(
|
||
mComputedReferrer.isSome() ? mComputedReferrer.value() : EmptyCString());
|
||
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.IsPrivateBrowsing();
|
||
|
||
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)
|
||
? UnderlyingValue(
|
||
MaxContiguousEnumValue<dom::ReferrerPolicy>::value) +
|
||
1
|
||
: 0;
|
||
|
||
Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT,
|
||
static_cast<uint32_t>(mPolicy) + telemetryOffset);
|
||
}
|
||
|
||
} // namespace mozilla::dom
|