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 "nsContentUtils.h"
20 #include "nsCharSeparatedTokenizer.h"
21 #include "nsScriptSecurityManager.h"
22 #include "nsStreamUtils.h"
23 #include "ReferrerInfo.h"
25 #include "mozilla/BasePrincipal.h"
26 #include "mozilla/ContentBlockingAllowList.h"
27 #include "mozilla/net/CookieJarSettings.h"
28 #include "mozilla/net/HttpBaseChannel.h"
29 #include "mozilla/dom/Document.h"
30 #include "mozilla/dom/Element.h"
31 #include "mozilla/dom/RequestBinding.h"
32 #include "mozilla/StaticPrefs_network.h"
33 #include "mozilla/StorageAccess.h"
34 #include "mozilla/StyleSheet.h"
35 #include "mozilla/Telemetry.h"
36 #include "nsIWebProgressListener.h"
38 static mozilla::LazyLogModule
gReferrerInfoLog("ReferrerInfo");
39 #define LOG(msg) MOZ_LOG(gReferrerInfoLog, mozilla::LogLevel::Debug, msg)
40 #define LOG_ENABLED() MOZ_LOG_TEST(gReferrerInfoLog, mozilla::LogLevel::Debug)
42 using namespace mozilla::net
;
44 namespace mozilla::dom
{
46 // Implementation of ClassInfo is required to serialize/deserialize
47 NS_IMPL_CLASSINFO(ReferrerInfo
, nullptr, nsIClassInfo::THREADSAFE
,
50 NS_IMPL_ISUPPORTS_CI(ReferrerInfo
, nsIReferrerInfo
, nsISerializable
)
52 #define MAX_REFERRER_SENDING_POLICY 2
53 #define MAX_CROSS_ORIGIN_SENDING_POLICY 2
54 #define MAX_TRIMMING_POLICY 2
56 #define MIN_REFERRER_SENDING_POLICY 0
57 #define MIN_CROSS_ORIGIN_SENDING_POLICY 0
58 #define MIN_TRIMMING_POLICY 0
61 * Default referrer policy to use
63 enum DefaultReferrerPolicy
: uint32_t {
64 eDefaultPolicyNoReferrer
= 0,
65 eDefaultPolicySameOrgin
= 1,
66 eDefaultPolicyStrictWhenXorigin
= 2,
67 eDefaultPolicyNoReferrerWhenDownGrade
= 3,
70 static uint32_t GetDefaultFirstPartyReferrerPolicyPref(bool aPrivateBrowsing
) {
71 return aPrivateBrowsing
72 ? StaticPrefs::network_http_referer_defaultPolicy_pbmode()
73 : StaticPrefs::network_http_referer_defaultPolicy();
76 static uint32_t GetDefaultThirdPartyReferrerPolicyPref(bool aPrivateBrowsing
) {
77 return aPrivateBrowsing
78 ? StaticPrefs::network_http_referer_defaultPolicy_trackers_pbmode()
79 : StaticPrefs::network_http_referer_defaultPolicy_trackers();
82 static ReferrerPolicy
DefaultReferrerPolicyToReferrerPolicy(
83 uint32_t aDefaultToUse
) {
84 switch (aDefaultToUse
) {
85 case DefaultReferrerPolicy::eDefaultPolicyNoReferrer
:
86 return ReferrerPolicy::No_referrer
;
87 case DefaultReferrerPolicy::eDefaultPolicySameOrgin
:
88 return ReferrerPolicy::Same_origin
;
89 case DefaultReferrerPolicy::eDefaultPolicyStrictWhenXorigin
:
90 return ReferrerPolicy::Strict_origin_when_cross_origin
;
93 return ReferrerPolicy::No_referrer_when_downgrade
;
96 struct LegacyReferrerPolicyTokenMap
{
98 ReferrerPolicy mPolicy
;
102 * Parse ReferrerPolicy from token.
103 * The supported tokens are defined in ReferrerPolicy.webidl.
104 * The legacy tokens are "never", "default", "always" and
105 * "origin-when-crossorigin". The legacy tokens are only supported in meta
108 * @param aContent content string to be transformed into
109 * ReferrerPolicyEnum, e.g. "origin".
111 ReferrerPolicy
ReferrerPolicyFromToken(const nsAString
& aContent
,
112 bool allowedLegacyToken
) {
113 nsString
lowerContent(aContent
);
114 ToLowerCase(lowerContent
);
116 if (allowedLegacyToken
) {
117 static const LegacyReferrerPolicyTokenMap sLegacyReferrerPolicyToken
[] = {
118 {"never", ReferrerPolicy::No_referrer
},
119 {"default", ReferrerPolicy::No_referrer_when_downgrade
},
120 {"always", ReferrerPolicy::Unsafe_url
},
121 {"origin-when-crossorigin", ReferrerPolicy::Origin_when_cross_origin
},
124 uint8_t numStr
= (sizeof(sLegacyReferrerPolicyToken
) /
125 sizeof(sLegacyReferrerPolicyToken
[0]));
126 for (uint8_t i
= 0; i
< numStr
; i
++) {
127 if (lowerContent
.EqualsASCII(sLegacyReferrerPolicyToken
[i
].mToken
)) {
128 return sLegacyReferrerPolicyToken
[i
].mPolicy
;
133 // Return no referrer policy (empty string) if it's not a valid enum value.
134 return StringToEnum
<ReferrerPolicy
>(lowerContent
)
135 .valueOr(ReferrerPolicy::_empty
);
139 ReferrerPolicy
ReferrerInfo::ReferrerPolicyFromMetaString(
140 const nsAString
& aContent
) {
141 // This is implemented as described in
142 // https://html.spec.whatwg.org/multipage/semantics.html#meta-referrer
143 // Meta referrer accepts both supported tokens in ReferrerPolicy.webidl and
145 return ReferrerPolicyFromToken(aContent
, true);
149 ReferrerPolicy
ReferrerInfo::ReferrerPolicyAttributeFromString(
150 const nsAString
& aContent
) {
151 // This is implemented as described in
152 // https://html.spec.whatwg.org/multipage/infrastructure.html#referrer-policy-attribute
153 // referrerpolicy attribute only accepts supported tokens in
154 // ReferrerPolicy.webidl
155 return ReferrerPolicyFromToken(aContent
, false);
159 ReferrerPolicy
ReferrerInfo::ReferrerPolicyFromHeaderString(
160 const nsAString
& aContent
) {
161 // Multiple headers could be concatenated into one comma-separated
162 // list of policies. Need to tokenize the multiple headers.
163 ReferrerPolicyEnum referrerPolicy
= ReferrerPolicy::_empty
;
164 for (const auto& token
: nsCharSeparatedTokenizer(aContent
, ',').ToRange()) {
165 if (token
.IsEmpty()) {
169 // Referrer-Policy header only accepts supported tokens in
170 // ReferrerPolicy.webidl
171 ReferrerPolicyEnum policy
= ReferrerPolicyFromToken(token
, false);
172 // If there are multiple policies available, the last valid policy should be
174 // https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
175 if (policy
!= ReferrerPolicy::_empty
) {
176 referrerPolicy
= policy
;
179 return referrerPolicy
;
183 uint32_t ReferrerInfo::GetUserReferrerSendingPolicy() {
184 return std::clamp
<uint32_t>(
185 StaticPrefs::network_http_sendRefererHeader_DoNotUseDirectly(),
186 MIN_REFERRER_SENDING_POLICY
, MAX_REFERRER_SENDING_POLICY
);
190 uint32_t ReferrerInfo::GetUserXOriginSendingPolicy() {
191 return std::clamp
<uint32_t>(
192 StaticPrefs::network_http_referer_XOriginPolicy_DoNotUseDirectly(),
193 MIN_CROSS_ORIGIN_SENDING_POLICY
, MAX_CROSS_ORIGIN_SENDING_POLICY
);
197 uint32_t ReferrerInfo::GetUserTrimmingPolicy() {
198 return std::clamp
<uint32_t>(
199 StaticPrefs::network_http_referer_trimmingPolicy_DoNotUseDirectly(),
200 MIN_TRIMMING_POLICY
, MAX_TRIMMING_POLICY
);
204 uint32_t ReferrerInfo::GetUserXOriginTrimmingPolicy() {
205 return std::clamp
<uint32_t>(
207 network_http_referer_XOriginTrimmingPolicy_DoNotUseDirectly(),
208 MIN_TRIMMING_POLICY
, MAX_TRIMMING_POLICY
);
212 ReferrerPolicy
ReferrerInfo::GetDefaultReferrerPolicy(nsIHttpChannel
* aChannel
,
214 bool aPrivateBrowsing
) {
215 bool thirdPartyTrackerIsolated
= false;
216 if (aChannel
&& aURI
) {
217 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
218 nsCOMPtr
<nsICookieJarSettings
> cjs
;
219 Unused
<< loadInfo
->GetCookieJarSettings(getter_AddRefs(cjs
));
221 bool shouldResistFingerprinting
=
222 nsContentUtils::ShouldResistFingerprinting(
223 aChannel
, RFPTarget::IsAlwaysEnabledForPrecompute
);
224 cjs
= aPrivateBrowsing
225 ? net::CookieJarSettings::Create(CookieJarSettings::ePrivate
,
226 shouldResistFingerprinting
)
227 : net::CookieJarSettings::Create(CookieJarSettings::eRegular
,
228 shouldResistFingerprinting
);
231 // We only check if the channel is isolated if it's in the parent process
232 // with the rejection of third party contexts is enabled. We don't need to
233 // check this in content processes since the tracking state of the channel
234 // is unknown here and the referrer policy would be updated when the channel
235 // starts connecting in the parent process.
236 if (XRE_IsParentProcess() && cjs
->GetRejectThirdPartyContexts()) {
237 uint32_t rejectedReason
= 0;
238 thirdPartyTrackerIsolated
=
239 !ShouldAllowAccessFor(aChannel
, aURI
, &rejectedReason
) &&
241 static_cast<uint32_t>(
242 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
);
243 // Here we intentionally do not notify about the rejection reason, if any
244 // in order to avoid this check to have any visible side-effects (e.g. a
245 // web console report.)
249 // Select the appropriate pref starting with
250 // "network.http.referer.defaultPolicy" to use based on private-browsing
251 // ("pbmode") AND third-party trackers ("trackers").
252 return DefaultReferrerPolicyToReferrerPolicy(
253 thirdPartyTrackerIsolated
254 ? GetDefaultThirdPartyReferrerPolicyPref(aPrivateBrowsing
)
255 : GetDefaultFirstPartyReferrerPolicyPref(aPrivateBrowsing
));
259 bool ReferrerInfo::IsReferrerSchemeAllowed(nsIURI
* aReferrer
) {
260 NS_ENSURE_TRUE(aReferrer
, false);
262 nsAutoCString scheme
;
263 nsresult rv
= aReferrer
->GetScheme(scheme
);
264 if (NS_WARN_IF(NS_FAILED(rv
))) {
268 return scheme
.EqualsIgnoreCase("https") || scheme
.EqualsIgnoreCase("http");
272 bool ReferrerInfo::ShouldResponseInheritReferrerInfo(nsIChannel
* aChannel
) {
277 nsCOMPtr
<nsIURI
> channelURI
;
278 nsresult rv
= aChannel
->GetURI(getter_AddRefs(channelURI
));
279 NS_ENSURE_SUCCESS(rv
, false);
281 bool isAbout
= channelURI
->SchemeIs("about");
286 nsAutoCString aboutSpec
;
287 rv
= channelURI
->GetSpec(aboutSpec
);
288 NS_ENSURE_SUCCESS(rv
, false);
290 return aboutSpec
.EqualsLiteral("about:srcdoc");
294 nsresult
ReferrerInfo::HandleSecureToInsecureReferral(
295 nsIURI
* aOriginalURI
, nsIURI
* aURI
, ReferrerPolicyEnum aPolicy
,
297 NS_ENSURE_ARG(aOriginalURI
);
302 bool referrerIsHttpsScheme
= aOriginalURI
->SchemeIs("https");
303 if (!referrerIsHttpsScheme
) {
308 // It's ok to send referrer for https-to-http scenarios if the referrer
309 // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
310 // in other referrer policies, https->http is not allowed...
311 bool uriIsHttpsScheme
= aURI
->SchemeIs("https");
312 if (aPolicy
!= ReferrerPolicy::Unsafe_url
&&
313 aPolicy
!= ReferrerPolicy::Origin_when_cross_origin
&&
314 aPolicy
!= ReferrerPolicy::Origin
&& !uriIsHttpsScheme
) {
322 nsresult
ReferrerInfo::HandleUserXOriginSendingPolicy(nsIURI
* aURI
,
324 bool& aAllowed
) const {
328 nsAutoCString uriHost
;
329 nsAutoCString referrerHost
;
331 nsresult rv
= aURI
->GetAsciiHost(uriHost
);
332 if (NS_WARN_IF(NS_FAILED(rv
))) {
336 rv
= aReferrer
->GetAsciiHost(referrerHost
);
337 if (NS_WARN_IF(NS_FAILED(rv
))) {
341 // Send an empty referrer if xorigin and leaving a .onion domain.
342 if (StaticPrefs::network_http_referer_hideOnionSource() &&
343 !uriHost
.Equals(referrerHost
) &&
344 StringEndsWith(referrerHost
, ".onion"_ns
)) {
348 switch (GetUserXOriginSendingPolicy()) {
349 // Check policy for sending referrer only when hosts match
350 case XOriginSendingPolicy::ePolicySendWhenSameHost
: {
351 if (!uriHost
.Equals(referrerHost
)) {
357 case XOriginSendingPolicy::ePolicySendWhenSameDomain
: {
358 nsCOMPtr
<nsIEffectiveTLDService
> eTLDService
=
359 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
);
361 // check policy for sending only when effective top level domain
362 // matches. this falls back on using host if eTLDService does not work
363 if (!uriHost
.Equals(referrerHost
)) {
369 nsAutoCString uriDomain
;
370 nsAutoCString referrerDomain
;
371 uint32_t extraDomains
= 0;
373 rv
= eTLDService
->GetBaseDomain(aURI
, extraDomains
, uriDomain
);
374 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
375 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
376 // uri is either an IP address, an alias such as 'localhost', an eTLD
377 // such as 'co.uk', or the empty string. Uses the normalized host in
379 rv
= aURI
->GetAsciiHost(uriDomain
);
382 if (NS_WARN_IF(NS_FAILED(rv
))) {
386 rv
= eTLDService
->GetBaseDomain(aReferrer
, extraDomains
, referrerDomain
);
387 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
388 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
389 // referrer is either an IP address, an alias such as 'localhost', an
390 // eTLD such as 'co.uk', or the empty string. Uses the normalized host
392 rv
= aReferrer
->GetAsciiHost(referrerDomain
);
395 if (NS_WARN_IF(NS_FAILED(rv
))) {
399 if (!uriDomain
.Equals(referrerDomain
)) {
413 // This roughly implements Step 3.1. of
414 // https://fetch.spec.whatwg.org/#append-a-request-origin-header
416 bool ReferrerInfo::ShouldSetNullOriginHeader(net::HttpBaseChannel
* aChannel
,
417 nsIURI
* aOriginURI
) {
418 MOZ_ASSERT(aChannel
);
419 MOZ_ASSERT(aOriginURI
);
421 // If request’s mode is not "cors", then switch on request’s referrer policy:
422 RequestMode requestMode
= RequestMode::No_cors
;
423 MOZ_ALWAYS_SUCCEEDS(aChannel
->GetRequestMode(&requestMode
));
424 if (requestMode
== RequestMode::Cors
) {
428 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
429 NS_ENSURE_SUCCESS(aChannel
->GetReferrerInfo(getter_AddRefs(referrerInfo
)),
436 enum ReferrerPolicy policy
= referrerInfo
->ReferrerPolicy();
437 if (policy
== ReferrerPolicy::No_referrer
) {
438 // Set serializedOrigin to `null`.
439 // Note: Returning true is the same as setting the serializedOrigin to null
444 // "no-referrer-when-downgrade":
446 // "strict-origin-when-cross-origin":
447 // If request’s origin is a tuple origin, its scheme is "https", and
448 // request’s current URL’s scheme is not "https", then set serializedOrigin
450 bool allowed
= false;
451 nsCOMPtr
<nsIURI
> uri
;
452 NS_ENSURE_SUCCESS(aChannel
->GetURI(getter_AddRefs(uri
)), false);
453 if (NS_SUCCEEDED(ReferrerInfo::HandleSecureToInsecureReferral(
454 aOriginURI
, uri
, policy
, allowed
)) &&
460 if (policy
== ReferrerPolicy::Same_origin
) {
461 // If request’s origin is not same origin with request’s current URL’s
462 // origin, then set serializedOrigin to `null`.
463 return ReferrerInfo::IsCrossOriginRequest(aChannel
);
471 nsresult
ReferrerInfo::HandleUserReferrerSendingPolicy(nsIHttpChannel
* aChannel
,
472 bool& aAllowed
) const {
474 uint32_t referrerSendingPolicy
;
476 nsresult rv
= aChannel
->GetLoadFlags(&loadFlags
);
477 if (NS_WARN_IF(NS_FAILED(rv
))) {
481 if (loadFlags
& nsIHttpChannel::LOAD_INITIAL_DOCUMENT_URI
) {
482 referrerSendingPolicy
= ReferrerSendingPolicy::ePolicySendWhenUserTrigger
;
484 referrerSendingPolicy
= ReferrerSendingPolicy::ePolicySendInlineContent
;
486 if (GetUserReferrerSendingPolicy() < referrerSendingPolicy
) {
495 bool ReferrerInfo::IsCrossOriginRequest(nsIHttpChannel
* aChannel
) {
496 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
498 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
499 LOG(("no triggering URI via loadInfo, assuming load is cross-origin"));
504 nsAutoCString triggeringURISpec
;
505 loadInfo
->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec
);
506 LOG(("triggeringURI=%s\n", triggeringURISpec
.get()));
509 nsCOMPtr
<nsIURI
> uri
;
510 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
511 if (NS_WARN_IF(NS_FAILED(rv
))) {
515 return !loadInfo
->TriggeringPrincipal()->IsSameOrigin(uri
);
519 bool ReferrerInfo::IsReferrerCrossOrigin(nsIHttpChannel
* aChannel
,
521 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
523 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
524 LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
528 nsCOMPtr
<nsIURI
> uri
;
529 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
530 if (NS_WARN_IF(NS_FAILED(rv
))) {
534 return !nsScriptSecurityManager::SecurityCompareURIs(uri
, aReferrer
);
538 bool ReferrerInfo::IsCrossSiteRequest(nsIHttpChannel
* aChannel
) {
539 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
541 if (!loadInfo
->TriggeringPrincipal()->GetIsContentPrincipal()) {
542 LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
547 nsAutoCString triggeringURISpec
;
548 loadInfo
->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec
);
549 LOG(("triggeringURI=%s\n", triggeringURISpec
.get()));
552 nsCOMPtr
<nsIURI
> uri
;
553 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
554 if (NS_WARN_IF(NS_FAILED(rv
))) {
558 bool isCrossSite
= true;
559 rv
= loadInfo
->TriggeringPrincipal()->IsThirdPartyURI(uri
, &isCrossSite
);
567 ReferrerInfo::TrimmingPolicy
ReferrerInfo::ComputeTrimmingPolicy(
568 nsIHttpChannel
* aChannel
, nsIURI
* aReferrer
) const {
569 uint32_t trimmingPolicy
= GetUserTrimmingPolicy();
572 case ReferrerPolicy::Origin
:
573 case ReferrerPolicy::Strict_origin
:
574 trimmingPolicy
= TrimmingPolicy::ePolicySchemeHostPort
;
577 case ReferrerPolicy::Origin_when_cross_origin
:
578 case ReferrerPolicy::Strict_origin_when_cross_origin
:
579 if (trimmingPolicy
!= TrimmingPolicy::ePolicySchemeHostPort
&&
580 IsReferrerCrossOrigin(aChannel
, aReferrer
)) {
581 // Ignore set trimmingPolicy if it is already the strictest
583 trimmingPolicy
= TrimmingPolicy::ePolicySchemeHostPort
;
587 // This function is called when a nonempty referrer value is allowed to
588 // send. For the next 3 policies: same-origin, no-referrer-when-downgrade,
589 // unsafe-url, without trimming we should have a full uri. And the trimming
590 // policy only depends on user prefs.
591 case ReferrerPolicy::Same_origin
:
592 case ReferrerPolicy::No_referrer_when_downgrade
:
593 case ReferrerPolicy::Unsafe_url
:
594 if (trimmingPolicy
!= TrimmingPolicy::ePolicySchemeHostPort
) {
595 // Ignore set trimmingPolicy if it is already the strictest
596 // policy. Apply the user cross-origin trimming policy if it's more
597 // restrictive than the general one.
598 if (GetUserXOriginTrimmingPolicy() != TrimmingPolicy::ePolicyFullURI
&&
599 IsCrossOriginRequest(aChannel
)) {
601 std::max(trimmingPolicy
, GetUserXOriginTrimmingPolicy());
606 case ReferrerPolicy::No_referrer
:
607 case ReferrerPolicy::_empty
:
609 MOZ_ASSERT_UNREACHABLE("Unexpected value");
613 return static_cast<TrimmingPolicy
>(trimmingPolicy
);
616 nsresult
ReferrerInfo::LimitReferrerLength(
617 nsIHttpChannel
* aChannel
, nsIURI
* aReferrer
, TrimmingPolicy aTrimmingPolicy
,
618 nsACString
& aInAndOutTrimmedReferrer
) const {
619 if (!StaticPrefs::network_http_referer_referrerLengthLimit()) {
623 if (aInAndOutTrimmedReferrer
.Length() <=
624 StaticPrefs::network_http_referer_referrerLengthLimit()) {
628 nsAutoString referrerLengthLimit
;
629 referrerLengthLimit
.AppendInt(
630 StaticPrefs::network_http_referer_referrerLengthLimit());
631 if (aTrimmingPolicy
== ePolicyFullURI
||
632 aTrimmingPolicy
== ePolicySchemeHostPortPath
) {
633 // If referrer header is over max Length, down to origin
634 nsresult rv
= GetOriginFromReferrerURI(aReferrer
, aInAndOutTrimmedReferrer
);
635 if (NS_WARN_IF(NS_FAILED(rv
))) {
639 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
640 // states that the trailing "/" does not need to get stripped. However,
641 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
643 aInAndOutTrimmedReferrer
.AppendLiteral("/");
644 if (aInAndOutTrimmedReferrer
.Length() <=
645 StaticPrefs::network_http_referer_referrerLengthLimit()) {
646 AutoTArray
<nsString
, 2> params
= {
647 referrerLengthLimit
, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer
)};
648 LogMessageToConsole(aChannel
, "ReferrerLengthOverLimitation", params
);
653 // If we end up here either the trimmingPolicy is equal to
654 // 'ePolicySchemeHostPort' or the 'origin' of any other policy is still over
655 // the length limit. If so, truncate the referrer entirely.
656 AutoTArray
<nsString
, 2> params
= {
657 referrerLengthLimit
, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer
)};
658 LogMessageToConsole(aChannel
, "ReferrerOriginLengthOverLimitation", params
);
659 aInAndOutTrimmedReferrer
.Truncate();
664 nsresult
ReferrerInfo::GetOriginFromReferrerURI(nsIURI
* aReferrer
,
665 nsACString
& aResult
) const {
666 MOZ_ASSERT(aReferrer
);
668 // We want the IDN-normalized PrePath. That's not something currently
669 // available and there doesn't yet seem to be justification for adding it to
670 // the interfaces, so just build it up from scheme+AsciiHostPort
671 nsAutoCString scheme
, asciiHostPort
;
672 nsresult rv
= aReferrer
->GetScheme(scheme
);
673 if (NS_WARN_IF(NS_FAILED(rv
))) {
678 aResult
.AppendLiteral("://");
679 // Note we explicitly cleared UserPass above, so do not need to build it.
680 rv
= aReferrer
->GetAsciiHostPort(asciiHostPort
);
681 if (NS_WARN_IF(NS_FAILED(rv
))) {
685 aResult
.Append(asciiHostPort
);
689 nsresult
ReferrerInfo::TrimReferrerWithPolicy(nsIURI
* aReferrer
,
690 TrimmingPolicy aTrimmingPolicy
,
691 nsACString
& aResult
) const {
692 MOZ_ASSERT(aReferrer
);
694 if (aTrimmingPolicy
== TrimmingPolicy::ePolicyFullURI
) {
695 return aReferrer
->GetAsciiSpec(aResult
);
698 nsresult rv
= GetOriginFromReferrerURI(aReferrer
, aResult
);
699 if (NS_WARN_IF(NS_FAILED(rv
))) {
703 if (aTrimmingPolicy
== TrimmingPolicy::ePolicySchemeHostPortPath
) {
704 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aReferrer
));
707 rv
= url
->GetFilePath(path
);
708 if (NS_WARN_IF(NS_FAILED(rv
))) {
712 aResult
.Append(path
);
717 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url
718 // states that the trailing "/" does not need to get stripped. However,
719 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to
721 aResult
.AppendLiteral("/");
725 bool ReferrerInfo::ShouldIgnoreLessRestrictedPolicies(
726 nsIHttpChannel
* aChannel
, const ReferrerPolicyEnum aPolicy
) const {
727 MOZ_ASSERT(aChannel
);
729 // We only care about the less restricted policies.
730 if (aPolicy
!= ReferrerPolicy::Unsafe_url
&&
731 aPolicy
!= ReferrerPolicy::No_referrer_when_downgrade
&&
732 aPolicy
!= ReferrerPolicy::Origin_when_cross_origin
) {
736 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
737 bool isPrivate
= NS_UsePrivateBrowsing(aChannel
);
739 // Return early if we don't want to ignore less restricted policies for the
741 if (loadInfo
->GetExternalContentPolicyType() ==
742 ExtContentPolicy::TYPE_DOCUMENT
) {
743 bool isEnabledForTopNavigation
=
746 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode_top_navigation()
748 network_http_referer_disallowCrossSiteRelaxingDefault_top_navigation();
749 if (!isEnabledForTopNavigation
) {
753 // We have to get the value of the contentBlockingAllowList earlier because
754 // the channel hasn't been opened yet here. Note that we only need to do
755 // this for first-party navigation. For third-party loads, the value is
756 // inherited from the parent.
757 if (XRE_IsParentProcess()) {
758 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
;
759 Unused
<< loadInfo
->GetCookieJarSettings(
760 getter_AddRefs(cookieJarSettings
));
762 net::CookieJarSettings::Cast(cookieJarSettings
)
763 ->UpdateIsOnContentBlockingAllowList(aChannel
);
767 // We don't ignore less restricted referrer policies if ETP is toggled off.
768 // This would affect iframe loads and top navigation. For iframes, it will
769 // stop ignoring if the first-party site toggled ETP off. For top navigation,
770 // it depends on the ETP toggle for the destination site.
771 if (ContentBlockingAllowList::Check(aChannel
)) {
775 bool isCrossSite
= IsCrossSiteRequest(aChannel
);
779 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode()
781 network_http_referer_disallowCrossSiteRelaxingDefault();
784 // Log the warning message to console to inform that we will ignore
785 // less restricted policies for cross-site requests in the future.
787 nsCOMPtr
<nsIURI
> uri
;
788 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
789 NS_ENSURE_SUCCESS(rv
, false);
791 AutoTArray
<nsString
, 1> params
= {
792 NS_ConvertUTF8toUTF16(uri
->GetSpecOrDefault())};
793 LogMessageToConsole(aChannel
, "ReferrerPolicyDisallowRelaxingWarning",
799 // Check if the channel is triggered by the system or the extension.
800 auto* triggerBasePrincipal
=
801 BasePrincipal::Cast(loadInfo
->TriggeringPrincipal());
802 if (triggerBasePrincipal
->IsSystemPrincipal() ||
803 triggerBasePrincipal
->AddonPolicy()) {
808 // Log the console message to say that the less restricted policy was
810 nsCOMPtr
<nsIURI
> uri
;
811 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
812 NS_ENSURE_SUCCESS(rv
, true);
814 AutoTArray
<nsString
, 2> params
= {
815 NS_ConvertUTF8toUTF16(GetEnumString(aPolicy
)),
816 NS_ConvertUTF8toUTF16(uri
->GetSpecOrDefault())};
817 LogMessageToConsole(aChannel
, "ReferrerPolicyDisallowRelaxingMessage",
824 void ReferrerInfo::LogMessageToConsole(
825 nsIHttpChannel
* aChannel
, const char* aMsg
,
826 const nsTArray
<nsString
>& aParams
) const {
827 MOZ_ASSERT(aChannel
);
829 nsCOMPtr
<nsIURI
> uri
;
830 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
831 if (NS_WARN_IF(NS_FAILED(rv
))) {
835 uint64_t windowID
= 0;
837 rv
= aChannel
->GetTopLevelContentWindowId(&windowID
);
838 if (NS_WARN_IF(NS_FAILED(rv
))) {
843 nsCOMPtr
<nsILoadGroup
> loadGroup
;
844 rv
= aChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
845 if (NS_WARN_IF(NS_FAILED(rv
))) {
850 windowID
= nsContentUtils::GetInnerWindowID(loadGroup
);
854 nsAutoString localizedMsg
;
855 rv
= nsContentUtils::FormatLocalizedString(
856 nsContentUtils::eSECURITY_PROPERTIES
, aMsg
, aParams
, localizedMsg
);
857 if (NS_WARN_IF(NS_FAILED(rv
))) {
861 rv
= nsContentUtils::ReportToConsoleByWindowID(
862 localizedMsg
, nsIScriptError::infoFlag
, "Security"_ns
, windowID
,
863 SourceLocation(std::move(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
, const bool aSendReferrer
)
953 InitWithDocument(&aDoc
);
954 mSendReferrer
= aSendReferrer
;
957 ReferrerInfo::ReferrerInfo(const Element
& aElement
) : ReferrerInfo() {
958 InitWithElement(&aElement
);
961 ReferrerInfo::ReferrerInfo(const Element
& aElement
,
962 ReferrerPolicyEnum aOverridePolicy
)
963 : ReferrerInfo(aElement
) {
964 // Override referrer policy if not empty
965 if (aOverridePolicy
!= ReferrerPolicyEnum::_empty
) {
966 mPolicy
= aOverridePolicy
;
967 mOriginalPolicy
= aOverridePolicy
;
971 ReferrerInfo::ReferrerInfo(nsIURI
* aOriginalReferrer
,
972 ReferrerPolicyEnum aPolicy
, bool aSendReferrer
,
973 const Maybe
<nsCString
>& aComputedReferrer
)
974 : mOriginalReferrer(aOriginalReferrer
),
976 mOriginalPolicy(aPolicy
),
977 mSendReferrer(aSendReferrer
),
979 mOverridePolicyByDefault(false),
980 mComputedReferrer(aComputedReferrer
) {}
982 ReferrerInfo::ReferrerInfo(const ReferrerInfo
& rhs
)
983 : mOriginalReferrer(rhs
.mOriginalReferrer
),
984 mPolicy(rhs
.mPolicy
),
985 mOriginalPolicy(rhs
.mOriginalPolicy
),
986 mSendReferrer(rhs
.mSendReferrer
),
987 mInitialized(rhs
.mInitialized
),
988 mOverridePolicyByDefault(rhs
.mOverridePolicyByDefault
),
989 mComputedReferrer(rhs
.mComputedReferrer
) {}
991 already_AddRefed
<ReferrerInfo
> ReferrerInfo::Clone() const {
992 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
993 return copy
.forget();
996 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewPolicy(
997 ReferrerPolicyEnum aPolicy
) const {
998 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
999 copy
->mPolicy
= aPolicy
;
1000 copy
->mOriginalPolicy
= aPolicy
;
1001 return copy
.forget();
1004 already_AddRefed
<ReferrerInfo
> ReferrerInfo::CloneWithNewOriginalReferrer(
1005 nsIURI
* aOriginalReferrer
) const {
1006 RefPtr
<ReferrerInfo
> copy(new ReferrerInfo(*this));
1007 copy
->mOriginalReferrer
= aOriginalReferrer
;
1008 return copy
.forget();
1012 ReferrerInfo::GetOriginalReferrer(nsIURI
** aOriginalReferrer
) {
1013 *aOriginalReferrer
= mOriginalReferrer
;
1014 NS_IF_ADDREF(*aOriginalReferrer
);
1019 ReferrerInfo::GetReferrerPolicy(
1020 JSContext
* aCx
, nsIReferrerInfo::ReferrerPolicyIDL
* aReferrerPolicy
) {
1021 *aReferrerPolicy
= ReferrerPolicyToReferrerPolicyIDL(mPolicy
);
1026 ReferrerInfo::GetReferrerPolicyString(nsACString
& aResult
) {
1027 aResult
.AssignASCII(GetEnumString(mPolicy
));
1031 ReferrerPolicy
ReferrerInfo::ReferrerPolicy() { return mPolicy
; }
1034 ReferrerInfo::GetSendReferrer(bool* aSendReferrer
) {
1035 *aSendReferrer
= mSendReferrer
;
1040 ReferrerInfo::Equals(nsIReferrerInfo
* aOther
, bool* aResult
) {
1041 NS_ENSURE_TRUE(aOther
, NS_ERROR_INVALID_ARG
);
1042 MOZ_ASSERT(mInitialized
);
1043 if (aOther
== this) {
1049 ReferrerInfo
* other
= static_cast<ReferrerInfo
*>(aOther
);
1050 MOZ_ASSERT(other
->mInitialized
);
1052 if (mPolicy
!= other
->mPolicy
|| mSendReferrer
!= other
->mSendReferrer
||
1053 mOverridePolicyByDefault
!= other
->mOverridePolicyByDefault
||
1054 mComputedReferrer
!= other
->mComputedReferrer
) {
1058 if (!mOriginalReferrer
!= !other
->mOriginalReferrer
) {
1059 // One or the other has mOriginalReferrer, but not both... not equal
1063 bool originalReferrerEquals
;
1064 if (mOriginalReferrer
&&
1065 (NS_FAILED(mOriginalReferrer
->Equals(other
->mOriginalReferrer
,
1066 &originalReferrerEquals
)) ||
1067 !originalReferrerEquals
)) {
1076 ReferrerInfo::GetComputedReferrerSpec(nsACString
& aComputedReferrerSpec
) {
1077 aComputedReferrerSpec
.Assign(
1078 mComputedReferrer
.isSome() ? mComputedReferrer
.value() : EmptyCString());
1082 already_AddRefed
<nsIURI
> ReferrerInfo::GetComputedReferrer() {
1083 if (!mComputedReferrer
.isSome() || mComputedReferrer
.value().IsEmpty()) {
1087 nsCOMPtr
<nsIURI
> result
;
1088 nsresult rv
= NS_NewURI(getter_AddRefs(result
), mComputedReferrer
.value());
1089 if (NS_FAILED(rv
)) {
1093 return result
.forget();
1096 HashNumber
ReferrerInfo::Hash() const {
1097 MOZ_ASSERT(mInitialized
);
1098 nsAutoCString originalReferrerSpec
;
1099 if (mOriginalReferrer
) {
1100 Unused
<< mOriginalReferrer
->GetSpec(originalReferrerSpec
);
1103 return mozilla::AddToHash(
1104 static_cast<uint32_t>(mPolicy
), mSendReferrer
, mOverridePolicyByDefault
,
1105 mozilla::HashString(originalReferrerSpec
),
1106 mozilla::HashString(mComputedReferrer
.isSome() ? mComputedReferrer
.value()
1111 ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy
,
1112 bool aSendReferrer
, nsIURI
* aOriginalReferrer
) {
1113 MOZ_ASSERT(!mInitialized
);
1115 return NS_ERROR_ALREADY_INITIALIZED
;
1118 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy
);
1119 mOriginalPolicy
= mPolicy
;
1120 mSendReferrer
= aSendReferrer
;
1121 mOriginalReferrer
= aOriginalReferrer
;
1122 mInitialized
= true;
1127 ReferrerInfo::InitWithDocument(const Document
* aDocument
) {
1128 MOZ_ASSERT(!mInitialized
);
1130 return NS_ERROR_ALREADY_INITIALIZED
;
1133 mPolicy
= aDocument
->GetReferrerPolicy();
1134 mOriginalPolicy
= mPolicy
;
1135 mSendReferrer
= true;
1136 mOriginalReferrer
= aDocument
->GetDocumentURIAsReferrer();
1137 mInitialized
= true;
1142 * Check whether the given node has referrerpolicy attribute and parse
1143 * referrer policy from the attribute.
1144 * Currently, referrerpolicy attribute is supported in a, area, img, iframe,
1145 * script, or link element.
1147 static ReferrerPolicy
ReferrerPolicyFromAttribute(const Element
& aElement
) {
1148 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1149 nsGkAtoms::script
, nsGkAtoms::iframe
,
1150 nsGkAtoms::link
, nsGkAtoms::img
)) {
1151 return ReferrerPolicy::_empty
;
1153 return aElement
.GetReferrerPolicyAsEnum();
1156 static bool HasRelNoReferrer(const Element
& aElement
) {
1157 // rel=noreferrer is only supported in <a>, <area>, and <form>
1158 if (!aElement
.IsAnyOfHTMLElements(nsGkAtoms::a
, nsGkAtoms::area
,
1160 !aElement
.IsSVGElement(nsGkAtoms::a
)) {
1165 aElement
.GetAttr(nsGkAtoms::rel
, rel
);
1166 nsWhitespaceTokenizerTemplate
<nsContentUtils::IsHTMLWhitespace
> tok(rel
);
1168 while (tok
.hasMoreTokens()) {
1169 const nsAString
& token
= tok
.nextToken();
1170 if (token
.LowerCaseEqualsLiteral("noreferrer")) {
1179 ReferrerInfo::InitWithElement(const Element
* aElement
) {
1180 MOZ_ASSERT(!mInitialized
);
1182 return NS_ERROR_ALREADY_INITIALIZED
;
1185 // Referrer policy from referrerpolicy attribute will have a higher priority
1186 // than referrer policy from <meta> tag and Referrer-Policy header.
1187 mPolicy
= ReferrerPolicyFromAttribute(*aElement
);
1188 if (mPolicy
== ReferrerPolicy::_empty
) {
1189 // Fallback to use document's referrer poicy if we don't have referrer
1190 // policy from attribute.
1191 mPolicy
= aElement
->OwnerDoc()->GetReferrerPolicy();
1194 mOriginalPolicy
= mPolicy
;
1195 mSendReferrer
= !HasRelNoReferrer(*aElement
);
1196 mOriginalReferrer
= aElement
->OwnerDoc()->GetDocumentURIAsReferrer();
1198 mInitialized
= true;
1203 already_AddRefed
<nsIReferrerInfo
>
1204 ReferrerInfo::CreateFromDocumentAndPolicyOverride(
1205 Document
* aDoc
, ReferrerPolicyEnum aPolicyOverride
) {
1207 ReferrerPolicyEnum policy
= aPolicyOverride
!= ReferrerPolicy::_empty
1209 : aDoc
->GetReferrerPolicy();
1210 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
=
1211 new ReferrerInfo(aDoc
->GetDocumentURIAsReferrer(), policy
);
1212 return referrerInfo
.forget();
1216 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForFetch(
1217 nsIPrincipal
* aPrincipal
, Document
* aDoc
) {
1218 MOZ_ASSERT(aPrincipal
);
1220 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1221 if (!aPrincipal
|| aPrincipal
->IsSystemPrincipal()) {
1222 referrerInfo
= new ReferrerInfo(nullptr);
1223 return referrerInfo
.forget();
1227 aPrincipal
->CreateReferrerInfo(ReferrerPolicy::_empty
,
1228 getter_AddRefs(referrerInfo
));
1229 return referrerInfo
.forget();
1232 // If it weren't for history.push/replaceState, we could just use the
1233 // principal's URI here. But since we want changes to the URI effected
1234 // by push/replaceState to be reflected in the XHR referrer, we have to
1237 // If the document's original URI (before any push/replaceStates) matches
1238 // our principal, then we use the document's current URI (after
1239 // push/replaceStates). Otherwise (if the document is, say, a data:
1240 // URI), we just use the principal's URI.
1241 nsCOMPtr
<nsIURI
> docCurURI
= aDoc
->GetDocumentURI();
1242 nsCOMPtr
<nsIURI
> docOrigURI
= aDoc
->GetOriginalURI();
1244 if (docCurURI
&& docOrigURI
) {
1246 aPrincipal
->EqualsURI(docOrigURI
, &equal
);
1248 referrerInfo
= new ReferrerInfo(docCurURI
, aDoc
->GetReferrerPolicy());
1249 return referrerInfo
.forget();
1252 aPrincipal
->CreateReferrerInfo(aDoc
->GetReferrerPolicy(),
1253 getter_AddRefs(referrerInfo
));
1254 return referrerInfo
.forget();
1258 already_AddRefed
<nsIReferrerInfo
> ReferrerInfo::CreateForExternalCSSResources(
1259 mozilla::StyleSheet
* aExternalSheet
, ReferrerPolicyEnum aPolicy
) {
1260 MOZ_ASSERT(aExternalSheet
&& !aExternalSheet
->IsInline());
1261 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
1264 // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css
1265 // Use empty policy at the beginning and update it later from Referrer-Policy
1267 referrerInfo
= new ReferrerInfo(aExternalSheet
->GetSheetURI(), aPolicy
);
1268 return referrerInfo
.forget();
1272 already_AddRefed
<nsIReferrerInfo
>
1273 ReferrerInfo::CreateForInternalCSSAndSVGResources(Document
* aDocument
) {
1274 MOZ_ASSERT(aDocument
);
1275 return do_AddRef(new ReferrerInfo(aDocument
->GetDocumentURI(),
1276 aDocument
->GetReferrerPolicy()));
1279 nsresult
ReferrerInfo::ComputeReferrer(nsIHttpChannel
* aChannel
) {
1280 NS_ENSURE_ARG(aChannel
);
1281 MOZ_ASSERT(NS_IsMainThread());
1283 // If the referrerInfo is passed around when redirect, just use the last
1284 // computedReferrer to recompute
1285 nsCOMPtr
<nsIURI
> referrer
;
1286 nsresult rv
= NS_OK
;
1287 mOverridePolicyByDefault
= false;
1289 if (mComputedReferrer
.isSome()) {
1290 if (mComputedReferrer
.value().IsEmpty()) {
1294 rv
= NS_NewURI(getter_AddRefs(referrer
), mComputedReferrer
.value());
1295 if (NS_WARN_IF(NS_FAILED(rv
))) {
1300 mComputedReferrer
.reset();
1301 // Emplace mComputedReferrer with an empty string, which means we have
1302 // computed the referrer and the result referrer value is empty (not send
1303 // referrer). So any early return later than this line will use that empty
1305 mComputedReferrer
.emplace(""_ns
);
1307 if (!mSendReferrer
|| !mOriginalReferrer
||
1308 mPolicy
== ReferrerPolicy::No_referrer
) {
1312 if (mPolicy
== ReferrerPolicy::_empty
||
1313 ShouldIgnoreLessRestrictedPolicies(aChannel
, mOriginalPolicy
)) {
1314 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1315 OriginAttributes attrs
= loadInfo
->GetOriginAttributes();
1316 bool isPrivate
= attrs
.IsPrivateBrowsing();
1318 nsCOMPtr
<nsIURI
> uri
;
1319 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1320 if (NS_WARN_IF(NS_FAILED(rv
))) {
1324 mPolicy
= GetDefaultReferrerPolicy(aChannel
, uri
, isPrivate
);
1325 mOverridePolicyByDefault
= true;
1328 // This is for the case where the ETP toggle is off. In this case, we need to
1329 // reset the referrer and the policy if the original policy is different from
1330 // the current policy in order to recompute the referrer policy with the
1332 if (!mOverridePolicyByDefault
&& mOriginalPolicy
!= ReferrerPolicy::_empty
&&
1333 mPolicy
!= mOriginalPolicy
) {
1335 mPolicy
= mOriginalPolicy
;
1338 if (mPolicy
== ReferrerPolicy::No_referrer
) {
1342 bool isUserReferrerSendingAllowed
= false;
1343 rv
= HandleUserReferrerSendingPolicy(aChannel
, isUserReferrerSendingAllowed
);
1344 if (NS_WARN_IF(NS_FAILED(rv
))) {
1348 if (!isUserReferrerSendingAllowed
) {
1352 // Enforce Referrer allowlist, only http, https scheme are allowed
1353 if (!IsReferrerSchemeAllowed(mOriginalReferrer
)) {
1357 nsCOMPtr
<nsIURI
> uri
;
1358 rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1359 if (NS_WARN_IF(NS_FAILED(rv
))) {
1363 bool isSecureToInsecureAllowed
= false;
1364 rv
= HandleSecureToInsecureReferral(mOriginalReferrer
, uri
, mPolicy
,
1365 isSecureToInsecureAllowed
);
1366 if (NS_WARN_IF(NS_FAILED(rv
))) {
1370 if (!isSecureToInsecureAllowed
) {
1374 // Strip away any fragment per RFC 2616 section 14.36
1375 // and Referrer Policy section 6.3.5.
1377 rv
= NS_GetURIWithoutRef(mOriginalReferrer
, getter_AddRefs(referrer
));
1378 if (NS_WARN_IF(NS_FAILED(rv
))) {
1383 bool isUserXOriginAllowed
= false;
1384 rv
= HandleUserXOriginSendingPolicy(uri
, referrer
, isUserXOriginAllowed
);
1385 if (NS_WARN_IF(NS_FAILED(rv
))) {
1389 if (!isUserXOriginAllowed
) {
1393 // Handle user pref network.http.referer.spoofSource, send spoofed referrer if
1395 if (StaticPrefs::network_http_referer_spoofSource()) {
1396 nsCOMPtr
<nsIURI
> userSpoofReferrer
;
1397 rv
= NS_GetURIWithoutRef(uri
, getter_AddRefs(userSpoofReferrer
));
1398 if (NS_WARN_IF(NS_FAILED(rv
))) {
1401 referrer
= userSpoofReferrer
;
1404 // strip away any userpass; we don't want to be giving out passwords ;-)
1405 // This is required by Referrer Policy stripping algorithm.
1406 nsCOMPtr
<nsIURI
> exposableURI
= nsIOService::CreateExposableURI(referrer
);
1407 referrer
= exposableURI
;
1409 // Don't send referrer when the request is cross-origin and policy is
1411 if (mPolicy
== ReferrerPolicy::Same_origin
&&
1412 IsReferrerCrossOrigin(aChannel
, referrer
)) {
1416 TrimmingPolicy trimmingPolicy
= ComputeTrimmingPolicy(aChannel
, referrer
);
1418 nsAutoCString trimmedReferrer
;
1419 // We first trim the referrer according to the policy by calling
1420 // 'TrimReferrerWithPolicy' and right after we have to call
1421 // 'LimitReferrerLength' (using the same arguments) because the trimmed
1422 // referrer might exceed the allowed max referrer length.
1423 rv
= TrimReferrerWithPolicy(referrer
, trimmingPolicy
, trimmedReferrer
);
1424 if (NS_WARN_IF(NS_FAILED(rv
))) {
1428 rv
= LimitReferrerLength(aChannel
, referrer
, trimmingPolicy
, trimmedReferrer
);
1429 if (NS_WARN_IF(NS_FAILED(rv
))) {
1433 // finally, remember the referrer spec.
1434 mComputedReferrer
.reset();
1435 mComputedReferrer
.emplace(trimmedReferrer
);
1440 /* ===== nsISerializable implementation ====== */
1442 nsresult
ReferrerInfo::ReadTailDataBeforeGecko100(
1443 const uint32_t& aData
, nsIObjectInputStream
* aInputStream
) {
1444 MOZ_ASSERT(aInputStream
);
1446 nsCOMPtr
<nsIInputStream
> reader
;
1447 nsCOMPtr
<nsIOutputStream
> writer
;
1449 // We need to create a new pipe in order to read the aData and the rest of
1450 // the input stream together in the old format. This would also help us with
1451 // handling big endian correctly.
1452 NS_NewPipe(getter_AddRefs(reader
), getter_AddRefs(writer
));
1454 nsCOMPtr
<nsIBinaryOutputStream
> binaryPipeWriter
=
1455 NS_NewObjectOutputStream(writer
);
1457 // Write back the aData so that we can read bytes from it and handle big
1458 // endian correctly.
1459 nsresult rv
= binaryPipeWriter
->Write32(aData
);
1460 if (NS_WARN_IF(NS_FAILED(rv
))) {
1464 nsCOMPtr
<nsIBinaryInputStream
> binaryPipeReader
=
1465 NS_NewObjectInputStream(reader
);
1467 rv
= binaryPipeReader
->ReadBoolean(&mSendReferrer
);
1468 if (NS_WARN_IF(NS_FAILED(rv
))) {
1473 rv
= binaryPipeReader
->ReadBoolean(&isComputed
);
1474 if (NS_WARN_IF(NS_FAILED(rv
))) {
1478 // We need to handle the following string if isComputed is true.
1480 // Comsume the following 2 bytes from the input stream. They are the half
1481 // part of the length prefix of the following string.
1483 rv
= aInputStream
->Read16(&data
);
1484 if (NS_WARN_IF(NS_FAILED(rv
))) {
1488 // Write the bytes to the pipe so that we can read the length of the string.
1489 rv
= binaryPipeWriter
->Write16(data
);
1490 if (NS_WARN_IF(NS_FAILED(rv
))) {
1495 rv
= binaryPipeReader
->Read32(&length
);
1496 if (NS_WARN_IF(NS_FAILED(rv
))) {
1500 // Consume the string body from the input stream.
1501 nsAutoCString computedReferrer
;
1502 rv
= NS_ConsumeStream(aInputStream
, length
, computedReferrer
);
1503 if (NS_WARN_IF(NS_FAILED(rv
))) {
1506 mComputedReferrer
.emplace(computedReferrer
);
1508 // Read the remaining two bytes and write to the pipe.
1510 rv
= aInputStream
->Read16(&remain
);
1511 if (NS_WARN_IF(NS_FAILED(rv
))) {
1515 rv
= binaryPipeWriter
->Write16(remain
);
1516 if (NS_WARN_IF(NS_FAILED(rv
))) {
1521 rv
= binaryPipeReader
->ReadBoolean(&mInitialized
);
1522 if (NS_WARN_IF(NS_FAILED(rv
))) {
1526 rv
= binaryPipeReader
->ReadBoolean(&mOverridePolicyByDefault
);
1527 if (NS_WARN_IF(NS_FAILED(rv
))) {
1535 ReferrerInfo::Read(nsIObjectInputStream
* aStream
) {
1537 nsresult rv
= aStream
->ReadBoolean(&nonNull
);
1538 if (NS_WARN_IF(NS_FAILED(rv
))) {
1544 nsresult rv
= aStream
->ReadCString(spec
);
1545 if (NS_WARN_IF(NS_FAILED(rv
))) {
1549 rv
= NS_NewURI(getter_AddRefs(mOriginalReferrer
), spec
);
1550 if (NS_WARN_IF(NS_FAILED(rv
))) {
1554 mOriginalReferrer
= nullptr;
1557 // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store
1558 // to disk using the order of ReferrerPolicyIDL, so we convert to
1559 // ReferrerPolicyIDL to make it be compatible to the old format.
1561 rv
= aStream
->Read32(&policy
);
1562 if (NS_WARN_IF(NS_FAILED(rv
))) {
1565 mPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1566 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(policy
));
1568 uint32_t originalPolicy
;
1569 rv
= aStream
->Read32(&originalPolicy
);
1570 if (NS_WARN_IF(NS_FAILED(rv
))) {
1574 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more
1577 // We need to differentiate the old format and the new format here in order
1578 // to be able to read both formats. The check here helps us with verifying
1579 // which format it is.
1580 if (MOZ_UNLIKELY(originalPolicy
> 0xFF)) {
1581 mOriginalPolicy
= mPolicy
;
1583 return ReadTailDataBeforeGecko100(originalPolicy
, aStream
);
1586 mOriginalPolicy
= ReferrerPolicyIDLToReferrerPolicy(
1587 static_cast<nsIReferrerInfo::ReferrerPolicyIDL
>(originalPolicy
));
1589 rv
= aStream
->ReadBoolean(&mSendReferrer
);
1590 if (NS_WARN_IF(NS_FAILED(rv
))) {
1595 rv
= aStream
->ReadBoolean(&isComputed
);
1596 if (NS_WARN_IF(NS_FAILED(rv
))) {
1601 nsAutoCString computedReferrer
;
1602 rv
= aStream
->ReadCString(computedReferrer
);
1603 if (NS_WARN_IF(NS_FAILED(rv
))) {
1606 mComputedReferrer
.emplace(computedReferrer
);
1609 rv
= aStream
->ReadBoolean(&mInitialized
);
1610 if (NS_WARN_IF(NS_FAILED(rv
))) {
1614 rv
= aStream
->ReadBoolean(&mOverridePolicyByDefault
);
1615 if (NS_WARN_IF(NS_FAILED(rv
))) {
1623 ReferrerInfo::Write(nsIObjectOutputStream
* aStream
) {
1624 bool nonNull
= (mOriginalReferrer
!= nullptr);
1625 nsresult rv
= aStream
->WriteBoolean(nonNull
);
1626 if (NS_WARN_IF(NS_FAILED(rv
))) {
1632 nsresult rv
= mOriginalReferrer
->GetSpec(spec
);
1633 if (NS_WARN_IF(NS_FAILED(rv
))) {
1637 rv
= aStream
->WriteStringZ(spec
.get());
1638 if (NS_WARN_IF(NS_FAILED(rv
))) {
1643 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy
));
1644 if (NS_WARN_IF(NS_FAILED(rv
))) {
1648 rv
= aStream
->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy
));
1649 if (NS_WARN_IF(NS_FAILED(rv
))) {
1653 rv
= aStream
->WriteBoolean(mSendReferrer
);
1654 if (NS_WARN_IF(NS_FAILED(rv
))) {
1658 bool isComputed
= mComputedReferrer
.isSome();
1659 rv
= aStream
->WriteBoolean(isComputed
);
1660 if (NS_WARN_IF(NS_FAILED(rv
))) {
1665 rv
= aStream
->WriteStringZ(mComputedReferrer
.value().get());
1666 if (NS_WARN_IF(NS_FAILED(rv
))) {
1671 rv
= aStream
->WriteBoolean(mInitialized
);
1672 if (NS_WARN_IF(NS_FAILED(rv
))) {
1676 rv
= aStream
->WriteBoolean(mOverridePolicyByDefault
);
1677 if (NS_WARN_IF(NS_FAILED(rv
))) {
1683 void ReferrerInfo::RecordTelemetry(nsIHttpChannel
* aChannel
) {
1685 MOZ_ASSERT(!mTelemetryRecorded
);
1686 mTelemetryRecorded
= true;
1689 // The telemetry probe has 18 buckets. The first 9 buckets are for same-site
1690 // requests and the rest 9 buckets are for cross-site requests.
1691 uint32_t telemetryOffset
=
1692 IsCrossSiteRequest(aChannel
)
1694 MaxContiguousEnumValue
<dom::ReferrerPolicy
>::value
) +
1698 Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT
,
1699 static_cast<uint32_t>(mPolicy
) + telemetryOffset
);
1702 } // namespace mozilla::dom