1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/RefPtr.h"
8 #include "mozilla/dom/ReferrerPolicyBinding.h"
9 #include "nsIClassInfoImpl.h"
10 #include "nsIEffectiveTLDService.h"
11 #include "nsIHttpChannel.h"
12 #include "nsIObjectInputStream.h"
13 #include "nsIObjectOutputStream.h"
14 #include "nsIOService.h"
18 #include "nsWhitespaceTokenizer.h"
19 #include "nsAlgorithm.h"
20 #include "nsContentUtils.h"
21 #include "nsCharSeparatedTokenizer.h"
22 #include "nsScriptSecurityManager.h"
23 #include "nsStreamUtils.h"
24 #include "ReferrerInfo.h"
26 #include "mozilla/BasePrincipal.h"
27 #include "mozilla/ContentBlockingAllowList.h"
28 #include "mozilla/net/CookieJarSettings.h"
29 #include "mozilla/net/HttpBaseChannel.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/Element.h"
32 #include "mozilla/dom/RequestBinding.h"
33 #include "mozilla/StaticPrefs_network.h"
34 #include "mozilla/StorageAccess.h"
35 #include "mozilla/StyleSheet.h"
36 #include "mozilla/Telemetry.h"
37 #include "nsIWebProgressListener.h"
39 static mozilla::LazyLogModule
gReferrerInfoLog("ReferrerInfo");
40 #define LOG(msg) MOZ_LOG(gReferrerInfoLog, mozilla::LogLevel::Debug, msg)
41 #define LOG_ENABLED() MOZ_LOG_TEST(gReferrerInfoLog, mozilla::LogLevel::Debug)
43 using namespace mozilla::net
;
45 namespace mozilla::dom
{
47 // Implementation of ClassInfo is required to serialize/deserialize
48 NS_IMPL_CLASSINFO(ReferrerInfo
, nullptr, nsIClassInfo::THREADSAFE
,
51 NS_IMPL_ISUPPORTS_CI(ReferrerInfo
, nsIReferrerInfo
, nsISerializable
)
53 #define MAX_REFERRER_SENDING_POLICY 2
54 #define MAX_CROSS_ORIGIN_SENDING_POLICY 2
55 #define MAX_TRIMMING_POLICY 2
57 #define MIN_REFERRER_SENDING_POLICY 0
58 #define MIN_CROSS_ORIGIN_SENDING_POLICY 0
59 #define MIN_TRIMMING_POLICY 0
62 * Default referrer policy to use
64 enum DefaultReferrerPolicy
: uint32_t {
65 eDefaultPolicyNoReferrer
= 0,
66 eDefaultPolicySameOrgin
= 1,
67 eDefaultPolicyStrictWhenXorigin
= 2,
68 eDefaultPolicyNoReferrerWhenDownGrade
= 3,
71 static uint32_t GetDefaultFirstPartyReferrerPolicyPref(bool aPrivateBrowsing
) {
72 return aPrivateBrowsing
73 ? StaticPrefs::network_http_referer_defaultPolicy_pbmode()
74 : StaticPrefs::network_http_referer_defaultPolicy();
77 static uint32_t GetDefaultThirdPartyReferrerPolicyPref(bool aPrivateBrowsing
) {
78 return aPrivateBrowsing
79 ? StaticPrefs::network_http_referer_defaultPolicy_trackers_pbmode()
80 : StaticPrefs::network_http_referer_defaultPolicy_trackers();
83 static ReferrerPolicy
DefaultReferrerPolicyToReferrerPolicy(
84 uint32_t aDefaultToUse
) {
85 switch (aDefaultToUse
) {
86 case DefaultReferrerPolicy::eDefaultPolicyNoReferrer
:
87 return ReferrerPolicy::No_referrer
;
88 case DefaultReferrerPolicy::eDefaultPolicySameOrgin
:
89 return ReferrerPolicy::Same_origin
;
90 case DefaultReferrerPolicy::eDefaultPolicyStrictWhenXorigin
:
91 return ReferrerPolicy::Strict_origin_when_cross_origin
;
94 return ReferrerPolicy::No_referrer_when_downgrade
;
97 struct LegacyReferrerPolicyTokenMap
{
99 ReferrerPolicy mPolicy
;
103 * Parse ReferrerPolicy from token.
104 * The supported tokens are defined in ReferrerPolicy.webidl.
105 * The legacy tokens are "never", "default", "always" and
106 * "origin-when-crossorigin". The legacy tokens are only supported in meta
109 * @param aContent content string to be transformed into
110 * ReferrerPolicyEnum, e.g. "origin".
112 ReferrerPolicy
ReferrerPolicyFromToken(const nsAString
& aContent
,
113 bool allowedLegacyToken
) {
114 nsString
lowerContent(aContent
);
115 ToLowerCase(lowerContent
);
117 if (allowedLegacyToken
) {
118 static const LegacyReferrerPolicyTokenMap sLegacyReferrerPolicyToken
[] = {
119 {"never", ReferrerPolicy::No_referrer
},
120 {"default", ReferrerPolicy::No_referrer_when_downgrade
},
121 {"always", ReferrerPolicy::Unsafe_url
},
122 {"origin-when-crossorigin", ReferrerPolicy::Origin_when_cross_origin
},
125 uint8_t numStr
= (sizeof(sLegacyReferrerPolicyToken
) /
126 sizeof(sLegacyReferrerPolicyToken
[0]));
127 for (uint8_t i
= 0; i
< numStr
; i
++) {
128 if (lowerContent
.EqualsASCII(sLegacyReferrerPolicyToken
[i
].mToken
)) {
129 return sLegacyReferrerPolicyToken
[i
].mPolicy
;
134 // Return no referrer policy (empty string) if it's not a valid enum value.
135 return StringToEnum
<ReferrerPolicy
>(lowerContent
)
136 .valueOr(ReferrerPolicy::_empty
);
140 ReferrerPolicy
ReferrerInfo::ReferrerPolicyFromMetaString(
141 const nsAString
& aContent
) {
142 // This is implemented as described in
143 // https://html.spec.whatwg.org/multipage/semantics.html#meta-referrer
144 // Meta referrer accepts both supported tokens in ReferrerPolicy.webidl and
146 return ReferrerPolicyFromToken(aContent
, true);
150 ReferrerPolicy
ReferrerInfo::ReferrerPolicyAttributeFromString(
151 const nsAString
& aContent
) {
152 // This is implemented as described in
153 // https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute
154 // referrerpolicy attribute only accepts supported tokens in
155 // ReferrerPolicy.webidl
156 return ReferrerPolicyFromToken(aContent
, false);
160 ReferrerPolicy
ReferrerInfo::ReferrerPolicyFromHeaderString(
161 const nsAString
& aContent
) {
162 // Multiple headers could be concatenated into one comma-separated
163 // list of policies. Need to tokenize the multiple headers.
164 ReferrerPolicyEnum referrerPolicy
= ReferrerPolicy::_empty
;
165 for (const auto& token
: nsCharSeparatedTokenizer(aContent
, ',').ToRange()) {
166 if (token
.IsEmpty()) {
170 // Referrer-Policy header only accepts supported tokens in
171 // ReferrerPolicy.webidl
172 ReferrerPolicyEnum policy
= ReferrerPolicyFromToken(token
, false);
173 // If there are multiple policies available, the last valid policy should be
175 // https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
176 if (policy
!= ReferrerPolicy::_empty
) {
177 referrerPolicy
= policy
;
180 return referrerPolicy
;
184 uint32_t ReferrerInfo::GetUserReferrerSendingPolicy() {
185 return clamped
<uint32_t>(
186 StaticPrefs::network_http_sendRefererHeader_DoNotUseDirectly(),
187 MIN_REFERRER_SENDING_POLICY
, MAX_REFERRER_SENDING_POLICY
);
191 uint32_t ReferrerInfo::GetUserXOriginSendingPolicy() {
192 return clamped
<uint32_t>(
193 StaticPrefs::network_http_referer_XOriginPolicy_DoNotUseDirectly(),
194 MIN_CROSS_ORIGIN_SENDING_POLICY
, MAX_CROSS_ORIGIN_SENDING_POLICY
);
198 uint32_t ReferrerInfo::GetUserTrimmingPolicy() {
199 return clamped
<uint32_t>(
200 StaticPrefs::network_http_referer_trimmingPolicy_DoNotUseDirectly(),
201 MIN_TRIMMING_POLICY
, MAX_TRIMMING_POLICY
);
205 uint32_t ReferrerInfo::GetUserXOriginTrimmingPolicy() {
206 return clamped
<uint32_t>(
208 network_http_referer_XOriginTrimmingPolicy_DoNotUseDirectly(),
209 MIN_TRIMMING_POLICY
, MAX_TRIMMING_POLICY
);
213 ReferrerPolicy
ReferrerInfo::GetDefaultReferrerPolicy(nsIHttpChannel
* aChannel
,
215 bool aPrivateBrowsing
) {
216 bool thirdPartyTrackerIsolated
= false;
217 if (aChannel
&& aURI
) {
218 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
219 nsCOMPtr
<nsICookieJarSettings
> cjs
;
220 Unused
<< loadInfo
->GetCookieJarSettings(getter_AddRefs(cjs
));
222 bool shouldResistFingerprinting
=
223 nsContentUtils::ShouldResistFingerprinting(
224 aChannel
, RFPTarget::IsAlwaysEnabledForPrecompute
);
225 cjs
= aPrivateBrowsing
226 ? net::CookieJarSettings::Create(CookieJarSettings::ePrivate
,
227 shouldResistFingerprinting
)
228 : net::CookieJarSettings::Create(CookieJarSettings::eRegular
,
229 shouldResistFingerprinting
);
232 // We only check if the channel is isolated if it's in the parent process
233 // with the rejection of third party contexts is enabled. We don't need to
234 // check this in content processes since the tracking state of the channel
235 // is unknown here and the referrer policy would be updated when the channel
236 // starts connecting in the parent process.
237 if (XRE_IsParentProcess() && cjs
->GetRejectThirdPartyContexts()) {
238 uint32_t rejectedReason
= 0;
239 thirdPartyTrackerIsolated
=
240 !ShouldAllowAccessFor(aChannel
, aURI
, &rejectedReason
) &&
242 static_cast<uint32_t>(
243 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
);
244 // Here we intentionally do not notify about the rejection reason, if any
245 // in order to avoid this check to have any visible side-effects (e.g. a
246 // web console report.)
250 // Select the appropriate pref starting with
251 // "network.http.referer.defaultPolicy" to use based on private-browsing
252 // ("pbmode") AND third-party trackers ("trackers").
253 return DefaultReferrerPolicyToReferrerPolicy(
254 thirdPartyTrackerIsolated
255 ? GetDefaultThirdPartyReferrerPolicyPref(aPrivateBrowsing
)
256 : GetDefaultFirstPartyReferrerPolicyPref(aPrivateBrowsing
));
260 bool ReferrerInfo::IsReferrerSchemeAllowed(nsIURI
* aReferrer
) {
261 NS_ENSURE_TRUE(aReferrer
, false);
263 nsAutoCString scheme
;
264 nsresult rv
= aReferrer
->GetScheme(scheme
);
265 if (NS_WARN_IF(NS_FAILED(rv
))) {
269 return scheme
.EqualsIgnoreCase("https") || scheme
.EqualsIgnoreCase("http");
273 bool ReferrerInfo::ShouldResponseInheritReferrerInfo(nsIChannel
* aChannel
) {
278 nsCOMPtr
<nsIURI
> channelURI
;
279 nsresult rv
= aChannel
->GetURI(getter_AddRefs(channelURI
));
280 NS_ENSURE_SUCCESS(rv
, false);
282 bool isAbout
= channelURI
->SchemeIs("about");
287 nsAutoCString aboutSpec
;
288 rv
= channelURI
->GetSpec(aboutSpec
);
289 NS_ENSURE_SUCCESS(rv
, false);
291 return aboutSpec
.EqualsLiteral("about:srcdoc");
295 nsresult
ReferrerInfo::HandleSecureToInsecureReferral(
296 nsIURI
* aOriginalURI
, nsIURI
* aURI
, ReferrerPolicyEnum aPolicy
,
298 NS_ENSURE_ARG(aOriginalURI
);
303 bool referrerIsHttpsScheme
= aOriginalURI
->SchemeIs("https");
304 if (!referrerIsHttpsScheme
) {
309 // It's ok to send referrer for https-to-http scenarios if the referrer
310 // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
311 // in other referrer policies, https->http is not allowed...
312 bool uriIsHttpsScheme
= aURI
->SchemeIs("https");
313 if (aPolicy
!= ReferrerPolicy::Unsafe_url
&&
314 aPolicy
!= ReferrerPolicy::Origin_when_cross_origin
&&
315 aPolicy
!= ReferrerPolicy::Origin
&& !uriIsHttpsScheme
) {
323 nsresult
ReferrerInfo::HandleUserXOriginSendingPolicy(nsIURI
* aURI
,
325 bool& aAllowed
) const {
329 nsAutoCString uriHost
;
330 nsAutoCString referrerHost
;
332 nsresult rv
= aURI
->GetAsciiHost(uriHost
);
333 if (NS_WARN_IF(NS_FAILED(rv
))) {
337 rv
= aReferrer
->GetAsciiHost(referrerHost
);
338 if (NS_WARN_IF(NS_FAILED(rv
))) {
342 // Send an empty referrer if xorigin and leaving a .onion domain.
343 if (StaticPrefs::network_http_referer_hideOnionSource() &&
344 !uriHost
.Equals(referrerHost
) &&
345 StringEndsWith(referrerHost
, ".onion"_ns
)) {
349 switch (GetUserXOriginSendingPolicy()) {
350 // Check policy for sending referrer only when hosts match
351 case XOriginSendingPolicy::ePolicySendWhenSameHost
: {
352 if (!uriHost
.Equals(referrerHost
)) {
358 case XOriginSendingPolicy::ePolicySendWhenSameDomain
: {
359 nsCOMPtr
<nsIEffectiveTLDService
> eTLDService
=
360 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
);
362 // check policy for sending only when effective top level domain
363 // matches. this falls back on using host if eTLDService does not work
364 if (!uriHost
.Equals(referrerHost
)) {
370 nsAutoCString uriDomain
;
371 nsAutoCString referrerDomain
;
372 uint32_t extraDomains
= 0;
374 rv
= eTLDService
->GetBaseDomain(aURI
, extraDomains
, uriDomain
);
375 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
376 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
377 // uri is either an IP address, an alias such as 'localhost', an eTLD
378 // such as 'co.uk', or the empty string. Uses the normalized host in
380 rv
= aURI
->GetAsciiHost(uriDomain
);
383 if (NS_WARN_IF(NS_FAILED(rv
))) {
387 rv
= eTLDService
->GetBaseDomain(aReferrer
, extraDomains
, referrerDomain
);
388 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
389 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
390 // referrer is either an IP address, an alias such as 'localhost', an
391 // eTLD such as 'co.uk', or the empty string. Uses the normalized host
393 rv
= aReferrer
->GetAsciiHost(referrerDomain
);
396 if (NS_WARN_IF(NS_FAILED(rv
))) {
400 if (!uriDomain
.Equals(referrerDomain
)) {
414 // This roughly implements Step 3.1. of
415 // https://fetch.spec.whatwg.org/#append-a-request-origin-header
417 bool ReferrerInfo::ShouldSetNullOriginHeader(net::HttpBaseChannel
* aChannel
,
418 nsIURI
* aOriginURI
) {
419 MOZ_ASSERT(aChannel
);
420 MOZ_ASSERT(aOriginURI
);
422 // If request’s mode is not "cors", then switch on request’s referrer policy:
423 RequestMode requestMode
= RequestMode::No_cors
;
424 MOZ_ALWAYS_SUCCEEDS(aChannel
->GetRequestMode(&requestMode
));
425 if (requestMode
== RequestMode::Cors
) {
429 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
430 NS_ENSURE_SUCCESS(aChannel
->GetReferrerInfo(getter_AddRefs(referrerInfo
)),
437 enum ReferrerPolicy policy
= referrerInfo
->ReferrerPolicy();
438 if (policy
== ReferrerPolicy::No_referrer
) {
439 // Set serializedOrigin to `null`.
440 // Note: Returning true is the same as setting the serializedOrigin to null
445 // "no-referrer-when-downgrade":
447 // "strict-origin-when-cross-origin":
448 // If request’s origin is a tuple origin, its scheme is "https", and
449 // request’s current URL’s scheme is not "https", then set serializedOrigin
451 bool allowed
= false;
452 nsCOMPtr
<nsIURI
> uri
;
453 NS_ENSURE_SUCCESS(aChannel
->GetURI(getter_AddRefs(uri
)), false);
454 if (NS_SUCCEEDED(ReferrerInfo::HandleSecureToInsecureReferral(
455 aOriginURI
, uri
, policy
, allowed
)) &&
461 if (policy
== ReferrerPolicy::Same_origin
) {
462 // If request’s origin is not same origin with request’s current URL’s
463 // origin, then set serializedOrigin to `null`.
464 return ReferrerInfo::IsCrossOriginRequest(aChannel
);
472 nsresult
ReferrerInfo::HandleUserReferrerSendingPolicy(nsIHttpChannel
* aChannel
,
473 bool& aAllowed
) const {
475 uint32_t referrerSendingPolicy
;
477 nsresult rv
= aChannel
->GetLoadFlags(&loadFlags
);
478 if (NS_WARN_IF(NS_FAILED(rv
))) {
482 if (loadFlags
& nsIHttpChannel::LOAD_INITIAL_DOCUMENT_URI
) {
483 referrerSendingPolicy
= ReferrerSendingPolicy::ePolicySendWhenUserTrigger
;
485 referrerSendingPolicy
= ReferrerSendingPolicy::ePolicySendInlineContent
;
487 if (GetUserReferrerSendingPolicy() < referrerSendingPolicy
) {
496 bool ReferrerInfo::IsCrossOriginRequest(nsIHttpChannel
* aChannel
) {
497 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
499 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
500 LOG(("no triggering URI via loadInfo, assuming load is cross-origin"));
505 nsAutoCString triggeringURISpec
;
506 loadInfo
->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec
);
507 LOG(("triggeringURI=%s\n", triggeringURISpec
.get()));
510 nsCOMPtr
<nsIURI
> uri
;
511 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
512 if (NS_WARN_IF(NS_FAILED(rv
))) {
516 return !loadInfo
->TriggeringPrincipal()->IsSameOrigin(uri
);
520 bool ReferrerInfo::IsReferrerCrossOrigin(nsIHttpChannel
* aChannel
,
522 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
524 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
525 LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
529 nsCOMPtr
<nsIURI
> uri
;
530 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
531 if (NS_WARN_IF(NS_FAILED(rv
))) {
535 return !nsScriptSecurityManager::SecurityCompareURIs(uri
, aReferrer
);
539 bool ReferrerInfo::IsCrossSiteRequest(nsIHttpChannel
* aChannel
) {
540 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
542 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
543 LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
548 nsAutoCString triggeringURISpec
;
549 loadInfo
->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec
);
550 LOG(("triggeringURI=%s\n", triggeringURISpec
.get()));
553 nsCOMPtr
<nsIURI
> uri
;
554 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
555 if (NS_WARN_IF(NS_FAILED(rv
))) {
559 bool isCrossSite
= true;
560 rv
= loadInfo
->TriggeringPrincipal()->IsThirdPartyURI(uri
, &isCrossSite
);
568 ReferrerInfo::TrimmingPolicy
ReferrerInfo::ComputeTrimmingPolicy(
569 nsIHttpChannel
* aChannel
, nsIURI
* aReferrer
) const {
570 uint32_t trimmingPolicy
= GetUserTrimmingPolicy();
573 case ReferrerPolicy::Origin
:
574 case ReferrerPolicy::Strict_origin
:
575 trimmingPolicy
= TrimmingPolicy::ePolicySchemeHostPort
;
578 case ReferrerPolicy::Origin_when_cross_origin
:
579 case ReferrerPolicy::Strict_origin_when_cross_origin
:
580 if (trimmingPolicy
!= TrimmingPolicy::ePolicySchemeHostPort
&&
581 IsReferrerCrossOrigin(aChannel
, aReferrer
)) {
582 // Ignore set trimmingPolicy if it is already the strictest
584 trimmingPolicy
= TrimmingPolicy::ePolicySchemeHostPort
;
588 // This function is called when a nonempty referrer value is allowed to
589 // send. For the next 3 policies: same-origin, no-referrer-when-downgrade,
590 // unsafe-url, without trimming we should have a full uri. And the trimming
591 // policy only depends on user prefs.
592 case ReferrerPolicy::Same_origin
:
593 case ReferrerPolicy::No_referrer_when_downgrade
:
594 case ReferrerPolicy::Unsafe_url
:
595 if (trimmingPolicy
!= TrimmingPolicy::ePolicySchemeHostPort
) {
596 // Ignore set trimmingPolicy if it is already the strictest
597 // policy. Apply the user cross-origin trimming policy if it's more
598 // restrictive than the general one.
599 if (GetUserXOriginTrimmingPolicy() != TrimmingPolicy::ePolicyFullURI
&&
600 IsCrossOriginRequest(aChannel
)) {
602 std::max(trimmingPolicy
, GetUserXOriginTrimmingPolicy());
607 case ReferrerPolicy::No_referrer
:
608 case ReferrerPolicy::_empty
:
610 MOZ_ASSERT_UNREACHABLE("Unexpected value");
614 return static_cast<TrimmingPolicy
>(trimmingPolicy
);
617 nsresult
ReferrerInfo::LimitReferrerLength(
618 nsIHttpChannel
* aChannel
, nsIURI
* aReferrer
, TrimmingPolicy aTrimmingPolicy
,
619 nsACString
& aInAndOutTrimmedReferrer
) const {
620 if (!StaticPrefs::network_http_referer_referrerLengthLimit()) {
624 if (aInAndOutTrimmedReferrer
.Length() <=
625 StaticPrefs::network_http_referer_referrerLengthLimit()) {
629 nsAutoString referrerLengthLimit
;
630 referrerLengthLimit
.AppendInt(
631 StaticPrefs::network_http_referer_referrerLengthLimit());
632 if (aTrimmingPolicy
== ePolicyFullURI
||
633 aTrimmingPolicy
== ePolicySchemeHostPortPath
) {
634 // If referrer header is over max Length, down to origin
635 nsresult rv
= GetOriginFromReferrerURI(aReferrer
, aInAndOutTrimmedReferrer
);
636 if (NS_WARN_IF(NS_FAILED(rv
))) {
640 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
641 // states that the trailing "/" does not need to get stripped. However,
642 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
644 aInAndOutTrimmedReferrer
.AppendLiteral("/");
645 if (aInAndOutTrimmedReferrer
.Length() <=
646 StaticPrefs::network_http_referer_referrerLengthLimit()) {
647 AutoTArray
<nsString
, 2> params
= {
648 referrerLengthLimit
, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer
)};
649 LogMessageToConsole(aChannel
, "ReferrerLengthOverLimitation", params
);
654 // If we end up here either the trimmingPolicy is equal to
655 // 'ePolicySchemeHostPort' or the 'origin' of any other policy is still over
656 // the length limit. If so, truncate the referrer entirely.
657 AutoTArray
<nsString
, 2> params
= {
658 referrerLengthLimit
, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer
)};
659 LogMessageToConsole(aChannel
, "ReferrerOriginLengthOverLimitation", params
);
660 aInAndOutTrimmedReferrer
.Truncate();
665 nsresult
ReferrerInfo::GetOriginFromReferrerURI(nsIURI
* aReferrer
,
666 nsACString
& aResult
) const {
667 MOZ_ASSERT(aReferrer
);
669 // We want the IDN-normalized PrePath. That's not something currently
670 // available and there doesn't yet seem to be justification for adding it to
671 // the interfaces, so just build it up from scheme+AsciiHostPort
672 nsAutoCString scheme
, asciiHostPort
;
673 nsresult rv
= aReferrer
->GetScheme(scheme
);
674 if (NS_WARN_IF(NS_FAILED(rv
))) {
679 aResult
.AppendLiteral("://");
680 // Note we explicitly cleared UserPass above, so do not need to build it.
681 rv
= aReferrer
->GetAsciiHostPort(asciiHostPort
);
682 if (NS_WARN_IF(NS_FAILED(rv
))) {
686 aResult
.Append(asciiHostPort
);
690 nsresult
ReferrerInfo::TrimReferrerWithPolicy(nsIURI
* aReferrer
,
691 TrimmingPolicy aTrimmingPolicy
,
692 nsACString
& aResult
) const {
693 MOZ_ASSERT(aReferrer
);
695 if (aTrimmingPolicy
== TrimmingPolicy::ePolicyFullURI
) {
696 return aReferrer
->GetAsciiSpec(aResult
);
699 nsresult rv
= GetOriginFromReferrerURI(aReferrer
, aResult
);
700 if (NS_WARN_IF(NS_FAILED(rv
))) {
704 if (aTrimmingPolicy
== TrimmingPolicy::ePolicySchemeHostPortPath
) {
705 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aReferrer
));
708 rv
= url
->GetFilePath(path
);
709 if (NS_WARN_IF(NS_FAILED(rv
))) {
713 aResult
.Append(path
);
718 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
719 // states that the trailing "/" does not need to get stripped. However,
720 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
722 aResult
.AppendLiteral("/");
726 bool ReferrerInfo::ShouldIgnoreLessRestrictedPolicies(
727 nsIHttpChannel
* aChannel
, const ReferrerPolicyEnum aPolicy
) const {
728 MOZ_ASSERT(aChannel
);
730 // We only care about the less restricted policies.
731 if (aPolicy
!= ReferrerPolicy::Unsafe_url
&&
732 aPolicy
!= ReferrerPolicy::No_referrer_when_downgrade
&&
733 aPolicy
!= ReferrerPolicy::Origin_when_cross_origin
) {
737 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
738 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
740 // Return early if we don't want to ignore less restricted policies for the
742 if (loadInfo
->GetExternalContentPolicyType() ==
743 ExtContentPolicy::TYPE_DOCUMENT
) {
744 bool isEnabledForTopNavigation
=
747 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode_top_navigation()
749 network_http_referer_disallowCrossSiteRelaxingDefault_top_navigation();
750 if (!isEnabledForTopNavigation
) {
754 // We have to get the value of the contentBlockingAllowList earlier because
755 // the channel hasn't been opened yet here. Note that we only need to do
756 // this for first-party navigation. For third-party loads, the value is
757 // inherited from the parent.
758 if (XRE_IsParentProcess()) {
759 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
;
760 Unused
<< loadInfo
->GetCookieJarSettings(
761 getter_AddRefs(cookieJarSettings
));
763 net::CookieJarSettings::Cast(cookieJarSettings
)
764 ->UpdateIsOnContentBlockingAllowList(aChannel
);
768 // We don't ignore less restricted referrer policies if ETP is toggled off.
769 // This would affect iframe loads and top navigation. For iframes, it will
770 // stop ignoring if the first-party site toggled ETP off. For top navigation,
771 // it depends on the ETP toggle for the destination site.
772 if (ContentBlockingAllowList::Check(aChannel
)) {
776 bool isCrossSite
= IsCrossSiteRequest(aChannel
);
780 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode()
782 network_http_referer_disallowCrossSiteRelaxingDefault();
785 // Log the warning message to console to inform that we will ignore
786 // less restricted policies for cross-site requests in the future.
788 nsCOMPtr
<nsIURI
> uri
;
789 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
790 NS_ENSURE_SUCCESS(rv
, false);
792 AutoTArray
<nsString
, 1> params
= {
793 NS_ConvertUTF8toUTF16(uri
->GetSpecOrDefault())};
794 LogMessageToConsole(aChannel
, "ReferrerPolicyDisallowRelaxingWarning",
800 // Check if the channel is triggered by the system or the extension.
801 auto* triggerBasePrincipal
=
802 BasePrincipal::Cast(loadInfo
->TriggeringPrincipal());
803 if (triggerBasePrincipal
->IsSystemPrincipal() ||
804 triggerBasePrincipal
->AddonPolicy()) {
809 // Log the console message to say that the less restricted policy was
811 nsCOMPtr
<nsIURI
> uri
;
812 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
813 NS_ENSURE_SUCCESS(rv
, true);
815 AutoTArray
<nsString
, 2> params
= {
816 NS_ConvertUTF8toUTF16(GetEnumString(aPolicy
)),
817 NS_ConvertUTF8toUTF16(uri
->GetSpecOrDefault())};
818 LogMessageToConsole(aChannel
, "ReferrerPolicyDisallowRelaxingMessage",
825 void ReferrerInfo::LogMessageToConsole(
826 nsIHttpChannel
* aChannel
, const char* aMsg
,
827 const nsTArray
<nsString
>& aParams
) const {
828 MOZ_ASSERT(aChannel
);
830 nsCOMPtr
<nsIURI
> uri
;
831 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
832 if (NS_WARN_IF(NS_FAILED(rv
))) {
836 uint64_t windowID
= 0;
838 rv
= aChannel
->GetTopLevelContentWindowId(&windowID
);
839 if (NS_WARN_IF(NS_FAILED(rv
))) {
844 nsCOMPtr
<nsILoadGroup
> loadGroup
;
845 rv
= aChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
846 if (NS_WARN_IF(NS_FAILED(rv
))) {
851 windowID
= nsContentUtils::GetInnerWindowID(loadGroup
);
855 nsAutoString localizedMsg
;
856 rv
= nsContentUtils::FormatLocalizedString(
857 nsContentUtils::eSECURITY_PROPERTIES
, aMsg
, aParams
, localizedMsg
);
858 if (NS_WARN_IF(NS_FAILED(rv
))) {
862 rv
= nsContentUtils::ReportToConsoleByWindowID(
863 localizedMsg
, nsIScriptError::infoFlag
, "Security"_ns
, windowID
,
864 SourceLocation(std::move(uri
)));
865 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
868 ReferrerPolicy
ReferrerPolicyIDLToReferrerPolicy(
869 nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy
) {
870 switch (aReferrerPolicy
) {
871 case nsIReferrerInfo::EMPTY
:
872 return ReferrerPolicy::_empty
;
874 case nsIReferrerInfo::NO_REFERRER
:
875 return ReferrerPolicy::No_referrer
;
877 case nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE
:
878 return ReferrerPolicy::No_referrer_when_downgrade
;
880 case nsIReferrerInfo::ORIGIN
:
881 return ReferrerPolicy::Origin
;
883 case nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN
:
884 return ReferrerPolicy::Origin_when_cross_origin
;
886 case nsIReferrerInfo::UNSAFE_URL
:
887 return ReferrerPolicy::Unsafe_url
;
889 case nsIReferrerInfo::SAME_ORIGIN
:
890 return ReferrerPolicy::Same_origin
;
892 case nsIReferrerInfo::STRICT_ORIGIN
:
893 return ReferrerPolicy::Strict_origin
;
895 case nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
:
896 return ReferrerPolicy::Strict_origin_when_cross_origin
;
899 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
903 return ReferrerPolicy::_empty
;
906 nsIReferrerInfo::ReferrerPolicyIDL
ReferrerPolicyToReferrerPolicyIDL(
907 ReferrerPolicy aReferrerPolicy
) {
908 switch (aReferrerPolicy
) {
909 case ReferrerPolicy::_empty
:
910 return nsIReferrerInfo::EMPTY
;
912 case ReferrerPolicy::No_referrer
:
913 return nsIReferrerInfo::NO_REFERRER
;
915 case ReferrerPolicy::No_referrer_when_downgrade
:
916 return nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE
;
918 case ReferrerPolicy::Origin
:
919 return nsIReferrerInfo::ORIGIN
;
921 case ReferrerPolicy::Origin_when_cross_origin
:
922 return nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN
;
924 case ReferrerPolicy::Unsafe_url
:
925 return nsIReferrerInfo::UNSAFE_URL
;
927 case ReferrerPolicy::Same_origin
:
928 return nsIReferrerInfo::SAME_ORIGIN
;
930 case ReferrerPolicy::Strict_origin
:
931 return nsIReferrerInfo::STRICT_ORIGIN
;
933 case ReferrerPolicy::Strict_origin_when_cross_origin
:
934 return nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
;
937 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
941 return nsIReferrerInfo::EMPTY
;
944 ReferrerInfo::ReferrerInfo()
945 : mOriginalReferrer(nullptr),
946 mPolicy(ReferrerPolicy::_empty
),
947 mOriginalPolicy(ReferrerPolicy::_empty
),
950 mOverridePolicyByDefault(false) {}
952 ReferrerInfo::ReferrerInfo(const Document
& aDoc
) : ReferrerInfo() {
953 InitWithDocument(&aDoc
);
956 ReferrerInfo::ReferrerInfo(const Element
& aElement
) : ReferrerInfo() {
957 InitWithElement(&aElement
);
960 ReferrerInfo::ReferrerInfo(const Element
& aElement
,
961 ReferrerPolicyEnum aOverridePolicy
)
962 : ReferrerInfo(aElement
) {
963 // Override referrer policy if not empty
964 if (aOverridePolicy
!= ReferrerPolicyEnum::_empty
) {
965 mPolicy
= aOverridePolicy
;
966 mOriginalPolicy
= aOverridePolicy
;
970 ReferrerInfo::ReferrerInfo(nsIURI
* aOriginalReferrer
,
971 ReferrerPolicyEnum aPolicy
, bool aSendReferrer
,
972 const Maybe
<nsCString
>& aComputedReferrer
)
973 : mOriginalReferrer(aOriginalReferrer
),
975 mOriginalPolicy(aPolicy
),
976 mSendReferrer(aSendReferrer
),
978 mOverridePolicyByDefault(false),
979 mComputedReferrer(aComputedReferrer
) {}
981 ReferrerInfo::ReferrerInfo(const ReferrerInfo
& rhs
)
982 : mOriginalReferrer(rhs
.mOriginalReferrer
),
983 mPolicy(rhs
.mPolicy
),
984 mOriginalPolicy(rhs
.mOriginalPolicy
),
985 mSendReferrer(rhs
.mSendReferrer
),
986 mInitialized(rhs
.mInitialized
),
987 mOverridePolicyByDefault(rhs
.mOverridePolicyByDefault
),
988 mComputedReferrer(rhs
.mComputedReferrer
) {}
990 already_AddRefed
<ReferrerInfo
> ReferrerInfo::Clone() const {
991 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
992 return copy
.forget();
995 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewPolicy(
996 ReferrerPolicyEnum aPolicy
) const {
997 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
998 copy
->mPolicy
= aPolicy
;
999 copy
->mOriginalPolicy
= aPolicy
;
1000 return copy
.forget();
1003 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewSendReferrer(
1004 bool aSendReferrer
) const {
1005 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
1006 copy
->mSendReferrer
= aSendReferrer
;
1007 return copy
.forget();
1010 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewOriginalReferrer(
1011 nsIURI
* aOriginalReferrer
) const {
1012 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
1013 copy
->mOriginalReferrer
= aOriginalReferrer
;
1014 return copy
.forget();
1018 ReferrerInfo::GetOriginalReferrer(nsIURI
** aOriginalReferrer
) {
1019 *aOriginalReferrer
= mOriginalReferrer
;
1020 NS_IF_ADDREF(*aOriginalReferrer
);
1025 ReferrerInfo::GetReferrerPolicy(
1026 JSContext
* aCx
, nsIReferrerInfo::ReferrerPolicyIDL
* aReferrerPolicy
) {
1027 *aReferrerPolicy
= ReferrerPolicyToReferrerPolicyIDL(mPolicy
);
1032 ReferrerInfo::GetReferrerPolicyString(nsACString
& aResult
) {
1033 aResult
.AssignASCII(GetEnumString(mPolicy
));
1037 ReferrerPolicy
ReferrerInfo::ReferrerPolicy() { return mPolicy
; }
1040 ReferrerInfo::GetSendReferrer(bool* aSendReferrer
) {
1041 *aSendReferrer
= mSendReferrer
;
1046 ReferrerInfo::Equals(nsIReferrerInfo
* aOther
, bool* aResult
) {
1047 NS_ENSURE_TRUE(aOther
, NS_ERROR_INVALID_ARG
);
1048 MOZ_ASSERT(mInitialized
);
1049 if (aOther
== this) {
1055 ReferrerInfo
* other
= static_cast<ReferrerInfo
*>(aOther
);
1056 MOZ_ASSERT(other
->mInitialized
);
1058 if (mPolicy
!= other
->mPolicy
|| mSendReferrer
!= other
->mSendReferrer
||
1059 mOverridePolicyByDefault
!= other
->mOverridePolicyByDefault
||
1060 mComputedReferrer
!= other
->mComputedReferrer
) {
1064 if (!mOriginalReferrer
!= !other
->mOriginalReferrer
) {
1065 // One or the other has mOriginalReferrer, but not both... not equal
1069 bool originalReferrerEquals
;
1070 if (mOriginalReferrer
&&
1071 (NS_FAILED(mOriginalReferrer
->Equals(other
->mOriginalReferrer
,
1072 &originalReferrerEquals
)) ||
1073 !originalReferrerEquals
)) {
1082 ReferrerInfo::GetComputedReferrerSpec(nsACString
& aComputedReferrerSpec
) {
1083 aComputedReferrerSpec
.Assign(
1084 mComputedReferrer
.isSome() ? mComputedReferrer
.value() : EmptyCString());
1088 already_AddRefed
<nsIURI
> ReferrerInfo::GetComputedReferrer() {
1089 if (!mComputedReferrer
.isSome() || mComputedReferrer
.value().IsEmpty()) {
1093 nsCOMPtr
<nsIURI
> result
;
1094 nsresult rv
= NS_NewURI(getter_AddRefs(result
), mComputedReferrer
.value());
1095 if (NS_FAILED(rv
)) {
1099 return result
.forget();
1102 HashNumber
ReferrerInfo::Hash() const {
1103 MOZ_ASSERT(mInitialized
);
1104 nsAutoCString originalReferrerSpec
;
1105 if (mOriginalReferrer
) {
1106 Unused
<< mOriginalReferrer
->GetSpec(originalReferrerSpec
);
1109 return mozilla::AddToHash(
1110 static_cast<uint32_t>(mPolicy
), mSendReferrer
, mOverridePolicyByDefault
,
1111 mozilla::HashString(originalReferrerSpec
),
1112 mozilla::HashString(mComputedReferrer
.isSome() ? mComputedReferrer
.value()
1117 ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy
,
1118 bool aSendReferrer
, nsIURI
* aOriginalReferrer
) {
1119 MOZ_ASSERT(!mInitialized
);
1121 return NS_ERROR_ALREADY_INITIALIZED
;
1124 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy
);
1125 mOriginalPolicy
= mPolicy
;
1126 mSendReferrer
= aSendReferrer
;
1127 mOriginalReferrer
= aOriginalReferrer
;
1128 mInitialized
= true;
1133 ReferrerInfo::InitWithDocument(const Document
* aDocument
) {
1134 MOZ_ASSERT(!mInitialized
);
1136 return NS_ERROR_ALREADY_INITIALIZED
;
1139 mPolicy
= aDocument
->GetReferrerPolicy();
1140 mOriginalPolicy
= mPolicy
;
1141 mSendReferrer
= true;
1142 mOriginalReferrer
= aDocument
->GetDocumentURIAsReferrer();
1143 mInitialized
= true;
1148 * Check whether the given node has referrerpolicy attribute and parse
1149 * referrer policy from the attribute.
1150 * Currently, referrerpolicy attribute is supported in a, area, img, iframe,
1151 * script, or link element.
1153 static ReferrerPolicy
ReferrerPolicyFromAttribute(const Element
& aElement
) {
1154 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1155 nsGkAtoms::script
, nsGkAtoms::iframe
,
1156 nsGkAtoms::link
, nsGkAtoms::img
)) {
1157 return ReferrerPolicy::_empty
;
1159 return aElement
.GetReferrerPolicyAsEnum();
1162 static bool HasRelNoReferrer(const Element
& aElement
) {
1163 // rel=noreferrer is only supported in <a>, <area>, and <form>
1164 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1166 !aElement
.IsSVGElement(nsGkAtoms::a
)) {
1171 aElement
.GetAttr(nsGkAtoms::rel
, rel
);
1172 nsWhitespaceTokenizerTemplate
<nsContentUtils::IsHTMLWhitespace
> tok(rel
);
1174 while (tok
.hasMoreTokens()) {
1175 const nsAString
& token
= tok
.nextToken();
1176 if (token
.LowerCaseEqualsLiteral("noreferrer")) {
1185 ReferrerInfo::InitWithElement(const Element
* aElement
) {
1186 MOZ_ASSERT(!mInitialized
);
1188 return NS_ERROR_ALREADY_INITIALIZED
;
1191 // Referrer policy from referrerpolicy attribute will have a higher priority
1192 // than referrer policy from <meta> tag and Referrer-Policy header.
1193 mPolicy
= ReferrerPolicyFromAttribute(*aElement
);
1194 if (mPolicy
== ReferrerPolicy::_empty
) {
1195 // Fallback to use document's referrer poicy if we don't have referrer
1196 // policy from attribute.
1197 mPolicy
= aElement
->OwnerDoc()->GetReferrerPolicy();
1200 mOriginalPolicy
= mPolicy
;
1201 mSendReferrer
= !HasRelNoReferrer(*aElement
);
1202 mOriginalReferrer
= aElement
->OwnerDoc()->GetDocumentURIAsReferrer();
1204 mInitialized
= true;
1209 already_AddRefed
<nsIReferrerInfo
>
1210 ReferrerInfo::CreateFromDocumentAndPolicyOverride(
1211 Document
* aDoc
, ReferrerPolicyEnum aPolicyOverride
) {
1213 ReferrerPolicyEnum policy
= aPolicyOverride
!= ReferrerPolicy::_empty
1215 : aDoc
->GetReferrerPolicy();
1216 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
=
1217 new ReferrerInfo(aDoc
->GetDocumentURIAsReferrer(), policy
);
1218 return referrerInfo
.forget();
1222 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForFetch(
1223 nsIPrincipal
* aPrincipal
, Document
* aDoc
) {
1224 MOZ_ASSERT(aPrincipal
);
1226 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1227 if (!aPrincipal
|| aPrincipal
->IsSystemPrincipal()) {
1228 referrerInfo
= new ReferrerInfo(nullptr);
1229 return referrerInfo
.forget();
1233 aPrincipal
->CreateReferrerInfo(ReferrerPolicy::_empty
,
1234 getter_AddRefs(referrerInfo
));
1235 return referrerInfo
.forget();
1238 // If it weren't for history.push/replaceState, we could just use the
1239 // principal's URI here. But since we want changes to the URI effected
1240 // by push/replaceState to be reflected in the XHR referrer, we have to
1243 // If the document's original URI (before any push/replaceStates) matches
1244 // our principal, then we use the document's current URI (after
1245 // push/replaceStates). Otherwise (if the document is, say, a data:
1246 // URI), we just use the principal's URI.
1247 nsCOMPtr
<nsIURI
> docCurURI
= aDoc
->GetDocumentURI();
1248 nsCOMPtr
<nsIURI
> docOrigURI
= aDoc
->GetOriginalURI();
1250 if (docCurURI
&& docOrigURI
) {
1252 aPrincipal
->EqualsURI(docOrigURI
, &equal
);
1254 referrerInfo
= new ReferrerInfo(docCurURI
, aDoc
->GetReferrerPolicy());
1255 return referrerInfo
.forget();
1258 aPrincipal
->CreateReferrerInfo(aDoc
->GetReferrerPolicy(),
1259 getter_AddRefs(referrerInfo
));
1260 return referrerInfo
.forget();
1264 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForExternalCSSResources(
1265 mozilla::StyleSheet
* aExternalSheet
, ReferrerPolicyEnum aPolicy
) {
1266 MOZ_ASSERT(aExternalSheet
&& !aExternalSheet
->IsInline());
1267 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1270 // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css
1271 // Use empty policy at the beginning and update it later from Referrer-Policy
1273 referrerInfo
= new ReferrerInfo(aExternalSheet
->GetSheetURI(), aPolicy
);
1274 return referrerInfo
.forget();
1278 already_AddRefed
<nsIReferrerInfo
>
1279 ReferrerInfo::CreateForInternalCSSAndSVGResources(Document
* aDocument
) {
1280 MOZ_ASSERT(aDocument
);
1281 return do_AddRef(new ReferrerInfo(aDocument
->GetDocumentURI(),
1282 aDocument
->GetReferrerPolicy()));
1285 nsresult
ReferrerInfo::ComputeReferrer(nsIHttpChannel
* aChannel
) {
1286 NS_ENSURE_ARG(aChannel
);
1287 MOZ_ASSERT(NS_IsMainThread());
1289 // If the referrerInfo is passed around when redirect, just use the last
1290 // computedReferrer to recompute
1291 nsCOMPtr
<nsIURI
> referrer
;
1292 nsresult rv
= NS_OK
;
1293 mOverridePolicyByDefault
= false;
1295 if (mComputedReferrer
.isSome()) {
1296 if (mComputedReferrer
.value().IsEmpty()) {
1300 rv
= NS_NewURI(getter_AddRefs(referrer
), mComputedReferrer
.value());
1301 if (NS_WARN_IF(NS_FAILED(rv
))) {
1306 mComputedReferrer
.reset();
1307 // Emplace mComputedReferrer with an empty string, which means we have
1308 // computed the referrer and the result referrer value is empty (not send
1309 // referrer). So any early return later than this line will use that empty
1311 mComputedReferrer
.emplace(""_ns
);
1313 if (!mSendReferrer
|| !mOriginalReferrer
||
1314 mPolicy
== ReferrerPolicy::No_referrer
) {
1318 if (mPolicy
== ReferrerPolicy::_empty
||
1319 ShouldIgnoreLessRestrictedPolicies(aChannel
, mOriginalPolicy
)) {
1320 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1321 OriginAttributes attrs
= loadInfo
->GetOriginAttributes();
1322 bool isPrivate
= attrs
.IsPrivateBrowsing();
1324 nsCOMPtr
<nsIURI
> uri
;
1325 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1326 if (NS_WARN_IF(NS_FAILED(rv
))) {
1330 mPolicy
= GetDefaultReferrerPolicy(aChannel
, uri
, isPrivate
);
1331 mOverridePolicyByDefault
= true;
1334 // This is for the case where the ETP toggle is off. In this case, we need to
1335 // reset the referrer and the policy if the original policy is different from
1336 // the current policy in order to recompute the referrer policy with the
1338 if (!mOverridePolicyByDefault
&& mOriginalPolicy
!= ReferrerPolicy::_empty
&&
1339 mPolicy
!= mOriginalPolicy
) {
1341 mPolicy
= mOriginalPolicy
;
1344 if (mPolicy
== ReferrerPolicy::No_referrer
) {
1348 bool isUserReferrerSendingAllowed
= false;
1349 rv
= HandleUserReferrerSendingPolicy(aChannel
, isUserReferrerSendingAllowed
);
1350 if (NS_WARN_IF(NS_FAILED(rv
))) {
1354 if (!isUserReferrerSendingAllowed
) {
1358 // Enforce Referrer allowlist, only http, https scheme are allowed
1359 if (!IsReferrerSchemeAllowed(mOriginalReferrer
)) {
1363 nsCOMPtr
<nsIURI
> uri
;
1364 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1365 if (NS_WARN_IF(NS_FAILED(rv
))) {
1369 bool isSecureToInsecureAllowed
= false;
1370 rv
= HandleSecureToInsecureReferral(mOriginalReferrer
, uri
, mPolicy
,
1371 isSecureToInsecureAllowed
);
1372 if (NS_WARN_IF(NS_FAILED(rv
))) {
1376 if (!isSecureToInsecureAllowed
) {
1380 // Strip away any fragment per RFC 2616 section 14.36
1381 // and Referrer Policy section 6.3.5.
1383 rv
= NS_GetURIWithoutRef(mOriginalReferrer
, getter_AddRefs(referrer
));
1384 if (NS_WARN_IF(NS_FAILED(rv
))) {
1389 bool isUserXOriginAllowed
= false;
1390 rv
= HandleUserXOriginSendingPolicy(uri
, referrer
, isUserXOriginAllowed
);
1391 if (NS_WARN_IF(NS_FAILED(rv
))) {
1395 if (!isUserXOriginAllowed
) {
1399 // Handle user pref network.http.referer.spoofSource, send spoofed referrer if
1401 if (StaticPrefs::network_http_referer_spoofSource()) {
1402 nsCOMPtr
<nsIURI
> userSpoofReferrer
;
1403 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(userSpoofReferrer
));
1404 if (NS_WARN_IF(NS_FAILED(rv
))) {
1407 referrer
= userSpoofReferrer
;
1410 // strip away any userpass; we don't want to be giving out passwords ;-)
1411 // This is required by Referrer Policy stripping algorithm.
1412 nsCOMPtr
<nsIURI
> exposableURI
= nsIOService::CreateExposableURI(referrer
);
1413 referrer
= exposableURI
;
1415 // Don't send referrer when the request is cross-origin and policy is
1417 if (mPolicy
== ReferrerPolicy::Same_origin
&&
1418 IsReferrerCrossOrigin(aChannel
, referrer
)) {
1422 TrimmingPolicy trimmingPolicy
= ComputeTrimmingPolicy(aChannel
, referrer
);
1424 nsAutoCString trimmedReferrer
;
1425 // We first trim the referrer according to the policy by calling
1426 // 'TrimReferrerWithPolicy' and right after we have to call
1427 // 'LimitReferrerLength' (using the same arguments) because the trimmed
1428 // referrer might exceed the allowed max referrer length.
1429 rv
= TrimReferrerWithPolicy(referrer
, trimmingPolicy
, trimmedReferrer
);
1430 if (NS_WARN_IF(NS_FAILED(rv
))) {
1434 rv
= LimitReferrerLength(aChannel
, referrer
, trimmingPolicy
, trimmedReferrer
);
1435 if (NS_WARN_IF(NS_FAILED(rv
))) {
1439 // finally, remember the referrer spec.
1440 mComputedReferrer
.reset();
1441 mComputedReferrer
.emplace(trimmedReferrer
);
1446 /* ===== nsISerializable implementation ====== */
1448 nsresult
ReferrerInfo::ReadTailDataBeforeGecko100(
1449 const uint32_t& aData
, nsIObjectInputStream
* aInputStream
) {
1450 MOZ_ASSERT(aInputStream
);
1452 nsCOMPtr
<nsIInputStream
> reader
;
1453 nsCOMPtr
<nsIOutputStream
> writer
;
1455 // We need to create a new pipe in order to read the aData and the rest of
1456 // the input stream together in the old format. This would also help us with
1457 // handling big endian correctly.
1458 NS_NewPipe(getter_AddRefs(reader
), getter_AddRefs(writer
));
1460 nsCOMPtr
<nsIBinaryOutputStream
> binaryPipeWriter
=
1461 NS_NewObjectOutputStream(writer
);
1463 // Write back the aData so that we can read bytes from it and handle big
1464 // endian correctly.
1465 nsresult rv
= binaryPipeWriter
->Write32(aData
);
1466 if (NS_WARN_IF(NS_FAILED(rv
))) {
1470 nsCOMPtr
<nsIBinaryInputStream
> binaryPipeReader
=
1471 NS_NewObjectInputStream(reader
);
1473 rv
= binaryPipeReader
->ReadBoolean(&mSendReferrer
);
1474 if (NS_WARN_IF(NS_FAILED(rv
))) {
1479 rv
= binaryPipeReader
->ReadBoolean(&isComputed
);
1480 if (NS_WARN_IF(NS_FAILED(rv
))) {
1484 // We need to handle the following string if isComputed is true.
1486 // Comsume the following 2 bytes from the input stream. They are the half
1487 // part of the length prefix of the following string.
1489 rv
= aInputStream
->Read16(&data
);
1490 if (NS_WARN_IF(NS_FAILED(rv
))) {
1494 // Write the bytes to the pipe so that we can read the length of the string.
1495 rv
= binaryPipeWriter
->Write16(data
);
1496 if (NS_WARN_IF(NS_FAILED(rv
))) {
1501 rv
= binaryPipeReader
->Read32(&length
);
1502 if (NS_WARN_IF(NS_FAILED(rv
))) {
1506 // Consume the string body from the input stream.
1507 nsAutoCString computedReferrer
;
1508 rv
= NS_ConsumeStream(aInputStream
, length
, computedReferrer
);
1509 if (NS_WARN_IF(NS_FAILED(rv
))) {
1512 mComputedReferrer
.emplace(computedReferrer
);
1514 // Read the remaining two bytes and write to the pipe.
1516 rv
= aInputStream
->Read16(&remain
);
1517 if (NS_WARN_IF(NS_FAILED(rv
))) {
1521 rv
= binaryPipeWriter
->Write16(remain
);
1522 if (NS_WARN_IF(NS_FAILED(rv
))) {
1527 rv
= binaryPipeReader
->ReadBoolean(&mInitialized
);
1528 if (NS_WARN_IF(NS_FAILED(rv
))) {
1532 rv
= binaryPipeReader
->ReadBoolean(&mOverridePolicyByDefault
);
1533 if (NS_WARN_IF(NS_FAILED(rv
))) {
1541 ReferrerInfo::Read(nsIObjectInputStream
* aStream
) {
1543 nsresult rv
= aStream
->ReadBoolean(&nonNull
);
1544 if (NS_WARN_IF(NS_FAILED(rv
))) {
1550 nsresult rv
= aStream
->ReadCString(spec
);
1551 if (NS_WARN_IF(NS_FAILED(rv
))) {
1555 rv
= NS_NewURI(getter_AddRefs(mOriginalReferrer
), spec
);
1556 if (NS_WARN_IF(NS_FAILED(rv
))) {
1560 mOriginalReferrer
= nullptr;
1563 // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store
1564 // to disk using the order of ReferrerPolicyIDL, so we convert to
1565 // ReferrerPolicyIDL to make it be compatible to the old format.
1567 rv
= aStream
->Read32(&policy
);
1568 if (NS_WARN_IF(NS_FAILED(rv
))) {
1571 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1572 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(policy
));
1574 uint32_t originalPolicy
;
1575 rv
= aStream
->Read32(&originalPolicy
);
1576 if (NS_WARN_IF(NS_FAILED(rv
))) {
1580 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more
1583 // We need to differentiate the old format and the new format here in order
1584 // to be able to read both formats. The check here helps us with verifying
1585 // which format it is.
1586 if (MOZ_UNLIKELY(originalPolicy
> 0xFF)) {
1587 mOriginalPolicy
= mPolicy
;
1589 return ReadTailDataBeforeGecko100(originalPolicy
, aStream
);
1592 mOriginalPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1593 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(originalPolicy
));
1595 rv
= aStream
->ReadBoolean(&mSendReferrer
);
1596 if (NS_WARN_IF(NS_FAILED(rv
))) {
1601 rv
= aStream
->ReadBoolean(&isComputed
);
1602 if (NS_WARN_IF(NS_FAILED(rv
))) {
1607 nsAutoCString computedReferrer
;
1608 rv
= aStream
->ReadCString(computedReferrer
);
1609 if (NS_WARN_IF(NS_FAILED(rv
))) {
1612 mComputedReferrer
.emplace(computedReferrer
);
1615 rv
= aStream
->ReadBoolean(&mInitialized
);
1616 if (NS_WARN_IF(NS_FAILED(rv
))) {
1620 rv
= aStream
->ReadBoolean(&mOverridePolicyByDefault
);
1621 if (NS_WARN_IF(NS_FAILED(rv
))) {
1629 ReferrerInfo::Write(nsIObjectOutputStream
* aStream
) {
1630 bool nonNull
= (mOriginalReferrer
!= nullptr);
1631 nsresult rv
= aStream
->WriteBoolean(nonNull
);
1632 if (NS_WARN_IF(NS_FAILED(rv
))) {
1638 nsresult rv
= mOriginalReferrer
->GetSpec(spec
);
1639 if (NS_WARN_IF(NS_FAILED(rv
))) {
1643 rv
= aStream
->WriteStringZ(spec
.get());
1644 if (NS_WARN_IF(NS_FAILED(rv
))) {
1649 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy
));
1650 if (NS_WARN_IF(NS_FAILED(rv
))) {
1654 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy
));
1655 if (NS_WARN_IF(NS_FAILED(rv
))) {
1659 rv
= aStream
->WriteBoolean(mSendReferrer
);
1660 if (NS_WARN_IF(NS_FAILED(rv
))) {
1664 bool isComputed
= mComputedReferrer
.isSome();
1665 rv
= aStream
->WriteBoolean(isComputed
);
1666 if (NS_WARN_IF(NS_FAILED(rv
))) {
1671 rv
= aStream
->WriteStringZ(mComputedReferrer
.value().get());
1672 if (NS_WARN_IF(NS_FAILED(rv
))) {
1677 rv
= aStream
->WriteBoolean(mInitialized
);
1678 if (NS_WARN_IF(NS_FAILED(rv
))) {
1682 rv
= aStream
->WriteBoolean(mOverridePolicyByDefault
);
1683 if (NS_WARN_IF(NS_FAILED(rv
))) {
1689 void ReferrerInfo::RecordTelemetry(nsIHttpChannel
* aChannel
) {
1691 MOZ_ASSERT(!mTelemetryRecorded
);
1692 mTelemetryRecorded
= true;
1695 // The telemetry probe has 18 buckets. The first 9 buckets are for same-site
1696 // requests and the rest 9 buckets are for cross-site requests.
1697 uint32_t telemetryOffset
=
1698 IsCrossSiteRequest(aChannel
)
1700 MaxContiguousEnumValue
<dom::ReferrerPolicy
>::value
) +
1704 Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT
,
1705 static_cast<uint32_t>(mPolicy
) + telemetryOffset
);
1708 } // namespace mozilla::dom