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