diff options
Diffstat (limited to 'uriloader/base/nsURILoader.cpp')
-rw-r--r-- | uriloader/base/nsURILoader.cpp | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp new file mode 100644 index 0000000000..df286e5a73 --- /dev/null +++ b/uriloader/base/nsURILoader.cpp @@ -0,0 +1,832 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsURILoader.h" +#include "nsComponentManagerUtils.h" +#include "nsIURIContentListener.h" +#include "nsIContentHandler.h" +#include "nsILoadGroup.h" +#include "nsIDocumentLoader.h" +#include "nsIStreamListener.h" +#include "nsIURL.h" +#include "nsIChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInputStream.h" +#include "nsIStreamConverterService.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIHttpChannel.h" +#include "netCore.h" +#include "nsCRT.h" +#include "nsIDocShell.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "nsIChildChannel.h" +#include "nsExternalHelperAppService.h" + +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsError.h" + +#include "nsICategoryManager.h" +#include "nsCExternalHandlerService.h" + +#include "nsNetCID.h" + +#include "nsMimeTypes.h" + +#include "nsDocLoader.h" +#include "mozilla/Attributes.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_general.h" +#include "nsContentUtils.h" + +mozilla::LazyLogModule nsURILoader::mLog("URILoader"); + +#define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args) +#define LOG_ERROR(args) \ + MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args) +#define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug) + +NS_IMPL_ADDREF(nsDocumentOpenInfo) +NS_IMPL_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_ENTRY(nsIThreadRetargetableStreamListener) +NS_INTERFACE_MAP_END + +nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, + uint32_t aFlags, nsURILoader* aURILoader) + : m_originalContext(aWindowContext), + mFlags(aFlags), + mURILoader(aURILoader), + mDataConversionDepthLimit( + mozilla::StaticPrefs:: + general_document_open_conversion_depth_limit()) {} + +nsDocumentOpenInfo::nsDocumentOpenInfo(uint32_t aFlags, + bool aAllowListenerConversions) + : m_originalContext(nullptr), + mFlags(aFlags), + mURILoader(nullptr), + mDataConversionDepthLimit( + mozilla::StaticPrefs::general_document_open_conversion_depth_limit()), + mAllowListenerConversions(aAllowListenerConversions) {} + +nsDocumentOpenInfo::~nsDocumentOpenInfo() {} + +nsresult nsDocumentOpenInfo::Prepare() { + LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this)); + + nsresult rv; + + // ask our window context if it has a uri content listener... + m_contentListener = do_GetInterface(m_originalContext, &rv); + return rv; +} + +NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest* request) { + LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this)); + MOZ_ASSERT(request); + if (!request) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + + // + // Deal with "special" HTTP responses: + // + // - In the case of a 204 (No Content) or 205 (Reset Content) response, do + // not try to find a content handler. Return NS_BINDING_ABORTED to cancel + // the request. This has the effect of ensuring that the DocLoader does + // not try to interpret this as a real request. + // + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv)); + + if (NS_SUCCEEDED(rv)) { + uint32_t responseCode = 0; + + rv = httpChannel->GetResponseStatus(&responseCode); + + if (NS_FAILED(rv)) { + LOG_ERROR((" Failed to get HTTP response status")); + + // behave as in the canceled case + return NS_OK; + } + + LOG((" HTTP response status: %d", responseCode)); + + if (204 == responseCode || 205 == responseCode) { + return NS_BINDING_ABORTED; + } + } + + // + // Make sure that the transaction has succeeded, so far... + // + nsresult status; + + rv = request->GetStatus(&status); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!"); + if (NS_FAILED(rv)) return rv; + + if (NS_FAILED(status)) { + LOG_ERROR((" Request failed, status: 0x%08" PRIX32, + static_cast<uint32_t>(status))); + + // + // The transaction has already reported an error - so it will be torn + // down. Therefore, it is not necessary to return an error code... + // + return NS_OK; + } + + rv = DispatchContent(request); + + LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32, + m_targetStreamListener.get(), static_cast<uint32_t>(rv))); + + NS_ASSERTION( + NS_SUCCEEDED(rv) || !m_targetStreamListener, + "Must not have an m_targetStreamListener with a failure return!"); + + NS_ENSURE_SUCCESS(rv, rv); + + if (m_targetStreamListener) + rv = m_targetStreamListener->OnStartRequest(request); + + LOG((" OnStartRequest returning: 0x%08" PRIX32, static_cast<uint32_t>(rv))); + + return rv; +} + +NS_IMETHODIMP +nsDocumentOpenInfo::CheckListenerChain() { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(m_targetStreamListener, &rv); + if (retargetableListener) { + rv = retargetableListener->CheckListenerChain(); + } + LOG( + ("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv " + "%" PRIx32, + this, (NS_SUCCEEDED(rv) ? "success" : "failure"), + (nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv))); + return rv; +} + +NS_IMETHODIMP +nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t 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(request, inStr, sourceOffset, + count); + return rv; +} + +NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this)); + + if (m_targetStreamListener) { + nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener); + + // If this is a multipart stream, we could get another + // OnStartRequest after this... reset state. + m_targetStreamListener = nullptr; + mContentType.Truncate(); + listener->OnStopRequest(request, aStatus); + } + mUsedContentHandler = false; + + // Remember... + // In the case of multiplexed streams (such as multipart/x-mixed-replace) + // these stream listener methods could be called again :-) + // + return NS_OK; +} + +nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) { + LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, + mContentType.get())); + + MOZ_ASSERT(!m_targetStreamListener, + "Why do we already have a target stream listener?"); + + nsresult rv; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) { + LOG_ERROR((" Request is not a channel. Bailing.")); + return NS_ERROR_FAILURE; + } + + constexpr auto anyType = "*/*"_ns; + if (mContentType.IsEmpty() || mContentType == anyType) { + rv = aChannel->GetContentType(mContentType); + if (NS_FAILED(rv)) return rv; + LOG((" Got type from channel: '%s'", mContentType.get())); + } + + bool isGuessFromExt = + mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT); + if (isGuessFromExt) { + // Reset to application/octet-stream for now; no one other than the + // external helper app service should see APPLICATION_GUESS_FROM_EXT. + mContentType = APPLICATION_OCTET_STREAM; + aChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM)); + } + + // Check whether the data should be forced to be handled externally. This + // could happen because the Content-Disposition header is set so, or, in the + // future, because the user has specified external handling for the MIME + // type. + bool forceExternalHandling = false; + uint32_t disposition; + rv = aChannel->GetContentDisposition(&disposition); + + if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) { + forceExternalHandling = true; + } + + LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no")); + + if (forceExternalHandling && + mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline()) { + // Check if this is a PDF which should be opened internally. We also handle + // octet-streams that look like they might be PDFs based on their extension. + bool isPDF = mContentType.LowerCaseEqualsASCII(APPLICATION_PDF); + if (!isPDF && + (mContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) || + mContentType.IsEmpty())) { + nsAutoString flname; + aChannel->GetContentDispositionFilename(flname); + isPDF = StringEndsWith(flname, u".pdf"_ns); + if (!isPDF) { + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); + if (url) { + nsAutoCString ext; + url->GetFileExtension(ext); + isPDF = ext.EqualsLiteral("pdf"); + } + } + } + + // For a PDF, check if the preference is set that forces attachments to be + // opened inline. If so, treat it as a non-attachment by clearing + // 'forceExternalHandling' again. This allows it open a PDF directly + // instead of downloading it first. It may still end up being handled by + // a helper app depending anyway on the later checks. + if (isPDF) { + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + + nsCOMPtr<nsIMIMEInfo> mimeInfo; + + nsCOMPtr<nsIMIMEService> mimeSvc( + do_GetService(NS_MIMESERVICE_CONTRACTID)); + NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE); + mimeSvc->GetFromTypeAndExtension(nsLiteralCString(APPLICATION_PDF), ""_ns, + getter_AddRefs(mimeInfo)); + + if (mimeInfo) { + int32_t action = nsIMIMEInfo::saveToDisk; + mimeInfo->GetPreferredAction(&action); + + bool alwaysAsk = true; + mimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); + forceExternalHandling = + alwaysAsk || action != nsIMIMEInfo::handleInternally; + } + } + } + + if (!forceExternalHandling) { + // + // First step: See whether m_contentListener wants to handle this + // content type. + // + if (TryDefaultContentListener(aChannel)) { + LOG((" Success! Our default listener likes this type")); + // All done here + return NS_OK; + } + + // If we aren't allowed to try other listeners, just skip through to + // trying to convert the data. + if (!(mFlags & nsIURILoader::DONT_RETARGET)) { + // + // Second step: See whether some other registered listener wants + // to handle this content type. + // + int32_t count = mURILoader ? mURILoader->m_listeners.Count() : 0; + nsCOMPtr<nsIURIContentListener> listener; + for (int32_t i = 0; i < count; i++) { + listener = do_QueryReferent(mURILoader->m_listeners[i]); + if (listener) { + if (TryContentListener(listener, aChannel)) { + LOG((" Found listener registered on the URILoader")); + return NS_OK; + } + } else { + // remove from the listener list, reset i and update count + mURILoader->m_listeners.RemoveObjectAt(i--); + --count; + } + } + + // + // Third step: Try to find an nsIContentHandler for our type. + // + nsAutoCString handlerContractID(NS_CONTENT_HANDLER_CONTRACTID_PREFIX); + handlerContractID += mContentType; + + nsCOMPtr<nsIContentHandler> contentHandler = + do_CreateInstance(handlerContractID.get()); + if (contentHandler) { + LOG((" Content handler found")); + // Note that m_originalContext can be nullptr when running this in + // the parent process on behalf on a docshell in the content process, + // and in that case we only support content handlers that don't need + // the context. + rv = contentHandler->HandleContent(mContentType.get(), + m_originalContext, request); + // XXXbz returning an error code to represent handling the + // content is just bizarre! + if (rv != NS_ERROR_WONT_HANDLE_CONTENT) { + if (NS_FAILED(rv)) { + // The content handler has unexpectedly failed. Cancel the request + // just in case the handler didn't... + LOG((" Content handler failed. Aborting load")); + request->Cancel(rv); + } else { + LOG((" Content handler taking over load")); + mUsedContentHandler = true; + } + + return rv; + } + } + } else { + LOG( + (" DONT_RETARGET flag set, so skipped over random other content " + "listeners and content handlers")); + } + + // + // Fourth step: If no listener prefers this type, see if any stream + // converters exist to transform this content type into + // some other. + // + // Don't do this if the server sent us a MIME type of "*/*" because they saw + // it in our Accept header and got confused. + // XXXbz have to be careful here; may end up in some sort of bizarre + // infinite decoding loop. + if (mContentType != anyType) { + rv = TryStreamConversion(aChannel); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + } + } + + NS_ASSERTION(!m_targetStreamListener, + "If we found a listener, why are we not using it?"); + + if (mFlags & nsIURILoader::DONT_RETARGET) { + LOG( + (" External handling forced or (listener not interested and no " + "stream converter exists), and retargeting disallowed -> aborting")); + return NS_ERROR_WONT_HANDLE_CONTENT; + } + + // Before dispatching to the external helper app service, check for an HTTP + // error page. If we got one, we don't want to handle it with a helper app, + // really. + // The WPT a-download-click-404.html requires us to silently handle this + // without displaying an error page, so we just return early here. + // See bug 1604308 for discussion around what the ideal behaviour is. + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request)); + if (httpChannel) { + bool requestSucceeded; + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); + if (NS_FAILED(rv) || !requestSucceeded) { + return NS_OK; + } + } + + // Fifth step: + // + // All attempts to dispatch this content have failed. Just pass it off to + // the helper app service. + // + + nsCOMPtr<nsIExternalHelperAppService> helperAppService = + do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); + if (helperAppService) { + LOG((" Passing load off to helper app service")); + + // Set these flags to indicate that the channel has been targeted and that + // we are not using the original consumer. + nsLoadFlags loadFlags = 0; + request->GetLoadFlags(&loadFlags); + request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI | + nsIChannel::LOAD_TARGETED); + + if (isGuessFromExt || mContentType.IsEmpty()) { + mContentType = APPLICATION_GUESS_FROM_EXT; + aChannel->SetContentType(nsLiteralCString(APPLICATION_GUESS_FROM_EXT)); + } + + rv = TryExternalHelperApp(helperAppService, aChannel); + if (NS_FAILED(rv)) { + request->SetLoadFlags(loadFlags); + m_targetStreamListener = nullptr; + } + } + + NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv), + "There is no way we should be successful at this point without " + "a m_targetStreamListener"); + return rv; +} + +nsresult nsDocumentOpenInfo::TryExternalHelperApp( + nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel) { + return aHelperAppService->DoContent(mContentType, aChannel, m_originalContext, + false, nullptr, + getter_AddRefs(m_targetStreamListener)); +} + +nsresult nsDocumentOpenInfo::ConvertData(nsIRequest* request, + nsIURIContentListener* aListener, + const nsACString& aSrcContentType, + const nsACString& aOutContentType) { + LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this, + PromiseFlatCString(aSrcContentType).get(), + PromiseFlatCString(aOutContentType).get())); + + if (mDataConversionDepthLimit == 0) { + LOG( + ("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion " + "limit!", + this)); + // This will fall back to external helper app handling. + return NS_ERROR_ABORT; + } + + MOZ_ASSERT(aSrcContentType != aOutContentType, + "ConvertData called when the two types are the same!"); + + nsresult rv = NS_OK; + + nsCOMPtr<nsIStreamConverterService> StreamConvService = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + LOG((" Got converter service")); + + // 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... + // + RefPtr<nsDocumentOpenInfo> nextLink = Clone(); + + LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get())); + + // Decrease the conversion recursion limit by one to prevent infinite loops. + nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1; + + // Make sure nextLink starts with the contentListener that said it wanted + // the results of this decode. + nextLink->m_contentListener = aListener; + // Also make sure it has to look for a stream listener to pump data into. + nextLink->m_targetStreamListener = nullptr; + + // Make sure that nextLink treats the data as aOutContentType when + // dispatching; that way even if the stream converters don't change the type + // on the channel we will still do the right thing. If aOutContentType is + // */*, that's OK -- that will just indicate to nextLink that it should get + // the type off the channel. + nextLink->mContentType = aOutContentType; + + // The following call sets m_targetStreamListener to the input end of the + // stream converter and sets the output end of the stream converter to + // nextLink. As we pump data into m_targetStreamListener the stream + // converter will convert it and pass the converted data to nextLink. + return StreamConvService->AsyncConvertData( + PromiseFlatCString(aSrcContentType).get(), + PromiseFlatCString(aOutContentType).get(), nextLink, request, + getter_AddRefs(m_targetStreamListener)); +} + +nsresult nsDocumentOpenInfo::TryStreamConversion(nsIChannel* aChannel) { + constexpr auto anyType = "*/*"_ns; + + // A empty content type should be treated like the unknown content type. + nsCString srcContentType(mContentType); + if (srcContentType.IsEmpty()) { + srcContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); + } + + nsresult rv = + ConvertData(aChannel, m_contentListener, srcContentType, anyType); + if (NS_FAILED(rv)) { + m_targetStreamListener = nullptr; + } else if (m_targetStreamListener) { + // We found a converter for this MIME type. We'll just pump data into + // it and let the downstream nsDocumentOpenInfo handle things. + LOG((" Converter taking over now")); + } + return rv; +} + +bool nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener, + nsIChannel* aChannel) { + LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x", this, + mFlags)); + + MOZ_ASSERT(aListener, "Must have a non-null listener"); + MOZ_ASSERT(aChannel, "Must have a channel"); + + bool listenerWantsContent = false; + nsCString typeToUse; + + if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) { + aListener->IsPreferred(mContentType.get(), getter_Copies(typeToUse), + &listenerWantsContent); + } else { + aListener->CanHandleContent(mContentType.get(), false, + getter_Copies(typeToUse), + &listenerWantsContent); + } + if (!listenerWantsContent) { + LOG((" Listener is not interested")); + return false; + } + + if (!typeToUse.IsEmpty() && typeToUse != mContentType) { + // Need to do a conversion here. + + nsresult rv = NS_ERROR_NOT_AVAILABLE; + if (mAllowListenerConversions) { + rv = ConvertData(aChannel, aListener, mContentType, typeToUse); + } + + if (NS_FAILED(rv)) { + // No conversion path -- we don't want this listener, if we got one + m_targetStreamListener = nullptr; + } + + LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no")); + + // m_targetStreamListener is now the input end of the converter, and we can + // just pump the data in there, if it exists. If it does not, we need to + // try other nsIURIContentListeners. + return m_targetStreamListener != nullptr; + } + + // At this point, aListener wants data of type mContentType. Let 'em have + // it. But first, if we are retargeting, set an appropriate flag on the + // channel + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + + // Set this flag to indicate that the channel has been targeted at a final + // consumer. This load flag is tested in nsDocLoader::OnProgress. + nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED; + + nsCOMPtr<nsIURIContentListener> originalListener = + do_GetInterface(m_originalContext); + if (originalListener != aListener) { + newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI; + } + aChannel->SetLoadFlags(loadFlags | newLoadFlags); + + bool abort = false; + bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0; + nsresult rv = + aListener->DoContent(mContentType, isPreferred, aChannel, + getter_AddRefs(m_targetStreamListener), &abort); + + if (NS_FAILED(rv)) { + LOG_ERROR((" DoContent failed")); + + // Unset the RETARGETED_DOCUMENT_URI flag if we set it... + aChannel->SetLoadFlags(loadFlags); + m_targetStreamListener = nullptr; + return false; + } + + if (abort) { + // Nothing else to do here -- aListener is handling it all. Make + // sure m_targetStreamListener is null so we don't do anything + // after this point. + LOG((" Listener has aborted the load")); + m_targetStreamListener = nullptr; + } + + NS_ASSERTION(abort || m_targetStreamListener, + "DoContent returned no listener?"); + + // aListener is handling the load from this point on. + return true; +} + +bool nsDocumentOpenInfo::TryDefaultContentListener(nsIChannel* aChannel) { + if (m_contentListener) { + return TryContentListener(m_contentListener, aChannel); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of nsURILoader +/////////////////////////////////////////////////////////////////////////////////////////////// + +nsURILoader::nsURILoader() {} + +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; + + nsWeakPtr weakListener = do_GetWeakReference(aContentListener); + NS_ASSERTION(weakListener, + "your URIContentListener must support weak refs!\n"); + + if (weakListener) m_listeners.AppendObject(weakListener); + + return rv; +} + +NS_IMETHODIMP nsURILoader::UnRegisterContentListener( + nsIURIContentListener* aContentListener) { + nsWeakPtr weakListener = do_GetWeakReference(aContentListener); + if (weakListener) m_listeners.RemoveObject(weakListener); + + return NS_OK; +} + +NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext) { + NS_ENSURE_ARG_POINTER(channel); + + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsURILoader::OpenURI for %s", spec.get())); + } + + nsCOMPtr<nsIStreamListener> loader; + nsresult rv = OpenChannel(channel, aFlags, aWindowContext, false, + getter_AddRefs(loader)); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_WONT_HANDLE_CONTENT) { + // Not really an error, from this method's point of view + return NS_OK; + } + } + + // 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.... + rv = channel->AsyncOpen(loader); + + // no content from this load - that's OK. + if (rv == NS_ERROR_NO_CONTENT) { + LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing")); + return NS_OK; + } + return rv; +} + +nsresult nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext, + bool aChannelIsOpen, + nsIStreamListener** aListener) { + NS_ASSERTION(channel, "Trying to open a null channel!"); + NS_ASSERTION(aWindowContext, "Window context must not be null"); + + if (LOG_ENABLED()) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetAsciiSpec(spec); + LOG(("nsURILoader::OpenChannel for %s", spec.get())); + } + + // we need to create a DocumentOpenInfo object which will go ahead and open + // the url and discover the content type.... + RefPtr<nsDocumentOpenInfo> loader = + new nsDocumentOpenInfo(aWindowContext, aFlags, this); + + // Set the correct loadgroup on the channel + nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext)); + + if (!loadGroup) { + // XXXbz This context is violating what we'd like to be the new uriloader + // api.... Set up a nsDocLoader to handle the loadgroup for this context. + // This really needs to go away! + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext)); + if (listener) { + nsCOMPtr<nsISupports> cookie; + listener->GetLoadCookie(getter_AddRefs(cookie)); + if (!cookie) { + RefPtr<nsDocLoader> newDocLoader = new nsDocLoader(); + nsresult rv = newDocLoader->Init(); + if (NS_FAILED(rv)) return rv; + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader); + if (NS_FAILED(rv)) return rv; + cookie = nsDocLoader::GetAsSupports(newDocLoader); + listener->SetLoadCookie(cookie); + } + loadGroup = do_GetInterface(cookie); + } + } + + // If the channel is pending, then we need to remove it from its current + // loadgroup + nsCOMPtr<nsILoadGroup> oldGroup; + channel->GetLoadGroup(getter_AddRefs(oldGroup)); + if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) { + // It is important to add the channel to the new group before + // removing it from the old one, so that the load isn't considered + // done as soon as the request is removed. + loadGroup->AddRequest(channel, nullptr); + + if (oldGroup) { + oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED); + } + } + + channel->SetLoadGroup(loadGroup); + + // prepare the loader for receiving data + nsresult rv = loader->Prepare(); + if (NS_SUCCEEDED(rv)) NS_ADDREF(*aListener = loader); + return rv; +} + +NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags, + nsIInterfaceRequestor* aWindowContext, + nsIStreamListener** aListener) { + bool pending; + if (NS_FAILED(channel->IsPending(&pending))) { + pending = false; + } + + return OpenChannel(channel, aFlags, aWindowContext, pending, aListener); +} + +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; +} |