/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * 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 "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 "nsIHTTPChannel.h" #include "nsHTTPEnums.h" #include "nsVoidArray.h" #include "nsXPIDLString.h" #include "nsString.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 * aWindowContext); NS_DECL_ISUPPORTS nsresult Open(nsIChannel * aChannel, nsURILoadCommand aCommand, const char * aWindowTarget, nsISupports * aWindowContext, nsISupports * aOpenContext, nsISupports ** aCurrentOpenContext); nsresult DispatchContent(nsIChannel * aChannel, nsISupports * aCtxt); nsresult RetargetOutput(nsIChannel * aChannel, const char * aSrcContentType, const char * aOutContentType, nsIStreamListener * aStreamListener); // nsIStreamObserver methods: NS_DECL_NSISTREAMOBSERVER // nsIStreamListener methods: NS_DECL_NSISTREAMLISTENER protected: virtual ~nsDocumentOpenInfo(); nsDocumentOpenInfo* Clone(); protected: nsCOMPtr m_contentListener; nsCOMPtr m_targetStreamListener; nsURILoadCommand mCommand; nsCString m_windowTarget; }; NS_IMPL_ADDREF(nsDocumentOpenInfo); NS_IMPL_RELEASE(nsDocumentOpenInfo); NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_END nsDocumentOpenInfo::nsDocumentOpenInfo() { NS_INIT_ISUPPORTS(); } nsDocumentOpenInfo::~nsDocumentOpenInfo() { } nsresult nsDocumentOpenInfo::Init(nsISupports * aWindowContext) { // ask the window context if it has a uri content listener... nsresult rv = NS_OK; m_contentListener = do_GetInterface(aWindowContext, &rv); 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; } return newObject; } nsresult nsDocumentOpenInfo::Open(nsIChannel * aChannel, nsURILoadCommand aCommand, const char * aWindowTarget, nsISupports * aWindowContext, nsISupports * aOpenContext, nsISupports ** aCurrentOpenContext) { // 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; // get the requestor for the window context... nsCOMPtr requestor = do_QueryInterface(aWindowContext); // and get the load group out of the open context nsCOMPtr aLoadGroup = do_QueryInterface(aOpenContext); #if 0 if (!aLoadGroup) { // i haven't implemented this yet...it's going to be hard // because it requires a persistant stream observer (the uri loader maybe???) // that we don't have in this architecture in order to create a new load group return NS_ERROR_NOT_IMPLEMENTED; } #endif if (aCurrentOpenContext && aLoadGroup) aLoadGroup->QueryInterface(NS_GET_IID(nsISupports), (void **) aCurrentOpenContext); // now jut open the channel! if (aChannel) rv = aChannel->AsyncRead(0, -1, nsnull, this); return rv; } NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIChannel * aChannel, nsISupports * aCtxt) { nsresult rv = NS_OK; rv = DispatchContent(aChannel, aCtxt); if (m_targetStreamListener) m_targetStreamListener->OnStartRequest(aChannel, aCtxt); return rv; } NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIChannel * aChannel, 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 (m_targetStreamListener) rv = m_targetStreamListener->OnDataAvailable(aChannel, aCtxt, inStr, sourceOffset, count); return rv; } NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIChannel * aChannel, nsISupports *aCtxt, nsresult aStatus, const PRUnichar * errorMsg) { if (m_targetStreamListener) m_targetStreamListener->OnStopRequest(aChannel, aCtxt, aStatus, errorMsg); m_targetStreamListener = 0; return NS_OK; } nsresult nsDocumentOpenInfo::DispatchContent(nsIChannel * aChannel, nsISupports * aCtxt) { nsresult rv; nsXPIDLCString contentType; 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 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, aChannel, aCtxt, m_contentListener, 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) { 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 = RetargetOutput(aChannel, contentType, "*/*", nextLink); NS_RELEASE(nextLink); 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) { nsCOMPtr contentStreamListener; PRBool bAbortProcess = PR_FALSE; nsCAutoString contentTypeToUse; if (desiredContentType) contentTypeToUse = desiredContentType; else contentTypeToUse = contentType; rv = contentListener->DoContent(contentTypeToUse, mCommand, m_windowTarget, aChannel, getter_AddRefs(contentStreamListener), &bAbortProcess); // the listener is doing all the work from here...we are done!!! if (bAbortProcess) return rv; // 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(aChannel, contentType, desiredContentType, contentStreamListener); } } return rv; } nsresult nsDocumentOpenInfo::RetargetOutput(nsIChannel * aChannel, const char * aSrcContentType, const char * aOutContentType, nsIStreamListener * aStreamListener) { nsresult rv = NS_OK; // do we need to invoke the stream converter service? if (aOutContentType && *aOutContentType && nsCRT::strcasecmp(aSrcContentType, aOutContentType)) { NS_WITH_SERVICE(nsIStreamConverterService, StreamConvService, kStreamConverterServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsAutoString from_w (aSrcContentType); nsAutoString to_w (aOutContentType); // 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(), aStreamListener, aChannel, getter_AddRefs(m_targetStreamListener)); } else m_targetStreamListener = aStreamListener; // no converter necessary so use a direct pipe return rv; } /////////////////////////////////////////////////////////////////////////////////////////////// // Implementation of nsURILoader /////////////////////////////////////////////////////////////////////////////////////////////// nsURILoader::nsURILoader() { NS_INIT_ISUPPORTS(); m_listeners = new nsVoidArray(); } nsURILoader::~nsURILoader() { if (m_listeners) delete m_listeners; } 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::GetStringForCommand(nsURILoadCommand aCommand, char **aStringVersion) { if (aCommand == nsIURILoader::viewSource) *aStringVersion = nsCRT::strdup("view-source"); else // for now, default everything else to view normal *aStringVersion = nsCRT::strdup("view"); return NS_OK; } NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener) { nsresult rv = NS_OK; if (m_listeners) m_listeners->AppendElement(aContentListener); else rv = NS_ERROR_FAILURE; return rv; } NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener) { if (m_listeners) m_listeners->RemoveElement(aContentListener); return NS_OK; } NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel * aChannel, nsURILoadCommand aCommand, const char * aWindowTarget, nsISupports * aWindowContext, nsISupports *aOpenContext, nsISupports **aCurrentOpenContext) { return OpenURIVia(aChannel, aCommand, aWindowTarget, aWindowContext, aOpenContext, aCurrentOpenContext, 0 /* ip address */); } NS_IMETHODIMP nsURILoader::OpenURIVia(nsIChannel * aChannel, nsURILoadCommand aCommand, const char * aWindowTarget, nsISupports * aWindowContext, nsISupports *aOpenContext, nsISupports **aCurrentOpenContext, 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 (!aChannel) return NS_ERROR_NULL_POINTER; NS_NEWXPCOM(loader, nsDocumentOpenInfo); if (!loader) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(loader); loader->Init(aWindowContext); // Extra Info // now instruct the loader to go ahead and open the url rv = loader->Open(aChannel, aCommand, aWindowTarget, aWindowContext, aOpenContext, aCurrentOpenContext); NS_RELEASE(loader); return NS_OK; } nsresult nsURILoader::DispatchContent(const char * aContentType, nsURILoadCommand aCommand, const char * aWindowTarget, nsIChannel * aChannel, nsISupports * aCtxt, nsIURIContentListener * aContentListener, char ** aContentTypeToUse, nsIURIContentListener ** aContentListenerToUse, PRBool * aAbortProcess) { // okay, now we've discovered the content type. We need to do the following: // (1) Give our uri content listener first crack at handling this content type. nsresult rv = NS_OK; nsCOMPtr listenerToUse = aContentListener; // find a content handler that can and will handle the content PRBool canHandleContent = PR_FALSE; if (listenerToUse) listenerToUse->CanHandleContent(aContentType, aCommand, aWindowTarget, aContentTypeToUse, &canHandleContent); if (!canHandleContent) // if it can't handle the content, scan through the list of registered listeners { PRInt32 i = 0; // keep looping until we are told to abort or we get a content listener back for(i = 0; i < m_listeners->Count() && !canHandleContent; i++) { //nsIURIContentListener's aren't refcounted. nsIURIContentListener * listener =(nsIURIContentListener*)m_listeners->ElementAt(i); if (listener) { rv = listener->CanHandleContent(aContentType, aCommand, aWindowTarget, aContentTypeToUse, &canHandleContent); if (canHandleContent) listenerToUse = listener; } } // for loop } // if we can't handle the content if (canHandleContent && 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 handlerProgID (NS_CONTENT_HANDLER_PROGID_PREFIX); handlerProgID += aContentType; nsCOMPtr aContentHandler; rv = nsComponentManager::CreateInstance(handlerProgID, 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, aChannel); *aAbortProcess = PR_TRUE; } return rv; }