uriloader/base/nsURILoader.cpp
author Dave Townsend <dtownsend@oxymoronical.com>
Sun, 13 Jan 2019 17:44:29 -0800
changeset 453676 edca8877b0505cd1c31beaf6d907ca32e022aa52
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
child 455156 47630659e925fe8ecca4e153e0a2cb3b13d292cf
permissions -rw-r--r--
Backing out Bug 1518799 (changeset eace4709948c) because the target page is not yet available. a=backout

/* -*- 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 "nsAutoPtr.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 "nsIInterfaceRequestorUtils.h"
#include "nsIProgressEventSink.h"
#include "nsIInputStream.h"
#include "nsIStreamConverterService.h"
#include "nsIWeakReferenceUtils.h"
#include "nsIHttpChannel.h"
#include "nsIMultiPartChannel.h"
#include "netCore.h"
#include "nsCRT.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIThreadRetargetableStreamListener.h"

#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsReadableUtils.h"
#include "nsError.h"

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

#include "nsIMIMEHeaderParam.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 "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)

#define NS_PREF_DISABLE_BACKGROUND_HANDLING \
  "security.exthelperapp.disable_background_handling"

static uint32_t sConvertDataLimit = 20;

static bool InitPreferences() {
  nsresult rv = mozilla::Preferences::AddUintVarCache(
      &sConvertDataLimit, "general.document_open_conversion_depth_limit", 20);
  return NS_SUCCEEDED(rv);
}

/**
 * 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 final : public nsIStreamListener,
                                 public nsIThreadRetargetableStreamListener {
 public:
  // Real constructor
  // aFlags is a combination of the flags on nsIURILoader
  nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext, uint32_t aFlags,
                     nsURILoader* aURILoader);

  NS_DECL_THREADSAFE_ISUPPORTS

  /**
   * Prepares this object for receiving data. The stream
   * listener methods of this class must not be called before calling this
   * method.
   */
  nsresult Prepare();

  // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
  // take the data off our hands.
  nsresult DispatchContent(nsIRequest* request, nsISupports* aCtxt);

  // Call this if we need to insert a stream converter from aSrcContentType to
  // aOutContentType into the StreamListener chain.  DO NOT call it if the two
  // types are the same, since no conversion is needed in that case.
  nsresult ConvertData(nsIRequest* request, nsIURIContentListener* aListener,
                       const nsACString& aSrcContentType,
                       const nsACString& aOutContentType);

  /**
   * Function to attempt to use aListener to handle the load.  If
   * true is returned, nothing else needs to be done; if false
   * is returned, then a different way of handling the load should be
   * tried.
   */
  bool TryContentListener(nsIURIContentListener* aListener,
                          nsIChannel* aChannel);

  // nsIRequestObserver methods:
  NS_DECL_NSIREQUESTOBSERVER

  // nsIStreamListener methods:
  NS_DECL_NSISTREAMLISTENER

  // nsIThreadRetargetableStreamListener
  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 protected:
  ~nsDocumentOpenInfo();

 protected:
  /**
   * The first content listener to try dispatching data to.  Typically
   * the listener associated with the entity that originated the load.
   */
  nsCOMPtr<nsIURIContentListener> m_contentListener;

  /**
   * The stream listener to forward nsIStreamListener notifications
   * to.  This is set once the load is dispatched.
   */
  nsCOMPtr<nsIStreamListener> m_targetStreamListener;

  /**
   * A pointer to the entity that originated the load. We depend on getting
   * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
   */
  nsCOMPtr<nsIInterfaceRequestor> m_originalContext;

  /**
   * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
   * (also determines whether we use CanHandleContent or IsPreferred).
   * DONT_RETARGET means that we will only try m_originalContext, no other
   * listeners.
   */
  uint32_t mFlags;

  /**
   * The type of the data we will be trying to dispatch.
   */
  nsCString mContentType;

  /**
   * Reference to the URILoader service so we can access its list of
   * nsIURIContentListeners.
   */
  RefPtr<nsURILoader> mURILoader;

  /**
   * Limit of data conversion depth to prevent infinite conversion loops
   */
  uint32_t mDataConversionDepthLimit;
};

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(sConvertDataLimit) {}

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,
                                                 nsISupports* aCtxt) {
  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;
    }

    static bool sLargeAllocationTestingAllHttpLoads = false;
    static bool sLargeAllocationHeaderEnabled = false;
    static bool sCachedLargeAllocationPref = false;
    if (!sCachedLargeAllocationPref) {
      sCachedLargeAllocationPref = true;
      mozilla::Preferences::AddBoolVarCache(
          &sLargeAllocationHeaderEnabled, "dom.largeAllocationHeader.enabled");
      mozilla::Preferences::AddBoolVarCache(
          &sLargeAllocationTestingAllHttpLoads,
          "dom.largeAllocation.testing.allHttpLoads");
    }

    if (sLargeAllocationHeaderEnabled) {
      if (sLargeAllocationTestingAllHttpLoads) {
        nsCOMPtr<nsIURI> uri;
        rv = httpChannel->GetURI(getter_AddRefs(uri));
        if (NS_SUCCEEDED(rv) && uri) {
          bool httpScheme = false;
          bool httpsScheme = false;
          uri->SchemeIs("http", &httpScheme);
          uri->SchemeIs("https", &httpsScheme);
          if ((httpScheme || httpsScheme) &&
              nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
            return NS_BINDING_ABORTED;
          }
        }
      }

      // If we have a Large-Allocation header, let's check if we should perform
      // a process switch.
      nsAutoCString largeAllocationHeader;
      rv = httpChannel->GetResponseHeader(
          NS_LITERAL_CSTRING("Large-Allocation"), largeAllocationHeader);
      if (NS_SUCCEEDED(rv) &&
          nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
        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>(rv)));

    //
    // 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, aCtxt);

  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, aCtxt);

  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, nsISupports* aCtxt,
                                    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, aCtxt, inStr,
                                                 sourceOffset, count);
  return rv;
}

NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request,
                                                nsISupports* aCtxt,
                                                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, aCtxt, aStatus);
  }

  // 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,
                                             nsISupports* aCtxt) {
  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;
  }

  NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
  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(NS_LITERAL_CSTRING(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) {
    //
    // First step: See whether m_contentListener wants to handle this
    // content type.
    //
    if (m_contentListener && TryContentListener(m_contentListener, 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->m_listeners.Count();
      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 a content listener that has not yet had
      // the chance to register, as it is contained in a not-yet-loaded
      // module, but which has registered a contract ID.
      //
      nsCOMPtr<nsICategoryManager> catman =
          do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
      if (catman) {
        nsCString contractidString;
        rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
                                      mContentType, contractidString);
        if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
          LOG(("  Listener contractid for '%s' is '%s'", mContentType.get(),
               contractidString.get()));

          listener = do_CreateInstance(contractidString.get());
          LOG(("  Listener from category manager: 0x%p", listener.get()));

          if (listener && TryContentListener(listener, aChannel)) {
            LOG(("  Listener from category manager likes this type"));
            return NS_OK;
          }
        }
      }

      //
      // Fourth 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"));
        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"));
          }

          return rv;
        }
      }
    } else {
      LOG(
          ("  DONT_RETARGET flag set, so skipped over random other content "
           "listeners and content handlers"));
    }

    //
    // Fifth 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 = ConvertData(request, m_contentListener, mContentType, 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 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.
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
  if (httpChannel) {
    bool requestSucceeded;
    rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
    if (NS_FAILED(rv) || !requestSucceeded) {
      // returning error from OnStartRequest will cancel the channel
      return NS_ERROR_FILE_NOT_FOUND;
    }
  }

  // Sixth step:
  //
  // All attempts to dispatch this content have failed.  Just pass it off to
  // the helper app service.
  //

  //
  // Optionally, we may want to disable background handling by the external
  // helper application service.
  //
  if (mozilla::Preferences::GetBool(NS_PREF_DISABLE_BACKGROUND_HANDLING,
                                    false)) {
    // First, we will ensure that the parent docshell is in an active
    // state as we will disallow all external application handling unless it is
    // in the foreground.
    nsCOMPtr<nsIDocShell> docShell(do_GetInterface(m_originalContext));
    if (!docShell) {
      // If we can't perform our security check we definitely don't want to go
      // any further!
      LOG(
          ("Failed to get DocShell to ensure it is active before anding off to "
           "helper app service. Aborting."));
      return NS_ERROR_FAILURE;
    }

    // Ensure the DocShell is active before continuing.
    bool isActive = false;
    docShell->GetIsActive(&isActive);
    if (!isActive) {
      LOG(
          ("  Check for active DocShell returned false. Aborting hand off to "
           "helper app service."));
      return NS_ERROR_DOM_SECURITY_ERR;
    }
  }

  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 = APPLICATION_GUESS_FROM_EXT;
      aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
    }

    rv = helperAppService->DoContent(mContentType, request, m_originalContext,
                                     false, nullptr,
                                     getter_AddRefs(m_targetStreamListener));
    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::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 =
      new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);

  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));
}

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 = 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;
}

///////////////////////////////////////////////////////////////////////////////////////////////
// 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_SUCCEEDED(rv)) {
    // 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->AsyncOpen2(loader);

    // no content from this load - that's OK.
    if (rv == NS_ERROR_NO_CONTENT) {
      LOG(("  rv is NS_ERROR_NO_CONTENT -- doing nothing"));
      rv = NS_OK;
    }
  } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
    // Not really an error, from this method's point of view
    rv = 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()));
  }

  // 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(aWindowContext));
  if (winContextListener) {
    nsCOMPtr<nsIURI> uri;
    channel->GetURI(getter_AddRefs(uri));
    if (uri) {
      bool doAbort = false;
      winContextListener->OnStartURIOpen(uri, &doAbort);

      if (doAbort) {
        LOG(("  OnStartURIOpen aborted load"));
        return NS_ERROR_WONT_HANDLE_CONTENT;
      }
    }
  }

  static bool once = InitPreferences();
  mozilla::Unused << once;

  // 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;
}