michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sts=2 sw=2 et cin: */ 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 "nsURILoader.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIURIContentListener.h" michael@0: #include "nsIContentHandler.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsIDocumentLoader.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIProgressEventSink.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsWeakReference.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIMultiPartChannel.h" michael@0: #include "netCore.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsIThreadRetargetableStreamListener.h" michael@0: michael@0: #include "nsXPIDLString.h" michael@0: #include "nsString.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsError.h" michael@0: michael@0: #include "nsICategoryManager.h" michael@0: #include "nsCExternalHandlerService.h" // contains contractids for the helper app service michael@0: michael@0: #include "nsIMIMEHeaderParam.h" michael@0: #include "nsNetCID.h" michael@0: michael@0: #include "nsMimeTypes.h" michael@0: michael@0: #include "nsDocLoader.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* nsURILoader::mLog = nullptr; michael@0: #endif michael@0: michael@0: #define LOG(args) PR_LOG(nsURILoader::mLog, PR_LOG_DEBUG, args) michael@0: #define LOG_ERROR(args) PR_LOG(nsURILoader::mLog, PR_LOG_ERROR, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(nsURILoader::mLog, PR_LOG_DEBUG) michael@0: michael@0: #define NS_PREF_DISABLE_BACKGROUND_HANDLING \ michael@0: "security.exthelperapp.disable_background_handling" michael@0: michael@0: /** michael@0: * The nsDocumentOpenInfo contains the state required when a single michael@0: * document is being opened in order to discover the content type... michael@0: * Each instance remains alive until its target URL has been loaded michael@0: * (or aborted). michael@0: */ michael@0: class nsDocumentOpenInfo MOZ_FINAL : public nsIStreamListener michael@0: , public nsIThreadRetargetableStreamListener michael@0: { michael@0: public: michael@0: // Needed for nsCOMPtr to work right... Don't call this! michael@0: nsDocumentOpenInfo(); michael@0: michael@0: // Real constructor michael@0: // aFlags is a combination of the flags on nsIURILoader michael@0: nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, michael@0: uint32_t aFlags, michael@0: nsURILoader* aURILoader); michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: /** michael@0: * Prepares this object for receiving data. The stream michael@0: * listener methods of this class must not be called before calling this michael@0: * method. michael@0: */ michael@0: nsresult Prepare(); michael@0: michael@0: // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to michael@0: // take the data off our hands. michael@0: nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt); michael@0: michael@0: // Call this if we need to insert a stream converter from aSrcContentType to michael@0: // aOutContentType into the StreamListener chain. DO NOT call it if the two michael@0: // types are the same, since no conversion is needed in that case. michael@0: nsresult ConvertData(nsIRequest *request, michael@0: nsIURIContentListener *aListener, michael@0: const nsACString & aSrcContentType, michael@0: const nsACString & aOutContentType); michael@0: michael@0: /** michael@0: * Function to attempt to use aListener to handle the load. If michael@0: * true is returned, nothing else needs to be done; if false michael@0: * is returned, then a different way of handling the load should be michael@0: * tried. michael@0: */ michael@0: bool TryContentListener(nsIURIContentListener* aListener, michael@0: nsIChannel* aChannel); michael@0: michael@0: // nsIRequestObserver methods: michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: michael@0: // nsIStreamListener methods: michael@0: NS_DECL_NSISTREAMLISTENER michael@0: michael@0: // nsIThreadRetargetableStreamListener michael@0: NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER michael@0: protected: michael@0: ~nsDocumentOpenInfo(); michael@0: michael@0: protected: michael@0: /** michael@0: * The first content listener to try dispatching data to. Typically michael@0: * the listener associated with the entity that originated the load. michael@0: */ michael@0: nsCOMPtr m_contentListener; michael@0: michael@0: /** michael@0: * The stream listener to forward nsIStreamListener notifications michael@0: * to. This is set once the load is dispatched. michael@0: */ michael@0: nsCOMPtr m_targetStreamListener; michael@0: michael@0: /** michael@0: * A pointer to the entity that originated the load. We depend on getting michael@0: * things like nsIURIContentListeners, nsIDOMWindows, etc off of it. michael@0: */ michael@0: nsCOMPtr m_originalContext; michael@0: michael@0: /** michael@0: * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent michael@0: * (also determines whether we use CanHandleContent or IsPreferred). michael@0: * DONT_RETARGET means that we will only try m_originalContext, no other michael@0: * listeners. michael@0: */ michael@0: uint32_t mFlags; michael@0: michael@0: /** michael@0: * The type of the data we will be trying to dispatch. michael@0: */ michael@0: nsCString mContentType; michael@0: michael@0: /** michael@0: * Reference to the URILoader service so we can access its list of michael@0: * nsIURIContentListeners. michael@0: */ michael@0: nsRefPtr mURILoader; michael@0: }; michael@0: michael@0: NS_IMPL_ADDREF(nsDocumentOpenInfo) michael@0: NS_IMPL_RELEASE(nsDocumentOpenInfo) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) michael@0: NS_INTERFACE_MAP_END_THREADSAFE michael@0: michael@0: nsDocumentOpenInfo::nsDocumentOpenInfo() michael@0: { michael@0: NS_NOTREACHED("This should never be called\n"); michael@0: } michael@0: michael@0: nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, michael@0: uint32_t aFlags, michael@0: nsURILoader* aURILoader) michael@0: : m_originalContext(aWindowContext), michael@0: mFlags(aFlags), michael@0: mURILoader(aURILoader) michael@0: { michael@0: } michael@0: michael@0: nsDocumentOpenInfo::~nsDocumentOpenInfo() michael@0: { michael@0: } michael@0: michael@0: nsresult nsDocumentOpenInfo::Prepare() michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: // ask our window context if it has a uri content listener... michael@0: m_contentListener = do_GetInterface(m_originalContext, &rv); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt) michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this)); michael@0: MOZ_ASSERT(request); michael@0: if (!request) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // michael@0: // Deal with "special" HTTP responses: michael@0: // michael@0: // - In the case of a 204 (No Content) or 205 (Reset Content) response, do michael@0: // not try to find a content handler. Return NS_BINDING_ABORTED to cancel michael@0: // the request. This has the effect of ensuring that the DocLoader does michael@0: // not try to interpret this as a real request. michael@0: // michael@0: nsCOMPtr httpChannel(do_QueryInterface(request, &rv)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: uint32_t responseCode = 0; michael@0: michael@0: rv = httpChannel->GetResponseStatus(&responseCode); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG_ERROR((" Failed to get HTTP response status")); michael@0: michael@0: // behave as in the canceled case michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG((" HTTP response status: %d", responseCode)); michael@0: michael@0: if (204 == responseCode || 205 == responseCode) { michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Make sure that the transaction has succeeded, so far... michael@0: // michael@0: nsresult status; michael@0: michael@0: rv = request->GetStatus(&status); michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!"); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (NS_FAILED(status)) { michael@0: LOG_ERROR((" Request failed, status: 0x%08X", rv)); michael@0: michael@0: // michael@0: // The transaction has already reported an error - so it will be torn michael@0: // down. Therefore, it is not necessary to return an error code... michael@0: // michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = DispatchContent(request, aCtxt); michael@0: michael@0: LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv)); michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener, michael@0: "Must not have an m_targetStreamListener with a failure return!"); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (m_targetStreamListener) michael@0: rv = m_targetStreamListener->OnStartRequest(request, aCtxt); michael@0: michael@0: LOG((" OnStartRequest returning: 0x%08X", rv)); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentOpenInfo::CheckListenerChain() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr retargetableListener = michael@0: do_QueryInterface(m_targetStreamListener, &rv); michael@0: if (retargetableListener) { michael@0: rv = retargetableListener->CheckListenerChain(); michael@0: } michael@0: LOG(("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv %x", michael@0: this, (NS_SUCCEEDED(rv) ? "success" : "failure"), michael@0: (nsIStreamListener*)m_targetStreamListener, rv)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, michael@0: nsIInputStream * inStr, michael@0: uint64_t sourceOffset, uint32_t count) michael@0: { michael@0: // if we have retarged to the end stream listener, then forward the call.... michael@0: // otherwise, don't do anything michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (m_targetStreamListener) michael@0: rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this)); michael@0: michael@0: if ( m_targetStreamListener) michael@0: { michael@0: nsCOMPtr listener(m_targetStreamListener); michael@0: michael@0: // If this is a multipart stream, we could get another michael@0: // OnStartRequest after this... reset state. michael@0: m_targetStreamListener = 0; michael@0: mContentType.Truncate(); michael@0: listener->OnStopRequest(request, aCtxt, aStatus); michael@0: } michael@0: michael@0: // Remember... michael@0: // In the case of multiplexed streams (such as multipart/x-mixed-replace) michael@0: // these stream listener methods could be called again :-) michael@0: // michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt) michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get())); michael@0: michael@0: NS_PRECONDITION(!m_targetStreamListener, michael@0: "Why do we already have a target stream listener?"); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr aChannel = do_QueryInterface(request); michael@0: if (!aChannel) { michael@0: LOG_ERROR((" Request is not a channel. Bailing.")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(anyType, "*/*"); michael@0: if (mContentType.IsEmpty() || mContentType == anyType) { michael@0: rv = aChannel->GetContentType(mContentType); michael@0: if (NS_FAILED(rv)) return rv; michael@0: LOG((" Got type from channel: '%s'", mContentType.get())); michael@0: } michael@0: michael@0: bool isGuessFromExt = michael@0: mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT); michael@0: if (isGuessFromExt) { michael@0: // Reset to application/octet-stream for now; no one other than the michael@0: // external helper app service should see APPLICATION_GUESS_FROM_EXT. michael@0: mContentType = APPLICATION_OCTET_STREAM; michael@0: aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM)); michael@0: } michael@0: michael@0: // Check whether the data should be forced to be handled externally. This michael@0: // could happen because the Content-Disposition header is set so, or, in the michael@0: // future, because the user has specified external handling for the MIME michael@0: // type. michael@0: bool forceExternalHandling = false; michael@0: uint32_t disposition; michael@0: rv = aChannel->GetContentDisposition(&disposition); michael@0: if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) michael@0: forceExternalHandling = true; michael@0: michael@0: LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no")); michael@0: michael@0: // We're going to try to find a contentListener that can handle our data michael@0: nsCOMPtr contentListener; michael@0: // The type or data the contentListener wants. michael@0: nsXPIDLCString desiredContentType; michael@0: michael@0: if (!forceExternalHandling) michael@0: { michael@0: // michael@0: // First step: See whether m_contentListener wants to handle this michael@0: // content type. michael@0: // michael@0: if (m_contentListener && TryContentListener(m_contentListener, aChannel)) { michael@0: LOG((" Success! Our default listener likes this type")); michael@0: // All done here michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we aren't allowed to try other listeners, just skip through to michael@0: // trying to convert the data. michael@0: if (!(mFlags & nsIURILoader::DONT_RETARGET)) { michael@0: michael@0: // michael@0: // Second step: See whether some other registered listener wants michael@0: // to handle this content type. michael@0: // michael@0: int32_t count = mURILoader->m_listeners.Count(); michael@0: nsCOMPtr listener; michael@0: for (int32_t i = 0; i < count; i++) { michael@0: listener = do_QueryReferent(mURILoader->m_listeners[i]); michael@0: if (listener) { michael@0: if (TryContentListener(listener, aChannel)) { michael@0: LOG((" Found listener registered on the URILoader")); michael@0: return NS_OK; michael@0: } michael@0: } else { michael@0: // remove from the listener list, reset i and update count michael@0: mURILoader->m_listeners.RemoveObjectAt(i--); michael@0: --count; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Third step: Try to find a content listener that has not yet had michael@0: // the chance to register, as it is contained in a not-yet-loaded michael@0: // module, but which has registered a contract ID. michael@0: // michael@0: nsCOMPtr catman = michael@0: do_GetService(NS_CATEGORYMANAGER_CONTRACTID); michael@0: if (catman) { michael@0: nsXPIDLCString contractidString; michael@0: rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, michael@0: mContentType.get(), michael@0: getter_Copies(contractidString)); michael@0: if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) { michael@0: LOG((" Listener contractid for '%s' is '%s'", michael@0: mContentType.get(), contractidString.get())); michael@0: michael@0: listener = do_CreateInstance(contractidString); michael@0: LOG((" Listener from category manager: 0x%p", listener.get())); michael@0: michael@0: if (listener && TryContentListener(listener, aChannel)) { michael@0: LOG((" Listener from category manager likes this type")); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Fourth step: try to find an nsIContentHandler for our type. michael@0: // michael@0: nsAutoCString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX); michael@0: handlerContractID += mContentType; michael@0: michael@0: nsCOMPtr contentHandler = michael@0: do_CreateInstance(handlerContractID.get()); michael@0: if (contentHandler) { michael@0: LOG((" Content handler found")); michael@0: rv = contentHandler->HandleContent(mContentType.get(), michael@0: m_originalContext, request); michael@0: // XXXbz returning an error code to represent handling the michael@0: // content is just bizarre! michael@0: if (rv != NS_ERROR_WONT_HANDLE_CONTENT) { michael@0: if (NS_FAILED(rv)) { michael@0: // The content handler has unexpectedly failed. Cancel the request michael@0: // just in case the handler didn't... michael@0: LOG((" Content handler failed. Aborting load")); michael@0: request->Cancel(rv); michael@0: } michael@0: #ifdef PR_LOGGING michael@0: else { michael@0: LOG((" Content handler taking over load")); michael@0: } michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: } michael@0: } else { michael@0: LOG((" DONT_RETARGET flag set, so skipped over random other content " michael@0: "listeners and content handlers")); michael@0: } michael@0: michael@0: // michael@0: // Fifth step: If no listener prefers this type, see if any stream michael@0: // converters exist to transform this content type into michael@0: // some other. michael@0: // michael@0: // Don't do this if the server sent us a MIME type of "*/*" because they saw michael@0: // it in our Accept header and got confused. michael@0: // XXXbz have to be careful here; may end up in some sort of bizarre infinite michael@0: // decoding loop. michael@0: if (mContentType != anyType) { michael@0: rv = ConvertData(request, m_contentListener, mContentType, anyType); michael@0: if (NS_FAILED(rv)) { michael@0: m_targetStreamListener = nullptr; michael@0: } else if (m_targetStreamListener) { michael@0: // We found a converter for this MIME type. We'll just pump data into it michael@0: // and let the downstream nsDocumentOpenInfo handle things. michael@0: LOG((" Converter taking over now")); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!m_targetStreamListener, michael@0: "If we found a listener, why are we not using it?"); michael@0: michael@0: if (mFlags & nsIURILoader::DONT_RETARGET) { michael@0: LOG((" External handling forced or (listener not interested and no " michael@0: "stream converter exists), and retargeting disallowed -> aborting")); michael@0: return NS_ERROR_WONT_HANDLE_CONTENT; michael@0: } michael@0: michael@0: // Before dispatching to the external helper app service, check for an HTTP michael@0: // error page. If we got one, we don't want to handle it with a helper app, michael@0: // really. michael@0: nsCOMPtr httpChannel(do_QueryInterface(request)); michael@0: if (httpChannel) { michael@0: bool requestSucceeded; michael@0: httpChannel->GetRequestSucceeded(&requestSucceeded); michael@0: if (!requestSucceeded) { michael@0: // returning error from OnStartRequest will cancel the channel michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: } michael@0: michael@0: // Sixth step: michael@0: // michael@0: // All attempts to dispatch this content have failed. Just pass it off to michael@0: // the helper app service. michael@0: // michael@0: michael@0: // michael@0: // Optionally, we may want to disable background handling by the external michael@0: // helper application service. michael@0: // michael@0: if (mozilla::Preferences::GetBool(NS_PREF_DISABLE_BACKGROUND_HANDLING, michael@0: false)) { michael@0: // First, we will ensure that the parent docshell is in an active michael@0: // state as we will disallow all external application handling unless it is michael@0: // in the foreground. michael@0: nsCOMPtr docShell(do_GetInterface(m_originalContext)); michael@0: if (!docShell) { michael@0: // If we can't perform our security check we definitely don't want to go michael@0: // any further! michael@0: LOG(("Failed to get DocShell to ensure it is active before anding off to " michael@0: "helper app service. Aborting.")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Ensure the DocShell is active before continuing. michael@0: bool isActive = false; michael@0: docShell->GetIsActive(&isActive); michael@0: if (!isActive) { michael@0: LOG((" Check for active DocShell returned false. Aborting hand off to " michael@0: "helper app service.")); michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr helperAppService = michael@0: do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); michael@0: if (helperAppService) { michael@0: LOG((" Passing load off to helper app service")); michael@0: michael@0: // Set these flags to indicate that the channel has been targeted and that michael@0: // we are not using the original consumer. michael@0: nsLoadFlags loadFlags = 0; michael@0: request->GetLoadFlags(&loadFlags); michael@0: request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI michael@0: | nsIChannel::LOAD_TARGETED); michael@0: michael@0: if (isGuessFromExt) { michael@0: mContentType = APPLICATION_GUESS_FROM_EXT; michael@0: aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT)); michael@0: } michael@0: michael@0: rv = helperAppService->DoContent(mContentType, michael@0: request, michael@0: m_originalContext, michael@0: false, michael@0: getter_AddRefs(m_targetStreamListener)); michael@0: if (NS_FAILED(rv)) { michael@0: request->SetLoadFlags(loadFlags); michael@0: m_targetStreamListener = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv), michael@0: "There is no way we should be successful at this point without a m_targetStreamListener"); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDocumentOpenInfo::ConvertData(nsIRequest *request, michael@0: nsIURIContentListener* aListener, michael@0: const nsACString& aSrcContentType, michael@0: const nsACString& aOutContentType) michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this, michael@0: PromiseFlatCString(aSrcContentType).get(), michael@0: PromiseFlatCString(aOutContentType).get())); michael@0: michael@0: NS_PRECONDITION(aSrcContentType != aOutContentType, michael@0: "ConvertData called when the two types are the same!"); michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr StreamConvService = michael@0: do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: LOG((" Got converter service")); michael@0: michael@0: // When applying stream decoders, it is necessary to "insert" an michael@0: // intermediate nsDocumentOpenInfo instance to handle the targeting of michael@0: // the "final" stream or streams. michael@0: // michael@0: // For certain content types (ie. multi-part/x-mixed-replace) the input michael@0: // stream is split up into multiple destination streams. This michael@0: // intermediate instance is used to target these "decoded" streams... michael@0: // michael@0: nsRefPtr nextLink = michael@0: new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader); michael@0: if (!nextLink) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get())); michael@0: michael@0: // Make sure nextLink starts with the contentListener that said it wanted the michael@0: // results of this decode. michael@0: nextLink->m_contentListener = aListener; michael@0: // Also make sure it has to look for a stream listener to pump data into. michael@0: nextLink->m_targetStreamListener = nullptr; michael@0: michael@0: // Make sure that nextLink treats the data as aOutContentType when michael@0: // dispatching; that way even if the stream converters don't change the type michael@0: // on the channel we will still do the right thing. If aOutContentType is michael@0: // */*, that's OK -- that will just indicate to nextLink that it should get michael@0: // the type off the channel. michael@0: nextLink->mContentType = aOutContentType; michael@0: michael@0: // The following call sets m_targetStreamListener to the input end of the michael@0: // stream converter and sets the output end of the stream converter to michael@0: // nextLink. As we pump data into m_targetStreamListener the stream michael@0: // converter will convert it and pass the converted data to nextLink. michael@0: return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(), michael@0: PromiseFlatCString(aOutContentType).get(), michael@0: nextLink, michael@0: request, michael@0: getter_AddRefs(m_targetStreamListener)); michael@0: } michael@0: michael@0: bool michael@0: nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener, michael@0: nsIChannel* aChannel) michael@0: { michael@0: LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x", michael@0: this, mFlags)); michael@0: michael@0: NS_PRECONDITION(aListener, "Must have a non-null listener"); michael@0: NS_PRECONDITION(aChannel, "Must have a channel"); michael@0: michael@0: bool listenerWantsContent = false; michael@0: nsXPIDLCString typeToUse; michael@0: michael@0: if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) { michael@0: aListener->IsPreferred(mContentType.get(), michael@0: getter_Copies(typeToUse), michael@0: &listenerWantsContent); michael@0: } else { michael@0: aListener->CanHandleContent(mContentType.get(), false, michael@0: getter_Copies(typeToUse), michael@0: &listenerWantsContent); michael@0: } michael@0: if (!listenerWantsContent) { michael@0: LOG((" Listener is not interested")); michael@0: return false; michael@0: } michael@0: michael@0: if (!typeToUse.IsEmpty() && typeToUse != mContentType) { michael@0: // Need to do a conversion here. michael@0: michael@0: nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // No conversion path -- we don't want this listener, if we got one michael@0: m_targetStreamListener = nullptr; michael@0: } michael@0: michael@0: LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no")); michael@0: michael@0: // m_targetStreamListener is now the input end of the converter, and we can michael@0: // just pump the data in there, if it exists. If it does not, we need to michael@0: // try other nsIURIContentListeners. michael@0: return m_targetStreamListener != nullptr; michael@0: } michael@0: michael@0: // At this point, aListener wants data of type mContentType. Let 'em have michael@0: // it. But first, if we are retargeting, set an appropriate flag on the michael@0: // channel michael@0: nsLoadFlags loadFlags = 0; michael@0: aChannel->GetLoadFlags(&loadFlags); michael@0: michael@0: // Set this flag to indicate that the channel has been targeted at a final michael@0: // consumer. This load flag is tested in nsDocLoader::OnProgress. michael@0: nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED; michael@0: michael@0: nsCOMPtr originalListener = michael@0: do_GetInterface(m_originalContext); michael@0: if (originalListener != aListener) { michael@0: newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI; michael@0: } michael@0: aChannel->SetLoadFlags(loadFlags | newLoadFlags); michael@0: michael@0: bool abort = false; michael@0: bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0; michael@0: nsresult rv = aListener->DoContent(mContentType.get(), michael@0: isPreferred, michael@0: aChannel, michael@0: getter_AddRefs(m_targetStreamListener), michael@0: &abort); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG_ERROR((" DoContent failed")); michael@0: michael@0: // Unset the RETARGETED_DOCUMENT_URI flag if we set it... michael@0: aChannel->SetLoadFlags(loadFlags); michael@0: m_targetStreamListener = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: if (abort) { michael@0: // Nothing else to do here -- aListener is handling it all. Make michael@0: // sure m_targetStreamListener is null so we don't do anything michael@0: // after this point. michael@0: LOG((" Listener has aborted the load")); michael@0: m_targetStreamListener = nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?"); michael@0: michael@0: // aListener is handling the load from this point on. michael@0: return true; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////////////////////// michael@0: // Implementation of nsURILoader michael@0: /////////////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsURILoader::nsURILoader() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!mLog) { michael@0: mLog = PR_NewLogModule("URILoader"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsURILoader::~nsURILoader() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(nsURILoader) michael@0: NS_IMPL_RELEASE(nsURILoader) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsURILoader) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader) michael@0: NS_INTERFACE_MAP_ENTRY(nsIURILoader) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsWeakPtr weakListener = do_GetWeakReference(aContentListener); michael@0: NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n"); michael@0: michael@0: if (weakListener) michael@0: m_listeners.AppendObject(weakListener); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener) michael@0: { michael@0: nsWeakPtr weakListener = do_GetWeakReference(aContentListener); michael@0: if (weakListener) michael@0: m_listeners.RemoveObject(weakListener); michael@0: michael@0: return NS_OK; michael@0: michael@0: } michael@0: michael@0: NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel, michael@0: uint32_t aFlags, michael@0: nsIInterfaceRequestor *aWindowContext) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(channel); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetAsciiSpec(spec); michael@0: LOG(("nsURILoader::OpenURI for %s", spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr loader; michael@0: nsresult rv = OpenChannel(channel, michael@0: aFlags, michael@0: aWindowContext, michael@0: false, michael@0: getter_AddRefs(loader)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // this method is not complete!!! Eventually, we should first go michael@0: // to the content listener and ask them for a protocol handler... michael@0: // if they don't give us one, we need to go to the registry and get michael@0: // the preferred protocol handler. michael@0: michael@0: // But for now, I'm going to let necko do the work for us.... michael@0: rv = channel->AsyncOpen(loader, nullptr); michael@0: michael@0: // no content from this load - that's OK. michael@0: if (rv == NS_ERROR_NO_CONTENT) { michael@0: LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing")); michael@0: rv = NS_OK; michael@0: } michael@0: } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) { michael@0: // Not really an error, from this method's point of view michael@0: rv = NS_OK; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsURILoader::OpenChannel(nsIChannel* channel, michael@0: uint32_t aFlags, michael@0: nsIInterfaceRequestor* aWindowContext, michael@0: bool aChannelIsOpen, michael@0: nsIStreamListener** aListener) michael@0: { michael@0: NS_ASSERTION(channel, "Trying to open a null channel!"); michael@0: NS_ASSERTION(aWindowContext, "Window context must not be null"); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetAsciiSpec(spec); michael@0: LOG(("nsURILoader::OpenChannel for %s", spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: // Let the window context's uriListener know that the open is starting. This michael@0: // gives that window a chance to abort the load process. michael@0: nsCOMPtr winContextListener(do_GetInterface(aWindowContext)); michael@0: if (winContextListener) { michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: if (uri) { michael@0: bool doAbort = false; michael@0: winContextListener->OnStartURIOpen(uri, &doAbort); michael@0: michael@0: if (doAbort) { michael@0: LOG((" OnStartURIOpen aborted load")); michael@0: return NS_ERROR_WONT_HANDLE_CONTENT; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // we need to create a DocumentOpenInfo object which will go ahead and open michael@0: // the url and discover the content type.... michael@0: nsRefPtr loader = michael@0: new nsDocumentOpenInfo(aWindowContext, aFlags, this); michael@0: michael@0: if (!loader) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Set the correct loadgroup on the channel michael@0: nsCOMPtr loadGroup(do_GetInterface(aWindowContext)); michael@0: michael@0: if (!loadGroup) { michael@0: // XXXbz This context is violating what we'd like to be the new uriloader michael@0: // api.... Set up a nsDocLoader to handle the loadgroup for this context. michael@0: // This really needs to go away! michael@0: nsCOMPtr listener(do_GetInterface(aWindowContext)); michael@0: if (listener) { michael@0: nsCOMPtr cookie; michael@0: listener->GetLoadCookie(getter_AddRefs(cookie)); michael@0: if (!cookie) { michael@0: nsRefPtr newDocLoader = new nsDocLoader(); michael@0: if (!newDocLoader) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: nsresult rv = newDocLoader->Init(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: cookie = nsDocLoader::GetAsSupports(newDocLoader); michael@0: listener->SetLoadCookie(cookie); michael@0: } michael@0: loadGroup = do_GetInterface(cookie); michael@0: } michael@0: } michael@0: michael@0: // If the channel is pending, then we need to remove it from its current michael@0: // loadgroup michael@0: nsCOMPtr oldGroup; michael@0: channel->GetLoadGroup(getter_AddRefs(oldGroup)); michael@0: if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) { michael@0: // It is important to add the channel to the new group before michael@0: // removing it from the old one, so that the load isn't considered michael@0: // done as soon as the request is removed. michael@0: loadGroup->AddRequest(channel, nullptr); michael@0: michael@0: if (oldGroup) { michael@0: oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED); michael@0: } michael@0: } michael@0: michael@0: channel->SetLoadGroup(loadGroup); michael@0: michael@0: // prepare the loader for receiving data michael@0: nsresult rv = loader->Prepare(); michael@0: if (NS_SUCCEEDED(rv)) michael@0: NS_ADDREF(*aListener = loader); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel, michael@0: uint32_t aFlags, michael@0: nsIInterfaceRequestor* aWindowContext, michael@0: nsIStreamListener** aListener) michael@0: { michael@0: bool pending; michael@0: if (NS_FAILED(channel->IsPending(&pending))) { michael@0: pending = false; michael@0: } michael@0: michael@0: return OpenChannel(channel, aFlags, aWindowContext, pending, aListener); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr docLoader; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aLoadCookie); michael@0: michael@0: docLoader = do_GetInterface(aLoadCookie, &rv); michael@0: if (docLoader) { michael@0: rv = docLoader->Stop(); michael@0: } michael@0: return rv; michael@0: } michael@0: