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