michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDocShell.h" michael@0: #include "nsDSURIContentListener.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsDocShellCID.h" michael@0: #include "nsIWebNavigationInfo.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsError.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsDocShellLoadTypes.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: //***************************************************************************** michael@0: //*** nsDSURIContentListener: Object Management michael@0: //***************************************************************************** michael@0: michael@0: nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) michael@0: : mDocShell(aDocShell), michael@0: mParentContentListener(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsDSURIContentListener::~nsDSURIContentListener() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsDSURIContentListener::Init() michael@0: { michael@0: nsresult rv; michael@0: mNavInfo = do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get webnav info"); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsDSURIContentListener::nsISupports michael@0: //***************************************************************************** michael@0: michael@0: NS_IMPL_ADDREF(nsDSURIContentListener) michael@0: NS_IMPL_RELEASE(nsDSURIContentListener) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: //***************************************************************************** michael@0: // nsDSURIContentListener::nsIURIContentListener michael@0: //***************************************************************************** michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) michael@0: { michael@0: // If mDocShell is null here, that means someone's starting a load michael@0: // in our docshell after it's already been destroyed. Don't let michael@0: // that happen. michael@0: if (!mDocShell) { michael@0: *aAbortOpen = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr parentListener; michael@0: GetParentContentListener(getter_AddRefs(parentListener)); michael@0: if (parentListener) michael@0: return parentListener->OnStartURIOpen(aURI, aAbortOpen); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::DoContent(const char* aContentType, michael@0: bool aIsContentPreferred, michael@0: nsIRequest* request, michael@0: nsIStreamListener** aContentHandler, michael@0: bool* aAbortProcess) michael@0: { michael@0: nsresult rv; michael@0: NS_ENSURE_ARG_POINTER(aContentHandler); michael@0: NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); michael@0: michael@0: // Check whether X-Frame-Options permits us to load this content in an michael@0: // iframe and abort the load (unless we've disabled x-frame-options michael@0: // checking). michael@0: if (!CheckFrameOptions(request)) { michael@0: *aAbortProcess = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aAbortProcess = false; michael@0: michael@0: // determine if the channel has just been retargeted to us... michael@0: nsLoadFlags loadFlags = 0; michael@0: nsCOMPtr aOpenedChannel = do_QueryInterface(request); michael@0: michael@0: if (aOpenedChannel) michael@0: aOpenedChannel->GetLoadFlags(&loadFlags); michael@0: michael@0: if(loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) michael@0: { michael@0: // XXX: Why does this not stop the content too? michael@0: mDocShell->Stop(nsIWebNavigation::STOP_NETWORK); michael@0: michael@0: mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); michael@0: } michael@0: michael@0: rv = mDocShell->CreateContentViewer(aContentType, request, aContentHandler); michael@0: michael@0: if (rv == NS_ERROR_REMOTE_XUL) { michael@0: request->Cancel(rv); michael@0: *aAbortProcess = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // we don't know how to handle the content michael@0: *aContentHandler = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { michael@0: nsCOMPtr domWindow = do_GetInterface(static_cast(mDocShell)); michael@0: NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); michael@0: domWindow->Focus(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::IsPreferred(const char* aContentType, michael@0: char ** aDesiredContentType, michael@0: bool* aCanHandle) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanHandle); michael@0: NS_ENSURE_ARG_POINTER(aDesiredContentType); michael@0: michael@0: // the docshell has no idea if it is the preferred content provider or not. michael@0: // It needs to ask its parent if it is the preferred content handler or not... michael@0: michael@0: nsCOMPtr parentListener; michael@0: GetParentContentListener(getter_AddRefs(parentListener)); michael@0: if (parentListener) { michael@0: return parentListener->IsPreferred(aContentType, michael@0: aDesiredContentType, michael@0: aCanHandle); michael@0: } michael@0: // we used to return false here if we didn't have a parent properly michael@0: // registered at the top of the docshell hierarchy to dictate what michael@0: // content types this docshell should be a preferred handler for. But michael@0: // this really makes it hard for developers using iframe or browser tags michael@0: // because then they need to make sure they implement michael@0: // nsIURIContentListener otherwise all link clicks would get sent to michael@0: // another window because we said we weren't the preferred handler type. michael@0: // I'm going to change the default now...if we can handle the content, michael@0: // and someone didn't EXPLICITLY set a nsIURIContentListener at the top michael@0: // of our docshell chain, then we'll now always attempt to process the michael@0: // content ourselves... michael@0: return CanHandleContent(aContentType, michael@0: true, michael@0: aDesiredContentType, michael@0: aCanHandle); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::CanHandleContent(const char* aContentType, michael@0: bool aIsContentPreferred, michael@0: char ** aDesiredContentType, michael@0: bool* aCanHandleContent) michael@0: { michael@0: NS_PRECONDITION(aCanHandleContent, "Null out param?"); michael@0: NS_ENSURE_ARG_POINTER(aDesiredContentType); michael@0: michael@0: *aCanHandleContent = false; michael@0: *aDesiredContentType = nullptr; michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (aContentType) { michael@0: uint32_t canHandle = nsIWebNavigationInfo::UNSUPPORTED; michael@0: rv = mNavInfo->IsTypeSupported(nsDependentCString(aContentType), michael@0: mDocShell, michael@0: &canHandle); michael@0: *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::GetLoadCookie(nsISupports ** aLoadCookie) michael@0: { michael@0: NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::SetLoadCookie(nsISupports * aLoadCookie) michael@0: { michael@0: #ifdef DEBUG michael@0: nsRefPtr cookieAsDocLoader = michael@0: nsDocLoader::GetAsDocLoader(aLoadCookie); michael@0: NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell, michael@0: "Invalid load cookie being set!"); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::GetParentContentListener(nsIURIContentListener** michael@0: aParentListener) michael@0: { michael@0: if (mWeakParentContentListener) michael@0: { michael@0: nsCOMPtr tempListener = michael@0: do_QueryReferent(mWeakParentContentListener); michael@0: *aParentListener = tempListener; michael@0: NS_IF_ADDREF(*aParentListener); michael@0: } michael@0: else { michael@0: *aParentListener = mParentContentListener; michael@0: NS_IF_ADDREF(*aParentListener); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDSURIContentListener::SetParentContentListener(nsIURIContentListener* michael@0: aParentListener) michael@0: { michael@0: if (aParentListener) michael@0: { michael@0: // Store the parent listener as a weak ref. Parents not supporting michael@0: // nsISupportsWeakReference assert but may still be used. michael@0: mParentContentListener = nullptr; michael@0: mWeakParentContentListener = do_GetWeakReference(aParentListener); michael@0: if (!mWeakParentContentListener) michael@0: { michael@0: mParentContentListener = aParentListener; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: mWeakParentContentListener = nullptr; michael@0: mParentContentListener = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIHttpChannel *httpChannel, michael@0: const nsAString& policy) { michael@0: static const char allowFrom[] = "allow-from"; michael@0: const uint32_t allowFromLen = ArrayLength(allowFrom) - 1; michael@0: bool isAllowFrom = michael@0: StringHead(policy, allowFromLen).LowerCaseEqualsLiteral(allowFrom); michael@0: michael@0: // return early if header does not have one of the values with meaning michael@0: if (!policy.LowerCaseEqualsLiteral("deny") && michael@0: !policy.LowerCaseEqualsLiteral("sameorigin") && michael@0: !isAllowFrom) michael@0: return true; michael@0: michael@0: nsCOMPtr uri; michael@0: httpChannel->GetURI(getter_AddRefs(uri)); michael@0: michael@0: // XXXkhuey when does this happen? Is returning true safe here? michael@0: if (!mDocShell) { michael@0: return true; michael@0: } michael@0: michael@0: // We need to check the location of this window and the location of the top michael@0: // window, if we're not the top. X-F-O: SAMEORIGIN requires that the michael@0: // document must be same-origin with top window. X-F-O: DENY requires that michael@0: // the document must never be framed. michael@0: nsCOMPtr thisWindow = do_GetInterface(static_cast(mDocShell)); michael@0: // If we don't have DOMWindow there is no risk of clickjacking michael@0: if (!thisWindow) michael@0: return true; michael@0: michael@0: // GetScriptableTop, not GetTop, because we want this to respect michael@0: //