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 "ThirdPartyUtil.h"
10 #include "MainThreadUtils.h"
11 #include "mozIDOMWindow.h"
12 #include "mozilla/AlreadyAddRefed.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/BasePrincipal.h"
15 #include "mozilla/ClearOnShutdown.h"
16 #include "mozilla/ContentBlockingNotifier.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/MacroForEach.h"
19 #include "mozilla/Components.h"
20 #include "mozilla/StaticPtr.h"
21 #include "mozilla/StorageAccess.h"
22 #include "mozilla/TextUtils.h"
23 #include "mozilla/Unused.h"
24 #include "mozilla/dom/BrowsingContext.h"
25 #include "mozilla/dom/CanonicalBrowsingContext.h"
26 #include "mozilla/dom/Document.h"
27 #include "mozilla/dom/WindowContext.h"
28 #include "mozilla/dom/WindowGlobalParent.h"
31 #include "nsEffectiveTLDService.h"
33 #include "nsGlobalWindowOuter.h"
34 #include "nsIChannel.h"
35 #include "nsIClassifiedChannel.h"
36 #include "nsIContentPolicy.h"
37 #include "nsIHttpChannelInternal.h"
38 #include "nsILoadContext.h"
39 #include "nsILoadInfo.h"
40 #include "nsIPrincipal.h"
41 #include "nsIScriptObjectPrincipal.h"
43 #include "nsLiteralString.h"
44 #include "nsNetUtil.h"
45 #include "nsPIDOMWindow.h"
46 #include "nsPIDOMWindowInlines.h"
47 #include "nsServiceManagerUtils.h"
48 #include "nsTLiteralString.h"
50 using namespace mozilla
;
51 using namespace mozilla::dom
;
53 NS_IMPL_ISUPPORTS(ThirdPartyUtil
, mozIThirdPartyUtil
)
56 // MOZ_LOG=thirdPartyUtil:5
58 static mozilla::LazyLogModule
gThirdPartyLog("thirdPartyUtil");
60 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
62 static mozilla::StaticRefPtr
<ThirdPartyUtil
> gService
;
65 void ThirdPartyUtil::Startup() {
66 nsCOMPtr
<mozIThirdPartyUtil
> tpu
;
67 if (NS_WARN_IF(!(tpu
= do_GetService(THIRDPARTYUTIL_CONTRACTID
)))) {
68 NS_WARNING("Failed to get third party util!");
72 nsresult
ThirdPartyUtil::Init() {
73 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE
);
75 MOZ_ASSERT(!gService
);
77 mozilla::ClearOnShutdown(&gService
);
79 mTLDService
= nsEffectiveTLDService::GetInstance();
80 return mTLDService
? NS_OK
: NS_ERROR_FAILURE
;
83 ThirdPartyUtil::~ThirdPartyUtil() { gService
= nullptr; }
86 ThirdPartyUtil
* ThirdPartyUtil::GetInstance() {
90 nsCOMPtr
<mozIThirdPartyUtil
> tpuService
=
91 mozilla::components::ThirdPartyUtil::Service();
97 "gService must have been initialized in nsEffectiveTLDService::Init");
101 // Determine if aFirstDomain is a different base domain to aSecondURI; or, if
102 // the concept of base domain does not apply, determine if the two hosts are not
104 nsresult
ThirdPartyUtil::IsThirdPartyInternal(const nsCString
& aFirstDomain
,
108 return NS_ERROR_INVALID_ARG
;
111 // BlobURLs are always first-party.
112 if (aSecondURI
->SchemeIs("blob")) {
117 // Get the base domain for aSecondURI.
118 nsAutoCString secondDomain
;
119 nsresult rv
= GetBaseDomain(aSecondURI
, secondDomain
);
120 LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain
.get(),
121 secondDomain
.get()));
126 *aResult
= IsThirdPartyInternal(aFirstDomain
, secondDomain
);
130 nsCString
ThirdPartyUtil::GetBaseDomainFromWindow(nsPIDOMWindowOuter
* aWindow
) {
131 mozilla::dom::Document
* doc
= aWindow
? aWindow
->GetExtantDoc() : nullptr;
137 return doc
->GetBaseDomain();
141 ThirdPartyUtil::GetPrincipalFromWindow(mozIDOMWindowProxy
* aWin
,
142 nsIPrincipal
** result
) {
143 nsCOMPtr
<nsIScriptObjectPrincipal
> scriptObjPrin
= do_QueryInterface(aWin
);
144 if (!scriptObjPrin
) {
145 return NS_ERROR_INVALID_ARG
;
148 nsCOMPtr
<nsIPrincipal
> prin
= scriptObjPrin
->GetPrincipal();
150 return NS_ERROR_INVALID_ARG
;
157 // Get the URI associated with a window.
159 ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy
* aWin
, nsIURI
** result
) {
160 nsCOMPtr
<nsIPrincipal
> prin
;
161 nsresult rv
= GetPrincipalFromWindow(aWin
, getter_AddRefs(prin
));
166 if (prin
->GetIsNullPrincipal()) {
167 LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
168 return NS_ERROR_INVALID_ARG
;
170 auto* basePrin
= BasePrincipal::Cast(prin
);
171 return basePrin
->GetURI(result
);
174 // Determine if aFirstURI is third party with respect to aSecondURI. See docs
175 // for mozIThirdPartyUtil.
177 ThirdPartyUtil::IsThirdPartyURI(nsIURI
* aFirstURI
, nsIURI
* aSecondURI
,
179 NS_ENSURE_ARG(aFirstURI
);
180 NS_ENSURE_ARG(aSecondURI
);
181 NS_ASSERTION(aResult
, "null outparam pointer");
183 nsAutoCString firstHost
;
184 nsresult rv
= GetBaseDomain(aFirstURI
, firstHost
);
189 return IsThirdPartyInternal(firstHost
, aSecondURI
, aResult
);
192 // If the optional aURI is provided, determine whether aWindow is foreign with
193 // respect to aURI. If the optional aURI is not provided, determine whether the
194 // given "window hierarchy" is third party. See docs for mozIThirdPartyUtil.
196 ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy
* aWindow
, nsIURI
* aURI
,
198 NS_ENSURE_ARG(aWindow
);
199 NS_ASSERTION(aResult
, "null outparam pointer");
203 // Ignore about:blank URIs here since they have no domain and attempting to
204 // compare against them will fail.
205 if (aURI
&& !NS_IsAboutBlank(aURI
)) {
206 nsCOMPtr
<nsIPrincipal
> prin
;
207 nsresult rv
= GetPrincipalFromWindow(aWindow
, getter_AddRefs(prin
));
208 NS_ENSURE_SUCCESS(rv
, rv
);
209 // Determine whether aURI is foreign with respect to the current principal.
210 rv
= prin
->IsThirdPartyURI(aURI
, &result
);
221 nsPIDOMWindowOuter
* current
= nsPIDOMWindowOuter::From(aWindow
);
222 auto* const browsingContext
= current
->GetBrowsingContext();
223 MOZ_ASSERT(browsingContext
);
225 WindowContext
* wc
= browsingContext
->GetCurrentWindowContext();
226 if (NS_WARN_IF(!wc
)) {
231 *aResult
= wc
->GetIsThirdPartyWindow();
235 nsresult
ThirdPartyUtil::IsThirdPartyGlobal(
236 mozilla::dom::WindowGlobalParent
* aWindowGlobal
, bool* aResult
) {
237 NS_ENSURE_ARG(aWindowGlobal
);
238 NS_ASSERTION(aResult
, "null outparam pointer");
240 auto* currentWGP
= aWindowGlobal
;
242 MOZ_ASSERT(currentWGP
->BrowsingContext());
243 if (currentWGP
->BrowsingContext()->IsTop()) {
247 nsCOMPtr
<nsIPrincipal
> currentPrincipal
= currentWGP
->DocumentPrincipal();
248 RefPtr
<WindowGlobalParent
> parent
=
249 currentWGP
->BrowsingContext()->GetEmbedderWindowGlobal();
251 return NS_ERROR_FAILURE
;
253 nsCOMPtr
<nsIPrincipal
> parentPrincipal
= parent
->DocumentPrincipal();
255 currentPrincipal
->IsThirdPartyPrincipal(parentPrincipal
, aResult
);
268 // Determine if the URI associated with aChannel or any URI of the window
269 // hierarchy associated with the channel is foreign with respect to aSecondURI.
270 // See docs for mozIThirdPartyUtil.
272 ThirdPartyUtil::IsThirdPartyChannel(nsIChannel
* aChannel
, nsIURI
* aURI
,
274 LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel
));
275 NS_ENSURE_ARG(aChannel
);
276 NS_ASSERTION(aResult
, "null outparam pointer");
279 bool doForce
= false;
280 nsCOMPtr
<nsIHttpChannelInternal
> httpChannelInternal
=
281 do_QueryInterface(aChannel
);
282 if (httpChannelInternal
) {
284 // Avoid checking the return value here since some channel implementations
285 // may return NS_ERROR_NOT_IMPLEMENTED.
286 mozilla::Unused
<< httpChannelInternal
->GetThirdPartyFlags(&flags
);
288 doForce
= (flags
& nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW
);
290 // If aURI was not supplied, and we're forcing, then we're by definition
291 // not foreign. If aURI was supplied, we still want to check whether it's
292 // foreign with respect to the channel URI. (The forcing only applies to
293 // whatever window hierarchy exists above the channel.)
294 if (doForce
&& !aURI
) {
300 bool parentIsThird
= false;
301 nsAutoCString channelDomain
;
303 // Obtain the URI from the channel, and its base domain.
304 nsCOMPtr
<nsIURI
> channelURI
;
305 rv
= NS_GetFinalChannelURI(aChannel
, getter_AddRefs(channelURI
));
310 BasePrincipal
* loadingPrincipal
= nullptr;
311 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
314 parentIsThird
= loadInfo
->GetIsInThirdPartyContext();
315 if (!parentIsThird
&& loadInfo
->GetExternalContentPolicyType() !=
316 ExtContentPolicy::TYPE_DOCUMENT
) {
317 // Check if the channel itself is third-party to its own requestor.
318 // Unfortunately, we have to go through the loading principal.
319 loadingPrincipal
= BasePrincipal::Cast(loadInfo
->GetLoadingPrincipal());
323 // Special consideration must be done for about:blank URIs because those
324 // inherit the principal from the parent context. For them, let's consider the
326 if (NS_IsAboutBlank(channelURI
)) {
327 nsCOMPtr
<nsIPrincipal
> principalToInherit
=
328 loadInfo
->FindPrincipalToInherit(aChannel
);
329 if (!principalToInherit
) {
334 rv
= principalToInherit
->GetBaseDomain(channelDomain
);
339 if (loadingPrincipal
) {
340 rv
= loadingPrincipal
->IsThirdPartyPrincipal(principalToInherit
,
347 rv
= GetBaseDomain(channelURI
, channelDomain
);
352 if (loadingPrincipal
) {
353 rv
= loadingPrincipal
->IsThirdPartyURI(channelURI
, &parentIsThird
);
360 // If we're not comparing to a URI, we have our answer. Otherwise, if
361 // parentIsThird, we're not forcing and we know that we're a third-party
363 if (!aURI
|| parentIsThird
) {
364 *aResult
= parentIsThird
;
368 // Determine whether aURI is foreign with respect to channelURI.
369 return IsThirdPartyInternal(channelDomain
, aURI
, aResult
);
373 ThirdPartyUtil::GetTopWindowForChannel(nsIChannel
* aChannel
,
374 nsIURI
* aURIBeingLoaded
,
375 mozIDOMWindowProxy
** aWin
) {
378 // Find the associated window and its parent window.
379 nsCOMPtr
<nsILoadContext
> ctx
;
380 NS_QueryNotificationCallbacks(aChannel
, ctx
);
382 return NS_ERROR_INVALID_ARG
;
385 nsCOMPtr
<mozIDOMWindowProxy
> window
;
386 ctx
->GetAssociatedWindow(getter_AddRefs(window
));
388 return NS_ERROR_INVALID_ARG
;
391 nsCOMPtr
<nsPIDOMWindowOuter
> top
=
392 nsGlobalWindowOuter::Cast(window
)
393 ->GetTopExcludingExtensionAccessibleContentFrames(aURIBeingLoaded
);
398 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
399 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
400 // dot may be present. If aHostURI is an IP address, an alias such as
401 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
402 // be the exact host. The result of this function should only be used in exact
403 // string comparisons, since substring comparisons will not be valid for the
404 // special cases elided above.
406 ThirdPartyUtil::GetBaseDomain(nsIURI
* aHostURI
, nsACString
& aBaseDomain
) {
408 return NS_ERROR_INVALID_ARG
;
411 // Get the base domain. this will fail if the host contains a leading dot,
412 // more than one trailing dot, or is otherwise malformed.
413 nsresult rv
= mTLDService
->GetBaseDomain(aHostURI
, 0, aBaseDomain
);
414 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
415 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
416 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
417 // such as 'co.uk', or the empty string. Uses the normalized host in such
419 rv
= aHostURI
->GetAsciiHost(aBaseDomain
);
426 // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail.
427 if (aBaseDomain
.Length() == 1 && aBaseDomain
.Last() == '.')
428 return NS_ERROR_INVALID_ARG
;
430 // Reject any URIs without a host that aren't file:// URIs. This makes it the
431 // only way we can get a base domain consisting of the empty string, which
432 // means we can safely perform foreign tests on such URIs where "not foreign"
433 // means "the involved URIs are all file://".
434 if (aBaseDomain
.IsEmpty() && !aHostURI
->SchemeIs("file")) {
435 return NS_ERROR_INVALID_ARG
;
442 ThirdPartyUtil::GetBaseDomainFromSchemeHost(const nsACString
& aScheme
,
443 const nsACString
& aAsciiHost
,
444 nsACString
& aBaseDomain
) {
445 MOZ_DIAGNOSTIC_ASSERT(IsAscii(aAsciiHost
));
447 // Get the base domain. this will fail if the host contains a leading dot,
448 // more than one trailing dot, or is otherwise malformed.
449 nsresult rv
= mTLDService
->GetBaseDomainFromHost(aAsciiHost
, 0, aBaseDomain
);
450 if (rv
== NS_ERROR_HOST_IS_IP_ADDRESS
||
451 rv
== NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
) {
452 // aMozURL is either an IP address, an alias such as 'localhost', an eTLD
453 // such as 'co.uk', or the empty string. Uses the normalized host in such
455 aBaseDomain
= aAsciiHost
;
458 NS_ENSURE_SUCCESS(rv
, rv
);
460 // aMozURL (and thus aBaseDomain) may be the string '.'. If so, fail.
461 if (aBaseDomain
.Length() == 1 && aBaseDomain
.Last() == '.')
462 return NS_ERROR_INVALID_ARG
;
464 // Reject any URLs without a host that aren't file:// URLs. This makes it the
465 // only way we can get a base domain consisting of the empty string, which
466 // means we can safely perform foreign tests on such URLs where "not foreign"
467 // means "the involved URLs are all file://".
468 if (aBaseDomain
.IsEmpty() && !aScheme
.EqualsLiteral("file")) {
469 return NS_ERROR_INVALID_ARG
;
475 NS_IMETHODIMP_(ThirdPartyAnalysisResult
)
476 ThirdPartyUtil::AnalyzeChannel(nsIChannel
* aChannel
, bool aNotify
, nsIURI
* aURI
,
477 RequireThirdPartyCheck aRequireThirdPartyCheck
,
478 uint32_t* aRejectedReason
) {
479 MOZ_ASSERT_IF(aNotify
, aRejectedReason
);
481 ThirdPartyAnalysisResult result
;
483 nsCOMPtr
<nsIURI
> uri
;
484 if (!aURI
&& aChannel
) {
485 aChannel
->GetURI(getter_AddRefs(uri
));
487 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
? aChannel
->LoadInfo() : nullptr;
489 bool isForeign
= true;
491 (!aRequireThirdPartyCheck
|| aRequireThirdPartyCheck(loadInfo
))) {
492 IsThirdPartyChannel(aChannel
, aURI
? aURI
: uri
.get(), &isForeign
);
495 result
+= ThirdPartyAnalysis::IsForeign
;
498 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
499 do_QueryInterface(aChannel
);
500 if (classifiedChannel
) {
501 if (classifiedChannel
->IsThirdPartyTrackingResource()) {
502 result
+= ThirdPartyAnalysis::IsThirdPartyTrackingResource
;
504 if (classifiedChannel
->IsThirdPartySocialTrackingResource()) {
505 result
+= ThirdPartyAnalysis::IsThirdPartySocialTrackingResource
;
508 // Check first-party storage access even for non-tracking resources, since
509 // we will need the result when computing the access rights for the reject
510 // foreign cookie behavior mode.
512 // If the caller has requested third-party checks, we will only perform the
513 // storage access check once we know we're in the third-party context.
514 bool performStorageChecks
=
515 aRequireThirdPartyCheck
? result
.contains(ThirdPartyAnalysis::IsForeign
)
517 if (performStorageChecks
&&
518 ShouldAllowAccessFor(aChannel
, aURI
? aURI
: uri
.get(),
520 result
+= ThirdPartyAnalysis::IsStorageAccessPermissionGranted
;
523 if (aNotify
&& !result
.contains(
524 ThirdPartyAnalysis::IsStorageAccessPermissionGranted
)) {
525 ContentBlockingNotifier::OnDecision(
526 aChannel
, ContentBlockingNotifier::BlockingDecision::eBlock
,