Backed out changeset 8aaffdf63d09 (bug 1920575) for causing bc failures on browser_si...
[gecko.git] / dom / security / ReferrerInfo.cpp
blob76bd44b1b12b284fc7f1d199abc11fa94e91a2d5
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"
15 #include "nsIPipe.h"
16 #include "nsIURL.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,
49 REFERRERINFO_CID)
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 {
98 const char* mToken;
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
107 * referrer content
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);
139 // static
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
145 // legacy tokens.
146 return ReferrerPolicyFromToken(aContent, true);
149 // static
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);
159 // static
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()) {
167 continue;
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
174 // used.
175 // https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
176 if (policy != ReferrerPolicy::_empty) {
177 referrerPolicy = policy;
180 return referrerPolicy;
183 /* static */
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);
190 /* static */
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);
197 /* static */
198 uint32_t ReferrerInfo::GetUserTrimmingPolicy() {
199 return clamped<uint32_t>(
200 StaticPrefs::network_http_referer_trimmingPolicy_DoNotUseDirectly(),
201 MIN_TRIMMING_POLICY, MAX_TRIMMING_POLICY);
204 /* static */
205 uint32_t ReferrerInfo::GetUserXOriginTrimmingPolicy() {
206 return clamped<uint32_t>(
207 StaticPrefs::
208 network_http_referer_XOriginTrimmingPolicy_DoNotUseDirectly(),
209 MIN_TRIMMING_POLICY, MAX_TRIMMING_POLICY);
212 /* static */
213 ReferrerPolicy ReferrerInfo::GetDefaultReferrerPolicy(nsIHttpChannel* aChannel,
214 nsIURI* aURI,
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));
221 if (!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) &&
241 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));
259 /* static */
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))) {
266 return false;
269 return scheme.EqualsIgnoreCase("https") || scheme.EqualsIgnoreCase("http");
272 /* static */
273 bool ReferrerInfo::ShouldResponseInheritReferrerInfo(nsIChannel* aChannel) {
274 if (!aChannel) {
275 return false;
278 nsCOMPtr<nsIURI> channelURI;
279 nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
280 NS_ENSURE_SUCCESS(rv, false);
282 bool isAbout = channelURI->SchemeIs("about");
283 if (!isAbout) {
284 return false;
287 nsAutoCString aboutSpec;
288 rv = channelURI->GetSpec(aboutSpec);
289 NS_ENSURE_SUCCESS(rv, false);
291 return aboutSpec.EqualsLiteral("about:srcdoc");
294 /* static */
295 nsresult ReferrerInfo::HandleSecureToInsecureReferral(
296 nsIURI* aOriginalURI, nsIURI* aURI, ReferrerPolicyEnum aPolicy,
297 bool& aAllowed) {
298 NS_ENSURE_ARG(aOriginalURI);
299 NS_ENSURE_ARG(aURI);
301 aAllowed = false;
303 bool referrerIsHttpsScheme = aOriginalURI->SchemeIs("https");
304 if (!referrerIsHttpsScheme) {
305 aAllowed = true;
306 return NS_OK;
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) {
316 return NS_OK;
319 aAllowed = true;
320 return NS_OK;
323 nsresult ReferrerInfo::HandleUserXOriginSendingPolicy(nsIURI* aURI,
324 nsIURI* aReferrer,
325 bool& aAllowed) const {
326 NS_ENSURE_ARG(aURI);
327 aAllowed = false;
329 nsAutoCString uriHost;
330 nsAutoCString referrerHost;
332 nsresult rv = aURI->GetAsciiHost(uriHost);
333 if (NS_WARN_IF(NS_FAILED(rv))) {
334 return rv;
337 rv = aReferrer->GetAsciiHost(referrerHost);
338 if (NS_WARN_IF(NS_FAILED(rv))) {
339 return 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)) {
346 return NS_OK;
349 switch (GetUserXOriginSendingPolicy()) {
350 // Check policy for sending referrer only when hosts match
351 case XOriginSendingPolicy::ePolicySendWhenSameHost: {
352 if (!uriHost.Equals(referrerHost)) {
353 return NS_OK;
355 break;
358 case XOriginSendingPolicy::ePolicySendWhenSameDomain: {
359 nsCOMPtr<nsIEffectiveTLDService> eTLDService =
360 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
361 if (!eTLDService) {
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)) {
365 return NS_OK;
367 break;
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
379 // such cases.
380 rv = aURI->GetAsciiHost(uriDomain);
383 if (NS_WARN_IF(NS_FAILED(rv))) {
384 return 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
392 // in such cases.
393 rv = aReferrer->GetAsciiHost(referrerDomain);
396 if (NS_WARN_IF(NS_FAILED(rv))) {
397 return rv;
400 if (!uriDomain.Equals(referrerDomain)) {
401 return NS_OK;
403 break;
406 default:
407 break;
410 aAllowed = true;
411 return NS_OK;
414 // This roughly implements Step 3.1. of
415 // https://fetch.spec.whatwg.org/#append-a-request-origin-header
416 /* static */
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) {
426 return false;
429 nsCOMPtr<nsIReferrerInfo> referrerInfo;
430 NS_ENSURE_SUCCESS(aChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)),
431 false);
432 if (!referrerInfo) {
433 return false;
436 // "no-referrer":
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
441 // in this method.
442 return true;
445 // "no-referrer-when-downgrade":
446 // "strict-origin":
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
450 // to `null`.
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)) &&
456 !allowed) {
457 return true;
460 // "same-origin":
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);
467 // Otherwise:
468 // Do Nothing.
469 return false;
472 nsresult ReferrerInfo::HandleUserReferrerSendingPolicy(nsIHttpChannel* aChannel,
473 bool& aAllowed) const {
474 aAllowed = false;
475 uint32_t referrerSendingPolicy;
476 uint32_t loadFlags;
477 nsresult rv = aChannel->GetLoadFlags(&loadFlags);
478 if (NS_WARN_IF(NS_FAILED(rv))) {
479 return rv;
482 if (loadFlags & nsIHttpChannel::LOAD_INITIAL_DOCUMENT_URI) {
483 referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendWhenUserTrigger;
484 } else {
485 referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendInlineContent;
487 if (GetUserReferrerSendingPolicy() < referrerSendingPolicy) {
488 return NS_OK;
491 aAllowed = true;
492 return NS_OK;
495 /* static */
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"));
501 return true;
504 if (LOG_ENABLED()) {
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))) {
513 return true;
516 return !loadInfo->TriggeringPrincipal()->IsSameOrigin(uri);
519 /* static */
520 bool ReferrerInfo::IsReferrerCrossOrigin(nsIHttpChannel* aChannel,
521 nsIURI* aReferrer) {
522 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
524 if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) {
525 LOG(("no triggering URI via loadInfo, assuming load is cross-site"));
526 return true;
529 nsCOMPtr<nsIURI> uri;
530 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
531 if (NS_WARN_IF(NS_FAILED(rv))) {
532 return true;
535 return !nsScriptSecurityManager::SecurityCompareURIs(uri, aReferrer);
538 /* static */
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"));
544 return true;
547 if (LOG_ENABLED()) {
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))) {
556 return true;
559 bool isCrossSite = true;
560 rv = loadInfo->TriggeringPrincipal()->IsThirdPartyURI(uri, &isCrossSite);
561 if (NS_FAILED(rv)) {
562 return true;
565 return isCrossSite;
568 ReferrerInfo::TrimmingPolicy ReferrerInfo::ComputeTrimmingPolicy(
569 nsIHttpChannel* aChannel, nsIURI* aReferrer) const {
570 uint32_t trimmingPolicy = GetUserTrimmingPolicy();
572 switch (mPolicy) {
573 case ReferrerPolicy::Origin:
574 case ReferrerPolicy::Strict_origin:
575 trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort;
576 break;
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
583 // policy.
584 trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort;
586 break;
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)) {
601 trimmingPolicy =
602 std::max(trimmingPolicy, GetUserXOriginTrimmingPolicy());
605 break;
607 case ReferrerPolicy::No_referrer:
608 case ReferrerPolicy::_empty:
609 default:
610 MOZ_ASSERT_UNREACHABLE("Unexpected value");
611 break;
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()) {
621 return NS_OK;
624 if (aInAndOutTrimmedReferrer.Length() <=
625 StaticPrefs::network_http_referer_referrerLengthLimit()) {
626 return NS_OK;
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))) {
637 return 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
643 // add it back here.
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);
650 return NS_OK;
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();
662 return NS_OK;
665 nsresult ReferrerInfo::GetOriginFromReferrerURI(nsIURI* aReferrer,
666 nsACString& aResult) const {
667 MOZ_ASSERT(aReferrer);
668 aResult.Truncate();
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))) {
675 return rv;
678 aResult = scheme;
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))) {
683 return rv;
686 aResult.Append(asciiHostPort);
687 return NS_OK;
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))) {
701 return rv;
704 if (aTrimmingPolicy == TrimmingPolicy::ePolicySchemeHostPortPath) {
705 nsCOMPtr<nsIURL> url(do_QueryInterface(aReferrer));
706 if (url) {
707 nsAutoCString path;
708 rv = url->GetFilePath(path);
709 if (NS_WARN_IF(NS_FAILED(rv))) {
710 return rv;
713 aResult.Append(path);
714 return NS_OK;
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
721 // add it back here.
722 aResult.AppendLiteral("/");
723 return NS_OK;
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) {
734 return false;
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
741 // top navigation.
742 if (loadInfo->GetExternalContentPolicyType() ==
743 ExtContentPolicy::TYPE_DOCUMENT) {
744 bool isEnabledForTopNavigation =
745 isPrivate
746 ? StaticPrefs::
747 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode_top_navigation()
748 : StaticPrefs::
749 network_http_referer_disallowCrossSiteRelaxingDefault_top_navigation();
750 if (!isEnabledForTopNavigation) {
751 return false;
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)) {
773 return false;
776 bool isCrossSite = IsCrossSiteRequest(aChannel);
777 bool isEnabled =
778 isPrivate
779 ? StaticPrefs::
780 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode()
781 : StaticPrefs::
782 network_http_referer_disallowCrossSiteRelaxingDefault();
784 if (!isEnabled) {
785 // Log the warning message to console to inform that we will ignore
786 // less restricted policies for cross-site requests in the future.
787 if (isCrossSite) {
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",
795 params);
797 return false;
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()) {
805 return false;
808 if (isCrossSite) {
809 // Log the console message to say that the less restricted policy was
810 // ignored.
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",
819 params);
822 return isCrossSite;
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))) {
833 return;
836 uint64_t windowID = 0;
838 rv = aChannel->GetTopLevelContentWindowId(&windowID);
839 if (NS_WARN_IF(NS_FAILED(rv))) {
840 return;
843 if (!windowID) {
844 nsCOMPtr<nsILoadGroup> loadGroup;
845 rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
846 if (NS_WARN_IF(NS_FAILED(rv))) {
847 return;
850 if (loadGroup) {
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))) {
859 return;
862 rv = nsContentUtils::ReportToConsoleByWindowID(
863 localizedMsg, nsIScriptError::infoFlag, "Security"_ns, windowID,
864 SourceLocation(std::move(uri)));
865 Unused << NS_WARN_IF(NS_FAILED(rv));
868 ReferrerPolicy ReferrerPolicyIDLToReferrerPolicy(
869 nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy) {
870 switch (aReferrerPolicy) {
871 case nsIReferrerInfo::EMPTY:
872 return ReferrerPolicy::_empty;
873 break;
874 case nsIReferrerInfo::NO_REFERRER:
875 return ReferrerPolicy::No_referrer;
876 break;
877 case nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE:
878 return ReferrerPolicy::No_referrer_when_downgrade;
879 break;
880 case nsIReferrerInfo::ORIGIN:
881 return ReferrerPolicy::Origin;
882 break;
883 case nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN:
884 return ReferrerPolicy::Origin_when_cross_origin;
885 break;
886 case nsIReferrerInfo::UNSAFE_URL:
887 return ReferrerPolicy::Unsafe_url;
888 break;
889 case nsIReferrerInfo::SAME_ORIGIN:
890 return ReferrerPolicy::Same_origin;
891 break;
892 case nsIReferrerInfo::STRICT_ORIGIN:
893 return ReferrerPolicy::Strict_origin;
894 break;
895 case nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN:
896 return ReferrerPolicy::Strict_origin_when_cross_origin;
897 break;
898 default:
899 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
900 break;
903 return ReferrerPolicy::_empty;
906 nsIReferrerInfo::ReferrerPolicyIDL ReferrerPolicyToReferrerPolicyIDL(
907 ReferrerPolicy aReferrerPolicy) {
908 switch (aReferrerPolicy) {
909 case ReferrerPolicy::_empty:
910 return nsIReferrerInfo::EMPTY;
911 break;
912 case ReferrerPolicy::No_referrer:
913 return nsIReferrerInfo::NO_REFERRER;
914 break;
915 case ReferrerPolicy::No_referrer_when_downgrade:
916 return nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE;
917 break;
918 case ReferrerPolicy::Origin:
919 return nsIReferrerInfo::ORIGIN;
920 break;
921 case ReferrerPolicy::Origin_when_cross_origin:
922 return nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN;
923 break;
924 case ReferrerPolicy::Unsafe_url:
925 return nsIReferrerInfo::UNSAFE_URL;
926 break;
927 case ReferrerPolicy::Same_origin:
928 return nsIReferrerInfo::SAME_ORIGIN;
929 break;
930 case ReferrerPolicy::Strict_origin:
931 return nsIReferrerInfo::STRICT_ORIGIN;
932 break;
933 case ReferrerPolicy::Strict_origin_when_cross_origin:
934 return nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN;
935 break;
936 default:
937 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value");
938 break;
941 return nsIReferrerInfo::EMPTY;
944 ReferrerInfo::ReferrerInfo()
945 : mOriginalReferrer(nullptr),
946 mPolicy(ReferrerPolicy::_empty),
947 mOriginalPolicy(ReferrerPolicy::_empty),
948 mSendReferrer(true),
949 mInitialized(false),
950 mOverridePolicyByDefault(false) {}
952 ReferrerInfo::ReferrerInfo(const Document& aDoc) : ReferrerInfo() {
953 InitWithDocument(&aDoc);
956 ReferrerInfo::ReferrerInfo(const Element& aElement) : ReferrerInfo() {
957 InitWithElement(&aElement);
960 ReferrerInfo::ReferrerInfo(const Element& aElement,
961 ReferrerPolicyEnum aOverridePolicy)
962 : ReferrerInfo(aElement) {
963 // Override referrer policy if not empty
964 if (aOverridePolicy != ReferrerPolicyEnum::_empty) {
965 mPolicy = aOverridePolicy;
966 mOriginalPolicy = aOverridePolicy;
970 ReferrerInfo::ReferrerInfo(nsIURI* aOriginalReferrer,
971 ReferrerPolicyEnum aPolicy, bool aSendReferrer,
972 const Maybe<nsCString>& aComputedReferrer)
973 : mOriginalReferrer(aOriginalReferrer),
974 mPolicy(aPolicy),
975 mOriginalPolicy(aPolicy),
976 mSendReferrer(aSendReferrer),
977 mInitialized(true),
978 mOverridePolicyByDefault(false),
979 mComputedReferrer(aComputedReferrer) {}
981 ReferrerInfo::ReferrerInfo(const ReferrerInfo& rhs)
982 : mOriginalReferrer(rhs.mOriginalReferrer),
983 mPolicy(rhs.mPolicy),
984 mOriginalPolicy(rhs.mOriginalPolicy),
985 mSendReferrer(rhs.mSendReferrer),
986 mInitialized(rhs.mInitialized),
987 mOverridePolicyByDefault(rhs.mOverridePolicyByDefault),
988 mComputedReferrer(rhs.mComputedReferrer) {}
990 already_AddRefed<ReferrerInfo> ReferrerInfo::Clone() const {
991 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
992 return copy.forget();
995 already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewPolicy(
996 ReferrerPolicyEnum aPolicy) const {
997 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
998 copy->mPolicy = aPolicy;
999 copy->mOriginalPolicy = aPolicy;
1000 return copy.forget();
1003 already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewSendReferrer(
1004 bool aSendReferrer) const {
1005 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
1006 copy->mSendReferrer = aSendReferrer;
1007 return copy.forget();
1010 already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewOriginalReferrer(
1011 nsIURI* aOriginalReferrer) const {
1012 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this));
1013 copy->mOriginalReferrer = aOriginalReferrer;
1014 return copy.forget();
1017 NS_IMETHODIMP
1018 ReferrerInfo::GetOriginalReferrer(nsIURI** aOriginalReferrer) {
1019 *aOriginalReferrer = mOriginalReferrer;
1020 NS_IF_ADDREF(*aOriginalReferrer);
1021 return NS_OK;
1024 NS_IMETHODIMP
1025 ReferrerInfo::GetReferrerPolicy(
1026 JSContext* aCx, nsIReferrerInfo::ReferrerPolicyIDL* aReferrerPolicy) {
1027 *aReferrerPolicy = ReferrerPolicyToReferrerPolicyIDL(mPolicy);
1028 return NS_OK;
1031 NS_IMETHODIMP
1032 ReferrerInfo::GetReferrerPolicyString(nsACString& aResult) {
1033 aResult.AssignASCII(GetEnumString(mPolicy));
1034 return NS_OK;
1037 ReferrerPolicy ReferrerInfo::ReferrerPolicy() { return mPolicy; }
1039 NS_IMETHODIMP
1040 ReferrerInfo::GetSendReferrer(bool* aSendReferrer) {
1041 *aSendReferrer = mSendReferrer;
1042 return NS_OK;
1045 NS_IMETHODIMP
1046 ReferrerInfo::Equals(nsIReferrerInfo* aOther, bool* aResult) {
1047 NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
1048 MOZ_ASSERT(mInitialized);
1049 if (aOther == this) {
1050 *aResult = true;
1051 return NS_OK;
1054 *aResult = false;
1055 ReferrerInfo* other = static_cast<ReferrerInfo*>(aOther);
1056 MOZ_ASSERT(other->mInitialized);
1058 if (mPolicy != other->mPolicy || mSendReferrer != other->mSendReferrer ||
1059 mOverridePolicyByDefault != other->mOverridePolicyByDefault ||
1060 mComputedReferrer != other->mComputedReferrer) {
1061 return NS_OK;
1064 if (!mOriginalReferrer != !other->mOriginalReferrer) {
1065 // One or the other has mOriginalReferrer, but not both... not equal
1066 return NS_OK;
1069 bool originalReferrerEquals;
1070 if (mOriginalReferrer &&
1071 (NS_FAILED(mOriginalReferrer->Equals(other->mOriginalReferrer,
1072 &originalReferrerEquals)) ||
1073 !originalReferrerEquals)) {
1074 return NS_OK;
1077 *aResult = true;
1078 return NS_OK;
1081 NS_IMETHODIMP
1082 ReferrerInfo::GetComputedReferrerSpec(nsACString& aComputedReferrerSpec) {
1083 aComputedReferrerSpec.Assign(
1084 mComputedReferrer.isSome() ? mComputedReferrer.value() : EmptyCString());
1085 return NS_OK;
1088 already_AddRefed<nsIURI> ReferrerInfo::GetComputedReferrer() {
1089 if (!mComputedReferrer.isSome() || mComputedReferrer.value().IsEmpty()) {
1090 return nullptr;
1093 nsCOMPtr<nsIURI> result;
1094 nsresult rv = NS_NewURI(getter_AddRefs(result), mComputedReferrer.value());
1095 if (NS_FAILED(rv)) {
1096 return nullptr;
1099 return result.forget();
1102 HashNumber ReferrerInfo::Hash() const {
1103 MOZ_ASSERT(mInitialized);
1104 nsAutoCString originalReferrerSpec;
1105 if (mOriginalReferrer) {
1106 Unused << mOriginalReferrer->GetSpec(originalReferrerSpec);
1109 return mozilla::AddToHash(
1110 static_cast<uint32_t>(mPolicy), mSendReferrer, mOverridePolicyByDefault,
1111 mozilla::HashString(originalReferrerSpec),
1112 mozilla::HashString(mComputedReferrer.isSome() ? mComputedReferrer.value()
1113 : ""_ns));
1116 NS_IMETHODIMP
1117 ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy,
1118 bool aSendReferrer, nsIURI* aOriginalReferrer) {
1119 MOZ_ASSERT(!mInitialized);
1120 if (mInitialized) {
1121 return NS_ERROR_ALREADY_INITIALIZED;
1124 mPolicy = ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy);
1125 mOriginalPolicy = mPolicy;
1126 mSendReferrer = aSendReferrer;
1127 mOriginalReferrer = aOriginalReferrer;
1128 mInitialized = true;
1129 return NS_OK;
1132 NS_IMETHODIMP
1133 ReferrerInfo::InitWithDocument(const Document* aDocument) {
1134 MOZ_ASSERT(!mInitialized);
1135 if (mInitialized) {
1136 return NS_ERROR_ALREADY_INITIALIZED;
1139 mPolicy = aDocument->GetReferrerPolicy();
1140 mOriginalPolicy = mPolicy;
1141 mSendReferrer = true;
1142 mOriginalReferrer = aDocument->GetDocumentURIAsReferrer();
1143 mInitialized = true;
1144 return NS_OK;
1148 * Check whether the given node has referrerpolicy attribute and parse
1149 * referrer policy from the attribute.
1150 * Currently, referrerpolicy attribute is supported in a, area, img, iframe,
1151 * script, or link element.
1153 static ReferrerPolicy ReferrerPolicyFromAttribute(const Element& aElement) {
1154 if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
1155 nsGkAtoms::script, nsGkAtoms::iframe,
1156 nsGkAtoms::link, nsGkAtoms::img)) {
1157 return ReferrerPolicy::_empty;
1159 return aElement.GetReferrerPolicyAsEnum();
1162 static bool HasRelNoReferrer(const Element& aElement) {
1163 // rel=noreferrer is only supported in <a>, <area>, and <form>
1164 if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
1165 nsGkAtoms::form) &&
1166 !aElement.IsSVGElement(nsGkAtoms::a)) {
1167 return false;
1170 nsAutoString rel;
1171 aElement.GetAttr(nsGkAtoms::rel, rel);
1172 nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(rel);
1174 while (tok.hasMoreTokens()) {
1175 const nsAString& token = tok.nextToken();
1176 if (token.LowerCaseEqualsLiteral("noreferrer")) {
1177 return true;
1181 return false;
1184 NS_IMETHODIMP
1185 ReferrerInfo::InitWithElement(const Element* aElement) {
1186 MOZ_ASSERT(!mInitialized);
1187 if (mInitialized) {
1188 return NS_ERROR_ALREADY_INITIALIZED;
1191 // Referrer policy from referrerpolicy attribute will have a higher priority
1192 // than referrer policy from <meta> tag and Referrer-Policy header.
1193 mPolicy = ReferrerPolicyFromAttribute(*aElement);
1194 if (mPolicy == ReferrerPolicy::_empty) {
1195 // Fallback to use document's referrer poicy if we don't have referrer
1196 // policy from attribute.
1197 mPolicy = aElement->OwnerDoc()->GetReferrerPolicy();
1200 mOriginalPolicy = mPolicy;
1201 mSendReferrer = !HasRelNoReferrer(*aElement);
1202 mOriginalReferrer = aElement->OwnerDoc()->GetDocumentURIAsReferrer();
1204 mInitialized = true;
1205 return NS_OK;
1208 /* static */
1209 already_AddRefed<nsIReferrerInfo>
1210 ReferrerInfo::CreateFromDocumentAndPolicyOverride(
1211 Document* aDoc, ReferrerPolicyEnum aPolicyOverride) {
1212 MOZ_ASSERT(aDoc);
1213 ReferrerPolicyEnum policy = aPolicyOverride != ReferrerPolicy::_empty
1214 ? aPolicyOverride
1215 : aDoc->GetReferrerPolicy();
1216 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1217 new ReferrerInfo(aDoc->GetDocumentURIAsReferrer(), policy);
1218 return referrerInfo.forget();
1221 /* static */
1222 already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForFetch(
1223 nsIPrincipal* aPrincipal, Document* aDoc) {
1224 MOZ_ASSERT(aPrincipal);
1226 nsCOMPtr<nsIReferrerInfo> referrerInfo;
1227 if (!aPrincipal || aPrincipal->IsSystemPrincipal()) {
1228 referrerInfo = new ReferrerInfo(nullptr);
1229 return referrerInfo.forget();
1232 if (!aDoc) {
1233 aPrincipal->CreateReferrerInfo(ReferrerPolicy::_empty,
1234 getter_AddRefs(referrerInfo));
1235 return referrerInfo.forget();
1238 // If it weren't for history.push/replaceState, we could just use the
1239 // principal's URI here. But since we want changes to the URI effected
1240 // by push/replaceState to be reflected in the XHR referrer, we have to
1241 // be more clever.
1243 // If the document's original URI (before any push/replaceStates) matches
1244 // our principal, then we use the document's current URI (after
1245 // push/replaceStates). Otherwise (if the document is, say, a data:
1246 // URI), we just use the principal's URI.
1247 nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
1248 nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
1250 if (docCurURI && docOrigURI) {
1251 bool equal = false;
1252 aPrincipal->EqualsURI(docOrigURI, &equal);
1253 if (equal) {
1254 referrerInfo = new ReferrerInfo(docCurURI, aDoc->GetReferrerPolicy());
1255 return referrerInfo.forget();
1258 aPrincipal->CreateReferrerInfo(aDoc->GetReferrerPolicy(),
1259 getter_AddRefs(referrerInfo));
1260 return referrerInfo.forget();
1263 /* static */
1264 already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForExternalCSSResources(
1265 mozilla::StyleSheet* aExternalSheet, ReferrerPolicyEnum aPolicy) {
1266 MOZ_ASSERT(aExternalSheet && !aExternalSheet->IsInline());
1267 nsCOMPtr<nsIReferrerInfo> referrerInfo;
1269 // Step 2
1270 // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css
1271 // Use empty policy at the beginning and update it later from Referrer-Policy
1272 // header.
1273 referrerInfo = new ReferrerInfo(aExternalSheet->GetSheetURI(), aPolicy);
1274 return referrerInfo.forget();
1277 /* static */
1278 already_AddRefed<nsIReferrerInfo>
1279 ReferrerInfo::CreateForInternalCSSAndSVGResources(Document* aDocument) {
1280 MOZ_ASSERT(aDocument);
1281 return do_AddRef(new ReferrerInfo(aDocument->GetDocumentURI(),
1282 aDocument->GetReferrerPolicy()));
1285 nsresult ReferrerInfo::ComputeReferrer(nsIHttpChannel* aChannel) {
1286 NS_ENSURE_ARG(aChannel);
1287 MOZ_ASSERT(NS_IsMainThread());
1289 // If the referrerInfo is passed around when redirect, just use the last
1290 // computedReferrer to recompute
1291 nsCOMPtr<nsIURI> referrer;
1292 nsresult rv = NS_OK;
1293 mOverridePolicyByDefault = false;
1295 if (mComputedReferrer.isSome()) {
1296 if (mComputedReferrer.value().IsEmpty()) {
1297 return NS_OK;
1300 rv = NS_NewURI(getter_AddRefs(referrer), mComputedReferrer.value());
1301 if (NS_WARN_IF(NS_FAILED(rv))) {
1302 return rv;
1306 mComputedReferrer.reset();
1307 // Emplace mComputedReferrer with an empty string, which means we have
1308 // computed the referrer and the result referrer value is empty (not send
1309 // referrer). So any early return later than this line will use that empty
1310 // referrer.
1311 mComputedReferrer.emplace(""_ns);
1313 if (!mSendReferrer || !mOriginalReferrer ||
1314 mPolicy == ReferrerPolicy::No_referrer) {
1315 return NS_OK;
1318 if (mPolicy == ReferrerPolicy::_empty ||
1319 ShouldIgnoreLessRestrictedPolicies(aChannel, mOriginalPolicy)) {
1320 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
1321 OriginAttributes attrs = loadInfo->GetOriginAttributes();
1322 bool isPrivate = attrs.IsPrivateBrowsing();
1324 nsCOMPtr<nsIURI> uri;
1325 rv = aChannel->GetURI(getter_AddRefs(uri));
1326 if (NS_WARN_IF(NS_FAILED(rv))) {
1327 return rv;
1330 mPolicy = GetDefaultReferrerPolicy(aChannel, uri, isPrivate);
1331 mOverridePolicyByDefault = true;
1334 // This is for the case where the ETP toggle is off. In this case, we need to
1335 // reset the referrer and the policy if the original policy is different from
1336 // the current policy in order to recompute the referrer policy with the
1337 // original policy.
1338 if (!mOverridePolicyByDefault && mOriginalPolicy != ReferrerPolicy::_empty &&
1339 mPolicy != mOriginalPolicy) {
1340 referrer = nullptr;
1341 mPolicy = mOriginalPolicy;
1344 if (mPolicy == ReferrerPolicy::No_referrer) {
1345 return NS_OK;
1348 bool isUserReferrerSendingAllowed = false;
1349 rv = HandleUserReferrerSendingPolicy(aChannel, isUserReferrerSendingAllowed);
1350 if (NS_WARN_IF(NS_FAILED(rv))) {
1351 return rv;
1354 if (!isUserReferrerSendingAllowed) {
1355 return NS_OK;
1358 // Enforce Referrer allowlist, only http, https scheme are allowed
1359 if (!IsReferrerSchemeAllowed(mOriginalReferrer)) {
1360 return NS_OK;
1363 nsCOMPtr<nsIURI> uri;
1364 rv = aChannel->GetURI(getter_AddRefs(uri));
1365 if (NS_WARN_IF(NS_FAILED(rv))) {
1366 return rv;
1369 bool isSecureToInsecureAllowed = false;
1370 rv = HandleSecureToInsecureReferral(mOriginalReferrer, uri, mPolicy,
1371 isSecureToInsecureAllowed);
1372 if (NS_WARN_IF(NS_FAILED(rv))) {
1373 return rv;
1376 if (!isSecureToInsecureAllowed) {
1377 return NS_OK;
1380 // Strip away any fragment per RFC 2616 section 14.36
1381 // and Referrer Policy section 6.3.5.
1382 if (!referrer) {
1383 rv = NS_GetURIWithoutRef(mOriginalReferrer, getter_AddRefs(referrer));
1384 if (NS_WARN_IF(NS_FAILED(rv))) {
1385 return rv;
1389 bool isUserXOriginAllowed = false;
1390 rv = HandleUserXOriginSendingPolicy(uri, referrer, isUserXOriginAllowed);
1391 if (NS_WARN_IF(NS_FAILED(rv))) {
1392 return rv;
1395 if (!isUserXOriginAllowed) {
1396 return NS_OK;
1399 // Handle user pref network.http.referer.spoofSource, send spoofed referrer if
1400 // desired
1401 if (StaticPrefs::network_http_referer_spoofSource()) {
1402 nsCOMPtr<nsIURI> userSpoofReferrer;
1403 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(userSpoofReferrer));
1404 if (NS_WARN_IF(NS_FAILED(rv))) {
1405 return rv;
1407 referrer = userSpoofReferrer;
1410 // strip away any userpass; we don't want to be giving out passwords ;-)
1411 // This is required by Referrer Policy stripping algorithm.
1412 nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(referrer);
1413 referrer = exposableURI;
1415 // Don't send referrer when the request is cross-origin and policy is
1416 // "same-origin".
1417 if (mPolicy == ReferrerPolicy::Same_origin &&
1418 IsReferrerCrossOrigin(aChannel, referrer)) {
1419 return NS_OK;
1422 TrimmingPolicy trimmingPolicy = ComputeTrimmingPolicy(aChannel, referrer);
1424 nsAutoCString trimmedReferrer;
1425 // We first trim the referrer according to the policy by calling
1426 // 'TrimReferrerWithPolicy' and right after we have to call
1427 // 'LimitReferrerLength' (using the same arguments) because the trimmed
1428 // referrer might exceed the allowed max referrer length.
1429 rv = TrimReferrerWithPolicy(referrer, trimmingPolicy, trimmedReferrer);
1430 if (NS_WARN_IF(NS_FAILED(rv))) {
1431 return rv;
1434 rv = LimitReferrerLength(aChannel, referrer, trimmingPolicy, trimmedReferrer);
1435 if (NS_WARN_IF(NS_FAILED(rv))) {
1436 return rv;
1439 // finally, remember the referrer spec.
1440 mComputedReferrer.reset();
1441 mComputedReferrer.emplace(trimmedReferrer);
1443 return NS_OK;
1446 /* ===== nsISerializable implementation ====== */
1448 nsresult ReferrerInfo::ReadTailDataBeforeGecko100(
1449 const uint32_t& aData, nsIObjectInputStream* aInputStream) {
1450 MOZ_ASSERT(aInputStream);
1452 nsCOMPtr<nsIInputStream> reader;
1453 nsCOMPtr<nsIOutputStream> writer;
1455 // We need to create a new pipe in order to read the aData and the rest of
1456 // the input stream together in the old format. This would also help us with
1457 // handling big endian correctly.
1458 NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer));
1460 nsCOMPtr<nsIBinaryOutputStream> binaryPipeWriter =
1461 NS_NewObjectOutputStream(writer);
1463 // Write back the aData so that we can read bytes from it and handle big
1464 // endian correctly.
1465 nsresult rv = binaryPipeWriter->Write32(aData);
1466 if (NS_WARN_IF(NS_FAILED(rv))) {
1467 return rv;
1470 nsCOMPtr<nsIBinaryInputStream> binaryPipeReader =
1471 NS_NewObjectInputStream(reader);
1473 rv = binaryPipeReader->ReadBoolean(&mSendReferrer);
1474 if (NS_WARN_IF(NS_FAILED(rv))) {
1475 return rv;
1478 bool isComputed;
1479 rv = binaryPipeReader->ReadBoolean(&isComputed);
1480 if (NS_WARN_IF(NS_FAILED(rv))) {
1481 return rv;
1484 // We need to handle the following string if isComputed is true.
1485 if (isComputed) {
1486 // Comsume the following 2 bytes from the input stream. They are the half
1487 // part of the length prefix of the following string.
1488 uint16_t data;
1489 rv = aInputStream->Read16(&data);
1490 if (NS_WARN_IF(NS_FAILED(rv))) {
1491 return rv;
1494 // Write the bytes to the pipe so that we can read the length of the string.
1495 rv = binaryPipeWriter->Write16(data);
1496 if (NS_WARN_IF(NS_FAILED(rv))) {
1497 return rv;
1500 uint32_t length;
1501 rv = binaryPipeReader->Read32(&length);
1502 if (NS_WARN_IF(NS_FAILED(rv))) {
1503 return rv;
1506 // Consume the string body from the input stream.
1507 nsAutoCString computedReferrer;
1508 rv = NS_ConsumeStream(aInputStream, length, computedReferrer);
1509 if (NS_WARN_IF(NS_FAILED(rv))) {
1510 return rv;
1512 mComputedReferrer.emplace(computedReferrer);
1514 // Read the remaining two bytes and write to the pipe.
1515 uint16_t remain;
1516 rv = aInputStream->Read16(&remain);
1517 if (NS_WARN_IF(NS_FAILED(rv))) {
1518 return rv;
1521 rv = binaryPipeWriter->Write16(remain);
1522 if (NS_WARN_IF(NS_FAILED(rv))) {
1523 return rv;
1527 rv = binaryPipeReader->ReadBoolean(&mInitialized);
1528 if (NS_WARN_IF(NS_FAILED(rv))) {
1529 return rv;
1532 rv = binaryPipeReader->ReadBoolean(&mOverridePolicyByDefault);
1533 if (NS_WARN_IF(NS_FAILED(rv))) {
1534 return rv;
1537 return NS_OK;
1540 NS_IMETHODIMP
1541 ReferrerInfo::Read(nsIObjectInputStream* aStream) {
1542 bool nonNull;
1543 nsresult rv = aStream->ReadBoolean(&nonNull);
1544 if (NS_WARN_IF(NS_FAILED(rv))) {
1545 return rv;
1548 if (nonNull) {
1549 nsAutoCString spec;
1550 nsresult rv = aStream->ReadCString(spec);
1551 if (NS_WARN_IF(NS_FAILED(rv))) {
1552 return rv;
1555 rv = NS_NewURI(getter_AddRefs(mOriginalReferrer), spec);
1556 if (NS_WARN_IF(NS_FAILED(rv))) {
1557 return rv;
1559 } else {
1560 mOriginalReferrer = nullptr;
1563 // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store
1564 // to disk using the order of ReferrerPolicyIDL, so we convert to
1565 // ReferrerPolicyIDL to make it be compatible to the old format.
1566 uint32_t policy;
1567 rv = aStream->Read32(&policy);
1568 if (NS_WARN_IF(NS_FAILED(rv))) {
1569 return rv;
1571 mPolicy = ReferrerPolicyIDLToReferrerPolicy(
1572 static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(policy));
1574 uint32_t originalPolicy;
1575 rv = aStream->Read32(&originalPolicy);
1576 if (NS_WARN_IF(NS_FAILED(rv))) {
1577 return rv;
1580 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more
1581 // details.
1583 // We need to differentiate the old format and the new format here in order
1584 // to be able to read both formats. The check here helps us with verifying
1585 // which format it is.
1586 if (MOZ_UNLIKELY(originalPolicy > 0xFF)) {
1587 mOriginalPolicy = mPolicy;
1589 return ReadTailDataBeforeGecko100(originalPolicy, aStream);
1592 mOriginalPolicy = ReferrerPolicyIDLToReferrerPolicy(
1593 static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(originalPolicy));
1595 rv = aStream->ReadBoolean(&mSendReferrer);
1596 if (NS_WARN_IF(NS_FAILED(rv))) {
1597 return rv;
1600 bool isComputed;
1601 rv = aStream->ReadBoolean(&isComputed);
1602 if (NS_WARN_IF(NS_FAILED(rv))) {
1603 return rv;
1606 if (isComputed) {
1607 nsAutoCString computedReferrer;
1608 rv = aStream->ReadCString(computedReferrer);
1609 if (NS_WARN_IF(NS_FAILED(rv))) {
1610 return rv;
1612 mComputedReferrer.emplace(computedReferrer);
1615 rv = aStream->ReadBoolean(&mInitialized);
1616 if (NS_WARN_IF(NS_FAILED(rv))) {
1617 return rv;
1620 rv = aStream->ReadBoolean(&mOverridePolicyByDefault);
1621 if (NS_WARN_IF(NS_FAILED(rv))) {
1622 return rv;
1625 return NS_OK;
1628 NS_IMETHODIMP
1629 ReferrerInfo::Write(nsIObjectOutputStream* aStream) {
1630 bool nonNull = (mOriginalReferrer != nullptr);
1631 nsresult rv = aStream->WriteBoolean(nonNull);
1632 if (NS_WARN_IF(NS_FAILED(rv))) {
1633 return rv;
1636 if (nonNull) {
1637 nsAutoCString spec;
1638 nsresult rv = mOriginalReferrer->GetSpec(spec);
1639 if (NS_WARN_IF(NS_FAILED(rv))) {
1640 return rv;
1643 rv = aStream->WriteStringZ(spec.get());
1644 if (NS_WARN_IF(NS_FAILED(rv))) {
1645 return rv;
1649 rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy));
1650 if (NS_WARN_IF(NS_FAILED(rv))) {
1651 return rv;
1654 rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy));
1655 if (NS_WARN_IF(NS_FAILED(rv))) {
1656 return rv;
1659 rv = aStream->WriteBoolean(mSendReferrer);
1660 if (NS_WARN_IF(NS_FAILED(rv))) {
1661 return rv;
1664 bool isComputed = mComputedReferrer.isSome();
1665 rv = aStream->WriteBoolean(isComputed);
1666 if (NS_WARN_IF(NS_FAILED(rv))) {
1667 return rv;
1670 if (isComputed) {
1671 rv = aStream->WriteStringZ(mComputedReferrer.value().get());
1672 if (NS_WARN_IF(NS_FAILED(rv))) {
1673 return rv;
1677 rv = aStream->WriteBoolean(mInitialized);
1678 if (NS_WARN_IF(NS_FAILED(rv))) {
1679 return rv;
1682 rv = aStream->WriteBoolean(mOverridePolicyByDefault);
1683 if (NS_WARN_IF(NS_FAILED(rv))) {
1684 return rv;
1686 return NS_OK;
1689 void ReferrerInfo::RecordTelemetry(nsIHttpChannel* aChannel) {
1690 #ifdef DEBUG
1691 MOZ_ASSERT(!mTelemetryRecorded);
1692 mTelemetryRecorded = true;
1693 #endif // DEBUG
1695 // The telemetry probe has 18 buckets. The first 9 buckets are for same-site
1696 // requests and the rest 9 buckets are for cross-site requests.
1697 uint32_t telemetryOffset =
1698 IsCrossSiteRequest(aChannel)
1699 ? UnderlyingValue(
1700 MaxContiguousEnumValue<dom::ReferrerPolicy>::value) +
1702 : 0;
1704 Telemetry::Accumulate(Telemetry::REFERRER_POLICY_COUNT,
1705 static_cast<uint32_t>(mPolicy) + telemetryOffset);
1708 } // namespace mozilla::dom