1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "nsContentBlocker.h"
6 #include "nsIContent.h"
7 #include "nsIURI.h"
8 #include "nsIServiceManager.h"
9 #include "nsIDocShellTreeItem.h"
10 #include "nsIPrefService.h"
11 #include "nsIPrefBranch.h"
12 #include "nsIDocShell.h"
13 #include "nsString.h"
14 #include "nsContentPolicyUtils.h"
15 #include "nsIObjectLoadingContent.h"
16 #include "mozilla/ArrayUtils.h"
17 #include "nsContentUtils.h"
18
19 // Possible behavior pref values
20 // Those map to the nsIPermissionManager values where possible
21 #define BEHAVIOR_ACCEPT nsIPermissionManager::ALLOW_ACTION
22 #define BEHAVIOR_REJECT nsIPermissionManager::DENY_ACTION
23 #define BEHAVIOR_NOFOREIGN 3
24
25 // From nsIContentPolicy
26 static const char *kTypeString[] = {
27 "other", "script", "image", "stylesheet",
28 "object", "document", "subdocument", "refresh",
29 "xbl", "ping", "xmlhttprequest", "objectsubrequest",
30 "dtd", "font", "media", "websocket",
31 "csp_report", "xslt", "beacon", "fetch",
32 "image", "manifest",
33 "", // TYPE_INTERNAL_SCRIPT
34 "", // TYPE_INTERNAL_WORKER
35 "", // TYPE_INTERNAL_SHARED_WORKER
36 "", // TYPE_INTERNAL_EMBED
37 "", // TYPE_INTERNAL_OBJECT
38 "", // TYPE_INTERNAL_FRAME
39 "", // TYPE_INTERNAL_IFRAME
40 "", // TYPE_INTERNAL_AUDIO
41 "", // TYPE_INTERNAL_VIDEO
42 "", // TYPE_INTERNAL_TRACK
43 "", // TYPE_INTERNAL_XMLHTTPREQUEST
44 "", // TYPE_INTERNAL_EVENTSOURCE
45 "", // TYPE_INTERNAL_SERVICE_WORKER
46 "", // TYPE_INTERNAL_SCRIPT_PRELOAD
47 "", // TYPE_INTERNAL_IMAGE
48 "", // TYPE_INTERNAL_IMAGE_PRELOAD
49 "", // TYPE_INTERNAL_STYLESHEET
50 "", // TYPE_INTERNAL_STYLESHEET_PRELOAD
51 "", // TYPE_INTERNAL_IMAGE_FAVICON
52 "", // TYPE_INTERNAL_WORKERS_IMPORT_SCRIPTS
53 };
54
55 #define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString)
56 uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES];
57
NS_IMPL_ISUPPORTS(nsContentBlocker,nsIContentPolicy,nsIObserver,nsISupportsWeakReference)58 NS_IMPL_ISUPPORTS(nsContentBlocker, nsIContentPolicy, nsIObserver,
59 nsISupportsWeakReference)
60
61 nsContentBlocker::nsContentBlocker() {
62 memset(mBehaviorPref, BEHAVIOR_ACCEPT, NUMBER_OF_TYPES);
63 }
64
Init()65 nsresult nsContentBlocker::Init() {
66 nsresult rv;
67 mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
68 NS_ENSURE_SUCCESS(rv, rv);
69
70 nsCOMPtr<nsIPrefService> prefService =
71 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
72 NS_ENSURE_SUCCESS(rv, rv);
73
74 nsCOMPtr<nsIPrefBranch> prefBranch;
75 rv = prefService->GetBranch("permissions.default.",
76 getter_AddRefs(prefBranch));
77 NS_ENSURE_SUCCESS(rv, rv);
78
79 // Migrate old image blocker pref
80 nsCOMPtr<nsIPrefBranch> oldPrefBranch;
81 oldPrefBranch = do_QueryInterface(prefService);
82 int32_t oldPref;
83 rv = oldPrefBranch->GetIntPref("network.image.imageBehavior", &oldPref);
84 if (NS_SUCCEEDED(rv) && oldPref) {
85 int32_t newPref;
86 switch (oldPref) {
87 default:
88 newPref = BEHAVIOR_ACCEPT;
89 break;
90 case 1:
91 newPref = BEHAVIOR_NOFOREIGN;
92 break;
93 case 2:
94 newPref = BEHAVIOR_REJECT;
95 break;
96 }
97 prefBranch->SetIntPref("image", newPref);
98 oldPrefBranch->ClearUserPref("network.image.imageBehavior");
99 }
100
101 // The branch is not a copy of the prefservice, but a new object, because
102 // it is a non-default branch. Adding obeservers to it will only work if
103 // we make sure that the object doesn't die. So, keep a reference to it.
104 mPrefBranchInternal = do_QueryInterface(prefBranch, &rv);
105 NS_ENSURE_SUCCESS(rv, rv);
106
107 rv = mPrefBranchInternal->AddObserver("", this, true);
108 PrefChanged(prefBranch, nullptr);
109
110 return rv;
111 }
112
113 #undef LIMIT
114 #define LIMIT(x, low, high, default) \
115 ((x) >= (low) && (x) <= (high) ? (x) : (default))
116
PrefChanged(nsIPrefBranch * aPrefBranch,const char * aPref)117 void nsContentBlocker::PrefChanged(nsIPrefBranch *aPrefBranch,
118 const char *aPref) {
119 int32_t val;
120
121 #define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
122
123 for (uint32_t i = 0; i < NUMBER_OF_TYPES; ++i) {
124 if (*kTypeString[i] && PREF_CHANGED(kTypeString[i]) &&
125 NS_SUCCEEDED(aPrefBranch->GetIntPref(kTypeString[i], &val)))
126 mBehaviorPref[i] = LIMIT(val, 1, 3, 1);
127 }
128 }
129
130 // nsIContentPolicy Implementation
131 NS_IMETHODIMP
ShouldLoad(uint32_t aContentType,nsIURI * aContentLocation,nsIURI * aRequestingLocation,nsISupports * aRequestingContext,const nsACString & aMimeGuess,nsISupports * aExtra,nsIPrincipal * aRequestPrincipal,int16_t * aDecision)132 nsContentBlocker::ShouldLoad(uint32_t aContentType, nsIURI *aContentLocation,
133 nsIURI *aRequestingLocation,
134 nsISupports *aRequestingContext,
135 const nsACString &aMimeGuess, nsISupports *aExtra,
136 nsIPrincipal *aRequestPrincipal,
137 int16_t *aDecision) {
138 MOZ_ASSERT(
139 aContentType ==
140 nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
141 "We should only see external content policy types here.");
142
143 *aDecision = nsIContentPolicy::ACCEPT;
144 nsresult rv;
145
146 // Ony support NUMBER_OF_TYPES content types. that all there is at the
147 // moment, but you never know...
148 if (aContentType > NUMBER_OF_TYPES) return NS_OK;
149
150 // we can't do anything without this
151 if (!aContentLocation) return NS_OK;
152
153 // The final type of an object tag may mutate before it reaches
154 // shouldProcess, so we cannot make any sane blocking decisions here
155 if (aContentType == nsIContentPolicy::TYPE_OBJECT) return NS_OK;
156
157 // we only want to check http, https, ftp
158 // for chrome:// and resources and others, no need to check.
159 nsAutoCString scheme;
160 aContentLocation->GetScheme(scheme);
161 if (!scheme.LowerCaseEqualsLiteral("ftp") &&
162 !scheme.LowerCaseEqualsLiteral("http") &&
163 !scheme.LowerCaseEqualsLiteral("https"))
164 return NS_OK;
165
166 bool shouldLoad, fromPrefs;
167 rv = TestPermission(aContentLocation, aRequestingLocation, aContentType,
168 &shouldLoad, &fromPrefs);
169 NS_ENSURE_SUCCESS(rv, rv);
170 if (!shouldLoad) {
171 if (fromPrefs) {
172 *aDecision = nsIContentPolicy::REJECT_TYPE;
173 } else {
174 *aDecision = nsIContentPolicy::REJECT_SERVER;
175 }
176 }
177
178 return NS_OK;
179 }
180
181 NS_IMETHODIMP
ShouldProcess(uint32_t aContentType,nsIURI * aContentLocation,nsIURI * aRequestingLocation,nsISupports * aRequestingContext,const nsACString & aMimeGuess,nsISupports * aExtra,nsIPrincipal * aRequestPrincipal,int16_t * aDecision)182 nsContentBlocker::ShouldProcess(uint32_t aContentType, nsIURI *aContentLocation,
183 nsIURI *aRequestingLocation,
184 nsISupports *aRequestingContext,
185 const nsACString &aMimeGuess,
186 nsISupports *aExtra,
187 nsIPrincipal *aRequestPrincipal,
188 int16_t *aDecision) {
189 MOZ_ASSERT(
190 aContentType ==
191 nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
192 "We should only see external content policy types here.");
193
194 // For loads where aRequestingContext is chrome, we should just
195 // accept. Those are most likely toplevel loads in windows, and
196 // chrome generally knows what it's doing anyway.
197 nsCOMPtr<nsIDocShellTreeItem> item =
198 do_QueryInterface(NS_CP_GetDocShellFromContext(aRequestingContext));
199
200 if (item && item->ItemType() == nsIDocShellTreeItem::typeChrome) {
201 *aDecision = nsIContentPolicy::ACCEPT;
202 return NS_OK;
203 }
204
205 // For objects, we only check policy in shouldProcess, as the final type isn't
206 // determined until the channel is open -- We don't want to block images in
207 // object tags because plugins are disallowed.
208 // NOTE that this bypasses the aContentLocation checks in ShouldLoad - this is
209 // intentional, as aContentLocation may be null for plugins that load by type
210 // (e.g. java)
211 if (aContentType == nsIContentPolicy::TYPE_OBJECT) {
212 *aDecision = nsIContentPolicy::ACCEPT;
213
214 bool shouldLoad, fromPrefs;
215 nsresult rv = TestPermission(aContentLocation, aRequestingLocation,
216 aContentType, &shouldLoad, &fromPrefs);
217 NS_ENSURE_SUCCESS(rv, rv);
218 if (!shouldLoad) {
219 if (fromPrefs) {
220 *aDecision = nsIContentPolicy::REJECT_TYPE;
221 } else {
222 *aDecision = nsIContentPolicy::REJECT_SERVER;
223 }
224 }
225 return NS_OK;
226 }
227
228 // This isn't a load from chrome or an object tag - Just do a ShouldLoad()
229 // check -- we want the same answer here
230 return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
231 aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
232 aDecision);
233 }
234
TestPermission(nsIURI * aCurrentURI,nsIURI * aFirstURI,int32_t aContentType,bool * aPermission,bool * aFromPrefs)235 nsresult nsContentBlocker::TestPermission(nsIURI *aCurrentURI,
236 nsIURI *aFirstURI,
237 int32_t aContentType,
238 bool *aPermission, bool *aFromPrefs) {
239 *aFromPrefs = false;
240 nsresult rv;
241
242 if (!*kTypeString[aContentType - 1]) {
243 // Disallow internal content policy types, they should not be used here.
244 *aPermission = false;
245 return NS_OK;
246 }
247
248 // This default will also get used if there is an unknown value in the
249 // permission list, or if the permission manager returns unknown values.
250 *aPermission = true;
251
252 // check the permission list first; if we find an entry, it overrides
253 // default prefs.
254 // Don't forget the aContentType ranges from 1..8, while the
255 // array is indexed 0..7
256 // All permissions tested by this method are preload permissions, so don't
257 // bother actually checking with the permission manager unless we have a
258 // preload permission.
259 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
260 if (mPermissionManager->GetHasPreloadPermissions()) {
261 rv = mPermissionManager->TestPermission(
262 aCurrentURI, kTypeString[aContentType - 1], &permission);
263 NS_ENSURE_SUCCESS(rv, rv);
264 }
265
266 // If there is nothing on the list, use the default.
267 if (!permission) {
268 permission = mBehaviorPref[aContentType - 1];
269 *aFromPrefs = true;
270 }
271
272 // Use the fact that the nsIPermissionManager values map to
273 // the BEHAVIOR_* values above.
274 switch (permission) {
275 case BEHAVIOR_ACCEPT:
276 *aPermission = true;
277 break;
278 case BEHAVIOR_REJECT:
279 *aPermission = false;
280 break;
281
282 case BEHAVIOR_NOFOREIGN:
283 // Third party checking
284
285 // Need a requesting uri for third party checks to work.
286 if (!aFirstURI) return NS_OK;
287
288 bool trustedSource = false;
289 rv = aFirstURI->SchemeIs("chrome", &trustedSource);
290 NS_ENSURE_SUCCESS(rv, rv);
291 if (!trustedSource) {
292 rv = aFirstURI->SchemeIs("resource", &trustedSource);
293 NS_ENSURE_SUCCESS(rv, rv);
294 }
295 if (trustedSource) return NS_OK;
296
297 // compare tails of names checking to see if they have a common domain
298 // we do this by comparing the tails of both names where each tail
299 // includes at least one dot
300
301 // A more generic method somewhere would be nice
302
303 nsAutoCString currentHost;
304 rv = aCurrentURI->GetAsciiHost(currentHost);
305 NS_ENSURE_SUCCESS(rv, rv);
306
307 // Search for two dots, starting at the end.
308 // If there are no two dots found, ++dot will turn to zero,
309 // that will return the entire string.
310 int32_t dot = currentHost.RFindChar('.');
311 dot = currentHost.RFindChar('.', dot - 1);
312 ++dot;
313
314 // Get the domain, ie the last part of the host (www.domain.com ->
315 // domain.com) This will break on co.uk
316 const nsACString &tail =
317 Substring(currentHost, dot, currentHost.Length() - dot);
318
319 nsAutoCString firstHost;
320 rv = aFirstURI->GetAsciiHost(firstHost);
321 NS_ENSURE_SUCCESS(rv, rv);
322
323 // If the tail is longer then the whole firstHost, it will never match
324 if (firstHost.Length() < tail.Length()) {
325 *aPermission = false;
326 return NS_OK;
327 }
328
329 // Get the last part of the firstUri with the same length as |tail|
330 const nsACString &firstTail = Substring(
331 firstHost, firstHost.Length() - tail.Length(), tail.Length());
332
333 // Check that both tails are the same, and that just before the tail in
334 // |firstUri| there is a dot. That means both url are in the same domain
335 if ((firstHost.Length() > tail.Length() &&
336 firstHost.CharAt(firstHost.Length() - tail.Length() - 1) != '.') ||
337 !tail.Equals(firstTail)) {
338 *aPermission = false;
339 }
340 break;
341 }
342
343 return NS_OK;
344 }
345
346 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)347 nsContentBlocker::Observe(nsISupports *aSubject, const char *aTopic,
348 const char16_t *aData) {
349 NS_ASSERTION(!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
350 "unexpected topic - we only deal with pref changes!");
351
352 if (mPrefBranchInternal)
353 PrefChanged(mPrefBranchInternal, NS_LossyConvertUTF16toASCII(aData).get());
354 return NS_OK;
355 }
356