1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/docshell/base/nsDSURIContentListener.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,522 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsDocShell.h" 1.10 +#include "nsDSURIContentListener.h" 1.11 +#include "nsIChannel.h" 1.12 +#include "nsServiceManagerUtils.h" 1.13 +#include "nsDocShellCID.h" 1.14 +#include "nsIWebNavigationInfo.h" 1.15 +#include "nsIDocument.h" 1.16 +#include "nsIDOMWindow.h" 1.17 +#include "nsNetUtil.h" 1.18 +#include "nsAutoPtr.h" 1.19 +#include "nsIHttpChannel.h" 1.20 +#include "nsIScriptSecurityManager.h" 1.21 +#include "nsError.h" 1.22 +#include "nsCharSeparatedTokenizer.h" 1.23 +#include "nsIConsoleService.h" 1.24 +#include "nsIScriptError.h" 1.25 +#include "nsDocShellLoadTypes.h" 1.26 + 1.27 +using namespace mozilla; 1.28 + 1.29 +//***************************************************************************** 1.30 +//*** nsDSURIContentListener: Object Management 1.31 +//***************************************************************************** 1.32 + 1.33 +nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) 1.34 + : mDocShell(aDocShell), 1.35 + mParentContentListener(nullptr) 1.36 +{ 1.37 +} 1.38 + 1.39 +nsDSURIContentListener::~nsDSURIContentListener() 1.40 +{ 1.41 +} 1.42 + 1.43 +nsresult 1.44 +nsDSURIContentListener::Init() 1.45 +{ 1.46 + nsresult rv; 1.47 + mNavInfo = do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv); 1.48 + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get webnav info"); 1.49 + return rv; 1.50 +} 1.51 + 1.52 + 1.53 +//***************************************************************************** 1.54 +// nsDSURIContentListener::nsISupports 1.55 +//***************************************************************************** 1.56 + 1.57 +NS_IMPL_ADDREF(nsDSURIContentListener) 1.58 +NS_IMPL_RELEASE(nsDSURIContentListener) 1.59 + 1.60 +NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) 1.61 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) 1.62 + NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) 1.63 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.64 +NS_INTERFACE_MAP_END 1.65 + 1.66 +//***************************************************************************** 1.67 +// nsDSURIContentListener::nsIURIContentListener 1.68 +//***************************************************************************** 1.69 + 1.70 +NS_IMETHODIMP 1.71 +nsDSURIContentListener::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) 1.72 +{ 1.73 + // If mDocShell is null here, that means someone's starting a load 1.74 + // in our docshell after it's already been destroyed. Don't let 1.75 + // that happen. 1.76 + if (!mDocShell) { 1.77 + *aAbortOpen = true; 1.78 + return NS_OK; 1.79 + } 1.80 + 1.81 + nsCOMPtr<nsIURIContentListener> parentListener; 1.82 + GetParentContentListener(getter_AddRefs(parentListener)); 1.83 + if (parentListener) 1.84 + return parentListener->OnStartURIOpen(aURI, aAbortOpen); 1.85 + 1.86 + return NS_OK; 1.87 +} 1.88 + 1.89 +NS_IMETHODIMP 1.90 +nsDSURIContentListener::DoContent(const char* aContentType, 1.91 + bool aIsContentPreferred, 1.92 + nsIRequest* request, 1.93 + nsIStreamListener** aContentHandler, 1.94 + bool* aAbortProcess) 1.95 +{ 1.96 + nsresult rv; 1.97 + NS_ENSURE_ARG_POINTER(aContentHandler); 1.98 + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); 1.99 + 1.100 + // Check whether X-Frame-Options permits us to load this content in an 1.101 + // iframe and abort the load (unless we've disabled x-frame-options 1.102 + // checking). 1.103 + if (!CheckFrameOptions(request)) { 1.104 + *aAbortProcess = true; 1.105 + return NS_OK; 1.106 + } 1.107 + 1.108 + *aAbortProcess = false; 1.109 + 1.110 + // determine if the channel has just been retargeted to us... 1.111 + nsLoadFlags loadFlags = 0; 1.112 + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(request); 1.113 + 1.114 + if (aOpenedChannel) 1.115 + aOpenedChannel->GetLoadFlags(&loadFlags); 1.116 + 1.117 + if(loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) 1.118 + { 1.119 + // XXX: Why does this not stop the content too? 1.120 + mDocShell->Stop(nsIWebNavigation::STOP_NETWORK); 1.121 + 1.122 + mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); 1.123 + } 1.124 + 1.125 + rv = mDocShell->CreateContentViewer(aContentType, request, aContentHandler); 1.126 + 1.127 + if (rv == NS_ERROR_REMOTE_XUL) { 1.128 + request->Cancel(rv); 1.129 + *aAbortProcess = true; 1.130 + return NS_OK; 1.131 + } 1.132 + 1.133 + if (NS_FAILED(rv)) { 1.134 + // we don't know how to handle the content 1.135 + *aContentHandler = nullptr; 1.136 + return rv; 1.137 + } 1.138 + 1.139 + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { 1.140 + nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell)); 1.141 + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); 1.142 + domWindow->Focus(); 1.143 + } 1.144 + 1.145 + return NS_OK; 1.146 +} 1.147 + 1.148 +NS_IMETHODIMP 1.149 +nsDSURIContentListener::IsPreferred(const char* aContentType, 1.150 + char ** aDesiredContentType, 1.151 + bool* aCanHandle) 1.152 +{ 1.153 + NS_ENSURE_ARG_POINTER(aCanHandle); 1.154 + NS_ENSURE_ARG_POINTER(aDesiredContentType); 1.155 + 1.156 + // the docshell has no idea if it is the preferred content provider or not. 1.157 + // It needs to ask its parent if it is the preferred content handler or not... 1.158 + 1.159 + nsCOMPtr<nsIURIContentListener> parentListener; 1.160 + GetParentContentListener(getter_AddRefs(parentListener)); 1.161 + if (parentListener) { 1.162 + return parentListener->IsPreferred(aContentType, 1.163 + aDesiredContentType, 1.164 + aCanHandle); 1.165 + } 1.166 + // we used to return false here if we didn't have a parent properly 1.167 + // registered at the top of the docshell hierarchy to dictate what 1.168 + // content types this docshell should be a preferred handler for. But 1.169 + // this really makes it hard for developers using iframe or browser tags 1.170 + // because then they need to make sure they implement 1.171 + // nsIURIContentListener otherwise all link clicks would get sent to 1.172 + // another window because we said we weren't the preferred handler type. 1.173 + // I'm going to change the default now...if we can handle the content, 1.174 + // and someone didn't EXPLICITLY set a nsIURIContentListener at the top 1.175 + // of our docshell chain, then we'll now always attempt to process the 1.176 + // content ourselves... 1.177 + return CanHandleContent(aContentType, 1.178 + true, 1.179 + aDesiredContentType, 1.180 + aCanHandle); 1.181 +} 1.182 + 1.183 +NS_IMETHODIMP 1.184 +nsDSURIContentListener::CanHandleContent(const char* aContentType, 1.185 + bool aIsContentPreferred, 1.186 + char ** aDesiredContentType, 1.187 + bool* aCanHandleContent) 1.188 +{ 1.189 + NS_PRECONDITION(aCanHandleContent, "Null out param?"); 1.190 + NS_ENSURE_ARG_POINTER(aDesiredContentType); 1.191 + 1.192 + *aCanHandleContent = false; 1.193 + *aDesiredContentType = nullptr; 1.194 + 1.195 + nsresult rv = NS_OK; 1.196 + if (aContentType) { 1.197 + uint32_t canHandle = nsIWebNavigationInfo::UNSUPPORTED; 1.198 + rv = mNavInfo->IsTypeSupported(nsDependentCString(aContentType), 1.199 + mDocShell, 1.200 + &canHandle); 1.201 + *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); 1.202 + } 1.203 + 1.204 + return rv; 1.205 +} 1.206 + 1.207 +NS_IMETHODIMP 1.208 +nsDSURIContentListener::GetLoadCookie(nsISupports ** aLoadCookie) 1.209 +{ 1.210 + NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); 1.211 + return NS_OK; 1.212 +} 1.213 + 1.214 +NS_IMETHODIMP 1.215 +nsDSURIContentListener::SetLoadCookie(nsISupports * aLoadCookie) 1.216 +{ 1.217 +#ifdef DEBUG 1.218 + nsRefPtr<nsDocLoader> cookieAsDocLoader = 1.219 + nsDocLoader::GetAsDocLoader(aLoadCookie); 1.220 + NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell, 1.221 + "Invalid load cookie being set!"); 1.222 +#endif 1.223 + return NS_OK; 1.224 +} 1.225 + 1.226 +NS_IMETHODIMP 1.227 +nsDSURIContentListener::GetParentContentListener(nsIURIContentListener** 1.228 + aParentListener) 1.229 +{ 1.230 + if (mWeakParentContentListener) 1.231 + { 1.232 + nsCOMPtr<nsIURIContentListener> tempListener = 1.233 + do_QueryReferent(mWeakParentContentListener); 1.234 + *aParentListener = tempListener; 1.235 + NS_IF_ADDREF(*aParentListener); 1.236 + } 1.237 + else { 1.238 + *aParentListener = mParentContentListener; 1.239 + NS_IF_ADDREF(*aParentListener); 1.240 + } 1.241 + return NS_OK; 1.242 +} 1.243 + 1.244 +NS_IMETHODIMP 1.245 +nsDSURIContentListener::SetParentContentListener(nsIURIContentListener* 1.246 + aParentListener) 1.247 +{ 1.248 + if (aParentListener) 1.249 + { 1.250 + // Store the parent listener as a weak ref. Parents not supporting 1.251 + // nsISupportsWeakReference assert but may still be used. 1.252 + mParentContentListener = nullptr; 1.253 + mWeakParentContentListener = do_GetWeakReference(aParentListener); 1.254 + if (!mWeakParentContentListener) 1.255 + { 1.256 + mParentContentListener = aParentListener; 1.257 + } 1.258 + } 1.259 + else 1.260 + { 1.261 + mWeakParentContentListener = nullptr; 1.262 + mParentContentListener = nullptr; 1.263 + } 1.264 + return NS_OK; 1.265 +} 1.266 + 1.267 +bool nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIHttpChannel *httpChannel, 1.268 + const nsAString& policy) { 1.269 + static const char allowFrom[] = "allow-from"; 1.270 + const uint32_t allowFromLen = ArrayLength(allowFrom) - 1; 1.271 + bool isAllowFrom = 1.272 + StringHead(policy, allowFromLen).LowerCaseEqualsLiteral(allowFrom); 1.273 + 1.274 + // return early if header does not have one of the values with meaning 1.275 + if (!policy.LowerCaseEqualsLiteral("deny") && 1.276 + !policy.LowerCaseEqualsLiteral("sameorigin") && 1.277 + !isAllowFrom) 1.278 + return true; 1.279 + 1.280 + nsCOMPtr<nsIURI> uri; 1.281 + httpChannel->GetURI(getter_AddRefs(uri)); 1.282 + 1.283 + // XXXkhuey when does this happen? Is returning true safe here? 1.284 + if (!mDocShell) { 1.285 + return true; 1.286 + } 1.287 + 1.288 + // We need to check the location of this window and the location of the top 1.289 + // window, if we're not the top. X-F-O: SAMEORIGIN requires that the 1.290 + // document must be same-origin with top window. X-F-O: DENY requires that 1.291 + // the document must never be framed. 1.292 + nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell)); 1.293 + // If we don't have DOMWindow there is no risk of clickjacking 1.294 + if (!thisWindow) 1.295 + return true; 1.296 + 1.297 + // GetScriptableTop, not GetTop, because we want this to respect 1.298 + // <iframe mozbrowser> boundaries. 1.299 + nsCOMPtr<nsIDOMWindow> topWindow; 1.300 + thisWindow->GetScriptableTop(getter_AddRefs(topWindow)); 1.301 + 1.302 + // if the document is in the top window, it's not in a frame. 1.303 + if (thisWindow == topWindow) 1.304 + return true; 1.305 + 1.306 + // Find the top docshell in our parent chain that doesn't have the system 1.307 + // principal and use it for the principal comparison. Finding the top 1.308 + // content-type docshell doesn't work because some chrome documents are 1.309 + // loaded in content docshells (see bug 593387). 1.310 + nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface( 1.311 + static_cast<nsIDocShell*> (mDocShell))); 1.312 + nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem, 1.313 + curDocShellItem = thisDocShellItem; 1.314 + nsCOMPtr<nsIDocument> topDoc; 1.315 + nsresult rv; 1.316 + nsCOMPtr<nsIScriptSecurityManager> ssm = 1.317 + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); 1.318 + if (!ssm) { 1.319 + MOZ_CRASH(); 1.320 + } 1.321 + 1.322 + // Traverse up the parent chain and stop when we see a docshell whose 1.323 + // parent has a system principal, or a docshell corresponding to 1.324 + // <iframe mozbrowser>. 1.325 + while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) && 1.326 + parentDocShellItem) { 1.327 + 1.328 + nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem); 1.329 + if (curDocShell && curDocShell->GetIsBrowserOrApp()) { 1.330 + break; 1.331 + } 1.332 + 1.333 + bool system = false; 1.334 + topDoc = do_GetInterface(parentDocShellItem); 1.335 + if (topDoc) { 1.336 + if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(), 1.337 + &system)) && system) { 1.338 + // Found a system-principled doc: last docshell was top. 1.339 + break; 1.340 + } 1.341 + } 1.342 + else { 1.343 + return false; 1.344 + } 1.345 + curDocShellItem = parentDocShellItem; 1.346 + } 1.347 + 1.348 + // If this document has the top non-SystemPrincipal docshell it is not being 1.349 + // framed or it is being framed by a chrome document, which we allow. 1.350 + if (curDocShellItem == thisDocShellItem) 1.351 + return true; 1.352 + 1.353 + // If the value of the header is DENY, and the previous condition is 1.354 + // not met (current docshell is not the top docshell), prohibit the 1.355 + // load. 1.356 + if (policy.LowerCaseEqualsLiteral("deny")) { 1.357 + ReportXFOViolation(curDocShellItem, uri, eDENY); 1.358 + return false; 1.359 + } 1.360 + 1.361 + topDoc = do_GetInterface(curDocShellItem); 1.362 + nsCOMPtr<nsIURI> topUri; 1.363 + topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri)); 1.364 + 1.365 + // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the 1.366 + // parent chain must be from the same origin as this document. 1.367 + if (policy.LowerCaseEqualsLiteral("sameorigin")) { 1.368 + rv = ssm->CheckSameOriginURI(uri, topUri, true); 1.369 + if (NS_FAILED(rv)) { 1.370 + ReportXFOViolation(curDocShellItem, uri, eSAMEORIGIN); 1.371 + return false; /* wasn't same-origin */ 1.372 + } 1.373 + } 1.374 + 1.375 + // If the X-Frame-Options value is "allow-from [uri]", then the top 1.376 + // frame in the parent chain must be from that origin 1.377 + if (isAllowFrom) { 1.378 + if (policy.Length() == allowFromLen || 1.379 + (policy[allowFromLen] != ' ' && 1.380 + policy[allowFromLen] != '\t')) { 1.381 + ReportXFOViolation(curDocShellItem, uri, eALLOWFROM); 1.382 + return false; 1.383 + } 1.384 + rv = NS_NewURI(getter_AddRefs(uri), 1.385 + Substring(policy, allowFromLen)); 1.386 + if (NS_FAILED(rv)) 1.387 + return false; 1.388 + 1.389 + rv = ssm->CheckSameOriginURI(uri, topUri, true); 1.390 + if (NS_FAILED(rv)) { 1.391 + ReportXFOViolation(curDocShellItem, uri, eALLOWFROM); 1.392 + return false; 1.393 + } 1.394 + } 1.395 + 1.396 + return true; 1.397 +} 1.398 + 1.399 +// Check if X-Frame-Options permits this document to be loaded as a subdocument. 1.400 +// This will iterate through and check any number of X-Frame-Options policies 1.401 +// in the request (comma-separated in a header, multiple headers, etc). 1.402 +bool nsDSURIContentListener::CheckFrameOptions(nsIRequest *request) 1.403 +{ 1.404 + nsresult rv; 1.405 + nsCOMPtr<nsIChannel> chan = do_QueryInterface(request); 1.406 + if (!chan) { 1.407 + return true; 1.408 + } 1.409 + 1.410 + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(chan); 1.411 + if (!httpChannel) { 1.412 + // check if it is hiding in a multipart channel 1.413 + rv = mDocShell->GetHttpChannel(chan, getter_AddRefs(httpChannel)); 1.414 + if (NS_FAILED(rv)) 1.415 + return false; 1.416 + } 1.417 + 1.418 + if (!httpChannel) { 1.419 + return true; 1.420 + } 1.421 + 1.422 + nsAutoCString xfoHeaderCValue; 1.423 + httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"), 1.424 + xfoHeaderCValue); 1.425 + NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue); 1.426 + 1.427 + // if no header value, there's nothing to do. 1.428 + if (xfoHeaderValue.IsEmpty()) 1.429 + return true; 1.430 + 1.431 + // iterate through all the header values (usually there's only one, but can 1.432 + // be many. If any want to deny the load, deny the load. 1.433 + nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ','); 1.434 + while (tokenizer.hasMoreTokens()) { 1.435 + const nsSubstring& tok = tokenizer.nextToken(); 1.436 + if (!CheckOneFrameOptionsPolicy(httpChannel, tok)) { 1.437 + // cancel the load and display about:blank 1.438 + httpChannel->Cancel(NS_BINDING_ABORTED); 1.439 + if (mDocShell) { 1.440 + nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(mDocShell)); 1.441 + if (webNav) { 1.442 + webNav->LoadURI(MOZ_UTF16("about:blank"), 1.443 + 0, nullptr, nullptr, nullptr); 1.444 + } 1.445 + } 1.446 + return false; 1.447 + } 1.448 + } 1.449 + 1.450 + return true; 1.451 +} 1.452 + 1.453 +void 1.454 +nsDSURIContentListener::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem, 1.455 + nsIURI* aThisURI, 1.456 + XFOHeader aHeader) 1.457 +{ 1.458 + nsresult rv = NS_OK; 1.459 + 1.460 + nsCOMPtr<nsPIDOMWindow> topOuterWindow = do_GetInterface(aTopDocShellItem); 1.461 + if (!topOuterWindow) 1.462 + return; 1.463 + 1.464 + NS_ASSERTION(topOuterWindow->IsOuterWindow(), "Huh?"); 1.465 + nsPIDOMWindow* topInnerWindow = topOuterWindow->GetCurrentInnerWindow(); 1.466 + if (!topInnerWindow) 1.467 + return; 1.468 + 1.469 + nsCOMPtr<nsIURI> topURI; 1.470 + 1.471 + nsCOMPtr<nsIDocument> document; 1.472 + 1.473 + document = do_GetInterface(aTopDocShellItem); 1.474 + rv = document->NodePrincipal()->GetURI(getter_AddRefs(topURI)); 1.475 + if (NS_FAILED(rv)) 1.476 + return; 1.477 + 1.478 + if (!topURI) 1.479 + return; 1.480 + 1.481 + nsCString topURIString; 1.482 + nsCString thisURIString; 1.483 + 1.484 + rv = topURI->GetSpec(topURIString); 1.485 + if (NS_FAILED(rv)) 1.486 + return; 1.487 + 1.488 + rv = aThisURI->GetSpec(thisURIString); 1.489 + if (NS_FAILED(rv)) 1.490 + return; 1.491 + 1.492 + nsCOMPtr<nsIConsoleService> consoleService = 1.493 + do_GetService(NS_CONSOLESERVICE_CONTRACTID); 1.494 + nsCOMPtr<nsIScriptError> errorObject = 1.495 + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); 1.496 + 1.497 + if (!consoleService || !errorObject) 1.498 + return; 1.499 + 1.500 + nsString msg = NS_LITERAL_STRING("Load denied by X-Frame-Options: "); 1.501 + msg.Append(NS_ConvertUTF8toUTF16(thisURIString)); 1.502 + 1.503 + switch (aHeader) { 1.504 + case eDENY: 1.505 + msg.AppendLiteral(" does not permit framing."); 1.506 + break; 1.507 + case eSAMEORIGIN: 1.508 + msg.AppendLiteral(" does not permit cross-origin framing."); 1.509 + break; 1.510 + case eALLOWFROM: 1.511 + msg.AppendLiteral(" does not permit framing by "); 1.512 + msg.Append(NS_ConvertUTF8toUTF16(topURIString)); 1.513 + msg.AppendLiteral("."); 1.514 + break; 1.515 + } 1.516 + 1.517 + rv = errorObject->InitWithWindowID(msg, EmptyString(), EmptyString(), 0, 0, 1.518 + nsIScriptError::errorFlag, 1.519 + "X-Frame-Options", 1.520 + topInnerWindow->WindowID()); 1.521 + if (NS_FAILED(rv)) 1.522 + return; 1.523 + 1.524 + consoleService->LogMessage(errorObject); 1.525 +}