/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

#include "nsURILoader.h"
#include "nsIURIContentListener.h"
#include "nsIContentHandler.h"
#include "nsILoadGroup.h"
#include "nsIDocumentLoader.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsIIOService.h"
#include "nsIServiceManager.h"
#include "nsIStreamListener.h"
#include "nsIURI.h"
#include "nsIChannel.h"
#include "nsIInterfaceRequestor.h"
#include "nsIProgressEventSink.h"
#include "nsIInputStream.h"
#include "nsIStreamConverterService.h"
#include "nsWeakReference.h"
#include "nsIHTTPChannel.h"

#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"

#include "nsXPIDLString.h"
#include "nsString.h"

#include "nsIDOMWindowInternal.h"
#include "nsIUnkContentTypeHandler.h"
#include "nsDOMError.h"

#include "nsCExternalHandlerService.h" // contains contractids for the helper app service

// Following are for Bug 13871: Prevent frameset spoofing
#include "nsIPref.h"
#include "nsIWebNavigation.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsICodebasePrincipal.h"
#include "nsIHTMLDocument.h"

#include "nsIScriptGlobalObject.h"

static NS_DEFINE_CID(kURILoaderCID, NS_URI_LOADER_CID);
static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);


/* 
 * The nsDocumentOpenInfo contains the state required when a single document
 * is being opened in order to discover the content type...  Each instance remains alive until its target URL has 
 * been loaded (or aborted).
 *
 */
class nsDocumentOpenInfo : public nsIStreamListener
{
public:
    nsDocumentOpenInfo();

    nsresult Init(nsISupports * aRetargetedWindowContext, 
                  nsISupports * aOriginalWindowContext);

    NS_DECL_ISUPPORTS

    nsresult Open(nsIChannel* channel, 
                  nsURILoadCommand aCommand,
                  const char * aWindowTarget,
                  nsISupports * aWindowContext);

    nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
    nsresult RetargetOutput(nsIRequest *request, const char * aSrcContentType, 
                            const char * aOutContentType, nsIStreamListener * aStreamListener);

    // nsIRequestObserver methods:
    NS_DECL_NSIREQUESTOBSERVER

    // nsIStreamListener methods:
    NS_DECL_NSISTREAMLISTENER

protected:
    virtual ~nsDocumentOpenInfo();
    nsDocumentOpenInfo* Clone();
    // ProcessCanceledCase will do a couple of things....(1) it checks to see if the channel was canceled,
    // if it was, it will go out and release all of the document open info's local state for this load
    // and it will return TRUE.
    PRBool ProcessCanceledCase(nsIRequest *request);

protected:
    nsCOMPtr<nsIURIContentListener> m_contentListener;
    nsCOMPtr<nsIStreamListener> m_targetStreamListener;
    nsCOMPtr<nsISupports> m_originalContext;
    nsURILoadCommand mCommand;
    nsCString m_windowTarget;
    PRBool  mOnStopFired;
};

NS_IMPL_THREADSAFE_ADDREF(nsDocumentOpenInfo);
NS_IMPL_THREADSAFE_RELEASE(nsDocumentOpenInfo);

NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_END_THREADSAFE

nsDocumentOpenInfo::nsDocumentOpenInfo()
  : mOnStopFired (PR_FALSE)
{
  NS_INIT_ISUPPORTS();
}

nsDocumentOpenInfo::~nsDocumentOpenInfo()
{
}

nsresult nsDocumentOpenInfo::Init(nsISupports * aCurrentWindowContext, 
                                  nsISupports * aOriginalWindowContext)
{
  // ask the window context if it has a uri content listener...
  nsresult rv = NS_OK;
  m_contentListener = do_GetInterface(aCurrentWindowContext, &rv);
  m_originalContext = aOriginalWindowContext;
  return rv;
}

nsDocumentOpenInfo* nsDocumentOpenInfo::Clone()
{
  nsDocumentOpenInfo* newObject;

  newObject = new nsDocumentOpenInfo();
  if (newObject) {
    newObject->m_contentListener = m_contentListener;
    newObject->mCommand          = mCommand;
    newObject->m_windowTarget    = m_windowTarget;
    newObject->m_originalContext = m_originalContext;
  }

  return newObject;
}

// ProcessCanceledCase will do a couple of things....(1) it checks to see if the channel was canceled,
// if it was, it will go out and release all of the document open info's local state for this load
// and it will return TRUE.
PRBool nsDocumentOpenInfo::ProcessCanceledCase(nsIRequest *request)
{
  PRBool canceled = PR_FALSE;
  nsresult rv = NS_OK;

  if (request)
  {
    request->GetStatus(&rv);

    // if we were aborted or if the js returned no result (i.e. we aren't replacing any window content)
    if (rv == NS_BINDING_ABORTED || rv == NS_ERROR_DOM_RETVAL_UNDEFINED)
    {
      canceled = PR_TRUE;
      // free any local state for this load since we are aborting it so we 
      // can break any cycles...
      m_contentListener = nsnull;
      m_targetStreamListener = nsnull;
      m_originalContext = nsnull;
    }
  }

  return canceled;
}

nsresult nsDocumentOpenInfo::Open(nsIChannel *aChannel,  
                                  nsURILoadCommand aCommand,
                                  const char * aWindowTarget,
                                  nsISupports * aWindowContext)
{
   // this method is not complete!!! Eventually, we should first go
  // to the content listener and ask them for a protocol handler...
  // if they don't give us one, we need to go to the registry and get
  // the preferred protocol handler. 

  // But for now, I'm going to let necko do the work for us....

  nsresult rv = NS_OK;
  // store any local state
  m_windowTarget = aWindowTarget;
  mCommand = aCommand;

  // now just open the channel!
  if (aChannel){
    rv =  aChannel->AsyncOpen(this, nsnull);
  }

  if (rv == NS_ERROR_DOM_RETVAL_UNDEFINED) {
      NS_WARNING("js returned no result -- not replacing window contents");
      rv = NS_OK;
  }
  return rv;
}

NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
{
  nsresult rv = NS_OK;

  //
  // Deal with "special" HTTP responses:
  //
  // - In the case of a 204 (No Content) response, do not try to find a
  //   content handler.  Just return.  This causes the request to be
  //   ignored.
  //
  nsCOMPtr<nsIHTTPChannel> httpChannel(do_QueryInterface(request, &rv));

  if (NS_SUCCEEDED(rv)) {
    PRUint32 responseCode = 0;

    httpChannel->GetResponseStatus(&responseCode);
    if (204 == responseCode) {
      return NS_OK;
    }
  }

  if (ProcessCanceledCase(request))
    return NS_OK;

  rv = DispatchContent(request, aCtxt);
  if (m_targetStreamListener)
    rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
  return rv;
}

NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
                                                  nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
{
  // if we have retarged to the end stream listener, then forward the call....
  // otherwise, don't do anything

  nsresult rv = NS_OK;
  
  if (ProcessCanceledCase(request))
    return NS_OK;

  if (m_targetStreamListener)
    rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
  return rv;
}

NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 
                                                nsresult aStatus)
{
  nsresult rv = NS_OK;
  
  if (ProcessCanceledCase(request))
    return NS_OK;

  if (!mOnStopFired && m_targetStreamListener)
  {
    mOnStopFired = PR_TRUE;
    m_targetStreamListener->OnStopRequest(request, aCtxt, aStatus);
  }

  m_targetStreamListener = 0;

  return rv;
}

nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
{
  nsresult rv;
  nsXPIDLCString contentType;
  nsCOMPtr<nsISupports> originalWindowContext = m_originalContext; // local variable to keep track of this.
  nsCOMPtr<nsIStreamListener> contentStreamListener;
  nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
  if (!aChannel) {
      printf(">>> QI for channel from request failed!!\n");
      return NS_ERROR_FAILURE;
  }

  rv = aChannel->GetContentType(getter_Copies(contentType));
  if (NS_FAILED(rv)) return rv;

   // go to the uri dispatcher and give them our stuff...
  NS_WITH_SERVICE(nsIURILoader, pURILoader, kURILoaderCID, &rv);
  if (NS_SUCCEEDED(rv))
  {
    nsCOMPtr<nsIURIContentListener> contentListener;
    nsXPIDLCString desiredContentType;

    //
    // First step:  See if any nsIURIContentListener prefers to handle this
    //              content type.
    //
    PRBool abortDispatch = PR_FALSE;
    rv = pURILoader->DispatchContent(contentType, mCommand, m_windowTarget, 
                                     request, aCtxt, 
                                     m_contentListener, 
                                     m_originalContext,
                                     getter_Copies(desiredContentType), 
                                     getter_AddRefs(contentListener),
                                     &abortDispatch);  

    // if the uri loader says to abort the dispatch then someone
    // else must have stepped in and taken over for us...so stop..

    if (abortDispatch) return NS_OK;
    //
    // Second step:  If no listener prefers this type, see if any stream
    //               decoders exist to transform this content type into
    //               some other.
    //
    if (!contentListener) 
    {
      rv = RetargetOutput(request, contentType, "*/*", nsnull);
      if (m_targetStreamListener)
        return NS_OK;
    }
    
    //
    // Step 3:
    //
    // BIG TIME HACK ALERT!!!!! WE NEED THIS HACK IN PLACE UNTIL OUR NEW UNKNOWN CONTENT
    // HANDLER COMES ONLINE!!! 
    // Until that day, if we couldn't find a handler for the content type, then go back to the listener who
    // originated the url request and force them to handle the content....this forces us through the old code
    // path for unknown content types which brings up the file save as dialog...
    if (!contentListener) {
      contentListener = m_contentListener;
    }

    //
    // Good news!  Some content listener can handle this content type.
    //
    if (contentListener)
    {
      PRBool bAbortProcess = PR_FALSE;     
      nsCAutoString contentTypeToUse;
      if (desiredContentType)
        contentTypeToUse.Assign(desiredContentType);
      else
        contentTypeToUse.Assign(contentType);

      // We need to first figure out if we are retargeting the load to a content listener
      // that is different from the one that originated the request....if so, set
      // LOAD_RETARGETED_DOCUMENT_URI on the channel. 

      if (contentListener.get() != m_contentListener.get())
      {
         // we must be retargeting...so set an appropriate flag on the channel
        nsLoadFlags loadFlags = 0;
        aChannel->GetLoadFlags(&loadFlags);
        loadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
        aChannel->SetLoadFlags(loadFlags);
      }

      rv = contentListener->DoContent(contentTypeToUse, mCommand, m_windowTarget, 
                                    request, getter_AddRefs(contentStreamListener),
                                    &bAbortProcess);

      // the listener is doing all the work from here...we are done!!!
      if (bAbortProcess) return rv;

      // try to detect if there is a helper application we an use...
      if (/* mCommand == nsIURILoader::viewUserClick && */ !contentStreamListener)
      {
        nsCOMPtr<nsIURI> uri;
        PRBool abortProcess = PR_FALSE;
        aChannel->GetURI(getter_AddRefs(uri));
        nsCOMPtr<nsIExternalHelperAppService> helperAppService (do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID));
        if (helperAppService)
        {
            rv = helperAppService->DoContent(contentType, uri, m_originalContext, &abortProcess, getter_AddRefs(contentStreamListener));
            if (NS_SUCCEEDED(rv) && contentStreamListener)
              return RetargetOutput(request, contentType, contentType, contentStreamListener);
        }
        rv = NS_ERROR_FAILURE; // this will cause us to bring up the unknown content handler dialog.
      }

      // okay, all registered listeners have had a chance to handle this content...
      // did one of them give us a stream listener back? if so, let's start reading data
      // into it...
      rv = RetargetOutput(request, contentType, desiredContentType, contentStreamListener);
      m_originalContext = nsnull; // we don't need this anymore....
    } 
  }
  return rv;
}

nsresult nsDocumentOpenInfo::RetargetOutput(nsIRequest *request, const char * aSrcContentType, const char * aOutContentType,
                                            nsIStreamListener * aStreamListener)
{
  nsresult rv = NS_OK;

  // catch the case when some joker server sends back a content type of "*/*"
  // because we said we could handle "*/*" in our accept headers
  if (aOutContentType && *aOutContentType && nsCRT::strcasecmp(aSrcContentType, aOutContentType) && nsCRT::strcmp(aSrcContentType, "*/*"))
  {
      NS_WITH_SERVICE(nsIStreamConverterService, StreamConvService, kStreamConverterServiceCID, &rv);
      if (NS_FAILED(rv)) return rv;
      nsAutoString from_w; from_w.AssignWithConversion (aSrcContentType);
      nsAutoString to_w; to_w.AssignWithConversion (aOutContentType);
      
      nsDocumentOpenInfo* nextLink;

      // When applying stream decoders, it is necessary to "insert" an 
      // intermediate nsDocumentOpenInfo instance to handle the targeting of
      // the "final" stream or streams.
      //
      // For certain content types (ie. multi-part/x-mixed-replace) the input
      // stream is split up into multiple destination streams.  This
      // intermediate instance is used to target these "decoded" streams...
      //
      nextLink = Clone();
      if (!nextLink) return NS_ERROR_OUT_OF_MEMORY;
      NS_ADDREF(nextLink);

      // Set up the final destination listener.
      nextLink->m_targetStreamListener = nsnull;

      // The following call binds this channelListener's mNextListener (typically
      // the nsDocumentBindInfo) to the underlying stream converter, and returns
      // the underlying stream converter which we then set to be this channelListener's
      // mNextListener. This effectively nestles the stream converter down right
      // in between the raw stream and the final listener.
      rv = StreamConvService->AsyncConvertData(from_w.GetUnicode(), 
                                               to_w.GetUnicode(), 
                                               nextLink, 
                                               request,
                                               getter_AddRefs(m_targetStreamListener));
      NS_RELEASE(nextLink);
  }
  else
    m_targetStreamListener = aStreamListener; // no converter necessary so use a direct pipe

  return rv;
}

///////////////////////////////////////////////////////////////////////////////////////////////
// Implementation of nsURILoader
///////////////////////////////////////////////////////////////////////////////////////////////

nsURILoader::nsURILoader()
{
  NS_INIT_ISUPPORTS();
  NS_NewISupportsArray(getter_AddRefs(m_listeners));
 
  // Check pref to see if we should prevent frameset spoofing
  mValidateOrigin = PR_TRUE; // secure by default, pref disables check
  nsCOMPtr<nsIPref> prefs = do_GetService(NS_PREF_CONTRACTID);
  NS_ASSERTION(prefs,"nsURILoader: could not get prefs service!\n");
  if (prefs)
    prefs->GetBoolPref("browser.frame.validate_origin", &mValidateOrigin);
}

nsURILoader::~nsURILoader()
{
}

NS_IMPL_ADDREF(nsURILoader);
NS_IMPL_RELEASE(nsURILoader);

NS_INTERFACE_MAP_BEGIN(nsURILoader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
   NS_INTERFACE_MAP_ENTRY(nsIURILoader)
NS_INTERFACE_MAP_END

NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
{
  nsresult rv = NS_OK;
  if (!m_listeners) return NS_ERROR_FAILURE;

  nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
  NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
  
  if (weakListener)
    m_listeners->AppendElement(weakListener);

  return rv;
} 

NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
{
  if (!m_listeners) return NS_OK;
    
  nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
  if (weakListener)
    m_listeners->RemoveElement(weakListener);

  return NS_OK;
  
}

NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel, 
                                   nsURILoadCommand aCommand,
                                   const char * aWindowTarget,
                                   nsISupports * aWindowContext)
{
  return OpenURIVia(channel, aCommand, aWindowTarget, aWindowContext, 0 /* ip address */); 
}

//
// Bug 13871: Prevent frameset spoofing
// Check if origin document uri is the equivalent to target's principal.
// This takes into account subdomain checking if document.domain is set for
// Nav 4.x compatability.
//
// The following was derived from nsCodeBasePrincipal::Equals but in addition
// to the host PL_strcmp, it accepts a subdomain (nsHTMLDocument::SetDomain)
// if the document.domain was set.
//
static
PRBool SameOrSubdomainOfTarget(nsIURI* aOriginURI, nsIURI* aTargetURI, PRBool aDocumentDomainSet)
{
  nsXPIDLCString targetScheme;
  nsresult rv = aTargetURI->GetScheme(getter_Copies(targetScheme));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetScheme, PR_TRUE);

  nsXPIDLCString originScheme;
  rv = aOriginURI->GetScheme(getter_Copies(originScheme));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originScheme, PR_TRUE);

  if (PL_strcmp(targetScheme, originScheme))
    return PR_FALSE; // Different schemes - check fails

  if (! PL_strcmp(targetScheme, "file"))
    return PR_TRUE; // All file: urls are considered to have the same origin.

  if (! PL_strcmp(targetScheme, "imap") ||
      ! PL_strcmp(targetScheme, "mailbox") ||
      ! PL_strcmp(targetScheme, "news"))
  {

    // Each message is a distinct trust domain; use the whole spec for comparison
    nsXPIDLCString targetSpec;
    rv =aTargetURI->GetSpec(getter_Copies(targetSpec));
    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetSpec, PR_TRUE);

    nsXPIDLCString originSpec;
    rv = aOriginURI->GetSpec(getter_Copies(originSpec));
    NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originSpec, PR_TRUE);

    return (! PL_strcmp(targetSpec, originSpec)); // True if full spec is same, false otherwise
  }

  // Compare ports.
  int targetPort, originPort;
  rv = aTargetURI->GetPort(&targetPort);
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv), PR_TRUE);

  rv = aOriginURI->GetPort(&originPort);
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv), PR_TRUE);

  if (targetPort != originPort)
    return PR_FALSE; // Different port - check fails

  // Need to check the hosts
  nsXPIDLCString targetHost;
  rv = aTargetURI->GetHost(getter_Copies(targetHost));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetHost, PR_TRUE);

  nsXPIDLCString originHost;
  rv = aOriginURI->GetHost(getter_Copies(originHost));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originHost, PR_TRUE);

  if (!PL_strcmp(targetHost, originHost))
    return PR_TRUE; // Hosts are the same - check passed
  
  // If document.domain was set, do the relaxed check
  // Right align hostnames and compare - ensure preceeding char is . or /
  if (aDocumentDomainSet)
  {
    int targetHostLen = PL_strlen(targetHost);
    int originHostLen = PL_strlen(originHost);
    int prefixChar = originHostLen-targetHostLen-1;

    return ((originHostLen > targetHostLen) &&
            (! PL_strcmp((originHost+prefixChar+1), targetHost)) &&
            (originHost[prefixChar] == '.' || originHost[prefixChar] == '/'));
  }

  return PR_FALSE; // document.domain not set and hosts not same - check failed
}

//
// Bug 13871: Prevent frameset spoofing
//
// This routine answers: 'Is origin's document from same domain as target's document?'
// Be optimistic that domain is same - error cases all answer 'yes'.
//
// We have to compare the URI of the actual document loaded in the origin,
// ignoring any document.domain that was set, with the principal URI of the
// target (including any document.domain that was set).  This puts control
// of loading in the hands of the target, which is more secure. (per Nav 4.x)
//
static
PRBool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, nsIDocShellTreeItem* aTargetTreeItem)
{
  // Get origin document uri (ignoring document.domain)
  nsCOMPtr<nsIWebNavigation> originWebNav(do_QueryInterface(aOriginTreeItem));
  NS_ENSURE_TRUE(originWebNav, PR_TRUE);

  nsCOMPtr<nsIURI> originDocumentURI;
  nsresult rv = originWebNav->GetCurrentURI(getter_AddRefs(originDocumentURI));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originDocumentURI, PR_TRUE);

  // Get target principal uri (including document.domain)
  nsCOMPtr<nsIDOMDocument> targetDOMDocument(do_GetInterface(aTargetTreeItem));
  NS_ENSURE_TRUE(targetDOMDocument, NS_ERROR_FAILURE);

  nsCOMPtr<nsIDocument> targetDocument(do_QueryInterface(targetDOMDocument));
  NS_ENSURE_TRUE(targetDocument, NS_ERROR_FAILURE);

  nsCOMPtr<nsIPrincipal> targetPrincipal;
  rv = targetDocument->GetPrincipal(getter_AddRefs(targetPrincipal));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetPrincipal, rv);

  nsCOMPtr<nsICodebasePrincipal> targetCodebasePrincipal(do_QueryInterface(targetPrincipal));
  NS_ENSURE_TRUE(targetCodebasePrincipal, PR_TRUE);

  nsCOMPtr<nsIURI> targetPrincipalURI;
  rv = targetCodebasePrincipal->GetURI(getter_AddRefs(targetPrincipalURI));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetPrincipalURI, PR_TRUE);

  // Find out if document.domain was set for HTML documents
  PRBool documentDomainSet = PR_FALSE;
  nsCOMPtr<nsIHTMLDocument> targetHTMLDocument(do_QueryInterface(targetDocument));

  // If we don't have an HTML document, fall through with documentDomainSet false
  if (targetHTMLDocument) {
    targetHTMLDocument->WasDomainSet(&documentDomainSet);
  }

  // Is origin same principal or a subdomain of target's document.domain
  // Compare actual URI of origin document, not origin principal's URI. (Per Nav 4.x)
  return SameOrSubdomainOfTarget(originDocumentURI, targetPrincipalURI, documentDomainSet);
}

NS_IMETHODIMP nsURILoader::GetTarget(nsIChannel *channel, nsCString &aWindowTarget, 
                                     nsISupports * aWindowContext,
                                     nsISupports ** aRetargetedWindowContext)
{
  nsAutoString name; name.AssignWithConversion(aWindowTarget);
  nsCOMPtr<nsIDocShellTreeItem> windowCtxtAsTreeItem (do_GetInterface(aWindowContext));
  nsCOMPtr<nsIDocShellTreeItem>  treeItem;
  *aRetargetedWindowContext = nsnull;

  PRBool mustMakeNewWindow = PR_FALSE;

  if(!name.Length() || name.EqualsIgnoreCase("_self"))
  {
      *aRetargetedWindowContext = aWindowContext;
  }
  else if (name.EqualsIgnoreCase("_blank") || name.EqualsIgnoreCase("_new"))
  {
      mustMakeNewWindow = PR_TRUE;
      aWindowTarget.Assign("");
  }
  else if(name.EqualsIgnoreCase("_parent"))
  {
      windowCtxtAsTreeItem->GetSameTypeParent(getter_AddRefs(treeItem));
      if(!treeItem)
        *aRetargetedWindowContext = aWindowContext;
  }
  else if(name.EqualsIgnoreCase("_top"))
  {
      windowCtxtAsTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(treeItem));
      if(!treeItem)
        *aRetargetedWindowContext = aWindowContext;
  }
  else if(name.EqualsIgnoreCase("_content"))
  {
    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
    windowCtxtAsTreeItem->GetTreeOwner(getter_AddRefs(treeOwner));

    if (treeOwner)
       treeOwner->FindItemWithName(name.GetUnicode(), nsnull, getter_AddRefs(treeItem));
    else
    {
      NS_ERROR("Someone isn't setting up the tree owner.  You might like to try that."
          "Things will.....you know, work.");
    }
  }
  else
  {
    windowCtxtAsTreeItem->FindItemWithName(name.GetUnicode(), nsnull, getter_AddRefs(treeItem));

    // The named window cannot be found so it must be created to receive the
    // channel data.

    if (!treeItem) {
      mustMakeNewWindow = PR_TRUE;
    }

    // Bug 13871: Prevent frameset spoofing
    //     See BugSplat 336170, 338737 and XP_FindNamedContextInList in the classic codebase
    //     Per Nav's behaviour:
    //         - pref controlled: "browser.frame.validate_origin" (mValidateOrigin)
    //         - allow load if host of target or target's parent is same as host of origin
    //         - allow load if target is a top level window

    // Check to see if pref is true
    if (mValidateOrigin && windowCtxtAsTreeItem && treeItem) {

       // Is origin frame from the same domain as target frame?
       if (! ValidateOrigin(windowCtxtAsTreeItem, treeItem)) {

         // No.  Is origin frame from the same domain as target's parent?
         nsCOMPtr<nsIDocShellTreeItem> targetParentTreeItem;
         nsresult rv = treeItem->GetSameTypeParent(getter_AddRefs(targetParentTreeItem));
         if (NS_SUCCEEDED(rv) && targetParentTreeItem) {
           if (! ValidateOrigin(windowCtxtAsTreeItem, targetParentTreeItem)) {

             // Neither is from the origin domain, send load to a new window (_blank)
             *aRetargetedWindowContext = aWindowContext;
             aWindowTarget.Assign("_blank");
           } // else (target's parent from origin domain) allow this load
         } // else (no parent) allow this load since shell is a toplevel window
       } // else (target from origin domain) allow this load
     } // else (pref is false) allow this load
  }

  if (mustMakeNewWindow) {
      nsCOMPtr<nsIDOMWindowInternal> parentWindow;
      JSContext* jsContext = nsnull;

      if (aWindowContext)
      {
        parentWindow = do_GetInterface(aWindowContext);
        if (parentWindow)
        {
          nsCOMPtr<nsIScriptGlobalObject> sgo;     
          sgo = do_QueryInterface( parentWindow );
          if (sgo)
          {
            nsCOMPtr<nsIScriptContext> scriptContext;
            sgo->GetContext( getter_AddRefs( scriptContext ) );
            if (scriptContext)
              jsContext = (JSContext*)scriptContext->GetNativeContext();
          }
        }
      }
      if (!parentWindow || !jsContext)
      {
          return NS_ERROR_FAILURE;
      }

      // Create a new window (context) so that the uri loader has a proper
      // target to push the content into.

      void* mark;
      jsval* argv;

      nsAutoString uriValue; // Empty
      argv = JS_PushArguments(jsContext, &mark, "Ws",
          uriValue.GetUnicode(), aWindowTarget.get());
      NS_ENSURE_TRUE(argv, NS_ERROR_FAILURE);

      nsCOMPtr<nsIDOMWindowInternal> newWindow;
      parentWindow->Open(jsContext, argv, 2, getter_AddRefs(newWindow));
      JS_PopArguments(jsContext, mark);

      nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(newWindow);
      nsIDocShell *docShell = nsnull;
      sgo->GetDocShell(&docShell);

      *aRetargetedWindowContext = (nsISupports *) docShell;
  }

  nsCOMPtr<nsISupports> treeItemCtxt (do_QueryInterface(treeItem));
  if (!*aRetargetedWindowContext)  
  {
    *aRetargetedWindowContext = treeItemCtxt;
  }

  NS_IF_ADDREF(*aRetargetedWindowContext);
  return NS_OK;
}

NS_IMETHODIMP nsURILoader::OpenURIVia(nsIChannel *channel, 
                                      nsURILoadCommand aCommand,
                                      const char * aWindowTarget,
                                      nsISupports * aOriginalWindowContext,
                                      PRUint32 aLocalIP)
{
  // we need to create a DocumentOpenInfo object which will go ahead and open the url
  // and discover the content type....
  nsresult rv = NS_OK;
  nsDocumentOpenInfo* loader = nsnull;

  if (!channel) return NS_ERROR_NULL_POINTER;

  // Let the window context's uriListener know that the open is starting.  This
  // gives that window a chance to abort the load process.
  nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aOriginalWindowContext));
  if(winContextListener)
    {
      // get channel from request
    nsCOMPtr<nsIURI> uri;
    channel->GetURI(getter_AddRefs(uri));
    if(uri)
      {
      PRBool doAbort = PR_FALSE;
      winContextListener->OnStartURIOpen(uri, aWindowTarget, &doAbort);
         
      if(doAbort)
         return NS_OK;
      }   
    }

  nsCAutoString windowTarget(aWindowTarget);
  nsCOMPtr<nsISupports> retargetedWindowContext;
  NS_ENSURE_SUCCESS(GetTarget(channel, windowTarget, aOriginalWindowContext, getter_AddRefs(retargetedWindowContext)), NS_ERROR_FAILURE);

  NS_NEWXPCOM(loader, nsDocumentOpenInfo);
  if (!loader) return NS_ERROR_OUT_OF_MEMORY;
  NS_ADDREF(loader);

  nsCOMPtr<nsIInterfaceRequestor> loadCookie;
  SetupLoadCookie(retargetedWindowContext, getter_AddRefs(loadCookie));

  loader->Init(retargetedWindowContext, aOriginalWindowContext);    // Extra Info

  // now instruct the loader to go ahead and open the url
  rv = loader->Open(channel, aCommand, windowTarget, retargetedWindowContext);
  NS_RELEASE(loader);

  return rv;
}

NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
{
  nsresult rv;
  nsCOMPtr<nsIDocumentLoader> docLoader;

  NS_ENSURE_ARG_POINTER(aLoadCookie);

  docLoader = do_GetInterface(aLoadCookie, &rv);
  if (docLoader) {
    rv = docLoader->Stop();
  }
  return rv;
}

NS_IMETHODIMP
nsURILoader::GetLoadGroupForContext(nsISupports * aWindowContext,
                                    nsILoadGroup ** aLoadGroup)
{
  nsresult rv;
  nsCOMPtr<nsIInterfaceRequestor> loadCookieForWindow;

  // Initialize the [out] parameter...
  *aLoadGroup= nsnull;

  NS_ENSURE_ARG(aWindowContext);

  rv = SetupLoadCookie(aWindowContext, getter_AddRefs(loadCookieForWindow));
  if (NS_FAILED(rv)) return rv;
  
  rv = loadCookieForWindow->GetInterface(NS_GET_IID(nsILoadGroup),
                                         (void **) aLoadGroup);
  return rv;
}

NS_IMETHODIMP
nsURILoader::GetDocumentLoaderForContext(nsISupports * aWindowContext,
                                         nsIDocumentLoader ** aDocLoader)
{
  nsresult rv;
  nsCOMPtr<nsIInterfaceRequestor> loadCookieForWindow;

  // Initialize the [out] parameter...
  *aDocLoader = nsnull;

  NS_ENSURE_ARG(aWindowContext);

  rv = SetupLoadCookie(aWindowContext, getter_AddRefs(loadCookieForWindow));
  if (NS_FAILED(rv)) return rv;
  
  rv = loadCookieForWindow->GetInterface(NS_GET_IID(nsIDocumentLoader), 
                                         (void **) aDocLoader);
  return rv;
}

nsresult nsURILoader::SetupLoadCookie(nsISupports * aWindowContext, 
                                      nsIInterfaceRequestor ** aLoadCookie)
{
  // first, see if we have already set a load cookie on the cnt listener..
  // i.e. if this isn't the first time we've tried to run a url through this window
  // context then we don't need to create another load cookie, we can reuse the first one.
  nsresult rv = NS_OK;
  nsCOMPtr<nsISupports> loadCookie;

  // Initialize the [out] parameter...
  *aLoadCookie = nsnull;

  nsCOMPtr<nsIURIContentListener> cntListener (do_GetInterface(aWindowContext));
  if (cntListener) {
    // Get the load cookie for the requested window context...
    rv = cntListener->GetLoadCookie(getter_AddRefs(loadCookie));

    //
    // If we don't have a load cookie for this window context yet, then 
    // go create one! In order to create a load cookie, we need to get
    // the parent's load cookie if there is one...
    //
    if (!loadCookie) {
      nsCOMPtr<nsIURIContentListener> parentListener;
      nsCOMPtr<nsIDocumentLoader>     parentDocLoader;
      nsCOMPtr<nsIDocumentLoader>     newDocLoader;

      // Try to get the parent's load cookie...
      cntListener->GetParentContentListener(getter_AddRefs(parentListener));
      if (parentListener) {
        rv = parentListener->GetLoadCookie(getter_AddRefs(loadCookie));

        // if we had a parent cookie use it to help with the creation process      
        if (loadCookie) {
          parentDocLoader = do_GetInterface(loadCookie);
        }
      }
      // If there is no parent DocLoader, then use the global DocLoader
      // service as the parent...
      if (!parentDocLoader) {
        parentDocLoader = do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv);
      }
      if (NS_FAILED(rv)) return rv;

      //
      // Create a new document loader.  The document loader represents
      // the load cookie which the uriloader hands out...
      //
      rv = parentDocLoader->CreateDocumentLoader(getter_AddRefs(newDocLoader));
      if (NS_FAILED(rv)) return rv;

      newDocLoader->QueryInterface(NS_GET_IID(nsIInterfaceRequestor), 
                                   getter_AddRefs(loadCookie)); 
      rv = cntListener->SetLoadCookie(loadCookie);
    } // if we don't have  a load cookie already
  } // if we have a cntListener

  // loadCookie may be null - for example, <a target="popupWin"> if popupWin is
  // not a defined window.  The following prevents a crash (Bug 32898)
  if (loadCookie) {
    rv = loadCookie->QueryInterface(NS_GET_IID(nsIInterfaceRequestor),
                                  (void**)aLoadCookie);
  } else {
    rv = NS_ERROR_UNEXPECTED;
  }

  return rv;
}

PRBool nsURILoader::ShouldHandleContent(nsIURIContentListener * aCntListener, 
                                        const char * aContentType,
                                        nsURILoadCommand aCommand,
                                        const char * aWindowTarget,
                                        char ** aContentTypeToUse)
{
  PRBool foundContentHandler = PR_FALSE;
  if (aCommand == nsIURILoader::viewUserClick)
    aCntListener->IsPreferred(aContentType, aCommand, aWindowTarget, 
                                aContentTypeToUse, 
                                &foundContentHandler);
  else
    aCntListener->CanHandleContent(aContentType, aCommand, aWindowTarget, 
                                   aContentTypeToUse, 
                                   &foundContentHandler);
  return foundContentHandler;
} 

NS_IMETHODIMP nsURILoader::DispatchContent(const char * aContentType,
                                           nsURILoadCommand aCommand,
                                           const char * aWindowTarget,
                                           nsIRequest *request, 
                                           nsISupports * aCtxt, 
                                           nsIURIContentListener * aContentListener,
                                           nsISupports * aSrcWindowContext,
                                           char ** aContentTypeToUse,
                                           nsIURIContentListener ** aContentListenerToUse,
                                           PRBool * aAbortProcess)
{
  NS_ENSURE_ARG(aContentType);
  NS_ENSURE_ARG(request);

  // okay, now we've discovered the content type. We need to do the
  // following:
  // (1) We always start with the original content listener (if any)
  //     that originated the request and then ask if it can handle the
  //     content.
  // (2) if it can't, we'll move on to the registered content
  //     listeners and give them a crack at handling the content.
  // (3) if we cannot find a registered content lister to handle the
  //     type, then we move on to phase II which is to try to find a
  //     content handler in the registry for the content type.
  //     hitting this phase usually means we'll be creating a new
  //     window or handing off to an external application.

  nsresult rv = NS_OK;

  nsCOMPtr<nsIURIContentListener> listenerToUse = aContentListener;
  PRBool skipRetargetingSearch = PR_FALSE;
  // How do we determine whether we need to ask any registered content
  // listeners if they want a crack at the content?
  // (1) if the window target is blank or new, then we don't want to
  //     ask...
  if (!nsCRT::strcasecmp(aWindowTarget, "_blank") || !nsCRT::strcasecmp(aWindowTarget, "_new"))
    skipRetargetingSearch = PR_TRUE;
  else
  {
    // (2) if the original content listener is NULL and we have a
    //     target name then we must not be a window open with that
    //     target name so skip the content listener search and skip to
    //     the part that brings up the new window.
    if (aWindowTarget && *aWindowTarget && !aContentListener)
      skipRetargetingSearch = PR_TRUE;
  }
  
  // find a content handler that can and will handle the content
  if (!skipRetargetingSearch)
  {
    PRBool foundContentHandler = PR_FALSE;
    if (listenerToUse)
      foundContentHandler = ShouldHandleContent(listenerToUse, aContentType, 
                                                aCommand, aWindowTarget, aContentTypeToUse);
                                            

    // if it can't handle the content, scan through the list of
    // registered listeners
    if (!foundContentHandler)
    {
       PRInt32 i = 0;
       // keep looping until we get a content listener back
       PRUint32 count; m_listeners->Count(&count);
       for(i = 0; i < PRInt32(count) && !foundContentHandler; i++)
       {
          //nsIURIContentListener's aren't refcounted.
          nsWeakPtr weakListener;
          m_listeners->QueryElementAt(i, NS_GET_IID(nsIWeakReference),
                                      getter_AddRefs(weakListener));
         
          nsCOMPtr<nsIURIContentListener> listener =
            do_QueryReferent(weakListener);
          if (listener)
          {
              foundContentHandler = ShouldHandleContent(listener, aContentType, 
                                                        aCommand, aWindowTarget, aContentTypeToUse);
              if (foundContentHandler)
                listenerToUse = listener;
          } else {
            // remove from the listener list, and reset i
            m_listeners->RemoveElementAt(i);
            i--;
          }
      } // for loop
    } // if we can't handle the content


    if (foundContentHandler && listenerToUse)
    {
      *aContentListenerToUse = listenerToUse;
      NS_IF_ADDREF(*aContentListenerToUse);
      return rv;
    }
  }

  // no registered content listeners to handle this type!!! so go to the register 
  // and get a registered nsIContentHandler for our content type. Hand it off 
  // to them...
  // eventually we want to hit up the category manager so we can allow people to
  // over ride the default content type handlers....for now...i'm skipping that part.

  nsCAutoString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
  handlerContractID += aContentType;
  
  nsCOMPtr<nsIContentHandler> aContentHandler;
  rv = nsComponentManager::CreateInstance(handlerContractID, nsnull, NS_GET_IID(nsIContentHandler), getter_AddRefs(aContentHandler));
  if (NS_SUCCEEDED(rv)) // we did indeed have a content handler for this type!! yippee...
  {
      rv = aContentHandler->HandleContent(aContentType, "view", aWindowTarget, aSrcWindowContext, request);
      *aAbortProcess = PR_TRUE;
  }
  
  return rv;
}

