Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "nsMixedContentBlocker.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "nsContentPolicyUtils.h" |
michael@0 | 9 | #include "nsThreadUtils.h" |
michael@0 | 10 | #include "nsINode.h" |
michael@0 | 11 | #include "nsCOMPtr.h" |
michael@0 | 12 | #include "nsIDocShell.h" |
michael@0 | 13 | #include "nsISecurityEventSink.h" |
michael@0 | 14 | #include "nsIWebProgressListener.h" |
michael@0 | 15 | #include "nsContentUtils.h" |
michael@0 | 16 | #include "nsNetUtil.h" |
michael@0 | 17 | #include "nsIRequest.h" |
michael@0 | 18 | #include "nsIDocument.h" |
michael@0 | 19 | #include "nsIContentViewer.h" |
michael@0 | 20 | #include "nsIChannel.h" |
michael@0 | 21 | #include "nsIHttpChannel.h" |
michael@0 | 22 | #include "mozilla/Preferences.h" |
michael@0 | 23 | #include "nsIScriptObjectPrincipal.h" |
michael@0 | 24 | #include "nsISecureBrowserUI.h" |
michael@0 | 25 | #include "nsIDocumentLoader.h" |
michael@0 | 26 | #include "nsIWebNavigation.h" |
michael@0 | 27 | #include "nsLoadGroup.h" |
michael@0 | 28 | #include "nsIScriptError.h" |
michael@0 | 29 | |
michael@0 | 30 | #include "prlog.h" |
michael@0 | 31 | |
michael@0 | 32 | using namespace mozilla; |
michael@0 | 33 | |
michael@0 | 34 | enum nsMixedContentBlockerMessageType { |
michael@0 | 35 | eBlocked = 0x00, |
michael@0 | 36 | eUserOverride = 0x01 |
michael@0 | 37 | }; |
michael@0 | 38 | |
michael@0 | 39 | // Is mixed script blocking (fonts, plugin content, scripts, stylesheets, |
michael@0 | 40 | // iframes, websockets, XHR) enabled? |
michael@0 | 41 | bool nsMixedContentBlocker::sBlockMixedScript = false; |
michael@0 | 42 | |
michael@0 | 43 | // Is mixed display content blocking (images, audio, video, <a ping>) enabled? |
michael@0 | 44 | bool nsMixedContentBlocker::sBlockMixedDisplay = false; |
michael@0 | 45 | |
michael@0 | 46 | // Fired at the document that attempted to load mixed content. The UI could |
michael@0 | 47 | // handle this event, for example, by displaying an info bar that offers the |
michael@0 | 48 | // choice to reload the page with mixed content permitted. |
michael@0 | 49 | class nsMixedContentEvent : public nsRunnable |
michael@0 | 50 | { |
michael@0 | 51 | public: |
michael@0 | 52 | nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType) |
michael@0 | 53 | : mContext(aContext), mType(aType) |
michael@0 | 54 | {} |
michael@0 | 55 | |
michael@0 | 56 | NS_IMETHOD Run() |
michael@0 | 57 | { |
michael@0 | 58 | NS_ASSERTION(mContext, |
michael@0 | 59 | "You can't call this runnable without a requesting context"); |
michael@0 | 60 | |
michael@0 | 61 | // To update the security UI in the tab with the blocked mixed content, call |
michael@0 | 62 | // nsISecurityEventSink::OnSecurityChange. You can get to the event sink by |
michael@0 | 63 | // calling NS_CP_GetDocShellFromContext on the context, and QI'ing to |
michael@0 | 64 | // nsISecurityEventSink. |
michael@0 | 65 | |
michael@0 | 66 | |
michael@0 | 67 | // Mixed content was allowed and is about to load; get the document and |
michael@0 | 68 | // set the approriate flag to true if we are about to load Mixed Active |
michael@0 | 69 | // Content. |
michael@0 | 70 | nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(mContext); |
michael@0 | 71 | if (!docShell) { |
michael@0 | 72 | return NS_OK; |
michael@0 | 73 | } |
michael@0 | 74 | nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; |
michael@0 | 75 | docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); |
michael@0 | 76 | NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); |
michael@0 | 77 | |
michael@0 | 78 | // now get the document from sameTypeRoot |
michael@0 | 79 | nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(sameTypeRoot); |
michael@0 | 80 | NS_ASSERTION(rootDoc, "No root document from document shell root tree item."); |
michael@0 | 81 | |
michael@0 | 82 | |
michael@0 | 83 | if (mType == eMixedScript) { |
michael@0 | 84 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
michael@0 | 85 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
michael@0 | 86 | return NS_OK; |
michael@0 | 87 | } |
michael@0 | 88 | rootDoc->SetHasMixedActiveContentLoaded(true); |
michael@0 | 89 | |
michael@0 | 90 | // Update the security UI in the tab with the allowed mixed active content |
michael@0 | 91 | nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell); |
michael@0 | 92 | if (eventSink) { |
michael@0 | 93 | // If mixed display content is loaded, make sure to include that in the state. |
michael@0 | 94 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
michael@0 | 95 | eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 96 | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | |
michael@0 | 97 | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
michael@0 | 98 | } else { |
michael@0 | 99 | eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 100 | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | } else if (mType == eMixedDisplay) { |
michael@0 | 105 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
michael@0 | 106 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
michael@0 | 107 | return NS_OK; |
michael@0 | 108 | } |
michael@0 | 109 | rootDoc->SetHasMixedDisplayContentLoaded(true); |
michael@0 | 110 | |
michael@0 | 111 | // Update the security UI in the tab with the allowed mixed display content. |
michael@0 | 112 | nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell); |
michael@0 | 113 | if (eventSink) { |
michael@0 | 114 | // If mixed active content is loaded, make sure to include that in the state. |
michael@0 | 115 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
michael@0 | 116 | eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 117 | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | |
michael@0 | 118 | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
michael@0 | 119 | } else { |
michael@0 | 120 | eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 121 | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
michael@0 | 122 | } |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | return NS_OK; |
michael@0 | 127 | } |
michael@0 | 128 | private: |
michael@0 | 129 | // The requesting context for the content load. Generally, a DOM node from |
michael@0 | 130 | // the document that caused the load. |
michael@0 | 131 | nsCOMPtr<nsISupports> mContext; |
michael@0 | 132 | |
michael@0 | 133 | // The type of mixed content detected, e.g. active or display |
michael@0 | 134 | const MixedContentTypes mType; |
michael@0 | 135 | }; |
michael@0 | 136 | |
michael@0 | 137 | |
michael@0 | 138 | nsMixedContentBlocker::nsMixedContentBlocker() |
michael@0 | 139 | { |
michael@0 | 140 | // Cache the pref for mixed script blocking |
michael@0 | 141 | Preferences::AddBoolVarCache(&sBlockMixedScript, |
michael@0 | 142 | "security.mixed_content.block_active_content"); |
michael@0 | 143 | |
michael@0 | 144 | // Cache the pref for mixed display blocking |
michael@0 | 145 | Preferences::AddBoolVarCache(&sBlockMixedDisplay, |
michael@0 | 146 | "security.mixed_content.block_display_content"); |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | nsMixedContentBlocker::~nsMixedContentBlocker() |
michael@0 | 150 | { |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy) |
michael@0 | 154 | |
michael@0 | 155 | static void |
michael@0 | 156 | LogMixedContentMessage(MixedContentTypes aClassification, |
michael@0 | 157 | nsIURI* aContentLocation, |
michael@0 | 158 | nsIDocument* aRootDoc, |
michael@0 | 159 | nsMixedContentBlockerMessageType aMessageType) |
michael@0 | 160 | { |
michael@0 | 161 | nsAutoCString messageCategory; |
michael@0 | 162 | uint32_t severityFlag; |
michael@0 | 163 | nsAutoCString messageLookupKey; |
michael@0 | 164 | |
michael@0 | 165 | if (aMessageType == eBlocked) { |
michael@0 | 166 | severityFlag = nsIScriptError::errorFlag; |
michael@0 | 167 | messageCategory.AssignLiteral("Mixed Content Blocker"); |
michael@0 | 168 | if (aClassification == eMixedDisplay) { |
michael@0 | 169 | messageLookupKey.AssignLiteral("BlockMixedDisplayContent"); |
michael@0 | 170 | } else { |
michael@0 | 171 | messageLookupKey.AssignLiteral("BlockMixedActiveContent"); |
michael@0 | 172 | } |
michael@0 | 173 | } else { |
michael@0 | 174 | severityFlag = nsIScriptError::warningFlag; |
michael@0 | 175 | messageCategory.AssignLiteral("Mixed Content Message"); |
michael@0 | 176 | if (aClassification == eMixedDisplay) { |
michael@0 | 177 | messageLookupKey.AssignLiteral("LoadingMixedDisplayContent"); |
michael@0 | 178 | } else { |
michael@0 | 179 | messageLookupKey.AssignLiteral("LoadingMixedActiveContent"); |
michael@0 | 180 | } |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | nsAutoCString locationSpec; |
michael@0 | 184 | aContentLocation->GetSpec(locationSpec); |
michael@0 | 185 | NS_ConvertUTF8toUTF16 locationSpecUTF16(locationSpec); |
michael@0 | 186 | |
michael@0 | 187 | const char16_t* strings[] = { locationSpecUTF16.get() }; |
michael@0 | 188 | nsContentUtils::ReportToConsole(severityFlag, messageCategory, aRootDoc, |
michael@0 | 189 | nsContentUtils::eSECURITY_PROPERTIES, |
michael@0 | 190 | messageLookupKey.get(), strings, ArrayLength(strings)); |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | NS_IMETHODIMP |
michael@0 | 194 | nsMixedContentBlocker::ShouldLoad(uint32_t aContentType, |
michael@0 | 195 | nsIURI* aContentLocation, |
michael@0 | 196 | nsIURI* aRequestingLocation, |
michael@0 | 197 | nsISupports* aRequestingContext, |
michael@0 | 198 | const nsACString& aMimeGuess, |
michael@0 | 199 | nsISupports* aExtra, |
michael@0 | 200 | nsIPrincipal* aRequestPrincipal, |
michael@0 | 201 | int16_t* aDecision) |
michael@0 | 202 | { |
michael@0 | 203 | // Asserting that we are on the main thread here and hence do not have to lock |
michael@0 | 204 | // and unlock sBlockMixedScript and sBlockMixedDisplay before reading/writing |
michael@0 | 205 | // to them. |
michael@0 | 206 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 207 | |
michael@0 | 208 | // Assume active (high risk) content and blocked by default |
michael@0 | 209 | MixedContentTypes classification = eMixedScript; |
michael@0 | 210 | |
michael@0 | 211 | |
michael@0 | 212 | // Notes on non-obvious decisions: |
michael@0 | 213 | // |
michael@0 | 214 | // TYPE_DTD: A DTD can contain entity definitions that expand to scripts. |
michael@0 | 215 | // |
michael@0 | 216 | // TYPE_FONT: The TrueType hinting mechanism is basically a scripting |
michael@0 | 217 | // language that gets interpreted by the operating system's font rasterizer. |
michael@0 | 218 | // Mixed content web fonts are relatively uncommon, and we can can fall back |
michael@0 | 219 | // to built-in fonts with minimal disruption in almost all cases. |
michael@0 | 220 | // |
michael@0 | 221 | // TYPE_OBJECT_SUBREQUEST could actually be either active content (e.g. a |
michael@0 | 222 | // script that a plugin will execute) or display content (e.g. Flash video |
michael@0 | 223 | // content). Until we have a way to determine active vs passive content |
michael@0 | 224 | // from plugin requests (bug 836352), we will treat this as passive content. |
michael@0 | 225 | // This is to prevent false positives from causing users to become |
michael@0 | 226 | // desensitized to the mixed content blocker. |
michael@0 | 227 | // |
michael@0 | 228 | // TYPE_CSP_REPORT: High-risk because they directly leak information about |
michael@0 | 229 | // the content of the page, and because blocking them does not have any |
michael@0 | 230 | // negative effect on the page loading. |
michael@0 | 231 | // |
michael@0 | 232 | // TYPE_PING: Ping requests are POSTS, not GETs like images and media. |
michael@0 | 233 | // Also, PING requests have no bearing on the rendering or operation of |
michael@0 | 234 | // the page when used as designed, so even though they are lower risk than |
michael@0 | 235 | // scripts, blocking them is basically risk-free as far as compatibility is |
michael@0 | 236 | // concerned. Ping is turned off by default in Firefox, so unless a user |
michael@0 | 237 | // opts into ping, no request will be made. Categorizing this as Mixed |
michael@0 | 238 | // Display Content for now, but this is subject to change. |
michael@0 | 239 | // |
michael@0 | 240 | // TYPE_STYLESHEET: XSLT stylesheets can insert scripts. CSS positioning |
michael@0 | 241 | // and other advanced CSS features can possibly be exploited to cause |
michael@0 | 242 | // spoofing attacks (e.g. make a "grant permission" button look like a |
michael@0 | 243 | // "refuse permission" button). |
michael@0 | 244 | // |
michael@0 | 245 | // TYPE_BEACON: Beacon requests are similar to TYPE_PING, but are default on. |
michael@0 | 246 | // |
michael@0 | 247 | // TYPE_WEBSOCKET: The Websockets API requires browsers to |
michael@0 | 248 | // reject mixed-content websockets: "If secure is false but the origin of |
michael@0 | 249 | // the entry script has a scheme component that is itself a secure protocol, |
michael@0 | 250 | // e.g. HTTPS, then throw a SecurityError exception." We already block mixed |
michael@0 | 251 | // content websockets within the websockets implementation, so we don't need |
michael@0 | 252 | // to do any blocking here, nor do we need to provide a way to undo or |
michael@0 | 253 | // override the blocking. Websockets without TLS are very flaky anyway in the |
michael@0 | 254 | // face of many HTTP-aware proxies. Compared to psasive content, there is |
michael@0 | 255 | // additional risk that the script using WebSockets will disclose sensitive |
michael@0 | 256 | // information from the HTTPS page and/or eval (directly or indirectly) |
michael@0 | 257 | // received data. |
michael@0 | 258 | // |
michael@0 | 259 | // TYPE_XMLHTTPREQUEST: XHR requires either same origin or CORS, so most |
michael@0 | 260 | // mixed-content XHR will already be blocked by that check. This will also |
michael@0 | 261 | // block HTTPS-to-HTTP XHR with CORS. The same security concerns mentioned |
michael@0 | 262 | // above for WebSockets apply to XHR, and XHR should have the same security |
michael@0 | 263 | // properties as WebSockets w.r.t. mixed content. XHR's handling of redirects |
michael@0 | 264 | // amplifies these concerns. |
michael@0 | 265 | |
michael@0 | 266 | |
michael@0 | 267 | static_assert(TYPE_DATAREQUEST == TYPE_XMLHTTPREQUEST, |
michael@0 | 268 | "TYPE_DATAREQUEST is not a synonym for " |
michael@0 | 269 | "TYPE_XMLHTTPREQUEST"); |
michael@0 | 270 | |
michael@0 | 271 | switch (aContentType) { |
michael@0 | 272 | // The top-level document cannot be mixed content by definition |
michael@0 | 273 | case TYPE_DOCUMENT: |
michael@0 | 274 | *aDecision = ACCEPT; |
michael@0 | 275 | return NS_OK; |
michael@0 | 276 | // Creating insecure websocket connections in a secure page is blocked already |
michael@0 | 277 | // in the websocket constructor. We don't need to check the blocking here |
michael@0 | 278 | // and we don't want to un-block |
michael@0 | 279 | case TYPE_WEBSOCKET: |
michael@0 | 280 | *aDecision = ACCEPT; |
michael@0 | 281 | return NS_OK; |
michael@0 | 282 | |
michael@0 | 283 | |
michael@0 | 284 | // Static display content is considered moderate risk for mixed content so |
michael@0 | 285 | // these will be blocked according to the mixed display preference |
michael@0 | 286 | case TYPE_IMAGE: |
michael@0 | 287 | case TYPE_MEDIA: |
michael@0 | 288 | case TYPE_OBJECT_SUBREQUEST: |
michael@0 | 289 | case TYPE_PING: |
michael@0 | 290 | case TYPE_BEACON: |
michael@0 | 291 | classification = eMixedDisplay; |
michael@0 | 292 | break; |
michael@0 | 293 | |
michael@0 | 294 | // Active content (or content with a low value/risk-of-blocking ratio) |
michael@0 | 295 | // that has been explicitly evaluated; listed here for documentation |
michael@0 | 296 | // purposes and to avoid the assertion and warning for the default case. |
michael@0 | 297 | case TYPE_CSP_REPORT: |
michael@0 | 298 | case TYPE_DTD: |
michael@0 | 299 | case TYPE_FONT: |
michael@0 | 300 | case TYPE_OBJECT: |
michael@0 | 301 | case TYPE_SCRIPT: |
michael@0 | 302 | case TYPE_STYLESHEET: |
michael@0 | 303 | case TYPE_SUBDOCUMENT: |
michael@0 | 304 | case TYPE_XBL: |
michael@0 | 305 | case TYPE_XMLHTTPREQUEST: |
michael@0 | 306 | case TYPE_XSLT: |
michael@0 | 307 | case TYPE_OTHER: |
michael@0 | 308 | break; |
michael@0 | 309 | |
michael@0 | 310 | |
michael@0 | 311 | // This content policy works as a whitelist. |
michael@0 | 312 | default: |
michael@0 | 313 | MOZ_ASSERT(false, "Mixed content of unknown type"); |
michael@0 | 314 | break; |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | /* Get the scheme of the sub-document resource to be requested. If it is |
michael@0 | 318 | * a safe to load in an https context then mixed content doesn't apply. |
michael@0 | 319 | * |
michael@0 | 320 | * Check Protocol Flags to determine if scheme is safe to load: |
michael@0 | 321 | * URI_DOES_NOT_RETURN_DATA - e.g. |
michael@0 | 322 | * "mailto" |
michael@0 | 323 | * URI_IS_LOCAL_RESOURCE - e.g. |
michael@0 | 324 | * "data", |
michael@0 | 325 | * "resource", |
michael@0 | 326 | * "moz-icon" |
michael@0 | 327 | * URI_INHERITS_SECURITY_CONTEXT - e.g. |
michael@0 | 328 | * "javascript" |
michael@0 | 329 | * URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g. |
michael@0 | 330 | * "https", |
michael@0 | 331 | * "moz-safe-about" |
michael@0 | 332 | * |
michael@0 | 333 | */ |
michael@0 | 334 | bool schemeLocal = false; |
michael@0 | 335 | bool schemeNoReturnData = false; |
michael@0 | 336 | bool schemeInherits = false; |
michael@0 | 337 | bool schemeSecure = false; |
michael@0 | 338 | if (NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal)) || |
michael@0 | 339 | NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &schemeNoReturnData)) || |
michael@0 | 340 | NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &schemeInherits)) || |
michael@0 | 341 | NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT, &schemeSecure))) { |
michael@0 | 342 | return NS_ERROR_FAILURE; |
michael@0 | 343 | } |
michael@0 | 344 | |
michael@0 | 345 | if (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure) { |
michael@0 | 346 | return NS_OK; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | // Since there are cases where aRequestingLocation and aRequestPrincipal are |
michael@0 | 350 | // definitely not the owning document, we try to ignore them by extracting the |
michael@0 | 351 | // requestingLocation in the following order: |
michael@0 | 352 | // 1) from the aRequestingContext, either extracting |
michael@0 | 353 | // a) the node's principal, or the |
michael@0 | 354 | // b) script object's principal. |
michael@0 | 355 | // 2) if aRequestingContext yields a principal but no location, we check |
michael@0 | 356 | // if its the system principal. If it is, allow the load. |
michael@0 | 357 | // 3) Special case handling for: |
michael@0 | 358 | // a) speculative loads, where shouldLoad is called twice (bug 839235) |
michael@0 | 359 | // and the first speculative load does not include a context. |
michael@0 | 360 | // In this case we use aRequestingLocation to set requestingLocation. |
michael@0 | 361 | // b) TYPE_CSP_REPORT which does not provide a context. In this case we |
michael@0 | 362 | // use aRequestingLocation to set requestingLocation. |
michael@0 | 363 | // c) content scripts from addon code that do not provide aRequestingContext |
michael@0 | 364 | // or aRequestingLocation, but do provide aRequestPrincipal. |
michael@0 | 365 | // If aRequestPrincipal is an expanded principal, we allow the load. |
michael@0 | 366 | // 4) If we still end up not having a requestingLocation, we reject the load. |
michael@0 | 367 | |
michael@0 | 368 | nsCOMPtr<nsIPrincipal> principal; |
michael@0 | 369 | // 1a) Try to get the principal if aRequestingContext is a node. |
michael@0 | 370 | nsCOMPtr<nsINode> node = do_QueryInterface(aRequestingContext); |
michael@0 | 371 | if (node) { |
michael@0 | 372 | principal = node->NodePrincipal(); |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | // 1b) Try using the window's script object principal if it's not a node. |
michael@0 | 376 | if (!principal) { |
michael@0 | 377 | nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aRequestingContext); |
michael@0 | 378 | if (scriptObjPrin) { |
michael@0 | 379 | principal = scriptObjPrin->GetPrincipal(); |
michael@0 | 380 | } |
michael@0 | 381 | } |
michael@0 | 382 | |
michael@0 | 383 | nsCOMPtr<nsIURI> requestingLocation; |
michael@0 | 384 | if (principal) { |
michael@0 | 385 | principal->GetURI(getter_AddRefs(requestingLocation)); |
michael@0 | 386 | } |
michael@0 | 387 | |
michael@0 | 388 | // 2) if aRequestingContext yields a principal but no location, we check if its a system principal. |
michael@0 | 389 | if (principal && !requestingLocation) { |
michael@0 | 390 | if (nsContentUtils::IsSystemPrincipal(principal)) { |
michael@0 | 391 | *aDecision = ACCEPT; |
michael@0 | 392 | return NS_OK; |
michael@0 | 393 | } |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | // 3a,b) Special case handling for speculative loads and TYPE_CSP_REPORT. In |
michael@0 | 397 | // such cases, aRequestingContext doesn't exist, so we use aRequestingLocation. |
michael@0 | 398 | // Unfortunately we can not distinguish between speculative and normal loads here, |
michael@0 | 399 | // otherwise we could special case this assignment. |
michael@0 | 400 | if (!requestingLocation) { |
michael@0 | 401 | requestingLocation = aRequestingLocation; |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | // 3c) Special case handling for content scripts from addons code, which only |
michael@0 | 405 | // provide a aRequestPrincipal; aRequestingContext and aRequestingLocation are |
michael@0 | 406 | // both null; if the aRequestPrincipal is an expandedPrincipal, we allow the load. |
michael@0 | 407 | if (!principal && !requestingLocation && aRequestPrincipal) { |
michael@0 | 408 | nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aRequestPrincipal); |
michael@0 | 409 | if (expanded) { |
michael@0 | 410 | *aDecision = ACCEPT; |
michael@0 | 411 | return NS_OK; |
michael@0 | 412 | } |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | // 4) Giving up. We still don't have a requesting location, therefore we can't tell |
michael@0 | 416 | // if this is a mixed content load. Deny to be safe. |
michael@0 | 417 | if (!requestingLocation) { |
michael@0 | 418 | *aDecision = REJECT_REQUEST; |
michael@0 | 419 | return NS_OK; |
michael@0 | 420 | } |
michael@0 | 421 | |
michael@0 | 422 | // Check the parent scheme. If it is not an HTTPS page then mixed content |
michael@0 | 423 | // restrictions do not apply. |
michael@0 | 424 | bool parentIsHttps; |
michael@0 | 425 | nsresult rv = requestingLocation->SchemeIs("https", &parentIsHttps); |
michael@0 | 426 | if (NS_FAILED(rv)) { |
michael@0 | 427 | NS_ERROR("requestingLocation->SchemeIs failed"); |
michael@0 | 428 | *aDecision = REJECT_REQUEST; |
michael@0 | 429 | return NS_OK; |
michael@0 | 430 | } |
michael@0 | 431 | if (!parentIsHttps) { |
michael@0 | 432 | *aDecision = ACCEPT; |
michael@0 | 433 | return NS_OK; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | // Determine if the rootDoc is https and if the user decided to allow Mixed Content |
michael@0 | 437 | nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aRequestingContext); |
michael@0 | 438 | NS_ENSURE_TRUE(docShell, NS_OK); |
michael@0 | 439 | bool rootHasSecureConnection = false; |
michael@0 | 440 | bool allowMixedContent = false; |
michael@0 | 441 | bool isRootDocShell = false; |
michael@0 | 442 | rv = docShell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isRootDocShell); |
michael@0 | 443 | if (NS_FAILED(rv)) { |
michael@0 | 444 | return rv; |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | |
michael@0 | 448 | // Get the sameTypeRoot tree item from the docshell |
michael@0 | 449 | nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; |
michael@0 | 450 | docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); |
michael@0 | 451 | NS_ASSERTION(sameTypeRoot, "No root tree item from docshell!"); |
michael@0 | 452 | |
michael@0 | 453 | // When navigating an iframe, the iframe may be https |
michael@0 | 454 | // but its parents may not be. Check the parents to see if any of them are https. |
michael@0 | 455 | // If none of the parents are https, allow the load. |
michael@0 | 456 | if (aContentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) { |
michael@0 | 457 | |
michael@0 | 458 | bool httpsParentExists = false; |
michael@0 | 459 | |
michael@0 | 460 | nsCOMPtr<nsIDocShellTreeItem> parentTreeItem; |
michael@0 | 461 | parentTreeItem = docShell; |
michael@0 | 462 | |
michael@0 | 463 | while(!httpsParentExists && parentTreeItem) { |
michael@0 | 464 | nsCOMPtr<nsIWebNavigation> parentAsNav(do_QueryInterface(parentTreeItem)); |
michael@0 | 465 | NS_ASSERTION(parentAsNav, "No web navigation object from parent's docshell tree item"); |
michael@0 | 466 | nsCOMPtr<nsIURI> parentURI; |
michael@0 | 467 | |
michael@0 | 468 | parentAsNav->GetCurrentURI(getter_AddRefs(parentURI)); |
michael@0 | 469 | if (!parentURI || NS_FAILED(parentURI->SchemeIs("https", &httpsParentExists))) { |
michael@0 | 470 | // if getting the URI or the scheme fails, assume there is a https parent and break. |
michael@0 | 471 | httpsParentExists = true; |
michael@0 | 472 | break; |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | // When the parent and the root are the same, we have traversed all the way up |
michael@0 | 476 | // the same type docshell tree. Break out of the while loop. |
michael@0 | 477 | if(sameTypeRoot == parentTreeItem) { |
michael@0 | 478 | break; |
michael@0 | 479 | } |
michael@0 | 480 | |
michael@0 | 481 | // update the parent to the grandparent. |
michael@0 | 482 | nsCOMPtr<nsIDocShellTreeItem> newParentTreeItem; |
michael@0 | 483 | parentTreeItem->GetSameTypeParent(getter_AddRefs(newParentTreeItem)); |
michael@0 | 484 | parentTreeItem = newParentTreeItem; |
michael@0 | 485 | } // end while loop. |
michael@0 | 486 | |
michael@0 | 487 | if (!httpsParentExists) { |
michael@0 | 488 | *aDecision = nsIContentPolicy::ACCEPT; |
michael@0 | 489 | return NS_OK; |
michael@0 | 490 | } |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | // Get the root document from the sameTypeRoot |
michael@0 | 494 | nsCOMPtr<nsIDocument> rootDoc = do_GetInterface(sameTypeRoot); |
michael@0 | 495 | NS_ASSERTION(rootDoc, "No root document from document shell root tree item."); |
michael@0 | 496 | |
michael@0 | 497 | // Get eventSink and the current security state from the docShell |
michael@0 | 498 | nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell); |
michael@0 | 499 | NS_ASSERTION(eventSink, "No eventSink from docShell."); |
michael@0 | 500 | nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot); |
michael@0 | 501 | NS_ASSERTION(rootShell, "No root docshell from document shell root tree item."); |
michael@0 | 502 | uint32_t State = nsIWebProgressListener::STATE_IS_BROKEN; |
michael@0 | 503 | nsCOMPtr<nsISecureBrowserUI> securityUI; |
michael@0 | 504 | rootShell->GetSecurityUI(getter_AddRefs(securityUI)); |
michael@0 | 505 | // If there is no securityUI, document doesn't have a security state. |
michael@0 | 506 | // Allow load and return early. |
michael@0 | 507 | if (!securityUI) { |
michael@0 | 508 | *aDecision = nsIContentPolicy::ACCEPT; |
michael@0 | 509 | return NS_OK; |
michael@0 | 510 | } |
michael@0 | 511 | nsresult stateRV = securityUI->GetState(&State); |
michael@0 | 512 | |
michael@0 | 513 | // If the content is display content, and the pref says display content should be blocked, block it. |
michael@0 | 514 | if (sBlockMixedDisplay && classification == eMixedDisplay) { |
michael@0 | 515 | if (allowMixedContent) { |
michael@0 | 516 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
michael@0 | 517 | *aDecision = nsIContentPolicy::ACCEPT; |
michael@0 | 518 | rootDoc->SetHasMixedActiveContentLoaded(true); |
michael@0 | 519 | if (!rootDoc->GetHasMixedDisplayContentLoaded() && NS_SUCCEEDED(stateRV)) { |
michael@0 | 520 | rootDoc->SetHasMixedDisplayContentLoaded(true); |
michael@0 | 521 | eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
michael@0 | 522 | } |
michael@0 | 523 | } else { |
michael@0 | 524 | *aDecision = nsIContentPolicy::REJECT_REQUEST; |
michael@0 | 525 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked); |
michael@0 | 526 | if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) { |
michael@0 | 527 | rootDoc->SetHasMixedDisplayContentBlocked(true); |
michael@0 | 528 | eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT)); |
michael@0 | 529 | } |
michael@0 | 530 | } |
michael@0 | 531 | return NS_OK; |
michael@0 | 532 | |
michael@0 | 533 | } else if (sBlockMixedScript && classification == eMixedScript) { |
michael@0 | 534 | // If the content is active content, and the pref says active content should be blocked, block it |
michael@0 | 535 | // unless the user has choosen to override the pref |
michael@0 | 536 | if (allowMixedContent) { |
michael@0 | 537 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
michael@0 | 538 | *aDecision = nsIContentPolicy::ACCEPT; |
michael@0 | 539 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
michael@0 | 540 | if (rootDoc->GetHasMixedActiveContentLoaded()) { |
michael@0 | 541 | return NS_OK; |
michael@0 | 542 | } |
michael@0 | 543 | rootDoc->SetHasMixedActiveContentLoaded(true); |
michael@0 | 544 | |
michael@0 | 545 | if (rootHasSecureConnection) { |
michael@0 | 546 | // User has decided to override the pref and the root is https, so change the Security State. |
michael@0 | 547 | if (rootDoc->GetHasMixedDisplayContentLoaded()) { |
michael@0 | 548 | // If mixed display content is loaded, make sure to include that in the state. |
michael@0 | 549 | eventSink->OnSecurityChange(aRequestingContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 550 | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | |
michael@0 | 551 | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); |
michael@0 | 552 | } else { |
michael@0 | 553 | eventSink->OnSecurityChange(aRequestingContext, (nsIWebProgressListener::STATE_IS_BROKEN | |
michael@0 | 554 | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
michael@0 | 555 | } |
michael@0 | 556 | return NS_OK; |
michael@0 | 557 | } else { |
michael@0 | 558 | // User has already overriden the pref and the root is not https; |
michael@0 | 559 | // mixed content was allowed on an https subframe. |
michael@0 | 560 | if (NS_SUCCEEDED(stateRV)) { |
michael@0 | 561 | eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); |
michael@0 | 562 | } |
michael@0 | 563 | return NS_OK; |
michael@0 | 564 | } |
michael@0 | 565 | } else { |
michael@0 | 566 | //User has not overriden the pref by Disabling protection. Reject the request and update the security state. |
michael@0 | 567 | *aDecision = nsIContentPolicy::REJECT_REQUEST; |
michael@0 | 568 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked); |
michael@0 | 569 | // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. |
michael@0 | 570 | if (rootDoc->GetHasMixedActiveContentBlocked()) { |
michael@0 | 571 | return NS_OK; |
michael@0 | 572 | } |
michael@0 | 573 | rootDoc->SetHasMixedActiveContentBlocked(true); |
michael@0 | 574 | |
michael@0 | 575 | // The user has not overriden the pref, so make sure they still have an option by calling eventSink |
michael@0 | 576 | // which will invoke the doorhanger |
michael@0 | 577 | if (NS_SUCCEEDED(stateRV)) { |
michael@0 | 578 | eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT)); |
michael@0 | 579 | } |
michael@0 | 580 | return NS_OK; |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | } else { |
michael@0 | 584 | // The content is not blocked by the mixed content prefs. |
michael@0 | 585 | |
michael@0 | 586 | // Log a message that we are loading mixed content. |
michael@0 | 587 | LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride); |
michael@0 | 588 | |
michael@0 | 589 | // Fire the event from a script runner as it is unsafe to run script |
michael@0 | 590 | // from within ShouldLoad |
michael@0 | 591 | nsContentUtils::AddScriptRunner( |
michael@0 | 592 | new nsMixedContentEvent(aRequestingContext, classification)); |
michael@0 | 593 | return NS_OK; |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | *aDecision = REJECT_REQUEST; |
michael@0 | 597 | return NS_OK; |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | NS_IMETHODIMP |
michael@0 | 601 | nsMixedContentBlocker::ShouldProcess(uint32_t aContentType, |
michael@0 | 602 | nsIURI* aContentLocation, |
michael@0 | 603 | nsIURI* aRequestingLocation, |
michael@0 | 604 | nsISupports* aRequestingContext, |
michael@0 | 605 | const nsACString& aMimeGuess, |
michael@0 | 606 | nsISupports* aExtra, |
michael@0 | 607 | nsIPrincipal* aRequestPrincipal, |
michael@0 | 608 | int16_t* aDecision) |
michael@0 | 609 | { |
michael@0 | 610 | if (!aContentLocation) { |
michael@0 | 611 | // aContentLocation may be null when a plugin is loading without an associated URI resource |
michael@0 | 612 | if (aContentType == TYPE_OBJECT) { |
michael@0 | 613 | return NS_OK; |
michael@0 | 614 | } else { |
michael@0 | 615 | return NS_ERROR_FAILURE; |
michael@0 | 616 | } |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | return ShouldLoad(aContentType, aContentLocation, aRequestingLocation, |
michael@0 | 620 | aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal, |
michael@0 | 621 | aDecision); |
michael@0 | 622 | } |