/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ThirdPartyUtil.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsIChannel.h" #include "nsIServiceManager.h" #include "nsIHttpChannelInternal.h" #include "nsIDOMWindow.h" #include "nsILoadContext.h" #include "nsIPrincipal.h" #include "nsIScriptObjectPrincipal.h" #include "nsIURI.h" #include "nsThreadUtils.h" #include "mozilla/Logging.h" NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil) // // MOZ_LOG=thirdPartyUtil:5 // static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil"); #undef LOG #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args) nsresult ThirdPartyUtil::Init() { NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE); nsresult rv; mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); return rv; } // Determine if aFirstDomain is a different base domain to aSecondURI; or, if // the concept of base domain does not apply, determine if the two hosts are not // string-identical. nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain, nsIURI* aSecondURI, bool* aResult) { if (!aSecondURI) { return NS_ERROR_INVALID_ARG; } // Get the base domain for aSecondURI. nsCString secondDomain; nsresult rv = GetBaseDomain(aSecondURI, secondDomain); LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(), secondDomain.get())); if (NS_FAILED(rv)) return rv; // Check strict equality. *aResult = aFirstDomain != secondDomain; return NS_OK; } // Get the URI associated with a window. NS_IMETHODIMP ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) { nsresult rv; nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin); if (!scriptObjPrin) { return NS_ERROR_INVALID_ARG; } nsIPrincipal* prin = scriptObjPrin->GetPrincipal(); if (!prin) { return NS_ERROR_INVALID_ARG; } if (prin->GetIsNullPrincipal()) { LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n")); return NS_ERROR_INVALID_ARG; } rv = prin->GetURI(result); return rv; } // Determine if aFirstURI is third party with respect to aSecondURI. See docs // for mozIThirdPartyUtil. NS_IMETHODIMP ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI, bool* aResult) { NS_ENSURE_ARG(aFirstURI); NS_ENSURE_ARG(aSecondURI); NS_ASSERTION(aResult, "null outparam pointer"); nsCString firstHost; nsresult rv = GetBaseDomain(aFirstURI, firstHost); if (NS_FAILED(rv)) return rv; return IsThirdPartyInternal(firstHost, aSecondURI, aResult); } // Determine if any URI of the window hierarchy of aWindow is foreign with // respect to aSecondURI. See docs for mozIThirdPartyUtil. NS_IMETHODIMP ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI, bool* aResult) { NS_ENSURE_ARG(aWindow); NS_ASSERTION(aResult, "null outparam pointer"); bool result; // Get the URI of the window, and its base domain. nsresult rv; nsCOMPtr<nsIURI> currentURI; rv = GetURIFromWindow(aWindow, getter_AddRefs(currentURI)); if (NS_FAILED(rv)) return rv; nsCString bottomDomain; rv = GetBaseDomain(currentURI, bottomDomain); if (NS_FAILED(rv)) return rv; if (aURI) { // Determine whether aURI is foreign with respect to currentURI. rv = IsThirdPartyInternal(bottomDomain, aURI, &result); if (NS_FAILED(rv)) return rv; if (result) { *aResult = true; return NS_OK; } } nsCOMPtr<nsPIDOMWindowOuter> current = nsPIDOMWindowOuter::From(aWindow), parent; nsCOMPtr<nsIURI> parentURI; do { // We use GetScriptableParent rather than GetParent because we consider // <iframe mozbrowser/mozapp> to be a top-level frame. parent = current->GetScriptableParent(); if (SameCOMIdentity(parent, current)) { // We're at the topmost content window. We already know the answer. *aResult = false; return NS_OK; } rv = GetURIFromWindow(parent, getter_AddRefs(parentURI)); NS_ENSURE_SUCCESS(rv, rv); rv = IsThirdPartyInternal(bottomDomain, parentURI, &result); if (NS_FAILED(rv)) return rv; if (result) { *aResult = true; return NS_OK; } current = parent; currentURI = parentURI; } while (1); NS_NOTREACHED("should've returned"); return NS_ERROR_UNEXPECTED; } // Determine if the URI associated with aChannel or any URI of the window // hierarchy associated with the channel is foreign with respect to aSecondURI. // See docs for mozIThirdPartyUtil. NS_IMETHODIMP ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel, nsIURI* aURI, bool* aResult) { LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel)); NS_ENSURE_ARG(aChannel); NS_ASSERTION(aResult, "null outparam pointer"); nsresult rv; bool doForce = false; nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(aChannel); if (httpChannelInternal) { uint32_t flags; rv = httpChannelInternal->GetThirdPartyFlags(&flags); NS_ENSURE_SUCCESS(rv, rv); doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); // If aURI was not supplied, and we're forcing, then we're by definition // not foreign. If aURI was supplied, we still want to check whether it's // foreign with respect to the channel URI. (The forcing only applies to // whatever window hierarchy exists above the channel.) if (doForce && !aURI) { *aResult = false; return NS_OK; } } bool parentIsThird = false; // Obtain the URI from the channel, and its base domain. nsCOMPtr<nsIURI> channelURI; rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); if (NS_FAILED(rv)) return rv; nsCString channelDomain; rv = GetBaseDomain(channelURI, channelDomain); if (NS_FAILED(rv)) return rv; if (!doForce) { if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo()) { parentIsThird = loadInfo->GetIsInThirdPartyContext(); if (!parentIsThird && loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) { // Check if the channel itself is third-party to its own requestor. // Unforunately, we have to go through the loading principal. nsCOMPtr<nsIURI> parentURI; loadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(parentURI)); rv = IsThirdPartyInternal(channelDomain, parentURI, &parentIsThird); if (NS_FAILED(rv)) return rv; } } else { NS_WARNING("Found channel with no loadinfo, assuming third-party request"); parentIsThird = true; } } // If we're not comparing to a URI, we have our answer. Otherwise, if // parentIsThird, we're not forcing and we know that we're a third-party // request. if (!aURI || parentIsThird) { *aResult = parentIsThird; return NS_OK; } // Determine whether aURI is foreign with respect to channelURI. return IsThirdPartyInternal(channelDomain, aURI, aResult); } NS_IMETHODIMP ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel, mozIDOMWindowProxy** aWin) { NS_ENSURE_ARG(aWin); // Find the associated window and its parent window. nsCOMPtr<nsILoadContext> ctx; NS_QueryNotificationCallbacks(aChannel, ctx); if (!ctx) { return NS_ERROR_INVALID_ARG; } nsCOMPtr<mozIDOMWindowProxy> window; ctx->GetAssociatedWindow(getter_AddRefs(window)); if (!window) { return NS_ERROR_INVALID_ARG; } nsCOMPtr<nsPIDOMWindowOuter> top = nsPIDOMWindowOuter::From(window)->GetTop(); top.forget(aWin); return NS_OK; } // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing // dot may be present. If aHostURI is an IP address, an alias such as // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will // be the exact host. The result of this function should only be used in exact // string comparisons, since substring comparisons will not be valid for the // special cases elided above. NS_IMETHODIMP ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) { if (!aHostURI) { return NS_ERROR_INVALID_ARG; } // Get the base domain. this will fail if the host contains a leading dot, // more than one trailing dot, or is otherwise malformed. nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { // aHostURI is either an IP address, an alias such as 'localhost', an eTLD // such as 'co.uk', or the empty string. Uses the normalized host in such // cases. rv = aHostURI->GetAsciiHost(aBaseDomain); } NS_ENSURE_SUCCESS(rv, rv); // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail. if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') return NS_ERROR_INVALID_ARG; // Reject any URIs without a host that aren't file:// URIs. This makes it the // only way we can get a base domain consisting of the empty string, which // means we can safely perform foreign tests on such URIs where "not foreign" // means "the involved URIs are all file://". if (aBaseDomain.IsEmpty()) { bool isFileURI = false; aHostURI->SchemeIs("file", &isFileURI); if (!isFileURI) { return NS_ERROR_INVALID_ARG; } } return NS_OK; }