ReferrerInfo.cpp (54442B)
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/. */ 6 7 #include "ReferrerInfo.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/ContentBlockingAllowList.h" 11 #include "mozilla/RefPtr.h" 12 #include "mozilla/StaticPrefs_network.h" 13 #include "mozilla/StorageAccess.h" 14 #include "mozilla/StyleSheet.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/ReferrerPolicyBinding.h" 18 #include "mozilla/dom/RequestBinding.h" 19 #include "mozilla/glean/DomSecurityMetrics.h" 20 #include "mozilla/net/CookieJarSettings.h" 21 #include "mozilla/net/HttpBaseChannel.h" 22 #include "nsCharSeparatedTokenizer.h" 23 #include "nsContentUtils.h" 24 #include "nsIClassInfoImpl.h" 25 #include "nsIEffectiveTLDService.h" 26 #include "nsIHttpChannel.h" 27 #include "nsIOService.h" 28 #include "nsIObjectInputStream.h" 29 #include "nsIObjectOutputStream.h" 30 #include "nsIPipe.h" 31 #include "nsIURL.h" 32 #include "nsIWebProgressListener.h" 33 #include "nsNetUtil.h" 34 #include "nsScriptSecurityManager.h" 35 #include "nsStreamUtils.h" 36 #include "nsWhitespaceTokenizer.h" 37 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) 41 42 using namespace mozilla::net; 43 44 namespace mozilla::dom { 45 46 // Implementation of ClassInfo is required to serialize/deserialize 47 NS_IMPL_CLASSINFO(ReferrerInfo, nullptr, nsIClassInfo::THREADSAFE, 48 REFERRERINFO_CID) 49 50 NS_IMPL_ISUPPORTS_CI(ReferrerInfo, nsIReferrerInfo, nsISerializable) 51 52 #define MAX_REFERRER_SENDING_POLICY 2 53 #define MAX_CROSS_ORIGIN_SENDING_POLICY 2 54 #define MAX_TRIMMING_POLICY 2 55 56 #define MIN_REFERRER_SENDING_POLICY 0 57 #define MIN_CROSS_ORIGIN_SENDING_POLICY 0 58 #define MIN_TRIMMING_POLICY 0 59 60 /* 61 * Default referrer policy to use 62 */ 63 enum DefaultReferrerPolicy : uint32_t { 64 eDefaultPolicyNoReferrer = 0, 65 eDefaultPolicySameOrgin = 1, 66 eDefaultPolicyStrictWhenXorigin = 2, 67 eDefaultPolicyNoReferrerWhenDownGrade = 3, 68 }; 69 70 static uint32_t GetDefaultFirstPartyReferrerPolicyPref(bool aPrivateBrowsing) { 71 return aPrivateBrowsing 72 ? StaticPrefs::network_http_referer_defaultPolicy_pbmode() 73 : StaticPrefs::network_http_referer_defaultPolicy(); 74 } 75 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(); 80 } 81 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; 91 } 92 93 return ReferrerPolicy::No_referrer_when_downgrade; 94 } 95 96 struct LegacyReferrerPolicyTokenMap { 97 const char* mToken; 98 ReferrerPolicy mPolicy; 99 }; 100 101 /* 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 107 * 108 * @param aContent content string to be transformed into 109 * ReferrerPolicyEnum, e.g. "origin". 110 */ 111 ReferrerPolicy ReferrerPolicyFromToken(const nsAString& aContent, 112 bool allowedLegacyToken) { 113 nsString lowerContent(aContent); 114 ToLowerCase(lowerContent); 115 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}, 122 }; 123 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; 129 } 130 } 131 } 132 133 // Return no referrer policy (empty string) if it's not a valid enum value. 134 return StringToEnum<ReferrerPolicy>(lowerContent) 135 .valueOr(ReferrerPolicy::_empty); 136 } 137 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); 146 } 147 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); 156 } 157 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; 167 } 168 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; 177 } 178 } 179 return referrerPolicy; 180 } 181 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); 187 } 188 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); 194 } 195 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); 201 } 202 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); 209 } 210 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 (void)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); 229 } 230 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.) 246 } 247 } 248 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)); 256 } 257 258 /* static */ 259 bool ReferrerInfo::IsReferrerSchemeAllowed(nsIURI* aReferrer) { 260 NS_ENSURE_TRUE(aReferrer, false); 261 262 nsAutoCString scheme; 263 nsresult rv = aReferrer->GetScheme(scheme); 264 if (NS_WARN_IF(NS_FAILED(rv))) { 265 return false; 266 } 267 268 return scheme.EqualsIgnoreCase("https") || scheme.EqualsIgnoreCase("http"); 269 } 270 271 /* static */ 272 bool ReferrerInfo::ShouldResponseInheritReferrerInfo(nsIChannel* aChannel) { 273 if (!aChannel) { 274 return false; 275 } 276 277 nsCOMPtr<nsIURI> channelURI; 278 nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI)); 279 NS_ENSURE_SUCCESS(rv, false); 280 281 return NS_IsAboutSrcdoc(channelURI); 282 } 283 284 /* static */ 285 nsresult ReferrerInfo::HandleSecureToInsecureReferral( 286 nsIURI* aOriginalURI, nsIURI* aURI, ReferrerPolicyEnum aPolicy, 287 bool& aAllowed) { 288 NS_ENSURE_ARG(aOriginalURI); 289 NS_ENSURE_ARG(aURI); 290 291 aAllowed = false; 292 293 bool referrerIsHttpsScheme = aOriginalURI->SchemeIs("https"); 294 if (!referrerIsHttpsScheme) { 295 aAllowed = true; 296 return NS_OK; 297 } 298 299 // It's ok to send referrer for https-to-http scenarios if the referrer 300 // policy is "unsafe-url", "origin", or "origin-when-cross-origin". 301 // in other referrer policies, https->http is not allowed... 302 bool uriIsHttpsScheme = aURI->SchemeIs("https"); 303 if (aPolicy != ReferrerPolicy::Unsafe_url && 304 aPolicy != ReferrerPolicy::Origin_when_cross_origin && 305 aPolicy != ReferrerPolicy::Origin && !uriIsHttpsScheme) { 306 return NS_OK; 307 } 308 309 aAllowed = true; 310 return NS_OK; 311 } 312 313 nsresult ReferrerInfo::HandleUserXOriginSendingPolicy(nsIURI* aURI, 314 nsIURI* aReferrer, 315 bool& aAllowed) const { 316 NS_ENSURE_ARG(aURI); 317 aAllowed = false; 318 319 nsAutoCString uriHost; 320 nsAutoCString referrerHost; 321 322 nsresult rv = aURI->GetAsciiHost(uriHost); 323 if (NS_WARN_IF(NS_FAILED(rv))) { 324 return rv; 325 } 326 327 rv = aReferrer->GetAsciiHost(referrerHost); 328 if (NS_WARN_IF(NS_FAILED(rv))) { 329 return rv; 330 } 331 332 // Send an empty referrer if xorigin and leaving a .onion domain. 333 if (StaticPrefs::network_http_referer_hideOnionSource() && 334 !uriHost.Equals(referrerHost) && 335 StringEndsWith(referrerHost, ".onion"_ns)) { 336 return NS_OK; 337 } 338 339 switch (GetUserXOriginSendingPolicy()) { 340 // Check policy for sending referrer only when hosts match 341 case XOriginSendingPolicy::ePolicySendWhenSameHost: { 342 if (!uriHost.Equals(referrerHost)) { 343 return NS_OK; 344 } 345 break; 346 } 347 348 case XOriginSendingPolicy::ePolicySendWhenSameDomain: { 349 nsCOMPtr<nsIEffectiveTLDService> eTLDService = 350 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 351 if (!eTLDService) { 352 // check policy for sending only when effective top level domain 353 // matches. this falls back on using host if eTLDService does not work 354 if (!uriHost.Equals(referrerHost)) { 355 return NS_OK; 356 } 357 break; 358 } 359 360 nsAutoCString uriDomain; 361 nsAutoCString referrerDomain; 362 uint32_t extraDomains = 0; 363 364 rv = eTLDService->GetBaseDomain(aURI, extraDomains, uriDomain); 365 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || 366 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { 367 // uri is either an IP address, an alias such as 'localhost', an eTLD 368 // such as 'co.uk', or the empty string. Uses the normalized host in 369 // such cases. 370 rv = aURI->GetAsciiHost(uriDomain); 371 } 372 373 if (NS_WARN_IF(NS_FAILED(rv))) { 374 return rv; 375 } 376 377 rv = eTLDService->GetBaseDomain(aReferrer, extraDomains, referrerDomain); 378 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || 379 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { 380 // referrer is either an IP address, an alias such as 'localhost', an 381 // eTLD such as 'co.uk', or the empty string. Uses the normalized host 382 // in such cases. 383 rv = aReferrer->GetAsciiHost(referrerDomain); 384 } 385 386 if (NS_WARN_IF(NS_FAILED(rv))) { 387 return rv; 388 } 389 390 if (!uriDomain.Equals(referrerDomain)) { 391 return NS_OK; 392 } 393 break; 394 } 395 396 default: 397 break; 398 } 399 400 aAllowed = true; 401 return NS_OK; 402 } 403 404 // This roughly implements Step 3.1. of 405 // https://fetch.spec.whatwg.org/#append-a-request-origin-header 406 /* static */ 407 bool ReferrerInfo::ShouldSetNullOriginHeader(net::HttpBaseChannel* aChannel, 408 nsIURI* aOriginURI) { 409 MOZ_ASSERT(aChannel); 410 MOZ_ASSERT(aOriginURI); 411 412 // If request’s mode is not "cors", then switch on request’s referrer policy: 413 RequestMode requestMode = RequestMode::No_cors; 414 MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestMode(&requestMode)); 415 if (requestMode == RequestMode::Cors) { 416 return false; 417 } 418 419 nsCOMPtr<nsIReferrerInfo> referrerInfo; 420 NS_ENSURE_SUCCESS(aChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)), 421 false); 422 if (!referrerInfo) { 423 return false; 424 } 425 426 // "no-referrer": 427 enum ReferrerPolicy policy = referrerInfo->ReferrerPolicy(); 428 if (policy == ReferrerPolicy::No_referrer) { 429 // Set serializedOrigin to `null`. 430 // Note: Returning true is the same as setting the serializedOrigin to null 431 // in this method. 432 return true; 433 } 434 435 // "no-referrer-when-downgrade": 436 // "strict-origin": 437 // "strict-origin-when-cross-origin": 438 // If request’s origin is a tuple origin, its scheme is "https", and 439 // request’s current URL’s scheme is not "https", then set serializedOrigin 440 // to `null`. 441 bool allowed = false; 442 nsCOMPtr<nsIURI> uri; 443 NS_ENSURE_SUCCESS(aChannel->GetURI(getter_AddRefs(uri)), false); 444 if (NS_SUCCEEDED(ReferrerInfo::HandleSecureToInsecureReferral( 445 aOriginURI, uri, policy, allowed)) && 446 !allowed) { 447 return true; 448 } 449 450 // "same-origin": 451 if (policy == ReferrerPolicy::Same_origin) { 452 // If request’s origin is not same origin with request’s current URL’s 453 // origin, then set serializedOrigin to `null`. 454 return ReferrerInfo::IsCrossOriginRequest(aChannel); 455 } 456 457 // Otherwise: 458 // Do Nothing. 459 return false; 460 } 461 462 nsresult ReferrerInfo::HandleUserReferrerSendingPolicy(nsIHttpChannel* aChannel, 463 bool& aAllowed) const { 464 aAllowed = false; 465 uint32_t referrerSendingPolicy; 466 uint32_t loadFlags; 467 nsresult rv = aChannel->GetLoadFlags(&loadFlags); 468 if (NS_WARN_IF(NS_FAILED(rv))) { 469 return rv; 470 } 471 472 if (loadFlags & nsIHttpChannel::LOAD_INITIAL_DOCUMENT_URI) { 473 referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendWhenUserTrigger; 474 } else { 475 referrerSendingPolicy = ReferrerSendingPolicy::ePolicySendInlineContent; 476 } 477 if (GetUserReferrerSendingPolicy() < referrerSendingPolicy) { 478 return NS_OK; 479 } 480 481 aAllowed = true; 482 return NS_OK; 483 } 484 485 /* static */ 486 bool ReferrerInfo::IsCrossOriginRequest(nsIHttpChannel* aChannel) { 487 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 488 489 if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) { 490 LOG(("no triggering URI via loadInfo, assuming load is cross-origin")); 491 return true; 492 } 493 494 if (LOG_ENABLED()) { 495 nsAutoCString triggeringURISpec; 496 loadInfo->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec); 497 LOG(("triggeringURI=%s\n", triggeringURISpec.get())); 498 } 499 500 nsCOMPtr<nsIURI> uri; 501 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 502 if (NS_WARN_IF(NS_FAILED(rv))) { 503 return true; 504 } 505 506 return !loadInfo->TriggeringPrincipal()->IsSameOrigin(uri); 507 } 508 509 /* static */ 510 bool ReferrerInfo::IsReferrerCrossOrigin(nsIHttpChannel* aChannel, 511 nsIURI* aReferrer) { 512 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 513 514 if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) { 515 LOG(("no triggering URI via loadInfo, assuming load is cross-site")); 516 return true; 517 } 518 519 nsCOMPtr<nsIURI> uri; 520 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 521 if (NS_WARN_IF(NS_FAILED(rv))) { 522 return true; 523 } 524 525 return !nsScriptSecurityManager::SecurityCompareURIs(uri, aReferrer); 526 } 527 528 /* static */ 529 bool ReferrerInfo::IsCrossSiteRequest(nsIHttpChannel* aChannel) { 530 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 531 532 if (!loadInfo->TriggeringPrincipal()->GetIsContentPrincipal()) { 533 LOG(("no triggering URI via loadInfo, assuming load is cross-site")); 534 return true; 535 } 536 537 if (LOG_ENABLED()) { 538 nsAutoCString triggeringURISpec; 539 loadInfo->TriggeringPrincipal()->GetAsciiSpec(triggeringURISpec); 540 LOG(("triggeringURI=%s\n", triggeringURISpec.get())); 541 } 542 543 nsCOMPtr<nsIURI> uri; 544 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 545 if (NS_WARN_IF(NS_FAILED(rv))) { 546 return true; 547 } 548 549 bool isCrossSite = true; 550 rv = loadInfo->TriggeringPrincipal()->IsThirdPartyURI(uri, &isCrossSite); 551 if (NS_FAILED(rv)) { 552 return true; 553 } 554 555 return isCrossSite; 556 } 557 558 ReferrerInfo::TrimmingPolicy ReferrerInfo::ComputeTrimmingPolicy( 559 nsIHttpChannel* aChannel, nsIURI* aReferrer) const { 560 uint32_t trimmingPolicy = GetUserTrimmingPolicy(); 561 562 switch (mPolicy) { 563 case ReferrerPolicy::Origin: 564 case ReferrerPolicy::Strict_origin: 565 trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort; 566 break; 567 568 case ReferrerPolicy::Origin_when_cross_origin: 569 case ReferrerPolicy::Strict_origin_when_cross_origin: 570 if (trimmingPolicy != TrimmingPolicy::ePolicySchemeHostPort && 571 IsReferrerCrossOrigin(aChannel, aReferrer)) { 572 // Ignore set trimmingPolicy if it is already the strictest 573 // policy. 574 trimmingPolicy = TrimmingPolicy::ePolicySchemeHostPort; 575 } 576 break; 577 578 // This function is called when a nonempty referrer value is allowed to 579 // send. For the next 3 policies: same-origin, no-referrer-when-downgrade, 580 // unsafe-url, without trimming we should have a full uri. And the trimming 581 // policy only depends on user prefs. 582 case ReferrerPolicy::Same_origin: 583 case ReferrerPolicy::No_referrer_when_downgrade: 584 case ReferrerPolicy::Unsafe_url: 585 if (trimmingPolicy != TrimmingPolicy::ePolicySchemeHostPort) { 586 // Ignore set trimmingPolicy if it is already the strictest 587 // policy. Apply the user cross-origin trimming policy if it's more 588 // restrictive than the general one. 589 if (GetUserXOriginTrimmingPolicy() != TrimmingPolicy::ePolicyFullURI && 590 IsCrossOriginRequest(aChannel)) { 591 trimmingPolicy = 592 std::max(trimmingPolicy, GetUserXOriginTrimmingPolicy()); 593 } 594 } 595 break; 596 597 case ReferrerPolicy::No_referrer: 598 case ReferrerPolicy::_empty: 599 default: 600 MOZ_ASSERT_UNREACHABLE("Unexpected value"); 601 break; 602 } 603 604 return static_cast<TrimmingPolicy>(trimmingPolicy); 605 } 606 607 nsresult ReferrerInfo::LimitReferrerLength( 608 nsIHttpChannel* aChannel, nsIURI* aReferrer, TrimmingPolicy aTrimmingPolicy, 609 nsACString& aInAndOutTrimmedReferrer) const { 610 if (!StaticPrefs::network_http_referer_referrerLengthLimit()) { 611 return NS_OK; 612 } 613 614 if (aInAndOutTrimmedReferrer.Length() <= 615 StaticPrefs::network_http_referer_referrerLengthLimit()) { 616 return NS_OK; 617 } 618 619 nsAutoString referrerLengthLimit; 620 referrerLengthLimit.AppendInt( 621 StaticPrefs::network_http_referer_referrerLengthLimit()); 622 if (aTrimmingPolicy == ePolicyFullURI || 623 aTrimmingPolicy == ePolicySchemeHostPortPath) { 624 // If referrer header is over max Length, down to origin 625 nsresult rv = GetOriginFromReferrerURI(aReferrer, aInAndOutTrimmedReferrer); 626 if (NS_WARN_IF(NS_FAILED(rv))) { 627 return rv; 628 } 629 630 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url 631 // states that the trailing "/" does not need to get stripped. However, 632 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to 633 // add it back here. 634 aInAndOutTrimmedReferrer.AppendLiteral("/"); 635 if (aInAndOutTrimmedReferrer.Length() <= 636 StaticPrefs::network_http_referer_referrerLengthLimit()) { 637 AutoTArray<nsString, 2> params = { 638 referrerLengthLimit, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer)}; 639 LogMessageToConsole(aChannel, "ReferrerLengthOverLimitation", params); 640 return NS_OK; 641 } 642 } 643 644 // If we end up here either the trimmingPolicy is equal to 645 // 'ePolicySchemeHostPort' or the 'origin' of any other policy is still over 646 // the length limit. If so, truncate the referrer entirely. 647 AutoTArray<nsString, 2> params = { 648 referrerLengthLimit, NS_ConvertUTF8toUTF16(aInAndOutTrimmedReferrer)}; 649 LogMessageToConsole(aChannel, "ReferrerOriginLengthOverLimitation", params); 650 aInAndOutTrimmedReferrer.Truncate(); 651 652 return NS_OK; 653 } 654 655 nsresult ReferrerInfo::GetOriginFromReferrerURI(nsIURI* aReferrer, 656 nsACString& aResult) const { 657 MOZ_ASSERT(aReferrer); 658 aResult.Truncate(); 659 // We want the IDN-normalized PrePath. That's not something currently 660 // available and there doesn't yet seem to be justification for adding it to 661 // the interfaces, so just build it up from scheme+AsciiHostPort 662 nsAutoCString scheme, asciiHostPort; 663 nsresult rv = aReferrer->GetScheme(scheme); 664 if (NS_WARN_IF(NS_FAILED(rv))) { 665 return rv; 666 } 667 668 aResult = scheme; 669 aResult.AppendLiteral("://"); 670 // Note we explicitly cleared UserPass above, so do not need to build it. 671 rv = aReferrer->GetAsciiHostPort(asciiHostPort); 672 if (NS_WARN_IF(NS_FAILED(rv))) { 673 return rv; 674 } 675 676 aResult.Append(asciiHostPort); 677 return NS_OK; 678 } 679 680 nsresult ReferrerInfo::TrimReferrerWithPolicy(nsIURI* aReferrer, 681 TrimmingPolicy aTrimmingPolicy, 682 nsACString& aResult) const { 683 MOZ_ASSERT(aReferrer); 684 685 if (aTrimmingPolicy == TrimmingPolicy::ePolicyFullURI) { 686 return aReferrer->GetAsciiSpec(aResult); 687 } 688 689 nsresult rv = GetOriginFromReferrerURI(aReferrer, aResult); 690 if (NS_WARN_IF(NS_FAILED(rv))) { 691 return rv; 692 } 693 694 if (aTrimmingPolicy == TrimmingPolicy::ePolicySchemeHostPortPath) { 695 nsCOMPtr<nsIURL> url(do_QueryInterface(aReferrer)); 696 if (url) { 697 nsAutoCString path; 698 rv = url->GetFilePath(path); 699 if (NS_WARN_IF(NS_FAILED(rv))) { 700 return rv; 701 } 702 703 aResult.Append(path); 704 return NS_OK; 705 } 706 } 707 708 // Step 6 within https://w3c.github.io/webappsec-referrer-policy/#strip-url 709 // states that the trailing "/" does not need to get stripped. However, 710 // GetOriginFromReferrerURI() also removes any trailing "/" hence we have to 711 // add it back here. 712 aResult.AppendLiteral("/"); 713 return NS_OK; 714 } 715 716 bool ReferrerInfo::ShouldIgnoreLessRestrictedPolicies( 717 nsIHttpChannel* aChannel, const ReferrerPolicyEnum aPolicy) const { 718 MOZ_ASSERT(aChannel); 719 720 // We only care about the less restricted policies. 721 if (aPolicy != ReferrerPolicy::Unsafe_url && 722 aPolicy != ReferrerPolicy::No_referrer_when_downgrade && 723 aPolicy != ReferrerPolicy::Origin_when_cross_origin) { 724 return false; 725 } 726 727 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 728 bool isPrivate = NS_UsePrivateBrowsing(aChannel); 729 730 // Return early if we don't want to ignore less restricted policies for the 731 // top navigation. 732 if (loadInfo->GetExternalContentPolicyType() == 733 ExtContentPolicy::TYPE_DOCUMENT) { 734 bool isEnabledForTopNavigation = 735 isPrivate 736 ? StaticPrefs:: 737 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode_top_navigation() 738 : StaticPrefs:: 739 network_http_referer_disallowCrossSiteRelaxingDefault_top_navigation(); 740 if (!isEnabledForTopNavigation) { 741 return false; 742 } 743 744 // We have to get the value of the contentBlockingAllowList earlier because 745 // the channel hasn't been opened yet here. Note that we only need to do 746 // this for first-party navigation. For third-party loads, the value is 747 // inherited from the parent. 748 if (XRE_IsParentProcess()) { 749 nsCOMPtr<nsICookieJarSettings> cookieJarSettings; 750 (void)loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); 751 752 net::CookieJarSettings::Cast(cookieJarSettings) 753 ->UpdateIsOnContentBlockingAllowList(aChannel); 754 } 755 } 756 757 // We don't ignore less restricted referrer policies if ETP is toggled off. 758 // This would affect iframe loads and top navigation. For iframes, it will 759 // stop ignoring if the first-party site toggled ETP off. For top navigation, 760 // it depends on the ETP toggle for the destination site. 761 if (ContentBlockingAllowList::Check(aChannel)) { 762 return false; 763 } 764 765 bool isCrossSite = IsCrossSiteRequest(aChannel); 766 bool isEnabled = 767 isPrivate 768 ? StaticPrefs:: 769 network_http_referer_disallowCrossSiteRelaxingDefault_pbmode() 770 : StaticPrefs:: 771 network_http_referer_disallowCrossSiteRelaxingDefault(); 772 773 if (!isEnabled) { 774 // Log the warning message to console to inform that we will ignore 775 // less restricted policies for cross-site requests in the future. 776 if (isCrossSite) { 777 nsCOMPtr<nsIURI> uri; 778 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 779 NS_ENSURE_SUCCESS(rv, false); 780 781 AutoTArray<nsString, 1> params = { 782 NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())}; 783 LogMessageToConsole(aChannel, "ReferrerPolicyDisallowRelaxingWarning", 784 params); 785 } 786 return false; 787 } 788 789 // Check if the channel is triggered by the system or the extension. 790 auto* triggerBasePrincipal = 791 BasePrincipal::Cast(loadInfo->TriggeringPrincipal()); 792 if (triggerBasePrincipal->IsSystemPrincipal() || 793 triggerBasePrincipal->AddonPolicy()) { 794 return false; 795 } 796 797 if (isCrossSite) { 798 // Log the console message to say that the less restricted policy was 799 // ignored. 800 nsCOMPtr<nsIURI> uri; 801 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 802 NS_ENSURE_SUCCESS(rv, true); 803 804 AutoTArray<nsString, 2> params = { 805 NS_ConvertUTF8toUTF16(GetEnumString(aPolicy)), 806 NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault())}; 807 LogMessageToConsole(aChannel, "ReferrerPolicyDisallowRelaxingMessage", 808 params); 809 } 810 811 return isCrossSite; 812 } 813 814 void ReferrerInfo::LogMessageToConsole( 815 nsIHttpChannel* aChannel, const char* aMsg, 816 const nsTArray<nsString>& aParams) const { 817 MOZ_ASSERT(aChannel); 818 819 nsCOMPtr<nsIURI> uri; 820 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 821 if (NS_WARN_IF(NS_FAILED(rv))) { 822 return; 823 } 824 825 uint64_t windowID = 0; 826 827 rv = aChannel->GetTopLevelContentWindowId(&windowID); 828 if (NS_WARN_IF(NS_FAILED(rv))) { 829 return; 830 } 831 832 if (!windowID) { 833 nsCOMPtr<nsILoadGroup> loadGroup; 834 rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 835 if (NS_WARN_IF(NS_FAILED(rv))) { 836 return; 837 } 838 839 if (loadGroup) { 840 windowID = nsContentUtils::GetInnerWindowID(loadGroup); 841 } 842 } 843 844 nsAutoString localizedMsg; 845 rv = nsContentUtils::FormatLocalizedString( 846 nsContentUtils::eSECURITY_PROPERTIES, aMsg, aParams, localizedMsg); 847 if (NS_WARN_IF(NS_FAILED(rv))) { 848 return; 849 } 850 851 rv = nsContentUtils::ReportToConsoleByWindowID( 852 localizedMsg, nsIScriptError::infoFlag, "Security"_ns, windowID, 853 SourceLocation(std::move(uri))); 854 (void)NS_WARN_IF(NS_FAILED(rv)); 855 } 856 857 ReferrerPolicy ReferrerPolicyIDLToReferrerPolicy( 858 nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy) { 859 switch (aReferrerPolicy) { 860 case nsIReferrerInfo::EMPTY: 861 return ReferrerPolicy::_empty; 862 break; 863 case nsIReferrerInfo::NO_REFERRER: 864 return ReferrerPolicy::No_referrer; 865 break; 866 case nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE: 867 return ReferrerPolicy::No_referrer_when_downgrade; 868 break; 869 case nsIReferrerInfo::ORIGIN: 870 return ReferrerPolicy::Origin; 871 break; 872 case nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN: 873 return ReferrerPolicy::Origin_when_cross_origin; 874 break; 875 case nsIReferrerInfo::UNSAFE_URL: 876 return ReferrerPolicy::Unsafe_url; 877 break; 878 case nsIReferrerInfo::SAME_ORIGIN: 879 return ReferrerPolicy::Same_origin; 880 break; 881 case nsIReferrerInfo::STRICT_ORIGIN: 882 return ReferrerPolicy::Strict_origin; 883 break; 884 case nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN: 885 return ReferrerPolicy::Strict_origin_when_cross_origin; 886 break; 887 default: 888 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value"); 889 break; 890 } 891 892 return ReferrerPolicy::_empty; 893 } 894 895 nsIReferrerInfo::ReferrerPolicyIDL ReferrerPolicyToReferrerPolicyIDL( 896 ReferrerPolicy aReferrerPolicy) { 897 switch (aReferrerPolicy) { 898 case ReferrerPolicy::_empty: 899 return nsIReferrerInfo::EMPTY; 900 break; 901 case ReferrerPolicy::No_referrer: 902 return nsIReferrerInfo::NO_REFERRER; 903 break; 904 case ReferrerPolicy::No_referrer_when_downgrade: 905 return nsIReferrerInfo::NO_REFERRER_WHEN_DOWNGRADE; 906 break; 907 case ReferrerPolicy::Origin: 908 return nsIReferrerInfo::ORIGIN; 909 break; 910 case ReferrerPolicy::Origin_when_cross_origin: 911 return nsIReferrerInfo::ORIGIN_WHEN_CROSS_ORIGIN; 912 break; 913 case ReferrerPolicy::Unsafe_url: 914 return nsIReferrerInfo::UNSAFE_URL; 915 break; 916 case ReferrerPolicy::Same_origin: 917 return nsIReferrerInfo::SAME_ORIGIN; 918 break; 919 case ReferrerPolicy::Strict_origin: 920 return nsIReferrerInfo::STRICT_ORIGIN; 921 break; 922 case ReferrerPolicy::Strict_origin_when_cross_origin: 923 return nsIReferrerInfo::STRICT_ORIGIN_WHEN_CROSS_ORIGIN; 924 break; 925 default: 926 MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value"); 927 break; 928 } 929 930 return nsIReferrerInfo::EMPTY; 931 } 932 933 ReferrerInfo::ReferrerInfo() 934 : mOriginalReferrer(nullptr), 935 mPolicy(ReferrerPolicy::_empty), 936 mOriginalPolicy(ReferrerPolicy::_empty), 937 mSendReferrer(true), 938 mInitialized(false), 939 mOverridePolicyByDefault(false) {} 940 941 ReferrerInfo::ReferrerInfo(const Document& aDoc, const bool aSendReferrer) 942 : ReferrerInfo() { 943 InitWithDocument(&aDoc); 944 mSendReferrer = aSendReferrer; 945 } 946 947 ReferrerInfo::ReferrerInfo(const Element& aElement) : ReferrerInfo() { 948 InitWithElement(&aElement); 949 } 950 951 ReferrerInfo::ReferrerInfo(const Element& aElement, 952 ReferrerPolicyEnum aOverridePolicy) 953 : ReferrerInfo(aElement) { 954 // Override referrer policy if not empty 955 if (aOverridePolicy != ReferrerPolicyEnum::_empty) { 956 mPolicy = aOverridePolicy; 957 mOriginalPolicy = aOverridePolicy; 958 } 959 } 960 961 ReferrerInfo::ReferrerInfo(nsIURI* aOriginalReferrer, 962 ReferrerPolicyEnum aPolicy, bool aSendReferrer, 963 const Maybe<nsCString>& aComputedReferrer) 964 : mOriginalReferrer(aOriginalReferrer), 965 mPolicy(aPolicy), 966 mOriginalPolicy(aPolicy), 967 mSendReferrer(aSendReferrer), 968 mInitialized(true), 969 mOverridePolicyByDefault(false), 970 mComputedReferrer(aComputedReferrer) {} 971 972 ReferrerInfo::ReferrerInfo(const ReferrerInfo& rhs) 973 : mOriginalReferrer(rhs.mOriginalReferrer), 974 mPolicy(rhs.mPolicy), 975 mOriginalPolicy(rhs.mOriginalPolicy), 976 mSendReferrer(rhs.mSendReferrer), 977 mInitialized(rhs.mInitialized), 978 mOverridePolicyByDefault(rhs.mOverridePolicyByDefault), 979 mComputedReferrer(rhs.mComputedReferrer) {} 980 981 already_AddRefed<ReferrerInfo> ReferrerInfo::Clone() const { 982 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this)); 983 return copy.forget(); 984 } 985 986 already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewPolicy( 987 ReferrerPolicyEnum aPolicy) const { 988 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this)); 989 copy->mPolicy = aPolicy; 990 copy->mOriginalPolicy = aPolicy; 991 return copy.forget(); 992 } 993 994 already_AddRefed<ReferrerInfo> ReferrerInfo::CloneWithNewOriginalReferrer( 995 nsIURI* aOriginalReferrer) const { 996 RefPtr<ReferrerInfo> copy(new ReferrerInfo(*this)); 997 copy->mOriginalReferrer = aOriginalReferrer; 998 return copy.forget(); 999 } 1000 1001 NS_IMETHODIMP 1002 ReferrerInfo::GetOriginalReferrer(nsIURI** aOriginalReferrer) { 1003 *aOriginalReferrer = mOriginalReferrer; 1004 NS_IF_ADDREF(*aOriginalReferrer); 1005 return NS_OK; 1006 } 1007 1008 NS_IMETHODIMP 1009 ReferrerInfo::GetReferrerPolicy( 1010 JSContext* aCx, nsIReferrerInfo::ReferrerPolicyIDL* aReferrerPolicy) { 1011 *aReferrerPolicy = ReferrerPolicyToReferrerPolicyIDL(mPolicy); 1012 return NS_OK; 1013 } 1014 1015 NS_IMETHODIMP 1016 ReferrerInfo::GetReferrerPolicyString(nsACString& aResult) { 1017 aResult.AssignASCII(GetEnumString(mPolicy)); 1018 return NS_OK; 1019 } 1020 1021 ReferrerPolicy ReferrerInfo::ReferrerPolicy() { return mPolicy; } 1022 1023 NS_IMETHODIMP 1024 ReferrerInfo::GetSendReferrer(bool* aSendReferrer) { 1025 *aSendReferrer = mSendReferrer; 1026 return NS_OK; 1027 } 1028 1029 NS_IMETHODIMP 1030 ReferrerInfo::Equals(nsIReferrerInfo* aOther, bool* aResult) { 1031 NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG); 1032 MOZ_ASSERT(mInitialized); 1033 if (aOther == this) { 1034 *aResult = true; 1035 return NS_OK; 1036 } 1037 1038 *aResult = false; 1039 ReferrerInfo* other = static_cast<ReferrerInfo*>(aOther); 1040 MOZ_ASSERT(other->mInitialized); 1041 1042 if (mPolicy != other->mPolicy || mSendReferrer != other->mSendReferrer || 1043 mOverridePolicyByDefault != other->mOverridePolicyByDefault || 1044 mComputedReferrer != other->mComputedReferrer) { 1045 return NS_OK; 1046 } 1047 1048 if (!mOriginalReferrer != !other->mOriginalReferrer) { 1049 // One or the other has mOriginalReferrer, but not both... not equal 1050 return NS_OK; 1051 } 1052 1053 bool originalReferrerEquals; 1054 if (mOriginalReferrer && 1055 (NS_FAILED(mOriginalReferrer->Equals(other->mOriginalReferrer, 1056 &originalReferrerEquals)) || 1057 !originalReferrerEquals)) { 1058 return NS_OK; 1059 } 1060 1061 *aResult = true; 1062 return NS_OK; 1063 } 1064 1065 NS_IMETHODIMP 1066 ReferrerInfo::GetComputedReferrerSpec(nsACString& aComputedReferrerSpec) { 1067 aComputedReferrerSpec.Assign( 1068 mComputedReferrer.isSome() ? mComputedReferrer.value() : EmptyCString()); 1069 return NS_OK; 1070 } 1071 1072 already_AddRefed<nsIURI> ReferrerInfo::GetComputedReferrer() { 1073 if (!mComputedReferrer.isSome() || mComputedReferrer.value().IsEmpty()) { 1074 return nullptr; 1075 } 1076 1077 nsCOMPtr<nsIURI> result; 1078 nsresult rv = NS_NewURI(getter_AddRefs(result), mComputedReferrer.value()); 1079 if (NS_FAILED(rv)) { 1080 return nullptr; 1081 } 1082 1083 return result.forget(); 1084 } 1085 1086 HashNumber ReferrerInfo::Hash() const { 1087 MOZ_ASSERT(mInitialized); 1088 nsAutoCString originalReferrerSpec; 1089 if (mOriginalReferrer) { 1090 (void)mOriginalReferrer->GetSpec(originalReferrerSpec); 1091 } 1092 1093 return mozilla::AddToHash( 1094 static_cast<uint32_t>(mPolicy), mSendReferrer, mOverridePolicyByDefault, 1095 mozilla::HashString(originalReferrerSpec), 1096 mozilla::HashString(mComputedReferrer.isSome() ? mComputedReferrer.value() 1097 : ""_ns)); 1098 } 1099 1100 NS_IMETHODIMP 1101 ReferrerInfo::Init(nsIReferrerInfo::ReferrerPolicyIDL aReferrerPolicy, 1102 bool aSendReferrer, nsIURI* aOriginalReferrer) { 1103 MOZ_ASSERT(!mInitialized); 1104 if (mInitialized) { 1105 return NS_ERROR_ALREADY_INITIALIZED; 1106 }; 1107 1108 mPolicy = ReferrerPolicyIDLToReferrerPolicy(aReferrerPolicy); 1109 mOriginalPolicy = mPolicy; 1110 mSendReferrer = aSendReferrer; 1111 mOriginalReferrer = aOriginalReferrer; 1112 mInitialized = true; 1113 return NS_OK; 1114 } 1115 1116 NS_IMETHODIMP 1117 ReferrerInfo::InitWithDocument(const Document* aDocument) { 1118 MOZ_ASSERT(!mInitialized); 1119 if (mInitialized) { 1120 return NS_ERROR_ALREADY_INITIALIZED; 1121 }; 1122 1123 mPolicy = aDocument->GetReferrerPolicy(); 1124 mOriginalPolicy = mPolicy; 1125 mSendReferrer = true; 1126 mOriginalReferrer = aDocument->GetDocumentURIAsReferrer(); 1127 mInitialized = true; 1128 return NS_OK; 1129 } 1130 1131 /** 1132 * Check whether the given node has referrerpolicy attribute and parse 1133 * referrer policy from the attribute. 1134 * Currently, referrerpolicy attribute is supported in a, area, img, iframe, 1135 * script, or link element. 1136 */ 1137 static ReferrerPolicy ReferrerPolicyFromAttribute(const Element& aElement) { 1138 if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area, 1139 nsGkAtoms::script, nsGkAtoms::iframe, 1140 nsGkAtoms::link, nsGkAtoms::img)) { 1141 return ReferrerPolicy::_empty; 1142 } 1143 return aElement.GetReferrerPolicyAsEnum(); 1144 } 1145 1146 static bool HasRelNoReferrer(const Element& aElement) { 1147 // rel=noreferrer is only supported in <a>, <area>, and <form> 1148 if (!aElement.IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area, 1149 nsGkAtoms::form) && 1150 !aElement.IsSVGElement(nsGkAtoms::a)) { 1151 return false; 1152 } 1153 1154 nsAutoString rel; 1155 aElement.GetAttr(nsGkAtoms::rel, rel); 1156 nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(rel); 1157 1158 while (tok.hasMoreTokens()) { 1159 const nsAString& token = tok.nextToken(); 1160 if (token.LowerCaseEqualsLiteral("noreferrer")) { 1161 return true; 1162 } 1163 } 1164 1165 return false; 1166 } 1167 1168 NS_IMETHODIMP 1169 ReferrerInfo::InitWithElement(const Element* aElement) { 1170 MOZ_ASSERT(!mInitialized); 1171 if (mInitialized) { 1172 return NS_ERROR_ALREADY_INITIALIZED; 1173 }; 1174 1175 // Referrer policy from referrerpolicy attribute will have a higher priority 1176 // than referrer policy from <meta> tag and Referrer-Policy header. 1177 mPolicy = ReferrerPolicyFromAttribute(*aElement); 1178 if (mPolicy == ReferrerPolicy::_empty) { 1179 // Fallback to use document's referrer poicy if we don't have referrer 1180 // policy from attribute. 1181 mPolicy = aElement->OwnerDoc()->GetReferrerPolicy(); 1182 } 1183 1184 mOriginalPolicy = mPolicy; 1185 mSendReferrer = !HasRelNoReferrer(*aElement); 1186 mOriginalReferrer = aElement->OwnerDoc()->GetDocumentURIAsReferrer(); 1187 1188 mInitialized = true; 1189 return NS_OK; 1190 } 1191 1192 /* static */ 1193 already_AddRefed<nsIReferrerInfo> 1194 ReferrerInfo::CreateFromDocumentAndPolicyOverride( 1195 Document* aDoc, ReferrerPolicyEnum aPolicyOverride) { 1196 MOZ_ASSERT(aDoc); 1197 ReferrerPolicyEnum policy = aPolicyOverride != ReferrerPolicy::_empty 1198 ? aPolicyOverride 1199 : aDoc->GetReferrerPolicy(); 1200 nsCOMPtr<nsIReferrerInfo> referrerInfo = 1201 new ReferrerInfo(aDoc->GetDocumentURIAsReferrer(), policy); 1202 return referrerInfo.forget(); 1203 } 1204 1205 /* static */ 1206 already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForFetch( 1207 nsIPrincipal* aPrincipal, Document* aDoc) { 1208 MOZ_ASSERT(aPrincipal); 1209 1210 nsCOMPtr<nsIReferrerInfo> referrerInfo; 1211 if (!aPrincipal || aPrincipal->IsSystemPrincipal()) { 1212 referrerInfo = new ReferrerInfo(nullptr); 1213 return referrerInfo.forget(); 1214 } 1215 1216 if (!aDoc) { 1217 aPrincipal->CreateReferrerInfo(ReferrerPolicy::_empty, 1218 getter_AddRefs(referrerInfo)); 1219 return referrerInfo.forget(); 1220 } 1221 1222 // If it weren't for history.push/replaceState, we could just use the 1223 // principal's URI here. But since we want changes to the URI effected 1224 // by push/replaceState to be reflected in the XHR referrer, we have to 1225 // be more clever. 1226 // 1227 // If the document's original URI (before any push/replaceStates) matches 1228 // our principal, then we use the document's current URI (after 1229 // push/replaceStates). Otherwise (if the document is, say, a data: 1230 // URI), we just use the principal's URI. 1231 nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI(); 1232 nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI(); 1233 1234 if (docCurURI && docOrigURI) { 1235 bool equal = false; 1236 aPrincipal->EqualsURI(docOrigURI, &equal); 1237 if (equal) { 1238 referrerInfo = new ReferrerInfo(docCurURI, aDoc->GetReferrerPolicy()); 1239 return referrerInfo.forget(); 1240 } 1241 } 1242 aPrincipal->CreateReferrerInfo(aDoc->GetReferrerPolicy(), 1243 getter_AddRefs(referrerInfo)); 1244 return referrerInfo.forget(); 1245 } 1246 1247 /* static */ 1248 already_AddRefed<nsIReferrerInfo> ReferrerInfo::CreateForExternalCSSResources( 1249 mozilla::StyleSheet* aExternalSheet, nsIURI* aExternalSheetURI, 1250 ReferrerPolicyEnum aPolicy) { 1251 MOZ_ASSERT(aExternalSheet); 1252 MOZ_ASSERT(aExternalSheetURI); 1253 // Step 2 1254 // https://w3c.github.io/webappsec-referrer-policy/#integration-with-css 1255 // Use empty policy at the beginning and update it later from Referrer-Policy 1256 // header. 1257 nsCOMPtr<nsIReferrerInfo> referrerInfo = 1258 new ReferrerInfo(aExternalSheetURI, aPolicy); 1259 return referrerInfo.forget(); 1260 } 1261 1262 /* static */ 1263 already_AddRefed<nsIReferrerInfo> 1264 ReferrerInfo::CreateForInternalCSSAndSVGResources(Document* aDocument) { 1265 MOZ_ASSERT(aDocument); 1266 return do_AddRef(new ReferrerInfo(aDocument->GetDocumentURI(), 1267 aDocument->GetReferrerPolicy())); 1268 } 1269 1270 nsresult ReferrerInfo::ComputeReferrer(nsIHttpChannel* aChannel) { 1271 NS_ENSURE_ARG(aChannel); 1272 MOZ_ASSERT(NS_IsMainThread()); 1273 1274 // If the referrerInfo is passed around when redirect, just use the last 1275 // computedReferrer to recompute 1276 nsCOMPtr<nsIURI> referrer; 1277 nsresult rv = NS_OK; 1278 mOverridePolicyByDefault = false; 1279 1280 if (mComputedReferrer.isSome()) { 1281 if (mComputedReferrer.value().IsEmpty()) { 1282 return NS_OK; 1283 } 1284 1285 rv = NS_NewURI(getter_AddRefs(referrer), mComputedReferrer.value()); 1286 if (NS_WARN_IF(NS_FAILED(rv))) { 1287 return rv; 1288 } 1289 } 1290 1291 mComputedReferrer.reset(); 1292 // Emplace mComputedReferrer with an empty string, which means we have 1293 // computed the referrer and the result referrer value is empty (not send 1294 // referrer). So any early return later than this line will use that empty 1295 // referrer. 1296 mComputedReferrer.emplace(""_ns); 1297 1298 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 1299 nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal(); 1300 1301 // Special treatment for resources injected by add-ons. 1302 if (triggeringPrincipal && 1303 StaticPrefs::privacy_antitracking_isolateContentScriptResources() && 1304 triggeringPrincipal->GetIsAddonOrExpandedAddonPrincipal()) { 1305 mPolicy = ReferrerPolicy::No_referrer; 1306 mOverridePolicyByDefault = true; 1307 return NS_OK; 1308 } 1309 1310 if (!mSendReferrer || !mOriginalReferrer || 1311 mPolicy == ReferrerPolicy::No_referrer) { 1312 return NS_OK; 1313 } 1314 1315 if (mPolicy == ReferrerPolicy::_empty || 1316 ShouldIgnoreLessRestrictedPolicies(aChannel, mOriginalPolicy)) { 1317 OriginAttributes attrs = loadInfo->GetOriginAttributes(); 1318 bool isPrivate = attrs.IsPrivateBrowsing(); 1319 1320 nsCOMPtr<nsIURI> uri; 1321 rv = aChannel->GetURI(getter_AddRefs(uri)); 1322 if (NS_WARN_IF(NS_FAILED(rv))) { 1323 return rv; 1324 } 1325 1326 mPolicy = GetDefaultReferrerPolicy(aChannel, uri, isPrivate); 1327 mOverridePolicyByDefault = true; 1328 } 1329 1330 // This is for the case where the ETP toggle is off. In this case, we need to 1331 // reset the referrer and the policy if the original policy is different from 1332 // the current policy in order to recompute the referrer policy with the 1333 // original policy. 1334 if (!mOverridePolicyByDefault && mOriginalPolicy != ReferrerPolicy::_empty && 1335 mPolicy != mOriginalPolicy) { 1336 referrer = nullptr; 1337 mPolicy = mOriginalPolicy; 1338 } 1339 1340 if (mPolicy == ReferrerPolicy::No_referrer) { 1341 return NS_OK; 1342 } 1343 1344 bool isUserReferrerSendingAllowed = false; 1345 rv = HandleUserReferrerSendingPolicy(aChannel, isUserReferrerSendingAllowed); 1346 if (NS_WARN_IF(NS_FAILED(rv))) { 1347 return rv; 1348 } 1349 1350 if (!isUserReferrerSendingAllowed) { 1351 return NS_OK; 1352 } 1353 1354 // Enforce Referrer allowlist, only http, https scheme are allowed 1355 if (!IsReferrerSchemeAllowed(mOriginalReferrer)) { 1356 return NS_OK; 1357 } 1358 1359 nsCOMPtr<nsIURI> uri; 1360 rv = aChannel->GetURI(getter_AddRefs(uri)); 1361 if (NS_WARN_IF(NS_FAILED(rv))) { 1362 return rv; 1363 } 1364 1365 bool isSecureToInsecureAllowed = false; 1366 rv = HandleSecureToInsecureReferral(mOriginalReferrer, uri, mPolicy, 1367 isSecureToInsecureAllowed); 1368 if (NS_WARN_IF(NS_FAILED(rv))) { 1369 return rv; 1370 } 1371 1372 if (!isSecureToInsecureAllowed) { 1373 return NS_OK; 1374 } 1375 1376 // Strip away any fragment per RFC 2616 section 14.36 1377 // and Referrer Policy section 6.3.5. 1378 if (!referrer) { 1379 rv = NS_GetURIWithoutRef(mOriginalReferrer, getter_AddRefs(referrer)); 1380 if (NS_WARN_IF(NS_FAILED(rv))) { 1381 return rv; 1382 } 1383 } 1384 1385 bool isUserXOriginAllowed = false; 1386 rv = HandleUserXOriginSendingPolicy(uri, referrer, isUserXOriginAllowed); 1387 if (NS_WARN_IF(NS_FAILED(rv))) { 1388 return rv; 1389 } 1390 1391 if (!isUserXOriginAllowed) { 1392 return NS_OK; 1393 } 1394 1395 // Handle user pref network.http.referer.spoofSource, send spoofed referrer if 1396 // desired 1397 if (StaticPrefs::network_http_referer_spoofSource()) { 1398 nsCOMPtr<nsIURI> userSpoofReferrer; 1399 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(userSpoofReferrer)); 1400 if (NS_WARN_IF(NS_FAILED(rv))) { 1401 return rv; 1402 } 1403 referrer = userSpoofReferrer; 1404 } 1405 1406 // strip away any userpass; we don't want to be giving out passwords ;-) 1407 // This is required by Referrer Policy stripping algorithm. 1408 nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(referrer); 1409 referrer = exposableURI; 1410 1411 // Don't send referrer when the request is cross-origin and policy is 1412 // "same-origin". 1413 if (mPolicy == ReferrerPolicy::Same_origin && 1414 IsReferrerCrossOrigin(aChannel, referrer)) { 1415 return NS_OK; 1416 } 1417 1418 TrimmingPolicy trimmingPolicy = ComputeTrimmingPolicy(aChannel, referrer); 1419 1420 nsAutoCString trimmedReferrer; 1421 // We first trim the referrer according to the policy by calling 1422 // 'TrimReferrerWithPolicy' and right after we have to call 1423 // 'LimitReferrerLength' (using the same arguments) because the trimmed 1424 // referrer might exceed the allowed max referrer length. 1425 rv = TrimReferrerWithPolicy(referrer, trimmingPolicy, trimmedReferrer); 1426 if (NS_WARN_IF(NS_FAILED(rv))) { 1427 return rv; 1428 } 1429 1430 rv = LimitReferrerLength(aChannel, referrer, trimmingPolicy, trimmedReferrer); 1431 if (NS_WARN_IF(NS_FAILED(rv))) { 1432 return rv; 1433 } 1434 1435 // finally, remember the referrer spec. 1436 mComputedReferrer.reset(); 1437 mComputedReferrer.emplace(trimmedReferrer); 1438 1439 return NS_OK; 1440 } 1441 1442 /* ===== nsISerializable implementation ====== */ 1443 1444 nsresult ReferrerInfo::ReadTailDataBeforeGecko100( 1445 const uint32_t& aData, nsIObjectInputStream* aInputStream) { 1446 MOZ_ASSERT(aInputStream); 1447 1448 nsCOMPtr<nsIInputStream> reader; 1449 nsCOMPtr<nsIOutputStream> writer; 1450 1451 // We need to create a new pipe in order to read the aData and the rest of 1452 // the input stream together in the old format. This would also help us with 1453 // handling big endian correctly. 1454 NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer)); 1455 1456 nsCOMPtr<nsIBinaryOutputStream> binaryPipeWriter = 1457 NS_NewObjectOutputStream(writer); 1458 1459 // Write back the aData so that we can read bytes from it and handle big 1460 // endian correctly. 1461 nsresult rv = binaryPipeWriter->Write32(aData); 1462 if (NS_WARN_IF(NS_FAILED(rv))) { 1463 return rv; 1464 } 1465 1466 nsCOMPtr<nsIBinaryInputStream> binaryPipeReader = 1467 NS_NewObjectInputStream(reader); 1468 1469 rv = binaryPipeReader->ReadBoolean(&mSendReferrer); 1470 if (NS_WARN_IF(NS_FAILED(rv))) { 1471 return rv; 1472 } 1473 1474 bool isComputed; 1475 rv = binaryPipeReader->ReadBoolean(&isComputed); 1476 if (NS_WARN_IF(NS_FAILED(rv))) { 1477 return rv; 1478 } 1479 1480 // We need to handle the following string if isComputed is true. 1481 if (isComputed) { 1482 // Comsume the following 2 bytes from the input stream. They are the half 1483 // part of the length prefix of the following string. 1484 uint16_t data; 1485 rv = aInputStream->Read16(&data); 1486 if (NS_WARN_IF(NS_FAILED(rv))) { 1487 return rv; 1488 } 1489 1490 // Write the bytes to the pipe so that we can read the length of the string. 1491 rv = binaryPipeWriter->Write16(data); 1492 if (NS_WARN_IF(NS_FAILED(rv))) { 1493 return rv; 1494 } 1495 1496 uint32_t length; 1497 rv = binaryPipeReader->Read32(&length); 1498 if (NS_WARN_IF(NS_FAILED(rv))) { 1499 return rv; 1500 } 1501 1502 // Consume the string body from the input stream. 1503 nsAutoCString computedReferrer; 1504 rv = NS_ConsumeStream(aInputStream, length, computedReferrer); 1505 if (NS_WARN_IF(NS_FAILED(rv))) { 1506 return rv; 1507 } 1508 mComputedReferrer.emplace(computedReferrer); 1509 1510 // Read the remaining two bytes and write to the pipe. 1511 uint16_t remain; 1512 rv = aInputStream->Read16(&remain); 1513 if (NS_WARN_IF(NS_FAILED(rv))) { 1514 return rv; 1515 } 1516 1517 rv = binaryPipeWriter->Write16(remain); 1518 if (NS_WARN_IF(NS_FAILED(rv))) { 1519 return rv; 1520 } 1521 } 1522 1523 rv = binaryPipeReader->ReadBoolean(&mInitialized); 1524 if (NS_WARN_IF(NS_FAILED(rv))) { 1525 return rv; 1526 } 1527 1528 rv = binaryPipeReader->ReadBoolean(&mOverridePolicyByDefault); 1529 if (NS_WARN_IF(NS_FAILED(rv))) { 1530 return rv; 1531 } 1532 1533 return NS_OK; 1534 } 1535 1536 NS_IMETHODIMP 1537 ReferrerInfo::Read(nsIObjectInputStream* aStream) { 1538 bool nonNull; 1539 nsresult rv = aStream->ReadBoolean(&nonNull); 1540 if (NS_WARN_IF(NS_FAILED(rv))) { 1541 return rv; 1542 } 1543 1544 if (nonNull) { 1545 nsAutoCString spec; 1546 nsresult rv = aStream->ReadCString(spec); 1547 if (NS_WARN_IF(NS_FAILED(rv))) { 1548 return rv; 1549 } 1550 1551 rv = NS_NewURI(getter_AddRefs(mOriginalReferrer), spec); 1552 if (NS_WARN_IF(NS_FAILED(rv))) { 1553 return rv; 1554 } 1555 } else { 1556 mOriginalReferrer = nullptr; 1557 } 1558 1559 // ReferrerPolicy.webidl has different order with ReferrerPolicyIDL. We store 1560 // to disk using the order of ReferrerPolicyIDL, so we convert to 1561 // ReferrerPolicyIDL to make it be compatible to the old format. 1562 uint32_t policy; 1563 rv = aStream->Read32(&policy); 1564 if (NS_WARN_IF(NS_FAILED(rv))) { 1565 return rv; 1566 } 1567 mPolicy = ReferrerPolicyIDLToReferrerPolicy( 1568 static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(policy)); 1569 1570 uint32_t originalPolicy; 1571 rv = aStream->Read32(&originalPolicy); 1572 if (NS_WARN_IF(NS_FAILED(rv))) { 1573 return rv; 1574 } 1575 1576 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1784045#c6 for more 1577 // details. 1578 // 1579 // We need to differentiate the old format and the new format here in order 1580 // to be able to read both formats. The check here helps us with verifying 1581 // which format it is. 1582 if (MOZ_UNLIKELY(originalPolicy > 0xFF)) { 1583 mOriginalPolicy = mPolicy; 1584 1585 return ReadTailDataBeforeGecko100(originalPolicy, aStream); 1586 } 1587 1588 mOriginalPolicy = ReferrerPolicyIDLToReferrerPolicy( 1589 static_cast<nsIReferrerInfo::ReferrerPolicyIDL>(originalPolicy)); 1590 1591 rv = aStream->ReadBoolean(&mSendReferrer); 1592 if (NS_WARN_IF(NS_FAILED(rv))) { 1593 return rv; 1594 } 1595 1596 bool isComputed; 1597 rv = aStream->ReadBoolean(&isComputed); 1598 if (NS_WARN_IF(NS_FAILED(rv))) { 1599 return rv; 1600 } 1601 1602 if (isComputed) { 1603 nsAutoCString computedReferrer; 1604 rv = aStream->ReadCString(computedReferrer); 1605 if (NS_WARN_IF(NS_FAILED(rv))) { 1606 return rv; 1607 } 1608 mComputedReferrer.emplace(computedReferrer); 1609 } 1610 1611 rv = aStream->ReadBoolean(&mInitialized); 1612 if (NS_WARN_IF(NS_FAILED(rv))) { 1613 return rv; 1614 } 1615 1616 rv = aStream->ReadBoolean(&mOverridePolicyByDefault); 1617 if (NS_WARN_IF(NS_FAILED(rv))) { 1618 return rv; 1619 } 1620 1621 return NS_OK; 1622 } 1623 1624 NS_IMETHODIMP 1625 ReferrerInfo::Write(nsIObjectOutputStream* aStream) { 1626 bool nonNull = (mOriginalReferrer != nullptr); 1627 nsresult rv = aStream->WriteBoolean(nonNull); 1628 if (NS_WARN_IF(NS_FAILED(rv))) { 1629 return rv; 1630 } 1631 1632 if (nonNull) { 1633 nsAutoCString spec; 1634 nsresult rv = mOriginalReferrer->GetSpec(spec); 1635 if (NS_WARN_IF(NS_FAILED(rv))) { 1636 return rv; 1637 } 1638 1639 rv = aStream->WriteStringZ(spec.get()); 1640 if (NS_WARN_IF(NS_FAILED(rv))) { 1641 return rv; 1642 } 1643 } 1644 1645 rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mPolicy)); 1646 if (NS_WARN_IF(NS_FAILED(rv))) { 1647 return rv; 1648 } 1649 1650 rv = aStream->Write32(ReferrerPolicyToReferrerPolicyIDL(mOriginalPolicy)); 1651 if (NS_WARN_IF(NS_FAILED(rv))) { 1652 return rv; 1653 } 1654 1655 rv = aStream->WriteBoolean(mSendReferrer); 1656 if (NS_WARN_IF(NS_FAILED(rv))) { 1657 return rv; 1658 } 1659 1660 bool isComputed = mComputedReferrer.isSome(); 1661 rv = aStream->WriteBoolean(isComputed); 1662 if (NS_WARN_IF(NS_FAILED(rv))) { 1663 return rv; 1664 } 1665 1666 if (isComputed) { 1667 rv = aStream->WriteStringZ(mComputedReferrer.value().get()); 1668 if (NS_WARN_IF(NS_FAILED(rv))) { 1669 return rv; 1670 } 1671 } 1672 1673 rv = aStream->WriteBoolean(mInitialized); 1674 if (NS_WARN_IF(NS_FAILED(rv))) { 1675 return rv; 1676 } 1677 1678 rv = aStream->WriteBoolean(mOverridePolicyByDefault); 1679 if (NS_WARN_IF(NS_FAILED(rv))) { 1680 return rv; 1681 } 1682 return NS_OK; 1683 } 1684 1685 void ReferrerInfo::RecordTelemetry(nsIHttpChannel* aChannel) { 1686 #ifdef DEBUG 1687 MOZ_ASSERT(!mTelemetryRecorded); 1688 mTelemetryRecorded = true; 1689 #endif // DEBUG 1690 1691 // The telemetry probe has 18 buckets. The first 9 buckets are for same-site 1692 // requests and the rest 9 buckets are for cross-site requests. 1693 uint32_t telemetryOffset = 1694 IsCrossSiteRequest(aChannel) 1695 ? UnderlyingValue( 1696 MaxContiguousEnumValue<dom::ReferrerPolicy>::value) + 1697 1 1698 : 0; 1699 1700 glean::security::referrer_policy_count.AccumulateSingleSample( 1701 static_cast<uint32_t>(mPolicy) + telemetryOffset); 1702 } 1703 1704 } // namespace mozilla::dom 1705 1706 #undef LOG 1707 #undef LOG_ENABLED