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