Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / ThirdPartyUtil.cpp
blob0ced8aa7d8ee8c0173de98a18475d42466ef3f78
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"
9 #include <cstdint>
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"
29 #include "nsCOMPtr.h"
30 #include "nsDebug.h"
31 #include "nsEffectiveTLDService.h"
32 #include "nsError.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"
42 #include "nsIURI.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");
59 #undef LOG
60 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
62 static mozilla::StaticRefPtr<ThirdPartyUtil> gService;
64 // static
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);
76 gService = this;
77 mozilla::ClearOnShutdown(&gService);
79 mTLDService = nsEffectiveTLDService::GetInstance();
80 return mTLDService ? NS_OK : NS_ERROR_FAILURE;
83 ThirdPartyUtil::~ThirdPartyUtil() { gService = nullptr; }
85 // static
86 ThirdPartyUtil* ThirdPartyUtil::GetInstance() {
87 if (gService) {
88 return gService;
90 nsCOMPtr<mozIThirdPartyUtil> tpuService =
91 mozilla::components::ThirdPartyUtil::Service();
92 if (!tpuService) {
93 return nullptr;
95 MOZ_ASSERT(
96 gService,
97 "gService must have been initialized in nsEffectiveTLDService::Init");
98 return gService;
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
103 // string-identical.
104 nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
105 nsIURI* aSecondURI,
106 bool* aResult) {
107 if (!aSecondURI) {
108 return NS_ERROR_INVALID_ARG;
111 // BlobURLs are always first-party.
112 if (aSecondURI->SchemeIs("blob")) {
113 *aResult = false;
114 return NS_OK;
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()));
122 if (NS_FAILED(rv)) {
123 return rv;
126 *aResult = IsThirdPartyInternal(aFirstDomain, secondDomain);
127 return NS_OK;
130 nsCString ThirdPartyUtil::GetBaseDomainFromWindow(nsPIDOMWindowOuter* aWindow) {
131 mozilla::dom::Document* doc = aWindow ? aWindow->GetExtantDoc() : nullptr;
133 if (!doc) {
134 return ""_ns;
137 return doc->GetBaseDomain();
140 NS_IMETHODIMP
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();
149 if (!prin) {
150 return NS_ERROR_INVALID_ARG;
153 prin.forget(result);
154 return NS_OK;
157 // Get the URI associated with a window.
158 NS_IMETHODIMP
159 ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) {
160 nsCOMPtr<nsIPrincipal> prin;
161 nsresult rv = GetPrincipalFromWindow(aWin, getter_AddRefs(prin));
162 if (NS_FAILED(rv)) {
163 return rv;
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.
176 NS_IMETHODIMP
177 ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI,
178 bool* aResult) {
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);
185 if (NS_FAILED(rv)) {
186 return rv;
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.
195 NS_IMETHODIMP
196 ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI,
197 bool* aResult) {
198 NS_ENSURE_ARG(aWindow);
199 NS_ASSERTION(aResult, "null outparam pointer");
201 bool result;
203 // Ignore about:blank and about:srcdoc URIs here since they have no domain
204 // and attempting to compare against them will fail.
205 if (aURI && !NS_IsAboutBlank(aURI) && !NS_IsAboutSrcdoc(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);
211 if (NS_FAILED(rv)) {
212 return rv;
215 if (result) {
216 *aResult = true;
217 return NS_OK;
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)) {
227 *aResult = true;
228 return NS_OK;
231 *aResult = wc->GetIsThirdPartyWindow();
232 return NS_OK;
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;
241 do {
242 MOZ_ASSERT(currentWGP->BrowsingContext());
243 if (currentWGP->BrowsingContext()->IsTop()) {
244 *aResult = false;
245 return NS_OK;
247 nsCOMPtr<nsIPrincipal> currentPrincipal = currentWGP->DocumentPrincipal();
248 RefPtr<WindowGlobalParent> parent =
249 currentWGP->BrowsingContext()->GetEmbedderWindowGlobal();
250 if (!parent) {
251 return NS_ERROR_FAILURE;
253 nsCOMPtr<nsIPrincipal> parentPrincipal = parent->DocumentPrincipal();
254 nsresult rv =
255 currentPrincipal->IsThirdPartyPrincipal(parentPrincipal, aResult);
256 if (NS_FAILED(rv)) {
257 return rv;
260 if (*aResult) {
261 return NS_OK;
264 currentWGP = parent;
265 } while (true);
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.
271 NS_IMETHODIMP
272 ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel, nsIURI* aURI,
273 bool* aResult) {
274 LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel));
275 NS_ENSURE_ARG(aChannel);
276 NS_ASSERTION(aResult, "null outparam pointer");
278 nsresult rv;
279 bool doForce = false;
280 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
281 do_QueryInterface(aChannel);
282 if (httpChannelInternal) {
283 uint32_t flags = 0;
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) {
295 *aResult = false;
296 return NS_OK;
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));
306 if (NS_FAILED(rv)) {
307 return rv;
310 BasePrincipal* loadingPrincipal = nullptr;
311 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
313 if (!doForce) {
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 and about:srcdoc URIs
324 // because those inherit the principal from the parent context. For them,
325 // let's consider the principal URI.
326 if (NS_IsAboutBlank(channelURI) || NS_IsAboutSrcdoc(channelURI)) {
327 nsCOMPtr<nsIPrincipal> principalToInherit =
328 loadInfo->FindPrincipalToInherit(aChannel);
329 if (!principalToInherit) {
330 *aResult = true;
331 return NS_OK;
334 rv = principalToInherit->GetBaseDomain(channelDomain);
335 if (NS_FAILED(rv)) {
336 return rv;
339 if (loadingPrincipal) {
340 rv = loadingPrincipal->IsThirdPartyPrincipal(principalToInherit,
341 &parentIsThird);
342 if (NS_FAILED(rv)) {
343 return rv;
346 } else {
347 rv = GetBaseDomain(channelURI, channelDomain);
348 if (NS_FAILED(rv)) {
349 return rv;
352 if (loadingPrincipal) {
353 rv = loadingPrincipal->IsThirdPartyURI(channelURI, &parentIsThird);
354 if (NS_FAILED(rv)) {
355 return rv;
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
362 // request.
363 if (!aURI || parentIsThird) {
364 *aResult = parentIsThird;
365 return NS_OK;
368 // Determine whether aURI is foreign with respect to channelURI.
369 return IsThirdPartyInternal(channelDomain, aURI, aResult);
372 NS_IMETHODIMP
373 ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel,
374 nsIURI* aURIBeingLoaded,
375 mozIDOMWindowProxy** aWin) {
376 NS_ENSURE_ARG(aWin);
378 // Find the associated window and its parent window.
379 nsCOMPtr<nsILoadContext> ctx;
380 NS_QueryNotificationCallbacks(aChannel, ctx);
381 if (!ctx) {
382 return NS_ERROR_INVALID_ARG;
385 nsCOMPtr<mozIDOMWindowProxy> window;
386 ctx->GetAssociatedWindow(getter_AddRefs(window));
387 if (!window) {
388 return NS_ERROR_INVALID_ARG;
391 nsCOMPtr<nsPIDOMWindowOuter> top =
392 nsGlobalWindowOuter::Cast(window)
393 ->GetTopExcludingExtensionAccessibleContentFrames(aURIBeingLoaded);
394 top.forget(aWin);
395 return NS_OK;
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.
405 NS_IMETHODIMP
406 ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) {
407 if (!aHostURI) {
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
418 // cases.
419 rv = aHostURI->GetAsciiHost(aBaseDomain);
422 if (NS_FAILED(rv)) {
423 return rv;
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;
438 return NS_OK;
441 NS_IMETHODIMP
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
454 // cases.
455 aBaseDomain = aAsciiHost;
456 rv = NS_OK;
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;
472 return NS_OK;
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;
490 if (aChannel &&
491 (!aRequireThirdPartyCheck || aRequireThirdPartyCheck(loadInfo))) {
492 IsThirdPartyChannel(aChannel, aURI ? aURI : uri.get(), &isForeign);
494 if (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)
516 : true;
517 if (performStorageChecks &&
518 ShouldAllowAccessFor(aChannel, aURI ? aURI : uri.get(),
519 aRejectedReason)) {
520 result += ThirdPartyAnalysis::IsStorageAccessPermissionGranted;
523 if (aNotify && !result.contains(
524 ThirdPartyAnalysis::IsStorageAccessPermissionGranted)) {
525 ContentBlockingNotifier::OnDecision(
526 aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
527 *aRejectedReason);
531 return result;