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