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
, uri
);
864 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
867 ReferrerPolicy
ReferrerPolicyIDLToReferrerPolicy(
868 nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy
) {
869 switch (aReferrerPolicy
) {
870 case nsIReferrerInfo::EMPTY
:
871 return ReferrerPolicy::_empty
;
873 case nsIReferrerInfo::NO_REFERRER
:
874 return ReferrerPolicy::No_referrer
;
876 case nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE
:
877 return ReferrerPolicy::No_referrer_when_downgrade
;
879 case nsIReferrerInfo::ORIGIN
:
880 return ReferrerPolicy::Origin
;
882 case nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN
:
883 return ReferrerPolicy::Origin_when_cross_origin
;
885 case nsIReferrerInfo::UNSAFE_URL
:
886 return ReferrerPolicy::Unsafe_url
;
888 case nsIReferrerInfo::SAME_ORIGIN
:
889 return ReferrerPolicy::Same_origin
;
891 case nsIReferrerInfo::STRICT_ORIGIN
:
892 return ReferrerPolicy::Strict_origin
;
894 case nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
:
895 return ReferrerPolicy::Strict_origin_when_cross_origin
;
898 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
902 return ReferrerPolicy::_empty
;
905 nsIReferrerInfo::ReferrerPolicyIDL
ReferrerPolicyToReferrerPolicyIDL(
906 ReferrerPolicy aReferrerPolicy
) {
907 switch (aReferrerPolicy
) {
908 case ReferrerPolicy::_empty
:
909 return nsIReferrerInfo::EMPTY
;
911 case ReferrerPolicy::No_referrer
:
912 return nsIReferrerInfo::NO_REFERRER
;
914 case ReferrerPolicy::No_referrer_when_downgrade
:
915 return nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE
;
917 case ReferrerPolicy::Origin
:
918 return nsIReferrerInfo::ORIGIN
;
920 case ReferrerPolicy::Origin_when_cross_origin
:
921 return nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN
;
923 case ReferrerPolicy::Unsafe_url
:
924 return nsIReferrerInfo::UNSAFE_URL
;
926 case ReferrerPolicy::Same_origin
:
927 return nsIReferrerInfo::SAME_ORIGIN
;
929 case ReferrerPolicy::Strict_origin
:
930 return nsIReferrerInfo::STRICT_ORIGIN
;
932 case ReferrerPolicy::Strict_origin_when_cross_origin
:
933 return nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
;
936 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
940 return nsIReferrerInfo::EMPTY
;
943 ReferrerInfo::ReferrerInfo()
944 : mOriginalReferrer(nullptr),
945 mPolicy(ReferrerPolicy::_empty
),
946 mOriginalPolicy(ReferrerPolicy::_empty
),
949 mOverridePolicyByDefault(false) {}
951 ReferrerInfo::ReferrerInfo(const Document
& aDoc
) : ReferrerInfo() {
952 InitWithDocument(&aDoc
);
955 ReferrerInfo::ReferrerInfo(const Element
& aElement
) : ReferrerInfo() {
956 InitWithElement(&aElement
);
959 ReferrerInfo::ReferrerInfo(const Element
& aElement
,
960 ReferrerPolicyEnum aOverridePolicy
)
961 : ReferrerInfo(aElement
) {
962 // Override referrer policy if not empty
963 if (aOverridePolicy
!= ReferrerPolicyEnum::_empty
) {
964 mPolicy
= aOverridePolicy
;
965 mOriginalPolicy
= aOverridePolicy
;
969 ReferrerInfo::ReferrerInfo(nsIURI
* aOriginalReferrer
,
970 ReferrerPolicyEnum aPolicy
, bool aSendReferrer
,
971 const Maybe
<nsCString
>& aComputedReferrer
)
972 : mOriginalReferrer(aOriginalReferrer
),
974 mOriginalPolicy(aPolicy
),
975 mSendReferrer(aSendReferrer
),
977 mOverridePolicyByDefault(false),
978 mComputedReferrer(aComputedReferrer
) {}
980 ReferrerInfo::ReferrerInfo(const ReferrerInfo
& rhs
)
981 : mOriginalReferrer(rhs
.mOriginalReferrer
),
982 mPolicy(rhs
.mPolicy
),
983 mOriginalPolicy(rhs
.mOriginalPolicy
),
984 mSendReferrer(rhs
.mSendReferrer
),
985 mInitialized(rhs
.mInitialized
),
986 mOverridePolicyByDefault(rhs
.mOverridePolicyByDefault
),
987 mComputedReferrer(rhs
.mComputedReferrer
) {}
989 already_AddRefed
<ReferrerInfo
> ReferrerInfo::Clone() const {
990 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
991 return copy
.forget();
994 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewPolicy(
995 ReferrerPolicyEnum aPolicy
) const {
996 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
997 copy
->mPolicy
= aPolicy
;
998 copy
->mOriginalPolicy
= aPolicy
;
999 return copy
.forget();
1002 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewSendReferrer(
1003 bool aSendReferrer
) const {
1004 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
1005 copy
->mSendReferrer
= aSendReferrer
;
1006 return copy
.forget();
1009 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewOriginalReferrer(
1010 nsIURI
* aOriginalReferrer
) const {
1011 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
1012 copy
->mOriginalReferrer
= aOriginalReferrer
;
1013 return copy
.forget();
1017 ReferrerInfo::GetOriginalReferrer(nsIURI
** aOriginalReferrer
) {
1018 *aOriginalReferrer
= mOriginalReferrer
;
1019 NS_IF_ADDREF(*aOriginalReferrer
);
1024 ReferrerInfo::GetReferrerPolicy(
1025 JSContext
* aCx
, nsIReferrerInfo::ReferrerPolicyIDL
* aReferrerPolicy
) {
1026 *aReferrerPolicy
= ReferrerPolicyToReferrerPolicyIDL(mPolicy
);
1031 ReferrerInfo::GetReferrerPolicyString(nsACString
& aResult
) {
1032 aResult
.AssignASCII(GetEnumString(mPolicy
));
1036 ReferrerPolicy
ReferrerInfo::ReferrerPolicy() { return mPolicy
; }
1039 ReferrerInfo::GetSendReferrer(bool* aSendReferrer
) {
1040 *aSendReferrer
= mSendReferrer
;
1045 ReferrerInfo::Equals(nsIReferrerInfo
* aOther
, bool* aResult
) {
1046 NS_ENSURE_TRUE(aOther
, NS_ERROR_INVALID_ARG
);
1047 MOZ_ASSERT(mInitialized
);
1048 if (aOther
== this) {
1054 ReferrerInfo
* other
= static_cast<ReferrerInfo
*>(aOther
);
1055 MOZ_ASSERT(other
->mInitialized
);
1057 if (mPolicy
!= other
->mPolicy
|| mSendReferrer
!= other
->mSendReferrer
||
1058 mOverridePolicyByDefault
!= other
->mOverridePolicyByDefault
||
1059 mComputedReferrer
!= other
->mComputedReferrer
) {
1063 if (!mOriginalReferrer
!= !other
->mOriginalReferrer
) {
1064 // One or the other has mOriginalReferrer, but not both... not equal
1068 bool originalReferrerEquals
;
1069 if (mOriginalReferrer
&&
1070 (NS_FAILED(mOriginalReferrer
->Equals(other
->mOriginalReferrer
,
1071 &originalReferrerEquals
)) ||
1072 !originalReferrerEquals
)) {
1081 ReferrerInfo::GetComputedReferrerSpec(nsAString
& aComputedReferrerSpec
) {
1082 aComputedReferrerSpec
.Assign(
1083 mComputedReferrer
.isSome()
1084 ? NS_ConvertUTF8toUTF16(mComputedReferrer
.value())
1089 already_AddRefed
<nsIURI
> ReferrerInfo::GetComputedReferrer() {
1090 if (!mComputedReferrer
.isSome() || mComputedReferrer
.value().IsEmpty()) {
1094 nsCOMPtr
<nsIURI
> result
;
1095 nsresult rv
= NS_NewURI(getter_AddRefs(result
), mComputedReferrer
.value());
1096 if (NS_FAILED(rv
)) {
1100 return result
.forget();
1103 HashNumber
ReferrerInfo::Hash() const {
1104 MOZ_ASSERT(mInitialized
);
1105 nsAutoCString originalReferrerSpec
;
1106 if (mOriginalReferrer
) {
1107 Unused
<< mOriginalReferrer
->GetSpec(originalReferrerSpec
);
1110 return mozilla::AddToHash(
1111 static_cast<uint32_t>(mPolicy
), mSendReferrer
, mOverridePolicyByDefault
,
1112 mozilla::HashString(originalReferrerSpec
),
1113 mozilla::HashString(mComputedReferrer
.isSome() ? mComputedReferrer
.value()
1118 ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy
,
1119 bool aSendReferrer
, nsIURI
* aOriginalReferrer
) {
1120 MOZ_ASSERT(!mInitialized
);
1122 return NS_ERROR_ALREADY_INITIALIZED
;
1125 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy
);
1126 mOriginalPolicy
= mPolicy
;
1127 mSendReferrer
= aSendReferrer
;
1128 mOriginalReferrer
= aOriginalReferrer
;
1129 mInitialized
= true;
1134 ReferrerInfo::InitWithDocument(const Document
* aDocument
) {
1135 MOZ_ASSERT(!mInitialized
);
1137 return NS_ERROR_ALREADY_INITIALIZED
;
1140 mPolicy
= aDocument
->GetReferrerPolicy();
1141 mOriginalPolicy
= mPolicy
;
1142 mSendReferrer
= true;
1143 mOriginalReferrer
= aDocument
->GetDocumentURIAsReferrer();
1144 mInitialized
= true;
1149 * Check whether the given node has referrerpolicy attribute and parse
1150 * referrer policy from the attribute.
1151 * Currently, referrerpolicy attribute is supported in a, area, img, iframe,
1152 * script, or link element.
1154 static ReferrerPolicy
ReferrerPolicyFromAttribute(const Element
& aElement
) {
1155 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1156 nsGkAtoms::script
, nsGkAtoms::iframe
,
1157 nsGkAtoms::link
, nsGkAtoms::img
)) {
1158 return ReferrerPolicy::_empty
;
1160 return aElement
.GetReferrerPolicyAsEnum();
1163 static bool HasRelNoReferrer(const Element
& aElement
) {
1164 // rel=noreferrer is only supported in <a>, <area>, and <form>
1165 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1167 !aElement
.IsSVGElement(nsGkAtoms::a
)) {
1172 aElement
.GetAttr(nsGkAtoms::rel
, rel
);
1173 nsWhitespaceTokenizerTemplate
<nsContentUtils::IsHTMLWhitespace
> tok(rel
);
1175 while (tok
.hasMoreTokens()) {
1176 const nsAString
& token
= tok
.nextToken();
1177 if (token
.LowerCaseEqualsLiteral("noreferrer")) {
1186 ReferrerInfo::InitWithElement(const Element
* aElement
) {
1187 MOZ_ASSERT(!mInitialized
);
1189 return NS_ERROR_ALREADY_INITIALIZED
;
1192 // Referrer policy from referrerpolicy attribute will have a higher priority
1193 // than referrer policy from <meta> tag and Referrer-Policy header.
1194 mPolicy
= ReferrerPolicyFromAttribute(*aElement
);
1195 if (mPolicy
== ReferrerPolicy::_empty
) {
1196 // Fallback to use document's referrer poicy if we don't have referrer
1197 // policy from attribute.
1198 mPolicy
= aElement
->OwnerDoc()->GetReferrerPolicy();
1201 mOriginalPolicy
= mPolicy
;
1202 mSendReferrer
= !HasRelNoReferrer(*aElement
);
1203 mOriginalReferrer
= aElement
->OwnerDoc()->GetDocumentURIAsReferrer();
1205 mInitialized
= true;
1210 already_AddRefed
<nsIReferrerInfo
>
1211 ReferrerInfo::CreateFromDocumentAndPolicyOverride(
1212 Document
* aDoc
, ReferrerPolicyEnum aPolicyOverride
) {
1214 ReferrerPolicyEnum policy
= aPolicyOverride
!= ReferrerPolicy::_empty
1216 : aDoc
->GetReferrerPolicy();
1217 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
=
1218 new ReferrerInfo(aDoc
->GetDocumentURIAsReferrer(), policy
);
1219 return referrerInfo
.forget();
1223 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForFetch(
1224 nsIPrincipal
* aPrincipal
, Document
* aDoc
) {
1225 MOZ_ASSERT(aPrincipal
);
1227 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1228 if (!aPrincipal
|| aPrincipal
->IsSystemPrincipal()) {
1229 referrerInfo
= new ReferrerInfo(nullptr);
1230 return referrerInfo
.forget();
1234 aPrincipal
->CreateReferrerInfo(ReferrerPolicy::_empty
,
1235 getter_AddRefs(referrerInfo
));
1236 return referrerInfo
.forget();
1239 // If it weren't for history.push/replaceState, we could just use the
1240 // principal's URI here. But since we want changes to the URI effected
1241 // by push/replaceState to be reflected in the XHR referrer, we have to
1244 // If the document's original URI (before any push/replaceStates) matches
1245 // our principal, then we use the document's current URI (after
1246 // push/replaceStates). Otherwise (if the document is, say, a data:
1247 // URI), we just use the principal's URI.
1248 nsCOMPtr
<nsIURI
> docCurURI
= aDoc
->GetDocumentURI();
1249 nsCOMPtr
<nsIURI
> docOrigURI
= aDoc
->GetOriginalURI();
1251 if (docCurURI
&& docOrigURI
) {
1253 aPrincipal
->EqualsURI(docOrigURI
, &equal
);
1255 referrerInfo
= new ReferrerInfo(docCurURI
, aDoc
->GetReferrerPolicy());
1256 return referrerInfo
.forget();
1259 aPrincipal
->CreateReferrerInfo(aDoc
->GetReferrerPolicy(),
1260 getter_AddRefs(referrerInfo
));
1261 return referrerInfo
.forget();
1265 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForExternalCSSResources(
1266 mozilla::StyleSheet
* aExternalSheet
, ReferrerPolicyEnum aPolicy
) {
1267 MOZ_ASSERT(aExternalSheet
&& !aExternalSheet
->IsInline());
1268 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1271 // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css
1272 // Use empty policy at the beginning and update it later from Referrer-Policy
1274 referrerInfo
= new ReferrerInfo(aExternalSheet
->GetSheetURI(), aPolicy
);
1275 return referrerInfo
.forget();
1279 already_AddRefed
<nsIReferrerInfo
>
1280 ReferrerInfo::CreateForInternalCSSAndSVGResources(Document
* aDocument
) {
1281 MOZ_ASSERT(aDocument
);
1282 return do_AddRef(new ReferrerInfo(aDocument
->GetDocumentURI(),
1283 aDocument
->GetReferrerPolicy()));
1286 nsresult
ReferrerInfo::ComputeReferrer(nsIHttpChannel
* aChannel
) {
1287 NS_ENSURE_ARG(aChannel
);
1288 MOZ_ASSERT(NS_IsMainThread());
1290 // If the referrerInfo is passed around when redirect, just use the last
1291 // computedReferrer to recompute
1292 nsCOMPtr
<nsIURI
> referrer
;
1293 nsresult rv
= NS_OK
;
1294 mOverridePolicyByDefault
= false;
1296 if (mComputedReferrer
.isSome()) {
1297 if (mComputedReferrer
.value().IsEmpty()) {
1301 rv
= NS_NewURI(getter_AddRefs(referrer
), mComputedReferrer
.value());
1302 if (NS_WARN_IF(NS_FAILED(rv
))) {
1307 mComputedReferrer
.reset();
1308 // Emplace mComputedReferrer with an empty string, which means we have
1309 // computed the referrer and the result referrer value is empty (not send
1310 // referrer). So any early return later than this line will use that empty
1312 mComputedReferrer
.emplace(""_ns
);
1314 if (!mSendReferrer
|| !mOriginalReferrer
||
1315 mPolicy
== ReferrerPolicy::No_referrer
) {
1319 if (mPolicy
== ReferrerPolicy::_empty
||
1320 ShouldIgnoreLessRestrictedPolicies(aChannel
, mOriginalPolicy
)) {
1321 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1322 OriginAttributes attrs
= loadInfo
->GetOriginAttributes();
1323 bool isPrivate
= attrs
.mPrivateBrowsingId
> 0;
1325 nsCOMPtr
<nsIURI
> uri
;
1326 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1327 if (NS_WARN_IF(NS_FAILED(rv
))) {
1331 mPolicy
= GetDefaultReferrerPolicy(aChannel
, uri
, isPrivate
);
1332 mOverridePolicyByDefault
= true;
1335 // This is for the case where the ETP toggle is off. In this case, we need to
1336 // reset the referrer and the policy if the original policy is different from
1337 // the current policy in order to recompute the referrer policy with the
1339 if (!mOverridePolicyByDefault
&& mOriginalPolicy
!= ReferrerPolicy::_empty
&&
1340 mPolicy
!= mOriginalPolicy
) {
1342 mPolicy
= mOriginalPolicy
;
1345 if (mPolicy
== ReferrerPolicy::No_referrer
) {
1349 bool isUserReferrerSendingAllowed
= false;
1350 rv
= HandleUserReferrerSendingPolicy(aChannel
, isUserReferrerSendingAllowed
);
1351 if (NS_WARN_IF(NS_FAILED(rv
))) {
1355 if (!isUserReferrerSendingAllowed
) {
1359 // Enforce Referrer allowlist, only http, https scheme are allowed
1360 if (!IsReferrerSchemeAllowed(mOriginalReferrer
)) {
1364 nsCOMPtr
<nsIURI
> uri
;
1365 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1366 if (NS_WARN_IF(NS_FAILED(rv
))) {
1370 bool isSecureToInsecureAllowed
= false;
1371 rv
= HandleSecureToInsecureReferral(mOriginalReferrer
, uri
, mPolicy
,
1372 isSecureToInsecureAllowed
);
1373 if (NS_WARN_IF(NS_FAILED(rv
))) {
1377 if (!isSecureToInsecureAllowed
) {
1381 // Strip away any fragment per RFC 2616 section 14.36
1382 // and Referrer Policy section 6.3.5.
1384 rv
= NS_GetURIWithoutRef(mOriginalReferrer
, getter_AddRefs(referrer
));
1385 if (NS_WARN_IF(NS_FAILED(rv
))) {
1390 bool isUserXOriginAllowed
= false;
1391 rv
= HandleUserXOriginSendingPolicy(uri
, referrer
, isUserXOriginAllowed
);
1392 if (NS_WARN_IF(NS_FAILED(rv
))) {
1396 if (!isUserXOriginAllowed
) {
1400 // Handle user pref network.http.referer.spoofSource, send spoofed referrer if
1402 if (StaticPrefs::network_http_referer_spoofSource()) {
1403 nsCOMPtr
<nsIURI
> userSpoofReferrer
;
1404 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(userSpoofReferrer
));
1405 if (NS_WARN_IF(NS_FAILED(rv
))) {
1408 referrer
= userSpoofReferrer
;
1411 // strip away any userpass; we don't want to be giving out passwords ;-)
1412 // This is required by Referrer Policy stripping algorithm.
1413 nsCOMPtr
<nsIURI
> exposableURI
= nsIOService::CreateExposableURI(referrer
);
1414 referrer
= exposableURI
;
1416 // Don't send referrer when the request is cross-origin and policy is
1418 if (mPolicy
== ReferrerPolicy::Same_origin
&&
1419 IsReferrerCrossOrigin(aChannel
, referrer
)) {
1423 TrimmingPolicy trimmingPolicy
= ComputeTrimmingPolicy(aChannel
, referrer
);
1425 nsAutoCString trimmedReferrer
;
1426 // We first trim the referrer according to the policy by calling
1427 // 'TrimReferrerWithPolicy' and right after we have to call
1428 // 'LimitReferrerLength' (using the same arguments) because the trimmed
1429 // referrer might exceed the allowed max referrer length.
1430 rv
= TrimReferrerWithPolicy(referrer
, trimmingPolicy
, trimmedReferrer
);
1431 if (NS_WARN_IF(NS_FAILED(rv
))) {
1435 rv
= LimitReferrerLength(aChannel
, referrer
, trimmingPolicy
, trimmedReferrer
);
1436 if (NS_WARN_IF(NS_FAILED(rv
))) {
1440 // finally, remember the referrer spec.
1441 mComputedReferrer
.reset();
1442 mComputedReferrer
.emplace(trimmedReferrer
);
1447 /* ===== nsISerializable implementation ====== */
1449 nsresult
ReferrerInfo::ReadTailDataBeforeGecko100(
1450 const uint32_t& aData
, nsIObjectInputStream
* aInputStream
) {
1451 MOZ_ASSERT(aInputStream
);
1453 nsCOMPtr
<nsIInputStream
> reader
;
1454 nsCOMPtr
<nsIOutputStream
> writer
;
1456 // We need to create a new pipe in order to read the aData and the rest of
1457 // the input stream together in the old format. This would also help us with
1458 // handling big endian correctly.
1459 NS_NewPipe(getter_AddRefs(reader
), getter_AddRefs(writer
));
1461 nsCOMPtr
<nsIBinaryOutputStream
> binaryPipeWriter
=
1462 NS_NewObjectOutputStream(writer
);
1464 // Write back the aData so that we can read bytes from it and handle big
1465 // endian correctly.
1466 nsresult rv
= binaryPipeWriter
->Write32(aData
);
1467 if (NS_WARN_IF(NS_FAILED(rv
))) {
1471 nsCOMPtr
<nsIBinaryInputStream
> binaryPipeReader
=
1472 NS_NewObjectInputStream(reader
);
1474 rv
= binaryPipeReader
->ReadBoolean(&mSendReferrer
);
1475 if (NS_WARN_IF(NS_FAILED(rv
))) {
1480 rv
= binaryPipeReader
->ReadBoolean(&isComputed
);
1481 if (NS_WARN_IF(NS_FAILED(rv
))) {
1485 // We need to handle the following string if isComputed is true.
1487 // Comsume the following 2 bytes from the input stream. They are the half
1488 // part of the length prefix of the following string.
1490 rv
= aInputStream
->Read16(&data
);
1491 if (NS_WARN_IF(NS_FAILED(rv
))) {
1495 // Write the bytes to the pipe so that we can read the length of the string.
1496 rv
= binaryPipeWriter
->Write16(data
);
1497 if (NS_WARN_IF(NS_FAILED(rv
))) {
1502 rv
= binaryPipeReader
->Read32(&length
);
1503 if (NS_WARN_IF(NS_FAILED(rv
))) {
1507 // Consume the string body from the input stream.
1508 nsAutoCString computedReferrer
;
1509 rv
= NS_ConsumeStream(aInputStream
, length
, computedReferrer
);
1510 if (NS_WARN_IF(NS_FAILED(rv
))) {
1513 mComputedReferrer
.emplace(computedReferrer
);
1515 // Read the remaining two bytes and write to the pipe.
1517 rv
= aInputStream
->Read16(&remain
);
1518 if (NS_WARN_IF(NS_FAILED(rv
))) {
1522 rv
= binaryPipeWriter
->Write16(remain
);
1523 if (NS_WARN_IF(NS_FAILED(rv
))) {
1528 rv
= binaryPipeReader
->ReadBoolean(&mInitialized
);
1529 if (NS_WARN_IF(NS_FAILED(rv
))) {
1533 rv
= binaryPipeReader
->ReadBoolean(&mOverridePolicyByDefault
);
1534 if (NS_WARN_IF(NS_FAILED(rv
))) {
1542 ReferrerInfo::Read(nsIObjectInputStream
* aStream
) {
1544 nsresult rv
= aStream
->ReadBoolean(&nonNull
);
1545 if (NS_WARN_IF(NS_FAILED(rv
))) {
1551 nsresult rv
= aStream
->ReadCString(spec
);
1552 if (NS_WARN_IF(NS_FAILED(rv
))) {
1556 rv
= NS_NewURI(getter_AddRefs(mOriginalReferrer
), spec
);
1557 if (NS_WARN_IF(NS_FAILED(rv
))) {
1561 mOriginalReferrer
= nullptr;
1564 // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store
1565 // to disk using the order of ReferrerPolicyIDL, so we convert to
1566 // ReferrerPolicyIDL to make it be compatible to the old format.
1568 rv
= aStream
->Read32(&policy
);
1569 if (NS_WARN_IF(NS_FAILED(rv
))) {
1572 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1573 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(policy
));
1575 uint32_t originalPolicy
;
1576 rv
= aStream
->Read32(&originalPolicy
);
1577 if (NS_WARN_IF(NS_FAILED(rv
))) {
1581 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more
1584 // We need to differentiate the old format and the new format here in order
1585 // to be able to read both formats. The check here helps us with verifying
1586 // which format it is.
1587 if (MOZ_UNLIKELY(originalPolicy
> 0xFF)) {
1588 mOriginalPolicy
= mPolicy
;
1590 return ReadTailDataBeforeGecko100(originalPolicy
, aStream
);
1593 mOriginalPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1594 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(originalPolicy
));
1596 rv
= aStream
->ReadBoolean(&mSendReferrer
);
1597 if (NS_WARN_IF(NS_FAILED(rv
))) {
1602 rv
= aStream
->ReadBoolean(&isComputed
);
1603 if (NS_WARN_IF(NS_FAILED(rv
))) {
1608 nsAutoCString computedReferrer
;
1609 rv
= aStream
->ReadCString(computedReferrer
);
1610 if (NS_WARN_IF(NS_FAILED(rv
))) {
1613 mComputedReferrer
.emplace(computedReferrer
);
1616 rv
= aStream
->ReadBoolean(&mInitialized
);
1617 if (NS_WARN_IF(NS_FAILED(rv
))) {
1621 rv
= aStream
->ReadBoolean(&mOverridePolicyByDefault
);
1622 if (NS_WARN_IF(NS_FAILED(rv
))) {
1630 ReferrerInfo::Write(nsIObjectOutputStream
* aStream
) {
1631 bool nonNull
= (mOriginalReferrer
!= nullptr);
1632 nsresult rv
= aStream
->WriteBoolean(nonNull
);
1633 if (NS_WARN_IF(NS_FAILED(rv
))) {
1639 nsresult rv
= mOriginalReferrer
->GetSpec(spec
);
1640 if (NS_WARN_IF(NS_FAILED(rv
))) {
1644 rv
= aStream
->WriteStringZ(spec
.get());
1645 if (NS_WARN_IF(NS_FAILED(rv
))) {
1650 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy
));
1651 if (NS_WARN_IF(NS_FAILED(rv
))) {
1655 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy
));
1656 if (NS_WARN_IF(NS_FAILED(rv
))) {
1660 rv
= aStream
->WriteBoolean(mSendReferrer
);
1661 if (NS_WARN_IF(NS_FAILED(rv
))) {
1665 bool isComputed
= mComputedReferrer
.isSome();
1666 rv
= aStream
->WriteBoolean(isComputed
);
1667 if (NS_WARN_IF(NS_FAILED(rv
))) {
1672 rv
= aStream
->WriteStringZ(mComputedReferrer
.value().get());
1673 if (NS_WARN_IF(NS_FAILED(rv
))) {
1678 rv
= aStream
->WriteBoolean(mInitialized
);
1679 if (NS_WARN_IF(NS_FAILED(rv
))) {
1683 rv
= aStream
->WriteBoolean(mOverridePolicyByDefault
);
1684 if (NS_WARN_IF(NS_FAILED(rv
))) {
1690 void ReferrerInfo::RecordTelemetry(nsIHttpChannel
* aChannel
) {
1692 MOZ_ASSERT(!mTelemetryRecorded
);
1693 mTelemetryRecorded
= true;
1696 // The telemetry probe has 18 buckets. The first 9 buckets are for same-site
1697 // requests and the rest 9 buckets are for cross-site requests.
1698 uint32_t telemetryOffset
=
1699 IsCrossSiteRequest(aChannel
)
1701 MaxContiguousEnumValue
<dom::ReferrerPolicy
>::value
) +
1705 Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT
,
1706 static_cast<uint32_t>(mPolicy
) + telemetryOffset
);
1709 } // namespace mozilla::dom