1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/permissions/nsContentBlocker.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,343 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "nsContentBlocker.h" 1.9 +#include "nsIContent.h" 1.10 +#include "nsIURI.h" 1.11 +#include "nsIServiceManager.h" 1.12 +#include "nsIDocShellTreeItem.h" 1.13 +#include "nsIPrefService.h" 1.14 +#include "nsIPrefBranch.h" 1.15 +#include "nsIDocShell.h" 1.16 +#include "nsString.h" 1.17 +#include "nsContentPolicyUtils.h" 1.18 +#include "nsIObjectLoadingContent.h" 1.19 +#include "mozilla/ArrayUtils.h" 1.20 + 1.21 +// Possible behavior pref values 1.22 +// Those map to the nsIPermissionManager values where possible 1.23 +#define BEHAVIOR_ACCEPT nsIPermissionManager::ALLOW_ACTION 1.24 +#define BEHAVIOR_REJECT nsIPermissionManager::DENY_ACTION 1.25 +#define BEHAVIOR_NOFOREIGN 3 1.26 + 1.27 +// From nsIContentPolicy 1.28 +static const char *kTypeString[] = {"other", 1.29 + "script", 1.30 + "image", 1.31 + "stylesheet", 1.32 + "object", 1.33 + "document", 1.34 + "subdocument", 1.35 + "refresh", 1.36 + "xbl", 1.37 + "ping", 1.38 + "xmlhttprequest", 1.39 + "objectsubrequest", 1.40 + "dtd", 1.41 + "font", 1.42 + "media", 1.43 + "websocket", 1.44 + "csp_report", 1.45 + "xslt"}; 1.46 + 1.47 +#define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString) 1.48 +uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES]; 1.49 + 1.50 +NS_IMPL_ISUPPORTS(nsContentBlocker, 1.51 + nsIContentPolicy, 1.52 + nsIObserver, 1.53 + nsISupportsWeakReference) 1.54 + 1.55 +nsContentBlocker::nsContentBlocker() 1.56 +{ 1.57 + memset(mBehaviorPref, BEHAVIOR_ACCEPT, NUMBER_OF_TYPES); 1.58 +} 1.59 + 1.60 +nsresult 1.61 +nsContentBlocker::Init() 1.62 +{ 1.63 + nsresult rv; 1.64 + mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); 1.65 + NS_ENSURE_SUCCESS(rv, rv); 1.66 + 1.67 + nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); 1.68 + NS_ENSURE_SUCCESS(rv, rv); 1.69 + 1.70 + nsCOMPtr<nsIPrefBranch> prefBranch; 1.71 + rv = prefService->GetBranch("permissions.default.", getter_AddRefs(prefBranch)); 1.72 + NS_ENSURE_SUCCESS(rv, rv); 1.73 + 1.74 + // Migrate old image blocker pref 1.75 + nsCOMPtr<nsIPrefBranch> oldPrefBranch; 1.76 + oldPrefBranch = do_QueryInterface(prefService); 1.77 + int32_t oldPref; 1.78 + rv = oldPrefBranch->GetIntPref("network.image.imageBehavior", &oldPref); 1.79 + if (NS_SUCCEEDED(rv) && oldPref) { 1.80 + int32_t newPref; 1.81 + switch (oldPref) { 1.82 + default: 1.83 + newPref = BEHAVIOR_ACCEPT; 1.84 + break; 1.85 + case 1: 1.86 + newPref = BEHAVIOR_NOFOREIGN; 1.87 + break; 1.88 + case 2: 1.89 + newPref = BEHAVIOR_REJECT; 1.90 + break; 1.91 + } 1.92 + prefBranch->SetIntPref("image", newPref); 1.93 + oldPrefBranch->ClearUserPref("network.image.imageBehavior"); 1.94 + } 1.95 + 1.96 + 1.97 + // The branch is not a copy of the prefservice, but a new object, because 1.98 + // it is a non-default branch. Adding obeservers to it will only work if 1.99 + // we make sure that the object doesn't die. So, keep a reference to it. 1.100 + mPrefBranchInternal = do_QueryInterface(prefBranch, &rv); 1.101 + NS_ENSURE_SUCCESS(rv, rv); 1.102 + 1.103 + rv = mPrefBranchInternal->AddObserver("", this, true); 1.104 + PrefChanged(prefBranch, nullptr); 1.105 + 1.106 + return rv; 1.107 +} 1.108 + 1.109 +#undef LIMIT 1.110 +#define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default)) 1.111 + 1.112 +void 1.113 +nsContentBlocker::PrefChanged(nsIPrefBranch *aPrefBranch, 1.114 + const char *aPref) 1.115 +{ 1.116 + int32_t val; 1.117 + 1.118 +#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P)) 1.119 + 1.120 + for(uint32_t i = 0; i < NUMBER_OF_TYPES; ++i) { 1.121 + if (PREF_CHANGED(kTypeString[i]) && 1.122 + NS_SUCCEEDED(aPrefBranch->GetIntPref(kTypeString[i], &val))) 1.123 + mBehaviorPref[i] = LIMIT(val, 1, 3, 1); 1.124 + } 1.125 + 1.126 +} 1.127 + 1.128 +// nsIContentPolicy Implementation 1.129 +NS_IMETHODIMP 1.130 +nsContentBlocker::ShouldLoad(uint32_t aContentType, 1.131 + nsIURI *aContentLocation, 1.132 + nsIURI *aRequestingLocation, 1.133 + nsISupports *aRequestingContext, 1.134 + const nsACString &aMimeGuess, 1.135 + nsISupports *aExtra, 1.136 + nsIPrincipal *aRequestPrincipal, 1.137 + int16_t *aDecision) 1.138 +{ 1.139 + *aDecision = nsIContentPolicy::ACCEPT; 1.140 + nsresult rv; 1.141 + 1.142 + // Ony support NUMBER_OF_TYPES content types. that all there is at the 1.143 + // moment, but you never know... 1.144 + if (aContentType > NUMBER_OF_TYPES) 1.145 + return NS_OK; 1.146 + 1.147 + // we can't do anything without this 1.148 + if (!aContentLocation) 1.149 + return NS_OK; 1.150 + 1.151 + // The final type of an object tag may mutate before it reaches 1.152 + // shouldProcess, so we cannot make any sane blocking decisions here 1.153 + if (aContentType == nsIContentPolicy::TYPE_OBJECT) 1.154 + return NS_OK; 1.155 + 1.156 + // we only want to check http, https, ftp 1.157 + // for chrome:// and resources and others, no need to check. 1.158 + nsAutoCString scheme; 1.159 + aContentLocation->GetScheme(scheme); 1.160 + if (!scheme.LowerCaseEqualsLiteral("ftp") && 1.161 + !scheme.LowerCaseEqualsLiteral("http") && 1.162 + !scheme.LowerCaseEqualsLiteral("https")) 1.163 + return NS_OK; 1.164 + 1.165 + bool shouldLoad, fromPrefs; 1.166 + rv = TestPermission(aContentLocation, aRequestingLocation, aContentType, 1.167 + &shouldLoad, &fromPrefs); 1.168 + NS_ENSURE_SUCCESS(rv, rv); 1.169 + if (!shouldLoad) { 1.170 + if (fromPrefs) { 1.171 + *aDecision = nsIContentPolicy::REJECT_TYPE; 1.172 + } else { 1.173 + *aDecision = nsIContentPolicy::REJECT_SERVER; 1.174 + } 1.175 + } 1.176 + 1.177 + return NS_OK; 1.178 +} 1.179 + 1.180 +NS_IMETHODIMP 1.181 +nsContentBlocker::ShouldProcess(uint32_t aContentType, 1.182 + nsIURI *aContentLocation, 1.183 + nsIURI *aRequestingLocation, 1.184 + nsISupports *aRequestingContext, 1.185 + const nsACString &aMimeGuess, 1.186 + nsISupports *aExtra, 1.187 + nsIPrincipal *aRequestPrincipal, 1.188 + int16_t *aDecision) 1.189 +{ 1.190 + // For loads where aRequestingContext is chrome, we should just 1.191 + // accept. Those are most likely toplevel loads in windows, and 1.192 + // chrome generally knows what it's doing anyway. 1.193 + nsCOMPtr<nsIDocShellTreeItem> item = 1.194 + do_QueryInterface(NS_CP_GetDocShellFromContext(aRequestingContext)); 1.195 + 1.196 + if (item && item->ItemType() == nsIDocShellTreeItem::typeChrome) { 1.197 + *aDecision = nsIContentPolicy::ACCEPT; 1.198 + return NS_OK; 1.199 + } 1.200 + 1.201 + // For objects, we only check policy in shouldProcess, as the final type isn't 1.202 + // determined until the channel is open -- We don't want to block images in 1.203 + // object tags because plugins are disallowed. 1.204 + // NOTE that this bypasses the aContentLocation checks in ShouldLoad - this is 1.205 + // intentional, as aContentLocation may be null for plugins that load by type 1.206 + // (e.g. java) 1.207 + if (aContentType == nsIContentPolicy::TYPE_OBJECT) { 1.208 + *aDecision = nsIContentPolicy::ACCEPT; 1.209 + 1.210 + bool shouldLoad, fromPrefs; 1.211 + nsresult rv = TestPermission(aContentLocation, aRequestingLocation, 1.212 + aContentType, &shouldLoad, &fromPrefs); 1.213 + NS_ENSURE_SUCCESS(rv, rv); 1.214 + if (!shouldLoad) { 1.215 + if (fromPrefs) { 1.216 + *aDecision = nsIContentPolicy::REJECT_TYPE; 1.217 + } else { 1.218 + *aDecision = nsIContentPolicy::REJECT_SERVER; 1.219 + } 1.220 + } 1.221 + return NS_OK; 1.222 + } 1.223 + 1.224 + // This isn't a load from chrome or an object tag - Just do a ShouldLoad() 1.225 + // check -- we want the same answer here 1.226 + return ShouldLoad(aContentType, aContentLocation, aRequestingLocation, 1.227 + aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal, 1.228 + aDecision); 1.229 +} 1.230 + 1.231 +nsresult 1.232 +nsContentBlocker::TestPermission(nsIURI *aCurrentURI, 1.233 + nsIURI *aFirstURI, 1.234 + int32_t aContentType, 1.235 + bool *aPermission, 1.236 + bool *aFromPrefs) 1.237 +{ 1.238 + *aFromPrefs = false; 1.239 + // This default will also get used if there is an unknown value in the 1.240 + // permission list, or if the permission manager returns unknown values. 1.241 + *aPermission = true; 1.242 + 1.243 + // check the permission list first; if we find an entry, it overrides 1.244 + // default prefs. 1.245 + // Don't forget the aContentType ranges from 1..8, while the 1.246 + // array is indexed 0..7 1.247 + uint32_t permission; 1.248 + nsresult rv = mPermissionManager->TestPermission(aCurrentURI, 1.249 + kTypeString[aContentType - 1], 1.250 + &permission); 1.251 + NS_ENSURE_SUCCESS(rv, rv); 1.252 + 1.253 + // If there is nothing on the list, use the default. 1.254 + if (!permission) { 1.255 + permission = mBehaviorPref[aContentType - 1]; 1.256 + *aFromPrefs = true; 1.257 + } 1.258 + 1.259 + // Use the fact that the nsIPermissionManager values map to 1.260 + // the BEHAVIOR_* values above. 1.261 + switch (permission) { 1.262 + case BEHAVIOR_ACCEPT: 1.263 + *aPermission = true; 1.264 + break; 1.265 + case BEHAVIOR_REJECT: 1.266 + *aPermission = false; 1.267 + break; 1.268 + 1.269 + case BEHAVIOR_NOFOREIGN: 1.270 + // Third party checking 1.271 + 1.272 + // Need a requesting uri for third party checks to work. 1.273 + if (!aFirstURI) 1.274 + return NS_OK; 1.275 + 1.276 + bool trustedSource = false; 1.277 + rv = aFirstURI->SchemeIs("chrome", &trustedSource); 1.278 + NS_ENSURE_SUCCESS(rv,rv); 1.279 + if (!trustedSource) { 1.280 + rv = aFirstURI->SchemeIs("resource", &trustedSource); 1.281 + NS_ENSURE_SUCCESS(rv,rv); 1.282 + } 1.283 + if (trustedSource) 1.284 + return NS_OK; 1.285 + 1.286 + // compare tails of names checking to see if they have a common domain 1.287 + // we do this by comparing the tails of both names where each tail 1.288 + // includes at least one dot 1.289 + 1.290 + // A more generic method somewhere would be nice 1.291 + 1.292 + nsAutoCString currentHost; 1.293 + rv = aCurrentURI->GetAsciiHost(currentHost); 1.294 + NS_ENSURE_SUCCESS(rv, rv); 1.295 + 1.296 + // Search for two dots, starting at the end. 1.297 + // If there are no two dots found, ++dot will turn to zero, 1.298 + // that will return the entire string. 1.299 + int32_t dot = currentHost.RFindChar('.'); 1.300 + dot = currentHost.RFindChar('.', dot-1); 1.301 + ++dot; 1.302 + 1.303 + // Get the domain, ie the last part of the host (www.domain.com -> domain.com) 1.304 + // This will break on co.uk 1.305 + const nsCSubstring &tail = 1.306 + Substring(currentHost, dot, currentHost.Length() - dot); 1.307 + 1.308 + nsAutoCString firstHost; 1.309 + rv = aFirstURI->GetAsciiHost(firstHost); 1.310 + NS_ENSURE_SUCCESS(rv, rv); 1.311 + 1.312 + // If the tail is longer then the whole firstHost, it will never match 1.313 + if (firstHost.Length() < tail.Length()) { 1.314 + *aPermission = false; 1.315 + return NS_OK; 1.316 + } 1.317 + 1.318 + // Get the last part of the firstUri with the same length as |tail| 1.319 + const nsCSubstring &firstTail = 1.320 + Substring(firstHost, firstHost.Length() - tail.Length(), tail.Length()); 1.321 + 1.322 + // Check that both tails are the same, and that just before the tail in 1.323 + // |firstUri| there is a dot. That means both url are in the same domain 1.324 + if ((firstHost.Length() > tail.Length() && 1.325 + firstHost.CharAt(firstHost.Length() - tail.Length() - 1) != '.') || 1.326 + !tail.Equals(firstTail)) { 1.327 + *aPermission = false; 1.328 + } 1.329 + break; 1.330 + } 1.331 + 1.332 + return NS_OK; 1.333 +} 1.334 + 1.335 +NS_IMETHODIMP 1.336 +nsContentBlocker::Observe(nsISupports *aSubject, 1.337 + const char *aTopic, 1.338 + const char16_t *aData) 1.339 +{ 1.340 + NS_ASSERTION(!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic), 1.341 + "unexpected topic - we only deal with pref changes!"); 1.342 + 1.343 + if (mPrefBranchInternal) 1.344 + PrefChanged(mPrefBranchInternal, NS_LossyConvertUTF16toASCII(aData).get()); 1.345 + return NS_OK; 1.346 +}