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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AntiTrackingLog.h"
8 #include "ContentBlockingAllowList.h"
9 
10 #include "mozilla/dom/BrowsingContext.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/PermissionManager.h"
13 #include "mozilla/ScopeExit.h"
14 #include "nsContentUtils.h"
15 #include "nsIHttpChannel.h"
16 #include "nsIHttpChannelInternal.h"
17 
18 using namespace mozilla;
19 
Check(nsIPrincipal * aTopWinPrincipal,bool aIsPrivateBrowsing)20 /* static */ bool ContentBlockingAllowList::Check(
21     nsIPrincipal* aTopWinPrincipal, bool aIsPrivateBrowsing) {
22   bool isAllowed = false;
23   nsresult rv = Check(aTopWinPrincipal, aIsPrivateBrowsing, isAllowed);
24   if (NS_SUCCEEDED(rv) && isAllowed) {
25     LOG(
26         ("The top-level window is on the content blocking allow list, "
27          "bail out early"));
28     return true;
29   }
30   if (NS_FAILED(rv)) {
31     LOG(("Checking the content blocking allow list for failed with %" PRIx32,
32          static_cast<uint32_t>(rv)));
33   }
34   return false;
35 }
36 
Check(nsICookieJarSettings * aCookieJarSettings)37 /* static */ bool ContentBlockingAllowList::Check(
38     nsICookieJarSettings* aCookieJarSettings) {
39   if (!aCookieJarSettings) {
40     LOG(
41         ("Could not check the content blocking allow list because the cookie "
42          "jar settings wasn't available"));
43     return false;
44   }
45 
46   return aCookieJarSettings->GetIsOnContentBlockingAllowList();
47 }
48 
Check(nsPIDOMWindowInner * aWindow)49 /* static */ bool ContentBlockingAllowList::Check(nsPIDOMWindowInner* aWindow) {
50   // TODO: this is a quick fix to ensure that we allow storage permission for
51   // a chrome window. We should check if there is a better way to do this in
52   // Bug 1626223.
53   if (nsGlobalWindowInner::Cast(aWindow)->GetPrincipal() ==
54       nsContentUtils::GetSystemPrincipal()) {
55     return true;
56   }
57 
58   // We can check the IsOnContentBlockingAllowList flag in the document's
59   // CookieJarSettings. Because this flag represents the fact that whether the
60   // top-level document is on the content blocking allow list. And this flag was
61   // propagated from the top-level as the CookieJarSettings inherits from the
62   // parent.
63   RefPtr<dom::Document> doc = nsGlobalWindowInner::Cast(aWindow)->GetDocument();
64 
65   if (!doc) {
66     LOG(
67         ("Could not check the content blocking allow list because the document "
68          "wasn't available"));
69     return false;
70   }
71 
72   nsCOMPtr<nsICookieJarSettings> cookieJarSettings = doc->CookieJarSettings();
73 
74   return ContentBlockingAllowList::Check(cookieJarSettings);
75 }
76 
Check(nsIHttpChannel * aChannel)77 /* static */ bool ContentBlockingAllowList::Check(nsIHttpChannel* aChannel) {
78   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
79   nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
80 
81   Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
82 
83   return ContentBlockingAllowList::Check(cookieJarSettings);
84 }
85 
Check(nsIPrincipal * aContentBlockingAllowListPrincipal,bool aIsPrivateBrowsing,bool & aIsAllowListed)86 nsresult ContentBlockingAllowList::Check(
87     nsIPrincipal* aContentBlockingAllowListPrincipal, bool aIsPrivateBrowsing,
88     bool& aIsAllowListed) {
89   MOZ_ASSERT(XRE_IsParentProcess());
90   aIsAllowListed = false;
91 
92   if (!aContentBlockingAllowListPrincipal) {
93     // Nothing to do!
94     return NS_OK;
95   }
96 
97   LOG_PRIN(("Deciding whether the user has overridden content blocking for %s",
98             _spec),
99            aContentBlockingAllowListPrincipal);
100 
101   PermissionManager* permManager = PermissionManager::GetInstance();
102   NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
103 
104   // Check both the normal mode and private browsing mode user override
105   // permissions.
106   std::pair<const nsLiteralCString, bool> types[] = {
107       {"trackingprotection"_ns, false}, {"trackingprotection-pb"_ns, true}};
108 
109   for (const auto& type : types) {
110     if (aIsPrivateBrowsing != type.second) {
111       continue;
112     }
113 
114     uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
115     nsresult rv = permManager->TestPermissionFromPrincipal(
116         aContentBlockingAllowListPrincipal, type.first, &permissions);
117     NS_ENSURE_SUCCESS(rv, rv);
118 
119     if (permissions == nsIPermissionManager::ALLOW_ACTION) {
120       aIsAllowListed = true;
121       LOG(("Found user override type %s", type.first.get()));
122       // Stop checking the next permisson type if we decided to override.
123       break;
124     }
125   }
126 
127   if (!aIsAllowListed) {
128     LOG(("No user override found"));
129   }
130 
131   return NS_OK;
132 }
133 
ComputePrincipal(nsIPrincipal * aDocumentPrincipal,nsIPrincipal ** aPrincipal)134 /* static */ void ContentBlockingAllowList::ComputePrincipal(
135     nsIPrincipal* aDocumentPrincipal, nsIPrincipal** aPrincipal) {
136   MOZ_ASSERT(aPrincipal);
137 
138   auto returnInputArgument =
139       MakeScopeExit([&] { NS_IF_ADDREF(*aPrincipal = aDocumentPrincipal); });
140 
141   BasePrincipal* bp = BasePrincipal::Cast(aDocumentPrincipal);
142   if (!bp || !bp->IsContentPrincipal()) {
143     // If we have something other than a content principal, just return what we
144     // have.  This includes the case where we were passed a nullptr.
145     return;
146   }
147 
148   if (aDocumentPrincipal->SchemeIs("chrome") ||
149       aDocumentPrincipal->SchemeIs("about")) {
150     returnInputArgument.release();
151     *aPrincipal = nullptr;
152     return;
153   }
154 
155   // Take the host/port portion so we can allowlist by site. Also ignore the
156   // scheme, since users who put sites on the allowlist probably don't expect
157   // allowlisting to depend on scheme.
158   nsAutoCString escaped("https://"_ns);
159   nsAutoCString temp;
160   nsresult rv = aDocumentPrincipal->GetHostPort(temp);
161   // view-source URIs will be handled by the next block.
162   if (NS_FAILED(rv) && !aDocumentPrincipal->SchemeIs("view-source")) {
163     // Normal for some loads, no need to print a warning
164     return;
165   }
166 
167   // GetHostPort returns an empty string (with a success error code) for file://
168   // URIs.
169   if (temp.IsEmpty()) {
170     // In this case we want to make sure that our allow list principal would be
171     // computed as null.
172     returnInputArgument.release();
173     *aPrincipal = nullptr;
174     return;
175   }
176   escaped.Append(temp);
177   nsCOMPtr<nsIURI> uri;
178   rv = NS_NewURI(getter_AddRefs(uri), escaped);
179   if (NS_WARN_IF(NS_FAILED(rv))) {
180     return;
181   }
182 
183   nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
184       uri, aDocumentPrincipal->OriginAttributesRef());
185   if (NS_WARN_IF(!principal)) {
186     return;
187   }
188 
189   returnInputArgument.release();
190   principal.forget(aPrincipal);
191 }
192 
RecomputePrincipal(nsIURI * aURIBeingLoaded,const OriginAttributes & aAttrs,nsIPrincipal ** aPrincipal)193 /* static */ void ContentBlockingAllowList::RecomputePrincipal(
194     nsIURI* aURIBeingLoaded, const OriginAttributes& aAttrs,
195     nsIPrincipal** aPrincipal) {
196   MOZ_ASSERT(aPrincipal);
197 
198   auto returnInputArgument = MakeScopeExit([&] { *aPrincipal = nullptr; });
199 
200   // Take the host/port portion so we can allowlist by site. Also ignore the
201   // scheme, since users who put sites on the allowlist probably don't expect
202   // allowlisting to depend on scheme.
203   nsAutoCString escaped("https://"_ns);
204   nsAutoCString temp;
205   nsresult rv = aURIBeingLoaded->GetHostPort(temp);
206   // view-source URIs will be handled by the next block.
207   if (NS_FAILED(rv) && !aURIBeingLoaded->SchemeIs("view-source")) {
208     // Normal for some loads, no need to print a warning
209     return;
210   }
211 
212   // GetHostPort returns an empty string (with a success error code) for file://
213   // URIs.
214   if (temp.IsEmpty()) {
215     return;
216   }
217   escaped.Append(temp);
218 
219   nsCOMPtr<nsIURI> uri;
220   rv = NS_NewURI(getter_AddRefs(uri), escaped);
221   if (NS_WARN_IF(NS_FAILED(rv))) {
222     return;
223   }
224 
225   nsCOMPtr<nsIPrincipal> principal =
226       BasePrincipal::CreateContentPrincipal(uri, aAttrs);
227   if (NS_WARN_IF(!principal)) {
228     return;
229   }
230 
231   returnInputArgument.release();
232   principal.forget(aPrincipal);
233 }
234