Commit 96bbeedf authored by Mike Perry's avatar Mike Perry Committed by Georg Koppen
Browse files

Bug #5742: API allows you to get the url bar URI for a channel or nsIDocument.

Logs the URI of the source doc/channel that failed.
parent fd41a915
Loading
Loading
Loading
Loading
+304 −0
Original line number Diff line number Diff line
@@ -9,12 +9,18 @@
#include "nsIServiceManager.h"
#include "nsIHttpChannelInternal.h"
#include "nsIDOMWindow.h"
#include "nsICookiePermission.h"
#include "nsIDOMDocument.h"
#include "nsIDocument.h"
#include "nsILoadContext.h"
#include "nsIPrincipal.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsThreadUtils.h"
#include "prlog.h"
#include "nsPrintfCString.h"
#include "nsIConsoleService.h"
#include "nsContentUtils.h"

NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)

@@ -32,6 +38,7 @@ ThirdPartyUtil::Init()

  nsresult rv;
  mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
  mCookiePermissions = do_GetService(NS_COOKIEPERMISSION_CONTRACTID);

    if (!gThirdPartyLog)
        gThirdPartyLog = PR_NewLogModule("thirdPartyUtil");
@@ -60,6 +67,21 @@ ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
  return NS_OK;
}

// Return true if aURI's scheme is white listed, in which case
// getFirstPartyURI() will not require that the firstPartyURI contains a host.
bool ThirdPartyUtil::SchemeIsWhiteListed(nsIURI *aURI)
{
  if (!aURI)
    return false;

  nsAutoCString scheme;
  nsresult rv = aURI->GetScheme(scheme);
  NS_ENSURE_SUCCESS(rv, false);

  return (scheme.Equals("about") || scheme.Equals("moz-safe-about")
          || scheme.Equals("chrome"));
}

// Get the URI associated with a window.
NS_IMETHODIMP
ThirdPartyUtil::GetURIFromWindow(nsIDOMWindow* aWin, nsIURI** result)
@@ -84,6 +106,106 @@ ThirdPartyUtil::GetURIFromWindow(nsIDOMWindow* aWin, nsIURI** result)
  return rv;
}

nsresult
ThirdPartyUtil::GetOriginatingURI(nsIChannel *aChannel, nsIURI **aURI)
{
  /* to find the originating URI, we use the loadgroup of the channel to obtain
   * the window owning the load, and from there, we find the top same-type
   * window and its URI. there are several possible cases:
   *
   * 1) no channel.
   *
   * 2) a channel with the "force allow third party cookies" option set.
   *    since we may not have a window, we return the channel URI in this case.
   *
   * 3) a channel, but no window. this can occur when the consumer kicking
   *    off the load doesn't provide one to the channel, and should be limited
   *    to loads of certain types of resources.
   *
   * 4) a window equal to the top window of same type, with the channel its
   *    document channel. this covers the case of a freshly kicked-off load
   *    (e.g. the user typing something in the location bar, or clicking on a
   *    bookmark), where the window's URI hasn't yet been set, and will be
   *    bogus. we return the channel URI in this case.
   *
   * 5) Anything else. this covers most cases for an ordinary page load from
   *    the location bar, and will catch nested frames within a page, image
   *    loads, etc. we return the URI of the root window's document's principal
   *    in this case.
   */

  *aURI = nullptr;

  // case 1)
  if (!aChannel)
    return NS_ERROR_NULL_POINTER;

  // case 2)
  nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(aChannel);
  if (httpChannelInternal)
  {
    bool doForce = false;
    if (NS_SUCCEEDED(httpChannelInternal->GetForceAllowThirdPartyCookie(&doForce)) && doForce)
    {
      // return the channel's URI (we may not have a window)
      aChannel->GetURI(aURI);
      if (!*aURI)
        return NS_ERROR_NULL_POINTER;

      return NS_OK;
    }

    // TODO: Why don't we just use this here:
    // httpChannelInternal->GetDocumentURI(aURI);
  }

  // find the associated window and its top window
  nsCOMPtr<nsILoadContext> ctx;
  NS_QueryNotificationCallbacks(aChannel, ctx);
  nsCOMPtr<nsIDOMWindow> topWin, ourWin;
  if (ctx) {
    ctx->GetTopWindow(getter_AddRefs(topWin));
    ctx->GetAssociatedWindow(getter_AddRefs(ourWin));
  }

  // case 3)
  if (!topWin)
    return NS_ERROR_INVALID_ARG;

  // case 4)
  if (ourWin == topWin) {
    // Check whether this is the document channel for this window (representing
    // a load of a new page).  This is a bit of a nasty hack, but we will
    // hopefully flag these channels better later.
    nsLoadFlags flags;
    aChannel->GetLoadFlags(&flags);

    if (flags & nsIChannel::LOAD_DOCUMENT_URI) {
      // get the channel URI - the window's will be bogus
      aChannel->GetURI(aURI);
      if (!*aURI)
        return NS_ERROR_NULL_POINTER;

      return NS_OK;
    }
  }

  // case 5) - get the originating URI from the top window's principal
  nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(topWin);
  NS_ENSURE_TRUE(scriptObjPrin, NS_ERROR_UNEXPECTED);

  nsIPrincipal* prin = scriptObjPrin->GetPrincipal();
  NS_ENSURE_TRUE(prin, NS_ERROR_UNEXPECTED);

  prin->GetURI(aURI);

  if (!*aURI)
    return NS_ERROR_NULL_POINTER;

  // all done!
  return NS_OK;
}

// Determine if aFirstURI is third party with respect to aSecondURI. See docs
// for mozIThirdPartyUtil.
NS_IMETHODIMP
@@ -368,3 +490,185 @@ ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI,

  return NS_OK;
}

// Not scriptable due to the use of an nsIDocument parameter.
NS_IMETHODIMP
ThirdPartyUtil::GetFirstPartyURI(nsIChannel *aChannel,
                                 nsIDocument *aDoc,
                                 nsIURI **aOutput)
{
  return GetFirstPartyURIInternal(aChannel, aDoc, true, aOutput);
}

nsresult
ThirdPartyUtil::GetFirstPartyURIInternal(nsIChannel *aChannel,
                                         nsIDocument *aDoc,
                                         bool aLogErrors,
                                         nsIURI **aOutput)
{
  nsresult rv = NS_ERROR_NULL_POINTER;
  nsCOMPtr<nsIURI> srcURI;

  if (!aOutput)
    return rv;

  *aOutput = nullptr;

  if (!aChannel && aDoc) {
    aChannel = aDoc->GetChannel();
  }

  // If aChannel is specified or available, use the official route
  // for sure
  if (aChannel) {
    rv = GetOriginatingURI(aChannel, aOutput);
    aChannel->GetURI(getter_AddRefs(srcURI));
    if (NS_SUCCEEDED(rv) && *aOutput) {
      // At this point, about: and chrome: URLs have been mapped to file: or
      // jar: URLs.  Try to recover the original URL.
      nsAutoCString scheme;
      nsresult rv2 = (*aOutput)->GetScheme(scheme);
      NS_ENSURE_SUCCESS(rv2, rv2);
      if (scheme.Equals("file") || scheme.Equals("jar")) {
        nsCOMPtr<nsIURI> originalURI;
        rv2 = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
        if (NS_SUCCEEDED(rv2) && originalURI) {
          NS_RELEASE(*aOutput);
          NS_ADDREF(*aOutput = originalURI);
        }
      }
    }
  }

  // If the channel was missing, closed or broken, try the
  // window hierarchy directly.
  //
  // This might fail to work for first-party loads themselves, but
  // we don't need this codepath for that case.
  if (NS_FAILED(rv) && aDoc) {
    nsCOMPtr<nsIDOMWindow> top;
    nsCOMPtr<nsIDOMDocument> topDDoc;
    nsIURI *docURI = nullptr;
    srcURI = aDoc->GetDocumentURI();

    if (aDoc->GetWindow()) {
      aDoc->GetWindow()->GetTop(getter_AddRefs(top));
      top->GetDocument(getter_AddRefs(topDDoc));

      nsCOMPtr<nsIDocument> topDoc(do_QueryInterface(topDDoc));
      docURI = topDoc->GetOriginalURI();
      if (docURI) {
        // Give us a mutable URI and also addref
        rv = NS_EnsureSafeToReturn(docURI, aOutput);
      }
    } else {
      // XXX: Chrome callers (such as NoScript) can end up here
      // through getImageData/canvas usage with no document state
      // (no Window and a document URI of about:blank). Propogate
      // rv fail (by doing nothing), and hope caller recovers.
    }

    if (*aOutput)
      rv = NS_OK;
  }

  if (*aOutput && !SchemeIsWhiteListed(*aOutput)) {
    // If URI scheme is not whitelisted and the URI lacks a hostname, force a
    // failure.
    nsAutoCString host;
    rv = (*aOutput)->GetHost(host);
    if (NS_SUCCEEDED(rv) && (host.Length() == 0)) {
      rv = NS_ERROR_FAILURE;
    }
  }

  // Log failure to error console.
  if (aLogErrors && NS_FAILED(rv)) {
    nsCOMPtr<nsIConsoleService> console
                              (do_GetService(NS_CONSOLESERVICE_CONTRACTID));
    if (console) {
      nsCString spec;
      nsCString srcSpec("unknown");

      if (srcURI)
        srcURI->GetSpec(srcSpec);

      if (*aOutput)
        (*aOutput)->GetSpec(spec);
      if (spec.Length() > 0) {
        nsPrintfCString msg("getFirstPartyURI failed for %s: no host in first party URI %s",
                            srcSpec.get(), spec.get()); // TODO: L10N
        console->LogStringMessage(NS_ConvertUTF8toUTF16(msg).get());
      } else {
        nsPrintfCString msg("getFirstPartyURI failed for %s: 0x%x", srcSpec.get(), rv);
        console->LogStringMessage(NS_ConvertUTF8toUTF16(msg).get());
      }
    }

    if (*aOutput) {
      // discard return object.
      (*aOutput)->Release();
      *aOutput = nullptr;
    }
  }

  // TODO: We could provide a route through the loadgroup + notification
  // callbacks too, but either channel or document was always available
  // in the cases where this function was originally needed (the image cache).
  // The notification callbacks also appear to suffers from the same limitation
  // as the document path. See nsICookiePermissions.GetOriginatingURI() for
  // details.

  return rv;
}

NS_IMETHODIMP
ThirdPartyUtil::GetFirstPartyURIFromChannel(nsIChannel *aChannel,
                                            bool aLogErrors,
                                            nsIURI **aOutput)
{
  return GetFirstPartyURIInternal(aChannel, nullptr, aLogErrors, aOutput);
}

NS_IMETHODIMP
ThirdPartyUtil::GetFirstPartyHostForIsolation(nsIURI *aFirstPartyURI,
                                              nsACString& aHost)
{
  if (!aFirstPartyURI)
    return NS_ERROR_INVALID_ARG;

  if (!SchemeIsWhiteListed(aFirstPartyURI)) {
    nsresult rv = GetBaseDomain(aFirstPartyURI, aHost);
    return (aHost.Length() > 0) ? NS_OK : rv;
  }

  // This URI lacks a host, so construct and return a pseudo-host.
  aHost = "--NoFirstPartyHost-";

  // Append the scheme.  To ensure that the pseudo-hosts are consistent
  // when the hacky "moz-safe-about" scheme is used, map it back to "about".
  nsAutoCString scheme;
  nsresult rv = aFirstPartyURI->GetScheme(scheme);
  NS_ENSURE_SUCCESS(rv, rv);
  if (scheme.Equals("moz-safe-about"))
    aHost.Append("about");
  else
    aHost.Append(scheme);

  // Append the URL's file name (e.g., -browser.xul) or its path (e.g.,
  // -home for about:home)
  nsAutoCString s;
  nsCOMPtr<nsIURL> url = do_QueryInterface(aFirstPartyURI);
  if (url)
    url->GetFileName(s);
  else
    aFirstPartyURI->GetPath(s);

  if (s.Length() > 0) {
    aHost.Append("-");
    aHost.Append(s);
  }

  aHost.Append("--");
  return NS_OK;
}
+6 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
#include "nsString.h"
#include "mozIThirdPartyUtil.h"
#include "nsIEffectiveTLDService.h"
#include "nsICookiePermission.h"
#include "mozilla/Attributes.h"

class nsIURI;
@@ -28,8 +29,13 @@ private:

  nsresult IsThirdPartyInternal(const nsCString& aFirstDomain,
    nsIURI* aSecondURI, bool* aResult);
  bool SchemeIsWhiteListed(nsIURI *aURI);
  static nsresult GetOriginatingURI(nsIChannel  *aChannel, nsIURI **aURI);
  nsresult GetFirstPartyURIInternal(nsIChannel *aChannel, nsIDocument *aDoc,
                                    bool aLogErrors, nsIURI **aOutput);

  nsCOMPtr<nsIEffectiveTLDService> mTLDService;
  nsCOMPtr<nsICookiePermission> mCookiePermissions;
};

#endif
+63 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
interface nsIURI;
interface nsIDOMWindow;
interface nsIChannel;
interface nsIDocument;

/**
 * Utility functions for determining whether a given URI, channel, or window
@@ -155,6 +156,68 @@ interface mozIThirdPartyUtil : nsISupports
   * Returns the top-level window associated with the given channel.
   */
  nsIDOMWindow getTopWindowForChannel(in nsIChannel aChannel);

  /**
   * getFirstPartyURI
   *
   * Obtain the top-level url bar URI for either a channel or a document.
   * Either parameter may be null (but not both).
   *
   * @param aChannel
   *        An arbitrary channel for some content element of a first party
   *        load. Can be null.
   *
   * @param aDoc
   *        An arbitrary third party document. Can be null.
   *
   * @return the first party url bar URI for the load.
   *
   * @throws if the URI cannot be obtained or the URI lacks a hostname and the
   *         URI's scheme is not white listed.
   */
  [noscript] nsIURI getFirstPartyURI(in nsIChannel aChannel,
                                     in nsIDocument aDoc);

  /**
   * getFirstPartyURIFromChannel
   *
   * Obtain the top-level url bar URI for a channel.
   *
   * @param aChannel
   *        An arbitrary channel for some content element of a first party
   *        load.
   *
   * @param aLogErrors
   *        If true, log errors to the Error Console.
   *
   * @return the first party url bar URI for the load.
   *
   * @throws if the URI cannot be obtained or the URI lacks a hostname and the
   *         URI's scheme is not white listed.
   */
  nsIURI getFirstPartyURIFromChannel(in nsIChannel aChannel,
                                     in bool aLogErrors);

  /**
   * getFirstPartyHostForIsolation
   *
   * Obtain the host or pseudo-host for aFirstPartyURI.  Some examples:
   *    aFirstPartyURI                        Return Value
   *    --------------                        ------------
   *    https://news.google.com/nwshp?hl=en   "news.google.com"
   *    about:home                            "--NoFirstPartyHost-about-home--"
   *    chrome://browser/content/browser.xul  "--NoFirstPartyHost-chrome-browser.xul--"
   *
   * @param aFirstPartyURI
   *        The first party URI.
   *
   * @return host or pseudo host.
   *
   * @throws if the URI lacks a host and the scheme is not a whitelisted one
   *         for which we generate a pseudo host.
   */
  AUTF8String getFirstPartyHostForIsolation(in nsIURI aFirstPartyURI);

};

%{ C++