Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */

/* 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/. */

// HttpLog.h should generally be included first
#include "HttpLog.h"

#include "mozilla/net/HttpBaseChannel.h"

#include "nsHttpHandler.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsReadableUtils.h"

#include "nsICachingChannel.h"
#include "nsIDOMDocument.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsISeekableStream.h"
#include "nsIStorageStream.h"
#include "nsITimedChannel.h"
#include "nsIEncodedChannel.h"
#include "nsIApplicationCacheChannel.h"
#include "nsIMutableArray.h"
#include "nsEscape.h"
#include "nsStreamListenerWrapper.h"
#include "nsISecurityConsoleMessage.h"
#include "nsURLHelper.h"
#include "nsICookieService.h"
#include "nsIStreamConverterService.h"
#include "nsCRT.h"
#include "nsContentUtils.h"
#include "nsIMutableArray.h"
#include "nsIScriptSecurityManager.h"
#include "nsIObserverService.h"
#include "nsProxyRelease.h"
#include "nsPIDOMWindow.h"
#include "nsIDocShell.h"
#include "nsINetworkInterceptController.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozIThirdPartyUtil.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsContentSecurityManager.h"
#include "nsIChannelEventSink.h"
#include "nsILoadGroupChild.h"
#include "mozilla/ConsoleReportCollector.h"
#include "LoadInfo.h"
#include "NullPrincipal.h"
#include "nsISSLSocketControl.h"
#include "mozilla/Telemetry.h"
#include "nsIURL.h"
#include "nsIConsoleService.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Move.h"
#include "mozilla/net/PartiallySeekableInputStream.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIMIMEInputStream.h"
#include "nsIXULRuntime.h"
#include "nsICacheInfoChannel.h"
#include "nsIDOMWindowUtils.h"
#include "nsHttpChannel.h"
#include "nsRedirectHistoryEntry.h"
#include "nsServerTiming.h"

#include <algorithm>
#include "HttpBaseChannel.h"

namespace mozilla {
namespace net {

static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
  // IMPORTANT: keep this list ASCII-code sorted
  static nsHttpAtom const* blackList[] = {&nsHttp::Accept,
                                          &nsHttp::Accept_Encoding,
                                          &nsHttp::Accept_Language,
                                          &nsHttp::Authentication,
                                          &nsHttp::Authorization,
                                          &nsHttp::Connection,
                                          &nsHttp::Content_Length,
                                          &nsHttp::Cookie,
                                          &nsHttp::Host,
                                          &nsHttp::If,
                                          &nsHttp::If_Match,
                                          &nsHttp::If_Modified_Since,
                                          &nsHttp::If_None_Match,
                                          &nsHttp::If_None_Match_Any,
                                          &nsHttp::If_Range,
                                          &nsHttp::If_Unmodified_Since,
                                          &nsHttp::Proxy_Authenticate,
                                          &nsHttp::Proxy_Authorization,
                                          &nsHttp::Range,
                                          &nsHttp::TE,
                                          &nsHttp::Transfer_Encoding,
                                          &nsHttp::Upgrade,
                                          &nsHttp::User_Agent,
                                          &nsHttp::WWW_Authenticate};

  class HttpAtomComparator {
    nsHttpAtom const& mTarget;

   public:
    explicit HttpAtomComparator(nsHttpAtom const& aTarget) : mTarget(aTarget) {}
    int operator()(nsHttpAtom const* aVal) const {
      if (mTarget == *aVal) {
        return 0;
      }
      return strcmp(mTarget._val, aVal->_val);
    }
  };

  size_t unused;
  return BinarySearchIf(blackList, 0, ArrayLength(blackList),
                        HttpAtomComparator(aHeader), &unused);
}

class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
 public:
  NS_DECL_ISUPPORTS

  explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
      : mChannel(aChannel) {}

  NS_IMETHOD VisitHeader(const nsACString& aHeader,
                         const nsACString& aValue) override {
    nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
    if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
      DebugOnly<nsresult> rv =
          mChannel->SetRequestHeader(aHeader, aValue, false);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }
    return NS_OK;
  }

 private:
  ~AddHeadersToChannelVisitor() {}

  nsCOMPtr<nsIHttpChannel> mChannel;
};

NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)

HttpBaseChannel::HttpBaseChannel()
    : mCanceled(false),
      mStartPos(UINT64_MAX),
      mStatus(NS_OK),
      mLoadFlags(LOAD_NORMAL),
      mCaps(0),
      mClassOfService(0),
      mPriority(PRIORITY_NORMAL),
      mRedirectionLimit(gHttpHandler->RedirectionLimit()),
      mUpgradeToSecure(false),
      mApplyConversion(true),
      mIsPending(false),
      mWasOpened(false),
      mRequestObserversCalled(false),
      mResponseHeadersModified(false),
      mAllowSTS(true),
      mThirdPartyFlags(0),
      mUploadStreamHasHeaders(false),
      mInheritApplicationCache(true),
      mChooseApplicationCache(false),
      mLoadedFromApplicationCache(false),
      mChannelIsForDownload(false),
      mTracingEnabled(true),
      mTimingEnabled(false),
      mReportTiming(true),
      mAllowSpdy(true),
      mAllowAltSvc(true),
      mBeConservative(false),
      mTRR(false),
      mResponseTimeoutEnabled(true),
      mAllRedirectsSameOrigin(true),
      mAllRedirectsPassTimingAllowCheck(true),
      mResponseCouldBeSynthesized(false),
      mBlockAuthPrompt(false),
      mAllowStaleCacheContent(false),
      mAddedAsNonTailRequest(false),
      mTlsFlags(0),
      mSuspendCount(0),
      mInitialRwin(0),
      mProxyResolveFlags(0),
      mContentDispositionHint(UINT32_MAX),
      mHttpHandler(gHttpHandler),
      mReferrerPolicy(NS_GetDefaultReferrerPolicy()),
      mRedirectCount(0),
      mInternalRedirectCount(0),
      mForcePending(false),
      mCorsIncludeCredentials(false),
      mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS),
      mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
      mOnStartRequestCalled(false),
      mOnStopRequestCalled(false),
      mUpgradableToSecure(true),
      mAfterOnStartRequestBegun(false),
      mTransferSize(0),
      mDecodedBodySize(0),
      mEncodedBodySize(0),
      mRequestContextID(0),
      mContentWindowId(0),
      mTopLevelOuterContentWindowId(0),
      mRequireCORSPreflight(false),
      mReportCollector(new ConsoleReportCollector()),
      mAltDataLength(0),
      mAltDataForChild(false),
      mForceMainDocumentChannel(false),
      mIsTrackingResource(false),
      mLastRedirectFlags(0),
      mReqContentLength(0U),
      mReqContentLengthDetermined(false) {
  LOG(("Creating HttpBaseChannel @%p\n", this));

  // Subfields of unions cannot be targeted in an initializer list.
#ifdef MOZ_VALGRIND
  // Zero the entire unions so that Valgrind doesn't complain when we send them
  // to another process.
  memset(&mSelfAddr, 0, sizeof(NetAddr));
  memset(&mPeerAddr, 0, sizeof(NetAddr));
#endif
  mSelfAddr.raw.family = PR_AF_UNSPEC;
  mPeerAddr.raw.family = PR_AF_UNSPEC;
}

HttpBaseChannel::~HttpBaseChannel() {
  LOG(("Destroying HttpBaseChannel @%p\n", this));

  // Make sure we don't leak
  CleanRedirectCacheChainIfNecessary();

  ReleaseMainThreadOnlyReferences();
}

namespace {  // anon

class NonTailRemover : public nsISupports {
  NS_DECL_THREADSAFE_ISUPPORTS

  explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}

 private:
  virtual ~NonTailRemover() {
    MOZ_ASSERT(NS_IsMainThread());
    mRequestContext->RemoveNonTailRequest();
  }

  nsCOMPtr<nsIRequestContext> mRequestContext;
};

NS_IMPL_ISUPPORTS0(NonTailRemover)

}  // namespace

void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
  if (NS_IsMainThread()) {
    // Already on main thread, let dtor to
    // take care of releasing references
    RemoveAsNonTailRequest();
    return;
  }

  nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
  arrayToRelease.AppendElement(mURI.forget());
  arrayToRelease.AppendElement(mOriginalURI.forget());
  arrayToRelease.AppendElement(mDocumentURI.forget());
  arrayToRelease.AppendElement(mLoadGroup.forget());
  arrayToRelease.AppendElement(mLoadInfo.forget());
  arrayToRelease.AppendElement(mCallbacks.forget());
  arrayToRelease.AppendElement(mProgressSink.forget());
  arrayToRelease.AppendElement(mReferrer.forget());
  arrayToRelease.AppendElement(mApplicationCache.forget());
  arrayToRelease.AppendElement(mAPIRedirectToURI.forget());
  arrayToRelease.AppendElement(mProxyURI.forget());
  arrayToRelease.AppendElement(mPrincipal.forget());
  arrayToRelease.AppendElement(mTopWindowURI.forget());
  arrayToRelease.AppendElement(mListener.forget());
  arrayToRelease.AppendElement(mListenerContext.forget());
  arrayToRelease.AppendElement(mCompressListener.forget());

  if (mAddedAsNonTailRequest) {
    // RemoveNonTailRequest() on our request context must be called on the main
    // thread
    MOZ_RELEASE_ASSERT(mRequestContext,
                       "Someone released rc or set flags w/o having it?");

    nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
    arrayToRelease.AppendElement(nonTailRemover.forget());
  }

  NS_DispatchToMainThread(new ProxyReleaseRunnable(Move(arrayToRelease)));
}

void HttpBaseChannel::SetIsTrackingResource() {
  LOG(("HttpBaseChannel::SetIsTrackingResource %p", this));
  mIsTrackingResource = true;
}

nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
                               nsProxyInfo* aProxyInfo,
                               uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
                               uint64_t aChannelId) {
  LOG(("HttpBaseChannel::Init [this=%p]\n", this));

  NS_PRECONDITION(aURI, "null uri");

  mURI = aURI;
  mOriginalURI = aURI;
  mDocumentURI = nullptr;
  mCaps = aCaps;
  mProxyResolveFlags = aProxyResolveFlags;
  mProxyURI = aProxyURI;
  mChannelId = aChannelId;

  // Construct connection info object
  nsAutoCString host;
  int32_t port = -1;
  bool isHTTPS = false;

  nsresult rv = mURI->SchemeIs("https", &isHTTPS);
  if (NS_FAILED(rv)) return rv;

  rv = mURI->GetAsciiHost(host);
  if (NS_FAILED(rv)) return rv;

  // Reject the URL if it doesn't specify a host
  if (host.IsEmpty()) return NS_ERROR_MALFORMED_URI;

  rv = mURI->GetPort(&port);
  if (NS_FAILED(rv)) return rv;

  LOG(("host=%s port=%d\n", host.get(), port));

  rv = mURI->GetAsciiSpec(mSpec);
  if (NS_FAILED(rv)) return rv;
  LOG(("uri=%s\n", mSpec.get()));

  // Assert default request method
  MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));

  // Set request headers
  nsAutoCString hostLine;
  rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
  if (NS_FAILED(rv)) return rv;

  rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
  if (NS_FAILED(rv)) return rv;

  rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead, isHTTPS);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString type;
  if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
      !type.EqualsLiteral("unknown"))
    mProxyInfo = aProxyInfo;

  return rv;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupports
//-----------------------------------------------------------------------------

NS_IMPL_ADDREF(HttpBaseChannel)
NS_IMPL_RELEASE(HttpBaseChannel)

NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
  NS_INTERFACE_MAP_ENTRY(nsIRequest)
  NS_INTERFACE_MAP_ENTRY(nsIChannel)
  NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
  NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
  NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
  NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
  NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
  NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
  NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
  NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
  NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
  NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
  NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
  NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
  NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
  NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
  if (aIID.Equals(NS_GET_IID(HttpBaseChannel))) {
    foundInterface = static_cast<nsIWritablePropertyBag*>(this);
  } else
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIRequest
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetName(nsACString& aName) {
  aName = mSpec;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::IsPending(bool* aIsPending) {
  NS_ENSURE_ARG_POINTER(aIsPending);
  *aIsPending = mIsPending || mForcePending;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetStatus(nsresult* aStatus) {
  NS_ENSURE_ARG_POINTER(aStatus);
  *aStatus = mStatus;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
  NS_ENSURE_ARG_POINTER(aLoadGroup);
  *aLoadGroup = mLoadGroup;
  NS_IF_ADDREF(*aLoadGroup);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
  MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");

  if (!CanSetLoadGroup(aLoadGroup)) {
    return NS_ERROR_FAILURE;
  }

  mLoadGroup = aLoadGroup;
  mProgressSink = nullptr;
  UpdatePrivateBrowsing();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
  NS_ENSURE_ARG_POINTER(aLoadFlags);
  *aLoadFlags = mLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
  bool synthesized = false;
  nsresult rv = GetResponseSynthesized(&synthesized);
  NS_ENSURE_SUCCESS(rv, rv);

  // If this channel is marked as awaiting a synthesized response,
  // modifying certain load flags can interfere with the implementation
  // of the network interception logic. This takes care of a couple
  // known cases that attempt to mark channels as anonymous due
  // to cross-origin redirects; since the response is entirely synthesized
  // this is an unnecessary precaution.
  // This should be removed when bug 1201683 is fixed.
  if (synthesized && aLoadFlags != mLoadFlags) {
    aLoadFlags &= ~LOAD_ANONYMOUS;
  }

  mLoadFlags = aLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetDocshellUserAgentOverride() {
  // This sets the docshell specific user agent override, it will be overwritten
  // by UserAgentOverrides.jsm if site-specific user agent overrides are set.
  nsresult rv;
  nsCOMPtr<nsILoadContext> loadContext;
  NS_QueryNotificationCallbacks(this, loadContext);
  if (!loadContext) {
    return NS_OK;
  }

  nsCOMPtr<mozIDOMWindowProxy> domWindow;
  loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
  if (!domWindow) {
    return NS_OK;
  }

  auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
  nsIDocShell* docshell = pDomWindow->GetDocShell();
  if (!docshell) {
    return NS_OK;
  }

  nsString customUserAgent;
  docshell->GetCustomUserAgent(customUserAgent);
  if (customUserAgent.IsEmpty()) {
    return NS_OK;
  }

  NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
  rv = SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), utf8CustomUserAgent,
                        false);
  if (NS_FAILED(rv)) return rv;

  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
  NS_ENSURE_ARG_POINTER(aOriginalURI);
  *aOriginalURI = mOriginalURI;
  NS_ADDREF(*aOriginalURI);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
  ENSURE_CALLED_BEFORE_CONNECT();

  NS_ENSURE_ARG_POINTER(aOriginalURI);
  mOriginalURI = aOriginalURI;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetURI(nsIURI** aURI) {
  NS_ENSURE_ARG_POINTER(aURI);
  *aURI = mURI;
  NS_ADDREF(*aURI);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetOwner(nsISupports** aOwner) {
  NS_ENSURE_ARG_POINTER(aOwner);
  *aOwner = mOwner;
  NS_IF_ADDREF(*aOwner);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetOwner(nsISupports* aOwner) {
  mOwner = aOwner;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
  mLoadInfo = aLoadInfo;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
  NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
  return NS_GetIsDocumentChannel(this, aIsDocument);
}

NS_IMETHODIMP
HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
  *aCallbacks = mCallbacks;
  NS_IF_ADDREF(*aCallbacks);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
  MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");

  if (!CanSetCallbacks(aCallbacks)) {
    return NS_ERROR_FAILURE;
  }

  mCallbacks = aCallbacks;
  mProgressSink = nullptr;

  UpdatePrivateBrowsing();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentType(nsACString& aContentType) {
  if (!mResponseHead) {
    aContentType.Truncate();
    return NS_ERROR_NOT_AVAILABLE;
  }

  mResponseHead->ContentType(aContentType);
  if (!aContentType.IsEmpty()) {
    return NS_OK;
  }

  aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetContentType(const nsACString& aContentType) {
  if (mListener || mWasOpened) {
    if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

    nsAutoCString contentTypeBuf, charsetBuf;
    bool hadCharset;
    net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);

    mResponseHead->SetContentType(contentTypeBuf);

    // take care not to stomp on an existing charset
    if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);

  } else {
    // We are being given a content-type hint.
    bool dummy;
    net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
                         &dummy);
  }

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

  mResponseHead->ContentCharset(aContentCharset);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
  if (mListener) {
    if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

    mResponseHead->SetContentCharset(aContentCharset);
  } else {
    // Charset hint
    mContentCharsetHint = aContentCharset;
  }
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
  nsresult rv;
  nsCString header;

  rv = GetContentDispositionHeader(header);
  if (NS_FAILED(rv)) {
    if (mContentDispositionHint == UINT32_MAX) return rv;

    *aContentDisposition = mContentDispositionHint;
    return NS_OK;
  }

  *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
  mContentDispositionHint = aContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentDispositionFilename(
    nsAString& aContentDispositionFilename) {
  aContentDispositionFilename.Truncate();
  nsresult rv;
  nsCString header;

  rv = GetContentDispositionHeader(header);
  if (NS_FAILED(rv)) {
    if (!mContentDispositionFilename) return rv;

    aContentDispositionFilename = *mContentDispositionFilename;
    return NS_OK;
  }

  return NS_GetFilenameFromDisposition(aContentDispositionFilename, header,
                                       mURI);
}

NS_IMETHODIMP
HttpBaseChannel::SetContentDispositionFilename(
    const nsAString& aContentDispositionFilename) {
  mContentDispositionFilename = new nsString(aContentDispositionFilename);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentDispositionHeader(
    nsACString& aContentDispositionHeader) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

  nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
                                         aContentDispositionHeader);
  if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty())
    return NS_ERROR_NOT_AVAILABLE;

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
  NS_ENSURE_ARG_POINTER(aContentLength);

  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

  if (!mAvailableCachedAltDataType.IsEmpty()) {
    *aContentLength = mAltDataLength;
    return NS_OK;
  }

  *aContentLength = mResponseHead->ContentLength();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetContentLength(int64_t value) {
  MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
HttpBaseChannel::Open(nsIInputStream** aResult) {
  NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);

  if (!gHttpHandler->Active()) {
    LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
    return NS_ERROR_NOT_AVAILABLE;
  }

  return NS_ImplementChannelOpen(this, aResult);
}

NS_IMETHODIMP
HttpBaseChannel::Open2(nsIInputStream** aStream) {
  if (!gHttpHandler->Active()) {
    LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCOMPtr<nsIStreamListener> listener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);
  return Open(aStream);
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIUploadChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
  NS_ENSURE_ARG_POINTER(stream);
  *stream = mUploadStream;
  NS_IF_ADDREF(*stream);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
                                 const nsACString& contentTypeArg,
                                 int64_t contentLength) {
  // NOTE: for backwards compatibility and for compatibility with old style
  // plugins, |stream| may include headers, specifically Content-Type and
  // Content-Length headers.  in this case, |contentType| and |contentLength|
  // would be unspecified.  this is traditionally the case of a POST request,
  // and so we select POST as the request method if contentType and
  // contentLength are unspecified.

  if (stream) {
    nsAutoCString method;
    bool hasHeaders = false;

    // This method and ExplicitSetUploadStream mean different things by "empty
    // content type string".  This method means "no header", but
    // ExplicitSetUploadStream means "header with empty value".  So we have to
    // massage the contentType argument into the form ExplicitSetUploadStream
    // expects.
    nsCOMPtr<nsIMIMEInputStream> mimeStream;
    nsCString contentType(contentTypeArg);
    if (contentType.IsEmpty()) {
      contentType.SetIsVoid(true);
      method = NS_LITERAL_CSTRING("POST");

      // MIME streams are a special case, and include headers which need to be
      // copied to the channel.
      mimeStream = do_QueryInterface(stream);
      if (mimeStream) {
        // Copy non-origin related headers to the channel.
        nsCOMPtr<nsIHttpHeaderVisitor> visitor =
            new AddHeadersToChannelVisitor(this);
        mimeStream->VisitHeaders(visitor);

        return ExplicitSetUploadStream(stream, contentType, contentLength,
                                       method, hasHeaders);
      }

      hasHeaders = true;
    } else {
      method = NS_LITERAL_CSTRING("PUT");

      MOZ_ASSERT(
          NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
          "nsIMIMEInputStream should not be set with an explicit content type");
    }
    return ExplicitSetUploadStream(stream, contentType, contentLength, method,
                                   hasHeaders);
  }

  // if stream is null, ExplicitSetUploadStream returns error.
  // So we need special case for GET method.
  mUploadStreamHasHeaders = false;
  mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET"));  // revert to GET request
  mUploadStream = stream;
  return NS_OK;
}

namespace {

void CopyComplete(void* aClosure, nsresult aStatus) {
#ifdef DEBUG
  // Called on the STS thread by NS_AsyncCopy
  nsCOMPtr<nsIEventTarget> sts =
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
  bool result = false;
  sts->IsOnCurrentThread(&result);
  MOZ_ASSERT(result, "Should only be called on the STS thread.");
#endif

  auto channel = static_cast<HttpBaseChannel*>(aClosure);
  channel->OnCopyComplete(aStatus);
}

}  // anonymous namespace

NS_IMETHODIMP
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback) {
  MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
  NS_ENSURE_ARG_POINTER(aCallback);

  // We could in theory allow multiple callers to use this method,
  // but the complexity does not seem worth it yet.  Just fail if
  // this is called more than once simultaneously.
  NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);

  // If the CloneUploadStream() will succeed, then synchronously invoke
  // the callback to indicate we're already cloneable.
  if (!mUploadStream || NS_InputStreamIsCloneable(mUploadStream)) {
    aCallback->Run();
    return NS_OK;
  }

  nsCOMPtr<nsIStorageStream> storageStream;
  nsresult rv =
      NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIInputStream> newUploadStream;
  rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIOutputStream> sink;
  rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIInputStream> source;
  if (NS_InputStreamIsBuffered(mUploadStream)) {
    source = mUploadStream;
  } else {
    rv = NS_NewBufferedInputStream(getter_AddRefs(source),
                                   mUploadStream.forget(), 4096);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIEventTarget> target =
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);

  mUploadCloneableCallback = aCallback;

  rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
                    4096,  // copy segment size
                    CopyComplete, this);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    mUploadCloneableCallback = nullptr;
    return rv;
  }

  // Since we're consuming the old stream, replace it with the new
  // stream immediately.
  mUploadStream = newUploadStream;

  // Explicity hold the stream alive until copying is complete.  This will
  // be released in EnsureUploadStreamIsCloneableComplete().
  AddRef();

  return NS_OK;
}

void HttpBaseChannel::OnCopyComplete(nsresult aStatus) {
  // Assert in parent process because we don't have to label the runnable
  // in parent process.
  MOZ_ASSERT(XRE_IsParentProcess());

  nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
      "net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
      &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
  NS_DispatchToMainThread(runnable.forget());
}

void HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus) {
  MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
  MOZ_ASSERT(mUploadCloneableCallback);

  if (NS_SUCCEEDED(mStatus)) {
    mStatus = aStatus;
  }

  mUploadCloneableCallback->Run();
  mUploadCloneableCallback = nullptr;

  // Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
  // that the copying is complete.
  Release();
}

NS_IMETHODIMP
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
                                   nsIInputStream** aClonedStream) {
  NS_ENSURE_ARG_POINTER(aContentLength);
  NS_ENSURE_ARG_POINTER(aClonedStream);
  *aClonedStream = nullptr;

  if (!mUploadStream) {
    return NS_OK;
  }

  nsCOMPtr<nsIInputStream> clonedStream;
  nsresult rv =
      NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
  NS_ENSURE_SUCCESS(rv, rv);

  clonedStream.forget(aClonedStream);

  if (mReqContentLengthDetermined) {
    *aContentLength = mReqContentLength;
  } else {
    *aContentLength = -1;
  }

  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIUploadChannel2
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
                                         const nsACString& aContentType,
                                         int64_t aContentLength,
                                         const nsACString& aMethod,
                                         bool aStreamHasHeaders) {
  // Ensure stream is set and method is valid
  NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);

  {
    DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
    MOZ_ASSERT(
        !aStreamHasHeaders || NS_FAILED(CallQueryInterface(
                                  aStream, getter_AddRefs(mimeStream.value))),
        "nsIMIMEInputStream should not include headers");
  }

  if (aContentLength < 0 && !aStreamHasHeaders) {
    nsresult rv =
        aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength));
    if (NS_FAILED(rv) || aContentLength < 0) {
      NS_ERROR("unable to determine content length");
      return NS_ERROR_FAILURE;
    }
  }

  nsresult rv = SetRequestMethod(aMethod);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!aStreamHasHeaders) {
    // SetRequestHeader propagates headers to chrome if HttpChannelChild
    nsAutoCString contentLengthStr;
    contentLengthStr.AppendInt(aContentLength);
    SetRequestHeader(NS_LITERAL_CSTRING("Content-Length"), contentLengthStr,
                     false);
    if (!aContentType.IsVoid()) {
      if (aContentType.IsEmpty()) {
        SetEmptyRequestHeader(NS_LITERAL_CSTRING("Content-Type"));
      } else {
        SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType,
                         false);
      }
    }
  }

  mUploadStreamHasHeaders = aStreamHasHeaders;

  // We already have the content length. We don't need to determinate it.
  if (aContentLength > 0) {
    mReqContentLength = aContentLength;
    mReqContentLengthDetermined = true;
  }

  nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
  if (!seekable) {
    nsCOMPtr<nsIInputStream> stream = aStream;
    aStream = new PartiallySeekableInputStream(stream.forget());
  }

  mUploadStream = aStream;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
  NS_ENSURE_ARG(hasHeaders);

  *hasHeaders = mUploadStreamHasHeaders;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIEncodedChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetApplyConversion(bool* value) {
  *value = mApplyConversion;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetApplyConversion(bool value) {
  LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
       value));
  mApplyConversion = value;
  return NS_OK;
}

nsresult HttpBaseChannel::DoApplyContentConversions(
    nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
  return DoApplyContentConversions(aNextListener, aNewNextListener,
                                   mListenerContext);
}

// create a listener chain that looks like this
// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
// channel-creator-listener
//
// we need to do this because not every decompressor has fully streamed output
// so may need a call to OnStopRequest to identify its completion state.. and if
// it creates an error there the channel status code needs to be updated before
// calling the terminal listener. Having the decompress do it via cancel() means
// channels cannot effectively be used in two contexts (specifically this one
// and a peek context for sniffing)
//
class InterceptFailedOnStop : public nsIStreamListener {
  virtual ~InterceptFailedOnStop() {}
  nsCOMPtr<nsIStreamListener> mNext;
  HttpBaseChannel* mChannel;

 public:
  InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
      : mNext(arg), mChannel(chan) {}
  NS_DECL_THREADSAFE_ISUPPORTS

  NS_IMETHOD OnStartRequest(nsIRequest* aRequest,
                            nsISupports* aContext) override {
    return mNext->OnStartRequest(aRequest, aContext);
  }

  NS_IMETHOD OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
                           nsresult aStatusCode) override {
    if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
      LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
           mChannel, static_cast<uint32_t>(aStatusCode)));
      mChannel->mStatus = aStatusCode;
    }
    return mNext->OnStopRequest(aRequest, aContext, aStatusCode);
  }

  NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
                             nsIInputStream* aInputStream, uint64_t aOffset,
                             uint32_t aCount) override {
    return mNext->OnDataAvailable(aRequest, aContext, aInputStream, aOffset,
                                  aCount);
  }
};

NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)

NS_IMETHODIMP
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
                                           nsIStreamListener** aNewNextListener,
                                           nsISupports* aCtxt) {
  *aNewNextListener = nullptr;
  if (!mResponseHead || !aNextListener) {
    return NS_OK;
  }

  LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));

  if (!mApplyConversion) {
    LOG(("not applying conversion per mApplyConversion\n"));
    return NS_OK;
  }

  if (!mAvailableCachedAltDataType.IsEmpty()) {
    LOG(("not applying conversion because delivering alt-data\n"));
    return NS_OK;
  }

  nsAutoCString contentEncoding;
  nsresult rv =
      mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
  if (NS_FAILED(rv) || contentEncoding.IsEmpty()) return NS_OK;

  nsCOMPtr<nsIStreamListener> nextListener =
      new InterceptFailedOnStop(aNextListener, this);

  // The encodings are listed in the order they were applied
  // (see rfc 2616 section 14.11), so they need to removed in reverse
  // order. This is accomplished because the converter chain ends up
  // being a stack with the last converter created being the first one
  // to accept the raw network data.

  char* cePtr = contentEncoding.BeginWriting();
  uint32_t count = 0;
  while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
    if (++count > 16) {
      // That's ridiculous. We only understand 2 different ones :)
      // but for compatibility with old code, we will just carry on without
      // removing the encodings
      LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
      break;
    }

    bool isHTTPS = false;
    mURI->SchemeIs("https", &isHTTPS);
    if (gHttpHandler->IsAcceptableEncoding(val, isHTTPS)) {
      nsCOMPtr<nsIStreamConverterService> serv;
      rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));

      // we won't fail to load the page just because we couldn't load the
      // stream converter service.. carry on..
      if (NS_FAILED(rv)) {
        if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
        continue;
      }

      nsCOMPtr<nsIStreamListener> converter;
      nsAutoCString from(val);
      ToLowerCase(from);
      rv = serv->AsyncConvertData(from.get(), "uncompressed", nextListener,
                                  aCtxt, getter_AddRefs(converter));
      if (NS_FAILED(rv)) {
        LOG(("Unexpected failure of AsyncConvertData %s\n", val));
        return rv;
      }

      LOG(("converter removed '%s' content-encoding\n", val));
      if (gHttpHandler->IsTelemetryEnabled()) {
        int mode = 0;
        if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
          mode = 1;
        } else if (from.EqualsLiteral("deflate") ||
                   from.EqualsLiteral("x-deflate")) {
          mode = 2;
        } else if (from.EqualsLiteral("br")) {
          mode = 3;
        }
        Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
      }
      nextListener = converter;
    } else {
      if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
    }
  }
  *aNewNextListener = nextListener;
  NS_IF_ADDREF(*aNewNextListener);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) {
  if (!mResponseHead) {
    *aEncodings = nullptr;
    return NS_OK;
  }

  nsAutoCString encoding;
  Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
  if (encoding.IsEmpty()) {
    *aEncodings = nullptr;
    return NS_OK;
  }
  nsContentEncodings* enumerator = new nsContentEncodings(this, encoding.get());
  NS_ADDREF(*aEncodings = enumerator);
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsContentEncodings <public>
//-----------------------------------------------------------------------------

HttpBaseChannel::nsContentEncodings::nsContentEncodings(
    nsIHttpChannel* aChannel, const char* aEncodingHeader)
    : mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(false) {
  mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
  mCurStart = mCurEnd;
}

HttpBaseChannel::nsContentEncodings::~nsContentEncodings() {}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings) {
  if (mReady) {
    *aMoreEncodings = true;
    return NS_OK;
  }

  nsresult rv = PrepareForNext();
  *aMoreEncodings = NS_SUCCEEDED(rv);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
  aNextEncoding.Truncate();
  if (!mReady) {
    nsresult rv = PrepareForNext();
    if (NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }
  }

  const nsACString& encoding = Substring(mCurStart, mCurEnd);

  nsACString::const_iterator start, end;
  encoding.BeginReading(start);
  encoding.EndReading(end);

  bool haveType = false;
  if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("gzip"), start, end)) {
    aNextEncoding.AssignLiteral(APPLICATION_GZIP);
    haveType = true;
  }

  if (!haveType) {
    encoding.BeginReading(start);
    if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"), start,
                                      end)) {
      aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
      haveType = true;
    }
  }

  if (!haveType) {
    encoding.BeginReading(start);
    if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"), start,
                                      end)) {
      aNextEncoding.AssignLiteral(APPLICATION_ZIP);
      haveType = true;
    }
  }

  if (!haveType) {
    encoding.BeginReading(start);
    if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("br"), start, end)) {
      aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
      haveType = true;
    }
  }

  // Prepare to fetch the next encoding
  mCurEnd = mCurStart;
  mReady = false;

  if (haveType) return NS_OK;

  NS_WARNING("Unknown encoding type");
  return NS_ERROR_FAILURE;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsContentEncodings::nsISupports
//-----------------------------------------------------------------------------

NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator)

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsContentEncodings <private>
//-----------------------------------------------------------------------------

nsresult HttpBaseChannel::nsContentEncodings::PrepareForNext(void) {
  MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");

  // At this point both mCurStart and mCurEnd point to somewhere
  // past the end of the next thing we want to return

  while (mCurEnd != mEncodingHeader) {
    --mCurEnd;
    if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd)) break;
  }
  if (mCurEnd == mEncodingHeader)
    return NS_ERROR_NOT_AVAILABLE;  // no more encodings
  ++mCurEnd;

  // At this point mCurEnd points to the first char _after_ the
  // header we want.  Furthermore, mCurEnd - 1 != mEncodingHeader

  mCurStart = mCurEnd - 1;
  while (mCurStart != mEncodingHeader && *mCurStart != ',' &&
         !nsCRT::IsAsciiSpace(*mCurStart))
    --mCurStart;
  if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
    ++mCurStart;  // we stopped because of a weird char, so move up one

  // At this point mCurStart and mCurEnd bracket the encoding string
  // we want.  Check that it's not "identity"
  if (Substring(mCurStart, mCurEnd)
          .Equals("identity", nsCaseInsensitiveCStringComparator())) {
    mCurEnd = mCurStart;
    return PrepareForNext();
  }

  mReady = true;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIHttpChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetChannelId(uint64_t* aChannelId) {
  NS_ENSURE_ARG_POINTER(aChannelId);
  *aChannelId = mChannelId;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetChannelId(uint64_t aChannelId) {
  mChannelId = aChannelId;
  return NS_OK;
}

NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
  if (!mContentWindowId) {
    nsCOMPtr<nsILoadContext> loadContext;
    GetCallback(loadContext);
    if (loadContext) {
      nsCOMPtr<mozIDOMWindowProxy> topWindow;
      loadContext->GetTopWindow(getter_AddRefs(topWindow));
      nsCOMPtr<nsIDOMWindowUtils> windowUtils = do_GetInterface(topWindow);
      if (windowUtils) {
        windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
      }
    }
  }
  *aWindowId = mContentWindowId;
  return NS_OK;
}

NS_IMETHODIMP HttpBaseChannel::SetTopLevelOuterContentWindowId(
    uint64_t aWindowId) {
  mTopLevelOuterContentWindowId = aWindowId;
  return NS_OK;
}

NS_IMETHODIMP HttpBaseChannel::GetTopLevelOuterContentWindowId(
    uint64_t* aWindowId) {
  EnsureTopLevelOuterContentWindowId();
  *aWindowId = mTopLevelOuterContentWindowId;
  return NS_OK;
}

NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
  mContentWindowId = aWindowId;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetIsTrackingResource(bool* aIsTrackingResource) {
  *aIsTrackingResource = mIsTrackingResource;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
  *aTransferSize = mTransferSize;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
  *aDecodedBodySize = mDecodedBodySize;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
  *aEncodedBodySize = mEncodedBodySize;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestMethod(nsACString& aMethod) {
  mRequestHead.Method(aMethod);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRequestMethod(const nsACString& aMethod) {
  ENSURE_CALLED_BEFORE_CONNECT();

  const nsCString& flatMethod = PromiseFlatCString(aMethod);

  // Method names are restricted to valid HTTP tokens.
  if (!nsHttp::IsValidToken(flatMethod)) return NS_ERROR_INVALID_ARG;

  mRequestHead.SetMethod(flatMethod);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetNetworkInterfaceId(nsACString& aNetworkInterfaceId) {
  aNetworkInterfaceId = mNetworkInterfaceId;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) {
  ENSURE_CALLED_BEFORE_CONNECT();
  mNetworkInterfaceId = aNetworkInterfaceId;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetReferrer(nsIURI** referrer) {
  NS_ENSURE_ARG_POINTER(referrer);
  *referrer = mReferrer;
  NS_IF_ADDREF(*referrer);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetReferrer(nsIURI* referrer) {
  bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
  return SetReferrerWithPolicy(referrer,
                               NS_GetDefaultReferrerPolicy(isPrivate));
}

NS_IMETHODIMP
HttpBaseChannel::GetReferrerPolicy(uint32_t* referrerPolicy) {
  NS_ENSURE_ARG_POINTER(referrerPolicy);
  *referrerPolicy = mReferrerPolicy;
  return NS_OK;
}

/* Computing whether our URI is cross-origin may be expensive, so please do
 * that in cases where we're going to use this information later on.
 */
bool HttpBaseChannel::IsCrossOriginWithReferrer() {
  nsresult rv;
  nsCOMPtr<nsIURI> triggeringURI;
  if (mLoadInfo) {
    nsCOMPtr<nsIPrincipal> triggeringPrincipal =
        mLoadInfo->TriggeringPrincipal();
    if (triggeringPrincipal) {
      triggeringPrincipal->GetURI(getter_AddRefs(triggeringURI));
    }
  }
  if (triggeringURI) {
    if (LOG_ENABLED()) {
      nsAutoCString triggeringURISpec;
      triggeringURI->GetAsciiSpec(triggeringURISpec);
      LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
    }
    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
    rv = ssm->CheckSameOriginURI(triggeringURI, mURI, false);
    return (NS_FAILED(rv));
  }

  LOG(
      ("no triggering principal available via loadInfo, assuming load is "
       "cross-origin"));
  return true;
}

NS_IMETHODIMP
HttpBaseChannel::SetReferrerWithPolicy(nsIURI* referrer,
                                       uint32_t referrerPolicy) {
  ENSURE_CALLED_BEFORE_CONNECT();

  mReferrerPolicy = referrerPolicy;

  // clear existing referrer, if any
  mReferrer = nullptr;
  nsresult rv = mRequestHead.ClearHeader(nsHttp::Referer);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (mReferrerPolicy == REFERRER_POLICY_UNSET) {
    bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
    mReferrerPolicy = NS_GetDefaultReferrerPolicy(isPrivate);
  }

  if (!referrer) {
    return NS_OK;
  }

  // Don't send referrer at all when the meta referrer setting is "no-referrer"
  if (mReferrerPolicy == REFERRER_POLICY_NO_REFERRER) {
    return NS_OK;
  }

  // 0: never send referer
  // 1: send referer for direct user action
  // 2: always send referer
  uint32_t userReferrerLevel = gHttpHandler->ReferrerLevel();

  // false: use real referrer
  // true: spoof with URI of the current request
  bool userSpoofReferrerSource = gHttpHandler->SpoofReferrerSource();

  // false: use real referrer when leaving .onion
  // true: use an empty referrer
  bool userHideOnionReferrerSource = gHttpHandler->HideOnionReferrerSource();

  // 0: send referer no matter what
  // 1: send referer ONLY when base domains match
  // 2: send referer ONLY when hosts match
  int userReferrerXOriginPolicy = gHttpHandler->ReferrerXOriginPolicy();

  // check referrer blocking pref
  uint32_t referrerLevel;
  if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
    referrerLevel = 1;  // user action
  } else {
    referrerLevel = 2;  // inline content
  }
  if (userReferrerLevel < referrerLevel) {
    return NS_OK;
  }

  nsCOMPtr<nsIURI> referrerGrip;
  bool match;

  //
  // Strip off "wyciwyg://123/" from wyciwyg referrers.
  //
  // XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
  //   perhaps some sort of generic nsINestedURI could be used.  then, if an URI
  //   fails the whitelist test, then we could check for an inner URI and try
  //   that instead.  though, that might be too automatic.
  //
  rv = referrer->SchemeIs("wyciwyg", &match);
  if (NS_FAILED(rv)) return rv;
  if (match) {
    nsAutoCString path;
    rv = referrer->GetPathQueryRef(path);
    if (NS_FAILED(rv)) return rv;

    uint32_t pathLength = path.Length();
    if (pathLength <= 2) return NS_ERROR_FAILURE;

    // Path is of the form "//123/http://foo/bar", with a variable number of
    // digits. To figure out where the "real" URL starts, search path for a
    // '/', starting at the third character.
    int32_t slashIndex = path.FindChar('/', 2);
    if (slashIndex == kNotFound) return NS_ERROR_FAILURE;

    // Replace |referrer| with a URI without wyciwyg://123/.
    rv =
        NS_NewURI(getter_AddRefs(referrerGrip),
                  Substring(path, slashIndex + 1, pathLength - slashIndex - 1));
    if (NS_FAILED(rv)) return rv;

    referrer = referrerGrip.get();
  }

  // Enforce Referrer whitelist
  if (!IsReferrerSchemeAllowed(referrer)) {
    return NS_OK;  // kick out....
  }

  //
  // Handle secure referrals.
  //
  // Support referrals from a secure server if this is a secure site
  // and (optionally) if the host names are the same.
  //
  rv = referrer->SchemeIs("https", &match);
  if (NS_FAILED(rv)) return rv;

  if (match) {
    rv = mURI->SchemeIs("https", &match);
    if (NS_FAILED(rv)) return rv;

    // It's ok to send referrer for https-to-http scenarios if the referrer
    // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
    if (mReferrerPolicy != REFERRER_POLICY_UNSAFE_URL &&
        mReferrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN &&
        mReferrerPolicy != REFERRER_POLICY_ORIGIN) {
      // in other referrer policies, https->http is not allowed...
      if (!match) return NS_OK;
    }
  }

  nsCOMPtr<nsIURI> clone;
  //
  // we need to clone the referrer, so we can:
  //  (1) modify it
  //  (2) keep a reference to it after returning from this function
  //
  // Use CloneIgnoringRef to strip away any fragment per RFC 2616 section 14.36
  // and Referrer Policy section 6.3.5.
  rv = referrer->CloneIgnoringRef(getter_AddRefs(clone));
  if (NS_FAILED(rv)) return rv;

  nsAutoCString currentHost;
  nsAutoCString referrerHost;

  rv = mURI->GetAsciiHost(currentHost);
  if (NS_FAILED(rv)) return rv;

  rv = clone->GetAsciiHost(referrerHost);
  if (NS_FAILED(rv)) return rv;

  // Send an empty referrer if leaving a .onion domain.
  if (userHideOnionReferrerSource && !currentHost.Equals(referrerHost) &&
      StringEndsWith(referrerHost, NS_LITERAL_CSTRING(".onion"))) {
    return NS_OK;
  }

  // check policy for sending ref only when hosts match
  if (userReferrerXOriginPolicy == 2 && !currentHost.Equals(referrerHost))
    return NS_OK;

  if (userReferrerXOriginPolicy == 1) {
    nsAutoCString currentDomain = currentHost;
    nsAutoCString referrerDomain = referrerHost;
    uint32_t extraDomains = 0;
    nsCOMPtr<nsIEffectiveTLDService> eTLDService =
        do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
    if (eTLDService) {
      rv = eTLDService->GetBaseDomain(mURI, extraDomains, currentDomain);
      if (NS_FAILED(rv)) return rv;
      rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain);
      if (NS_FAILED(rv)) return rv;
    }

    // check policy for sending only when effective top level domain matches.
    // this falls back on using host if eTLDService does not work
    if (!currentDomain.Equals(referrerDomain)) return NS_OK;
  }

  // send spoofed referrer if desired
  if (userSpoofReferrerSource) {
    nsCOMPtr<nsIURI> mURIclone;
    rv = mURI->CloneIgnoringRef(getter_AddRefs(mURIclone));
    if (NS_FAILED(rv)) return rv;
    clone = mURIclone;
    currentHost = referrerHost;
  }

  // strip away any userpass; we don't want to be giving out passwords ;-)
  // This is required by Referrer Policy stripping algorithm.
  rv = NS_MutateURI(clone).SetUserPass(EmptyCString()).Finalize(clone);
  if (NS_FAILED(rv)) return rv;

  // 0: full URI
  // 1: scheme+host+port+path
  // 2: scheme+host+port
  int userReferrerTrimmingPolicy = gHttpHandler->ReferrerTrimmingPolicy();
  int userReferrerXOriginTrimmingPolicy =
      gHttpHandler->ReferrerXOriginTrimmingPolicy();

  switch (mReferrerPolicy) {
    case REFERRER_POLICY_SAME_ORIGIN:
      // Don't send referrer when the request is cross-origin and policy is
      // "same-origin".
      if (IsCrossOriginWithReferrer()) {
        return NS_OK;
      }
      break;

    case REFERRER_POLICY_ORIGIN:
    case REFERRER_POLICY_STRICT_ORIGIN:
      userReferrerTrimmingPolicy = 2;
      break;

    case REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
    case REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN:
      if (userReferrerTrimmingPolicy != 2 && IsCrossOriginWithReferrer()) {
        // Ignore set userReferrerTrimmingPolicy if it is already the strictest
        // policy.
        userReferrerTrimmingPolicy = 2;
      }
      break;

    case REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
    case REFERRER_POLICY_UNSAFE_URL:
      if (userReferrerTrimmingPolicy != 2) {
        // Ignore set userReferrerTrimmingPolicy if it is already the strictest
        // policy. Apply the user cross-origin trimming policy if it's more
        // restrictive than the general one.
        if (userReferrerXOriginTrimmingPolicy != 0 &&
            IsCrossOriginWithReferrer()) {
          userReferrerTrimmingPolicy = std::max(
              userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy);
        }
      }

      break;

    case REFERRER_POLICY_NO_REFERRER:
    case REFERRER_POLICY_UNSET:
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected value");
      break;
  }

  nsAutoCString spec;
  // check how much referer to send
  if (userReferrerTrimmingPolicy) {
    // All output strings start with: scheme+host+port
    // We want the IDN-normalized PrePath.  That's not something currently
    // available and there doesn't yet seem to be justification for adding it to
    // the interfaces, so just build it up ourselves from scheme+AsciiHostPort
    nsAutoCString scheme, asciiHostPort;
    rv = clone->GetScheme(scheme);
    if (NS_FAILED(rv)) return rv;
    spec = scheme;
    spec.AppendLiteral("://");
    // Note we explicitly cleared UserPass above, so do not need to build it.
    rv = clone->GetAsciiHostPort(asciiHostPort);
    if (NS_FAILED(rv)) return rv;
    spec.Append(asciiHostPort);

    switch (userReferrerTrimmingPolicy) {
      case 1: {  // scheme+host+port+path
        nsCOMPtr<nsIURL> url(do_QueryInterface(clone));
        if (url) {
          nsAutoCString path;
          rv = url->GetFilePath(path);
          if (NS_FAILED(rv)) return rv;
          spec.Append(path);
          rv = NS_MutateURI(url)
                   .SetQuery(EmptyCString())
                   .SetRef(EmptyCString())
                   .Finalize(clone);
          if (NS_FAILED(rv)) return rv;
          break;
        }
        // No URL, so fall through to truncating the path and any query/ref off
        // as well.
      }
        MOZ_FALLTHROUGH;
      default
          :    // (Pref limited to [0,2] enforced by clamp, MOZ_CRASH overkill.)
      case 2:  // scheme+host+port+/
        spec.AppendLiteral("/");
        // This nukes any query/ref present as well in the case of nsStandardURL
        rv =
            NS_MutateURI(clone).SetPathQueryRef(EmptyCString()).Finalize(clone);
        if (NS_FAILED(rv)) return rv;
        break;
    }
  } else {
    // use the full URI
    rv = clone->GetAsciiSpec(spec);
    if (NS_FAILED(rv)) return rv;
  }

  // finally, remember the referrer URI and set the Referer header.
  rv = SetRequestHeader(NS_LITERAL_CSTRING("Referer"), spec, false);
  if (NS_FAILED(rv)) return rv;

  mReferrer = clone;
  return NS_OK;
}

// Return the channel's proxy URI, or if it doesn't exist, the
// channel's main URI.
NS_IMETHODIMP
HttpBaseChannel::GetProxyURI(nsIURI** aOut) {
  NS_ENSURE_ARG_POINTER(aOut);
  nsCOMPtr<nsIURI> result(mProxyURI);
  result.forget(aOut);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
                                  nsACString& aValue) {
  aValue.Truncate();

  // XXX might be better to search the header list directly instead of
  // hitting the http atom hash table.
  nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
  if (!atom) return NS_ERROR_NOT_AVAILABLE;

  return mRequestHead.GetHeader(atom, aValue);
}

NS_IMETHODIMP
HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
                                  const nsACString& aValue, bool aMerge) {
  const nsCString& flatHeader = PromiseFlatCString(aHeader);
  const nsCString& flatValue = PromiseFlatCString(aValue);

  LOG(
      ("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" "
       "merge=%u]\n",
       this, flatHeader.get(), flatValue.get(), aMerge));

  // Verify header names are valid HTTP tokens and header values are reasonably
  // close to whats allowed in RFC 2616.
  if (!nsHttp::IsValidToken(flatHeader) ||
      !nsHttp::IsReasonableHeaderValue(flatValue)) {
    return NS_ERROR_INVALID_ARG;
  }

  return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
}

NS_IMETHODIMP
HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
  const nsCString& flatHeader = PromiseFlatCString(aHeader);

  LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", this,
       flatHeader.get()));

  // Verify header names are valid HTTP tokens and header values are reasonably
  // close to whats allowed in RFC 2616.
  if (!nsHttp::IsValidToken(flatHeader)) {
    return NS_ERROR_INVALID_ARG;
  }

  return mRequestHead.SetEmptyHeader(aHeader);
}

NS_IMETHODIMP
HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) {
  return mRequestHead.VisitHeaders(visitor);
}

NS_IMETHODIMP
HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* visitor) {
  return mRequestHead.VisitHeaders(visitor,
                                   nsHttpHeaderArray::eFilterSkipDefault);
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseHeader(const nsACString& header,
                                   nsACString& value) {
  value.Truncate();

  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

  nsHttpAtom atom = nsHttp::ResolveAtom(header);
  if (!atom) return NS_ERROR_NOT_AVAILABLE;

  return mResponseHead->GetHeader(atom, value);
}

NS_IMETHODIMP
HttpBaseChannel::SetResponseHeader(const nsACString& header,
                                   const nsACString& value, bool merge) {
  LOG(
      ("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" "
       "merge=%u]\n",
       this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(),
       merge));

  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;

  nsHttpAtom atom = nsHttp::ResolveAtom(header);
  if (!atom) return NS_ERROR_NOT_AVAILABLE;

  // these response headers must not be changed
  if (atom == nsHttp::Content_Type || atom == nsHttp::Content_Length ||
      atom == nsHttp::Content_Encoding || atom == nsHttp::Trailer ||
      atom == nsHttp::Transfer_Encoding)
    return NS_ERROR_ILLEGAL_VALUE;

  mResponseHeadersModified = true;

  return mResponseHead->SetHeader(header, value, merge);
}

NS_IMETHODIMP
HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) {
  if (!mResponseHead) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return mResponseHead->VisitHeaders(visitor,
                                     nsHttpHeaderArray::eFilterResponse);
}

NS_IMETHODIMP
HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
                                           nsIHttpHeaderVisitor* aVisitor) {
  if (!mResponseHead) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
  if (!atom) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return mResponseHead->GetOriginalHeader(atom, aVisitor);
}

NS_IMETHODIMP
HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
  if (!mResponseHead) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return mResponseHead->VisitHeaders(
      aVisitor, nsHttpHeaderArray::eFilterResponseOriginal);
}

NS_IMETHODIMP
HttpBaseChannel::GetAllowPipelining(bool* value) {
  NS_ENSURE_ARG_POINTER(value);
  *value = false;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllowPipelining(bool value) {
  ENSURE_CALLED_BEFORE_CONNECT();
  // nop
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAllowSTS(bool* value) {
  NS_ENSURE_ARG_POINTER(value);
  *value = mAllowSTS;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllowSTS(bool value) {
  ENSURE_CALLED_BEFORE_CONNECT();

  mAllowSTS = value;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRedirectionLimit(uint32_t* value) {
  NS_ENSURE_ARG_POINTER(value);
  *value = mRedirectionLimit;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRedirectionLimit(uint32_t value) {
  ENSURE_CALLED_BEFORE_CONNECT();

  mRedirectionLimit = std::min<uint32_t>(value, 0xff);
  return NS_OK;
}

nsresult HttpBaseChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo) {
  MOZ_ASSERT(!mSecurityInfo,
             "This can only be called when we don't have a security info "
             "object already");
  MOZ_RELEASE_ASSERT(
      aSecurityInfo,
      "This can only be called with a valid security info object");
  MOZ_ASSERT(!BypassServiceWorker(),
             "This can only be called on channels that are not bypassing "
             "interception");
  MOZ_ASSERT(mResponseCouldBeSynthesized,
             "This can only be called on channels that can be intercepted");
  if (mSecurityInfo) {
    LOG(
        ("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
         "[this=%p]\n",
         this));
    return NS_ERROR_UNEXPECTED;
  }
  if (!mResponseCouldBeSynthesized) {
    LOG(
        ("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
         "[this=%p]\n",
         this));
    return NS_ERROR_UNEXPECTED;
  }

  mSecurityInfo = aSecurityInfo;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::IsNoStoreResponse(bool* value) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  *value = mResponseHead->NoStore();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::IsNoCacheResponse(bool* value) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  *value = mResponseHead->NoCache();
  if (!*value) *value = mResponseHead->ExpiresInPast();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::IsPrivateResponse(bool* value) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  *value = mResponseHead->Private();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseStatus(uint32_t* aValue) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  *aValue = mResponseHead->Status();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseStatusText(nsACString& aValue) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  mResponseHead->StatusText(aValue);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestSucceeded(bool* aValue) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  uint32_t status = mResponseHead->Status();
  *aValue = (status / 100 == 2);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::RedirectTo(nsIURI* targetURI) {
  NS_ENSURE_ARG(targetURI);

  nsAutoCString spec;
  targetURI->GetAsciiSpec(spec);
  LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));

  // We cannot redirect after OnStartRequest of the listener
  // has been called, since to redirect we have to switch channels
  // and the dance with OnStartRequest et al has to start over.
  // This would break the nsIStreamListener contract.
  NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);

  mAPIRedirectToURI = targetURI;
  // Only Web Extensions are allowed to redirect a channel to a data:
  // URI. To avoid any bypasses after the channel was flagged by
  // the WebRequst API, we are dropping the flag here.
  if (mLoadInfo) {
    mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
  }
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::UpgradeToSecure() {
  // Upgrades are handled internally between http-on-modify-request and
  // http-on-before-connect, which means upgrades are only possible during
  // on-modify, or WebRequest.onBeforeRequest in Web Extensions.  Once we are
  // past the code path where upgrades are handled, attempting an upgrade
  // will throw an error.
  NS_ENSURE_TRUE(mUpgradableToSecure, NS_ERROR_NOT_AVAILABLE);

  mUpgradeToSecure = true;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestContextID(uint64_t* aRCID) {
  NS_ENSURE_ARG_POINTER(aRCID);
  *aRCID = mRequestContextID;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRequestContextID(uint64_t aRCID) {
  mRequestContextID = aRCID;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) {
  NS_ENSURE_ARG_POINTER(aValue);
  *aValue = IsNavigation();
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetIsMainDocumentChannel(bool aValue) {
  mForceMainDocumentChannel = aValue;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
  nsresult rv;
  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo, &rv);
  nsAutoCString protocol;
  if (NS_SUCCEEDED(rv) && ssl &&
      NS_SUCCEEDED(ssl->GetNegotiatedNPN(protocol)) && !protocol.IsEmpty()) {
    // The negotiated protocol was not empty so we can use it.
    aProtocolVersion = protocol;
    return NS_OK;
  }

  if (mResponseHead) {
    uint32_t version = mResponseHead->Version();
    aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
    return NS_OK;
  }

  return NS_ERROR_NOT_AVAILABLE;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIHttpChannelInternal
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
  if (!aTopWindowURI) {
    return NS_ERROR_INVALID_ARG;
  }

  if (mTopWindowURI) {
    LOG(
        ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
         "mTopWindowURI is already set.\n",
         this));
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIURI> topWindowURI;
  Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));

  // Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
  if (topWindowURI) {
    LOG(
        ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
         "Return an error since we got a top window uri.\n",
         this));
    return NS_ERROR_FAILURE;
  }

  mTopWindowURI = aTopWindowURI;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
  nsresult rv = NS_OK;
  nsCOMPtr<mozIThirdPartyUtil> util;
  // Only compute the top window URI once. In e10s, this must be computed in the
  // child. The parent gets the top window URI through HttpChannelOpenArgs.
  if (!mTopWindowURI) {
    util = do_GetService(THIRDPARTYUTIL_CONTRACTID);
    if (!util) {
      return NS_ERROR_NOT_AVAILABLE;
    }
    nsCOMPtr<mozIDOMWindowProxy> win;
    rv = util->GetTopWindowForChannel(this, getter_AddRefs(win));
    if (NS_SUCCEEDED(rv)) {
      rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
#if DEBUG
      if (mTopWindowURI) {
        nsCString spec;
        if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
          LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
               spec.get(), this));
        }
      }
#endif
    }
  }
  NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
  return rv;
}

NS_IMETHODIMP
HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
  NS_ENSURE_ARG_POINTER(aDocumentURI);
  *aDocumentURI = mDocumentURI;
  NS_IF_ADDREF(*aDocumentURI);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetDocumentURI(nsIURI* aDocumentURI) {
  ENSURE_CALLED_BEFORE_CONNECT();

  mDocumentURI = aDocumentURI;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestVersion(uint32_t* major, uint32_t* minor) {
  nsHttpVersion version = mRequestHead.Version();

  if (major) {
    *major = version / 10;
  }
  if (minor) {
    *minor = version % 10;
  }

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseVersion(uint32_t* major, uint32_t* minor) {
  if (!mResponseHead) {
    *major = *minor = 0;  // we should at least be kind about it
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsHttpVersion version = mResponseHead->Version();

  if (major) {
    *major = version / 10;
  }
  if (minor) {
    *minor = version % 10;
  }

  return NS_OK;
}

void HttpBaseChannel::NotifySetCookie(char const* aCookie) {
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    nsAutoString cookie;
    CopyASCIItoUTF16(aCookie, cookie);
    obs->NotifyObservers(static_cast<nsIChannel*>(this),
                         "http-on-response-set-cookie", cookie.get());
  }
}

NS_IMETHODIMP
HttpBaseChannel::SetCookie(const char* aCookieHeader) {
  if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;

  // empty header isn't an error
  if (!(aCookieHeader && *aCookieHeader)) return NS_OK;

  nsICookieService* cs = gHttpHandler->GetCookieService();
  NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);

  nsAutoCString date;
  // empty date is not an error
  Unused << mResponseHead->GetHeader(nsHttp::Date, date);
  nsresult rv = cs->SetCookieStringFromHttp(mURI, nullptr, nullptr,
                                            aCookieHeader, date.get(), this);
  if (NS_SUCCEEDED(rv)) {
    NotifySetCookie(aCookieHeader);
  }
  return rv;
}

NS_IMETHODIMP
HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
  *aFlags = mThirdPartyFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
  ENSURE_CALLED_BEFORE_ASYNC_OPEN();

  mThirdPartyFlags = aFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
  *aForce =
      !!(mThirdPartyFlags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
  ENSURE_CALLED_BEFORE_ASYNC_OPEN();

  if (aForce)
    mThirdPartyFlags |= nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
  else
    mThirdPartyFlags &= ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetCanceled(bool* aCanceled) {
  *aCanceled = mCanceled;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetChannelIsForDownload(bool* aChannelIsForDownload) {
  *aChannelIsForDownload = mChannelIsForDownload;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
  mChannelIsForDownload = aChannelIsForDownload;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
  mRedirectedCachekeys = cacheKeys;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLocalAddress(nsACString& addr) {
  if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;

  addr.SetCapacity(kIPv6CStrBufSize);
  NetAddrToString(&mSelfAddr, addr.BeginWriting(), kIPv6CStrBufSize);
  addr.SetLength(strlen(addr.BeginReading()));

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::TakeAllSecurityMessages(
    nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
  MOZ_ASSERT(NS_IsMainThread());

  aMessages.Clear();
  for (auto pair : mSecurityConsoleMessages) {
    nsresult rv;
    nsCOMPtr<nsISecurityConsoleMessage> message =
        do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    message->SetTag(pair.first());
    message->SetCategory(pair.second());
    aMessages.AppendElement(message);
  }

  MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
  mSecurityConsoleMessages.Clear();

  return NS_OK;
}

/* Please use this method with care. This can cause the message
 * queue to grow large and cause the channel to take up a lot
 * of memory. Use only static string messages and do not add
 * server side data to the queue, as that can be large.
 * Add only a limited number of messages to the queue to keep
 * the channel size down and do so only in rare erroneous situations.
 * More information can be found here:
 * https://bugzilla.mozilla.org/show_bug.cgi?id=846918
 */
nsresult HttpBaseChannel::AddSecurityMessage(
    const nsAString& aMessageTag, const nsAString& aMessageCategory) {
  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv;

  // nsSecurityConsoleMessage is not thread-safe refcounted.
  // Delay the object construction until requested.
  // See TakeAllSecurityMessages()
  Pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
  mSecurityConsoleMessages.AppendElement(Move(pair));

  nsCOMPtr<nsIConsoleService> console(
      do_GetService(NS_CONSOLESERVICE_CONTRACTID));
  if (!console) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsILoadInfo> loadInfo;
  GetLoadInfo(getter_AddRefs(loadInfo));
  if (!loadInfo) {
    return NS_ERROR_FAILURE;
  }

  auto innerWindowID = loadInfo->GetInnerWindowID();

  nsAutoString errorText;
  rv = nsContentUtils::GetLocalizedString(
      nsContentUtils::eSECURITY_PROPERTIES,
      NS_ConvertUTF16toUTF8(aMessageTag).get(), errorText);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
  error->InitWithSourceURI(
      errorText, mURI, EmptyString(), 0, 0, nsIScriptError::warningFlag,
      NS_ConvertUTF16toUTF8(aMessageCategory), innerWindowID);

  console->LogMessage(error);

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLocalPort(int32_t* port) {
  NS_ENSURE_ARG_POINTER(port);

  if (mSelfAddr.raw.family == PR_AF_INET) {
    *port = (int32_t)ntohs(mSelfAddr.inet.port);
  } else if (mSelfAddr.raw.family == PR_AF_INET6) {
    *port = (int32_t)ntohs(mSelfAddr.inet6.port);
  } else
    return NS_ERROR_NOT_AVAILABLE;

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRemoteAddress(nsACString& addr) {
  if (mPeerAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;

  addr.SetCapacity(kIPv6CStrBufSize);
  NetAddrToString(&mPeerAddr, addr.BeginWriting(), kIPv6CStrBufSize);
  addr.SetLength(strlen(addr.BeginReading()));

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRemotePort(int32_t* port) {
  NS_ENSURE_ARG_POINTER(port);

  if (mPeerAddr.raw.family == PR_AF_INET) {
    *port = (int32_t)ntohs(mPeerAddr.inet.port);
  } else if (mPeerAddr.raw.family == PR_AF_INET6) {
    *port = (int32_t)ntohs(mPeerAddr.inet6.port);
  } else
    return NS_ERROR_NOT_AVAILABLE;

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::HTTPUpgrade(const nsACString& aProtocolName,
                             nsIHttpUpgradeListener* aListener) {
  NS_ENSURE_ARG(!aProtocolName.IsEmpty());
  NS_ENSURE_ARG_POINTER(aListener);

  mUpgradeProtocol = aProtocolName;
  mUpgradeProtocolCallback = aListener;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAllowSpdy(bool* aAllowSpdy) {
  NS_ENSURE_ARG_POINTER(aAllowSpdy);

  *aAllowSpdy = mAllowSpdy;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
  mAllowSpdy = aAllowSpdy;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
  NS_ENSURE_ARG_POINTER(aAllowAltSvc);

  *aAllowAltSvc = mAllowAltSvc;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
  mAllowAltSvc = aAllowAltSvc;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
  NS_ENSURE_ARG_POINTER(aBeConservative);

  *aBeConservative = mBeConservative;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetBeConservative(bool aBeConservative) {
  mBeConservative = aBeConservative;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTrr(bool* aTRR) {
  NS_ENSURE_ARG_POINTER(aTRR);

  *aTRR = mTRR;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetTrr(bool aTRR) {
  mTRR = aTRR;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTlsFlags(uint32_t* aTlsFlags) {
  NS_ENSURE_ARG_POINTER(aTlsFlags);

  *aTlsFlags = mTlsFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
  mTlsFlags = aTlsFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  NS_IF_ADDREF(*aResult = mAPIRedirectToURI);
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
  if (NS_WARN_IF(!aEnable)) {
    return NS_ERROR_NULL_POINTER;
  }
  *aEnable = mResponseTimeoutEnabled;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
  mResponseTimeoutEnabled = aEnable;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetInitialRwin(uint32_t* aRwin) {
  if (NS_WARN_IF(!aRwin)) {
    return NS_ERROR_NULL_POINTER;
  }
  *aRwin = mInitialRwin;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetInitialRwin(uint32_t aRwin) {
  ENSURE_CALLED_BEFORE_CONNECT();
  mInitialRwin = aRwin;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::ForcePending(bool aForcePending) {
  mForcePending = aForcePending;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime) {
  if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
  uint32_t lastMod;
  nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
  NS_ENSURE_SUCCESS(rv, rv);
  *lastModifiedTime = lastMod;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude) {
  *aInclude = mCorsIncludeCredentials;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
  mCorsIncludeCredentials = aInclude;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetCorsMode(uint32_t* aMode) {
  *aMode = mCorsMode;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetCorsMode(uint32_t aMode) {
  mCorsMode = aMode;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRedirectMode(uint32_t* aMode) {
  *aMode = mRedirectMode;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRedirectMode(uint32_t aMode) {
  mRedirectMode = aMode;
  return NS_OK;
}

namespace {

bool ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask) {
  return (aLoadFlags & aMask) == aMask;
}

}  // anonymous namespace

NS_IMETHODIMP
HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
  NS_ENSURE_ARG_POINTER(aFetchCacheMode);

  // Otherwise try to guess an appropriate cache mode from the load flags.
  if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
  } else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
  } else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS)) {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
  } else if (ContainsAllFlags(
                 mLoadFlags,
                 VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
  } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
  } else {
    *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
  }

  return NS_OK;
}

namespace {

void SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags) {
  // First, clear any possible cache related flags.
  uint32_t allPossibleFlags =
      nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
      nsIRequest::VALIDATE_ALWAYS | nsIRequest::LOAD_FROM_CACHE |
      nsICachingChannel::LOAD_ONLY_FROM_CACHE;
  aLoadFlags &= ~allPossibleFlags;

  // Then set the new flags.
  aLoadFlags |= aFlags;
}

}  // anonymous namespace

NS_IMETHODIMP
HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
  ENSURE_CALLED_BEFORE_CONNECT();

  // Now, set the load flags that implement each cache mode.
  switch (aFetchCacheMode) {
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
      // The "default" mode means to use the http cache normally and
      // respect any http cache-control headers.  We effectively want
      // to clear our cache related load flags.
      SetCacheFlags(mLoadFlags, 0);
      break;
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
      // no-store means don't consult the cache on the way to the network, and
      // don't store the response in the cache even if it's cacheable.
      SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
      break;
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
      // reload means don't consult the cache on the way to the network, but
      // do store the response in the cache if possible.
      SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
      break;
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
      // no-cache means always validate what's in the cache.
      SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
      break;
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
      // force-cache means don't validate unless if the response would vary.
      SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
      break;
    case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
      // only-if-cached means only from cache, no network, no validation,
      // generate a network error if the document was't in the cache. The
      // privacy implications of these flags (making it fast/easy to check if
      // the user has things in their cache without any network traffic side
      // effects) are addressed in the Request constructor which
      // enforces/requires same-origin request mode.
      SetCacheFlags(mLoadFlags,
                    VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
      break;
  }

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  uint32_t finalMode = 0;
  MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
  MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
#endif  // MOZ_DIAGNOSTIC_ASSERT_ENABLED

  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
  mIntegrityMetadata = aIntegrityMetadata;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
  aIntegrityMetadata = mIntegrityMetadata;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupportsPriority
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetPriority(int32_t* value) {
  *value = mPriority;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::AdjustPriority(int32_t delta) {
  return SetPriority(mPriority + delta);
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIResumableChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::GetEntityID(nsACString& aEntityID) {
  // Don't return an entity ID for Non-GET requests which require
  // additional data
  if (!mRequestHead.IsGet()) {
    return NS_ERROR_NOT_RESUMABLE;
  }

  uint64_t size = UINT64_MAX;
  nsAutoCString etag, lastmod;
  if (mResponseHead) {
    // Don't return an entity if the server sent the following header:
    // Accept-Ranges: none
    // Not sending the Accept-Ranges header means we can still try
    // sending range requests.
    nsAutoCString acceptRanges;
    Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
    if (!acceptRanges.IsEmpty() &&
        !nsHttp::FindToken(acceptRanges.get(), "bytes",
                           HTTP_HEADER_VALUE_SEPS)) {
      return NS_ERROR_NOT_RESUMABLE;
    }

    size = mResponseHead->TotalEntitySize();
    Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
    Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
  }
  nsCString entityID;
  NS_EscapeURL(etag.BeginReading(), etag.Length(),
               esc_AlwaysCopy | esc_FileBaseName | esc_Forced, entityID);
  entityID.Append('/');
  entityID.AppendInt(int64_t(size));
  entityID.Append('/');
  entityID.Append(lastmod);
  // NOTE: Appending lastmod as the last part avoids having to escape it

  aEntityID = entityID;

  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIConsoleReportCollector
//-----------------------------------------------------------------------------

void HttpBaseChannel::AddConsoleReport(
    uint32_t aErrorFlags, const nsACString& aCategory,
    nsContentUtils::PropertiesFile aPropertiesFile,
    const nsACString& aSourceFileURI, uint32_t aLineNumber,
    uint32_t aColumnNumber, const nsACString& aMessageName,
    const nsTArray<nsString>& aStringParams) {
  mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
                                     aSourceFileURI, aLineNumber, aColumnNumber,
                                     aMessageName, aStringParams);
}

void HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
                                            ReportAction aAction) {
  mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
}

void HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(
    const nsACString& aScope, ReportAction aAction) {
  mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
}

void HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument,
                                          ReportAction aAction) {
  mReportCollector->FlushConsoleReports(aDocument, aAction);
}

void HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
                                          ReportAction aAction) {
  mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
}

void HttpBaseChannel::FlushConsoleReports(
    nsIConsoleReportCollector* aCollector) {
  mReportCollector->FlushConsoleReports(aCollector);
}

void HttpBaseChannel::ClearConsoleReports() {
  mReportCollector->ClearConsoleReports();
}

nsIPrincipal* HttpBaseChannel::GetURIPrincipal() {
  if (mPrincipal) {
    return mPrincipal;
  }

  nsIScriptSecurityManager* securityManager =
      nsContentUtils::GetSecurityManager();

  if (!securityManager) {
    LOG(("HttpBaseChannel::GetURIPrincipal: No security manager [this=%p]",
         this));
    return nullptr;
  }

  securityManager->GetChannelURIPrincipal(this, getter_AddRefs(mPrincipal));
  if (!mPrincipal) {
    LOG(("HttpBaseChannel::GetURIPrincipal: No channel principal [this=%p]",
         this));
    return nullptr;
  }

  return mPrincipal;
}

bool HttpBaseChannel::IsNavigation() {
  return mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI);
}

bool HttpBaseChannel::BypassServiceWorker() const {
  return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
}

bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
  nsCOMPtr<nsINetworkInterceptController> controller;
  GetCallback(controller);
  bool shouldIntercept = false;

  // We should never intercept internal redirects.  The ServiceWorker code
  // can trigger interntal redirects as the result of a FetchEvent.  If
  // we re-intercept then an infinite loop can occur.
  //
  // Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
  // flag because an internal redirect occurs.  Its possible that another
  // interception should occur after the internal redirect.  For example,
  // if the ServiceWorker chooses not to call respondWith() the channel
  // will be reset with an internal redirect.  If the request is a navigation
  // and the network then triggers a redirect its possible the new URL
  // should be intercepted again.
  //
  // Note, HSTS upgrade redirects are often treated the same as internal
  // redirects.  In this case, however, we intentionally allow interception
  // of HSTS upgrade redirects.  This matches the expected spec behavior and
  // does not run the risk of infinite loops as described above.
  bool internalRedirect =
      mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;

  if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
    nsresult rv = controller->ShouldPrepareForIntercept(
        aURI ? aURI : mURI.get(), this, &shouldIntercept);
    if (NS_FAILED(rv)) {
      return false;
    }
  }
  return shouldIntercept;
}

void HttpBaseChannel::AddAsNonTailRequest() {
  MOZ_ASSERT(NS_IsMainThread());

  if (EnsureRequestContext()) {
    LOG((
        "HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
        this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));

    if (!mAddedAsNonTailRequest) {
      mRequestContext->AddNonTailRequest();
      mAddedAsNonTailRequest = true;
    }
  }
}

void HttpBaseChannel::RemoveAsNonTailRequest() {
  MOZ_ASSERT(NS_IsMainThread());

  if (mRequestContext) {
    LOG(
        ("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
         "added=%d",
         this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));

    if (mAddedAsNonTailRequest) {
      mRequestContext->RemoveNonTailRequest();
      mAddedAsNonTailRequest = false;
    }
  }
}

#ifdef DEBUG
void HttpBaseChannel::AssertPrivateBrowsingId() {
  nsCOMPtr<nsILoadContext> loadContext;
  NS_QueryNotificationCallbacks(this, loadContext);
  // For addons it's possible that mLoadInfo is null.
  if (!mLoadInfo) {
    return;
  }

  if (!loadContext) {
    return;
  }

  // We skip testing of favicon loading here since it could be triggered by XUL
  // image which uses SystemPrincipal. The SystemPrincpal doesn't have
  // mPrivateBrowsingId.
  if (nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal()) &&
      mLoadInfo->InternalContentPolicyType() ==
          nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
    return;
  }

  OriginAttributes docShellAttrs;
  loadContext->GetOriginAttributes(docShellAttrs);
  MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId ==
                 docShellAttrs.mPrivateBrowsingId,
             "PrivateBrowsingId values are not the same between LoadInfo and "
             "LoadContext.");
}
#endif

already_AddRefed<nsILoadInfo> HttpBaseChannel::CloneLoadInfoForRedirect(
    nsIURI* newURI, uint32_t redirectFlags) {
  // make a copy of the loadinfo, append to the redirectchain
  // this will be set on the newly created channel for the redirect target.
  if (!mLoadInfo) {
    return nullptr;
  }

  nsCOMPtr<nsILoadInfo> newLoadInfo =
      static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone();

  nsContentPolicyType contentPolicyType =
      mLoadInfo->GetExternalContentPolicyType();
  if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
      contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
    nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = NullPrincipal::Create();
    newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
  }

  // re-compute the origin attributes of the loadInfo if it's top-level load.
  bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
                       nsIContentPolicy::TYPE_DOCUMENT;

  if (isTopLevelDoc) {
    nsCOMPtr<nsILoadContext> loadContext;
    NS_QueryNotificationCallbacks(this, loadContext);
    OriginAttributes docShellAttrs;
    if (loadContext) {
      loadContext->GetOriginAttributes(docShellAttrs);
    }

    OriginAttributes attrs = newLoadInfo->GetOriginAttributes();

    MOZ_ASSERT(
        docShellAttrs.mUserContextId == attrs.mUserContextId,
        "docshell and necko should have the same userContextId attribute.");
    MOZ_ASSERT(
        docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
        "docshell and necko should have the same inIsolatedMozBrowser "
        "attribute.");
    MOZ_ASSERT(
        docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
        "docshell and necko should have the same privateBrowsingId attribute.");

    attrs = docShellAttrs;
    attrs.SetFirstPartyDomain(true, newURI);
    newLoadInfo->SetOriginAttributes(attrs);
  }

  // Leave empty, we want a 'clean ground' when creating the new channel.
  // This will be ensured to be either set by the protocol handler or set
  // to the redirect target URI properly after the channel creation.
  newLoadInfo->SetResultPrincipalURI(nullptr);

  bool isInternalRedirect =
      (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                        nsIChannelEventSink::REDIRECT_STS_UPGRADE));

  nsCString remoteAddress;
  Unused << GetRemoteAddress(remoteAddress);
  nsCOMPtr<nsIRedirectHistoryEntry> entry =
      new nsRedirectHistoryEntry(GetURIPrincipal(), mReferrer, remoteAddress);

  newLoadInfo->AppendRedirectHistoryEntry(entry, isInternalRedirect);

  return newLoadInfo.forget();
}

//-----------------------------------------------------------------------------
// nsHttpChannel::nsITraceableChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
                                nsIStreamListener** _retval) {
  LOG((
      "HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
      this, mListener.get(), aListener));

  if (!mTracingEnabled) return NS_ERROR_FAILURE;

  NS_ENSURE_STATE(mListener);
  NS_ENSURE_ARG_POINTER(aListener);

  nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);

  wrapper.forget(_retval);
  mListener = aListener;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel helpers
//-----------------------------------------------------------------------------

void HttpBaseChannel::ReleaseListeners() {
  MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");

  mListener = nullptr;
  mListenerContext = nullptr;
  mCallbacks = nullptr;
  mProgressSink = nullptr;
  mCompressListener = nullptr;
}

void HttpBaseChannel::DoNotifyListener() {
  LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));

  if (mListener) {
    MOZ_ASSERT(!mOnStartRequestCalled,
               "We should not call OnStartRequest twice");

    nsCOMPtr<nsIStreamListener> listener = mListener;
    listener->OnStartRequest(this, mListenerContext);

    mOnStartRequestCalled = true;
  }

  // Make sure mIsPending is set to false. At this moment we are done from
  // the point of view of our consumer and we have to report our self
  // as not-pending.
  mIsPending = false;

  if (mListener) {
    MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");

    nsCOMPtr<nsIStreamListener> listener = mListener;
    listener->OnStopRequest(this, mListenerContext, mStatus);

    mOnStopRequestCalled = true;
  }

  // notify "http-on-stop-connect" observers
  gHttpHandler->OnStopRequest(this);

  // This channel has finished its job, potentially release any tail-blocked
  // requests with this.
  RemoveAsNonTailRequest();

  // We have to make sure to drop the references to listeners and callbacks
  // no longer needed.
  ReleaseListeners();

  DoNotifyListenerCleanup();

  // If this is a navigation, then we must let the docshell flush the reports
  // to the console later.  The LoadDocument() is pointing at the detached
  // document that started the navigation.  We want to show the reports on the
  // new document.  Otherwise the console is wiped and the user never sees
  // the information.
  if (!IsNavigation()) {
    if (mLoadGroup) {
      FlushConsoleReports(mLoadGroup);
    } else if (mLoadInfo) {
      nsCOMPtr<nsIDOMDocument> dommyDoc;
      mLoadInfo->GetLoadingDocument(getter_AddRefs(dommyDoc));
      nsCOMPtr<nsIDocument> doc = do_QueryInterface(dommyDoc);
      FlushConsoleReports(doc);
    }
  }
}

void HttpBaseChannel::AddCookiesToRequest() {
  if (mLoadFlags & LOAD_ANONYMOUS) {
    return;
  }

  bool useCookieService = (XRE_IsParentProcess());
  nsCString cookie;
  if (useCookieService) {
    nsICookieService* cs = gHttpHandler->GetCookieService();
    if (cs) {
      cs->GetCookieStringFromHttp(mURI, nullptr, this, getter_Copies(cookie));
    }

    if (cookie.IsEmpty()) {
      cookie = mUserSetCookieHeader;
    } else if (!mUserSetCookieHeader.IsEmpty()) {
      cookie.AppendLiteral("; ");
      cookie.Append(mUserSetCookieHeader);
    }
  } else {
    cookie = mUserSetCookieHeader;
  }

  // If we are in the child process, we want the parent seeing any
  // cookie headers that might have been set by SetRequestHeader()
  SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false);
}

/* static */
bool HttpBaseChannel::IsReferrerSchemeAllowed(nsIURI* aReferrer) {
  NS_ENSURE_TRUE(aReferrer, false);

  nsAutoCString scheme;
  nsresult rv = aReferrer->GetScheme(scheme);
  NS_ENSURE_SUCCESS(rv, false);

  if (scheme.EqualsIgnoreCase("https") || scheme.EqualsIgnoreCase("http") ||
      scheme.EqualsIgnoreCase("ftp")) {
    return true;
  }
  return false;
}

/* static */
void HttpBaseChannel::PropagateReferenceIfNeeded(
    nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI) {
  bool hasRef = false;
  nsresult rv = aRedirectURI->GetHasRef(&hasRef);
  if (NS_SUCCEEDED(rv) && !hasRef) {
    nsAutoCString ref;
    aURI->GetRef(ref);
    if (!ref.IsEmpty()) {
      // NOTE: SetRef will fail if mRedirectURI is immutable
      // (e.g. an about: URI)... Oh well.
      Unused << NS_MutateURI(aRedirectURI).SetRef(ref).Finalize(aRedirectURI);
    }
  }
}

bool HttpBaseChannel::ShouldRewriteRedirectToGET(
    uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) {
  // for 301 and 302, only rewrite POST
  if (httpStatus == 301 || httpStatus == 302)
    return method == nsHttpRequestHead::kMethod_Post;

  // rewrite for 303 unless it was HEAD
  if (httpStatus == 303) return method != nsHttpRequestHead::kMethod_Head;

  // otherwise, such as for 307, do not rewrite
  return false;
}

nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
                                                  nsIChannel* newChannel,
                                                  bool preserveMethod,
                                                  uint32_t redirectFlags) {
  nsresult rv;

  LOG(
      ("HttpBaseChannel::SetupReplacementChannel "
       "[this=%p newChannel=%p preserveMethod=%d]",
       this, newChannel, preserveMethod));

  // Ensure the channel's loadInfo's result principal URI so that it's
  // either non-null or updated to the redirect target URI.
  // We must do this because in case the loadInfo's result principal URI
  // is null, it would be taken from OriginalURI of the channel.  But we
  // overwrite it with the whole redirect chain first URI before opening
  // the target channel, hence the information would be lost.
  // If the protocol handler that created the channel wants to use
  // the originalURI of the channel as the principal URI, this fulfills
  // that request - newURI is the original URI of the channel.
  nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->GetLoadInfo();
  if (newLoadInfo) {
    nsCOMPtr<nsIURI> resultPrincipalURI;
    rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
    NS_ENSURE_SUCCESS(rv, rv);

    if (!resultPrincipalURI) {
      rv = newLoadInfo->SetResultPrincipalURI(newURI);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE;
  // if the original channel was using SSL and this channel is not using
  // SSL, then no need to inhibit persistent caching.  however, if the
  // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
  // set, then allow the flag to apply to the redirected channel as well.
  // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
  // we only need to check if the original channel was using SSL.
  bool usingSSL = false;
  rv = mURI->SchemeIs("https", &usingSSL);
  if (NS_SUCCEEDED(rv) && usingSSL) newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;

  // Do not pass along LOAD_CHECK_OFFLINE_CACHE
  newLoadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;

  newChannel->SetLoadGroup(mLoadGroup);
  newChannel->SetNotificationCallbacks(mCallbacks);
  newChannel->SetLoadFlags(newLoadFlags);

  nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
  if (cos) {
    cos->SetClassFlags(mClassOfService);
  }

  // Try to preserve the privacy bit if it has been overridden
  if (mPrivateBrowsingOverriden) {
    nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
        do_QueryInterface(newChannel);
    if (newPBChannel) {
      newPBChannel->SetPrivate(mPrivateBrowsing);
    }
  }

  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
  if (!httpChannel) return NS_OK;  // no other options to set

  // Preserve the CORS preflight information.
  nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
  if (httpInternal) {
    httpInternal->SetLastRedirectFlags(redirectFlags);

    if (mRequireCORSPreflight) {
      httpInternal->SetCorsPreflightParameters(mUnsafeHeaders);
    }
  }

  if (preserveMethod) {
    nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
    nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
    if (mUploadStream && (uploadChannel2 || uploadChannel)) {
      // rewind upload stream
      nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
      MOZ_ASSERT(seekable);

      seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);

      // replicate original call to SetUploadStream...
      if (uploadChannel2) {
        nsAutoCString ctype;
        // If header is not present mRequestHead.HasHeaderValue will truncated
        // it.  But we want to end up with a void string, not an empty string,
        // because ExplicitSetUploadStream treats the former as "no header" and
        // the latter as "header with empty string value".
        nsresult ctypeOK = mRequestHead.GetHeader(nsHttp::Content_Type, ctype);
        if (NS_FAILED(ctypeOK)) {
          ctype.SetIsVoid(true);
        }
        nsAutoCString clen;
        Unused << mRequestHead.GetHeader(nsHttp::Content_Length, clen);
        nsAutoCString method;
        mRequestHead.Method(method);
        int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get());
        uploadChannel2->ExplicitSetUploadStream(
            mUploadStream, ctype, len, method, mUploadStreamHasHeaders);
      } else {
        if (mUploadStreamHasHeaders) {
          uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
        } else {
          nsAutoCString ctype;
          if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) {
            ctype = NS_LITERAL_CSTRING("application/octet-stream");
          }
          nsAutoCString clen;
          if (NS_SUCCEEDED(
                  mRequestHead.GetHeader(nsHttp::Content_Length, clen)) &&
              !clen.IsEmpty()) {
            uploadChannel->SetUploadStream(mUploadStream, ctype,
                                           nsCRT::atoll(clen.get()));
          }
        }
      }
    }
    // since preserveMethod is true, we need to ensure that the appropriate
    // request method gets set on the channel, regardless of whether or not
    // we set the upload stream above. This means SetRequestMethod() will
    // be called twice if ExplicitSetUploadStream() gets called above.

    nsAutoCString method;
    mRequestHead.Method(method);
    rv = httpChannel->SetRequestMethod(method);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
  // convey the referrer if one was used for this channel to the next one
  if (mReferrer) {
    rv = httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
  // convey the mAllowSTS flags
  rv = httpChannel->SetAllowSTS(mAllowSTS);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  // convey the Accept header value
  {
    nsAutoCString oldAcceptValue;
    nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
    if (NS_SUCCEEDED(hasHeader)) {
      rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                         oldAcceptValue, false);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }
  }

  // share the request context - see bug 1236650
  rv = httpChannel->SetRequestContextID(mRequestContextID);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  // When on the parent process, the channel can't attempt to get it itself.
  // When on the child process, it would be waste to query it again.
  rv = httpChannel->SetTopLevelOuterContentWindowId(
      mTopLevelOuterContentWindowId);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  // Preserve the loading order
  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
  if (p) {
    p->SetPriority(mPriority);
  }

  if (httpInternal) {
    // Convey third party cookie, conservative, and spdy flags.
    rv = httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = httpInternal->SetAllowSpdy(mAllowSpdy);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = httpInternal->SetAllowAltSvc(mAllowAltSvc);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = httpInternal->SetBeConservative(mBeConservative);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = httpInternal->SetTrr(mTRR);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = httpInternal->SetTlsFlags(mTlsFlags);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    RefPtr<nsHttpChannel> realChannel;
    CallQueryInterface(newChannel, realChannel.StartAssignment());
    if (realChannel) {
      rv = realChannel->SetTopWindowURI(mTopWindowURI);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }

    // update the DocumentURI indicator since we are being redirected.
    // if this was a top-level document channel, then the new channel
    // should have its mDocumentURI point to newURI; otherwise, we
    // just need to pass along our mDocumentURI to the new channel.
    if (newURI && (mURI == mDocumentURI))
      rv = httpInternal->SetDocumentURI(newURI);
    else
      rv = httpInternal->SetDocumentURI(mDocumentURI);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // if there is a chain of keys for redirect-responses we transfer it to
    // the new channel (see bug #561276)
    if (mRedirectedCachekeys) {
      LOG(
          ("HttpBaseChannel::SetupReplacementChannel "
           "[this=%p] transferring chain of redirect cache-keys",
           this));
      rv = httpInternal->SetCacheKeysRedirectChain(
          mRedirectedCachekeys.forget());
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }

    // Preserve CORS mode flag.
    rv = httpInternal->SetCorsMode(mCorsMode);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // Preserve Redirect mode flag.
    rv = httpInternal->SetRedirectMode(mRedirectMode);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // Preserve Integrity metadata.
    rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    httpInternal->SetAltDataForChild(mAltDataForChild);
  }

  // transfer application cache information
  nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
      do_QueryInterface(newChannel);
  if (appCacheChannel) {
    appCacheChannel->SetApplicationCache(mApplicationCache);
    appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
    // We purposely avoid transfering mChooseApplicationCache.
  }

  // transfer any properties
  nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
  if (bag) {
    for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
      bag->SetProperty(iter.Key(), iter.UserData());
    }
  }

  // Transfer the timing data (if we are dealing with an nsITimedChannel).
  nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
  nsCOMPtr<nsITimedChannel> oldTimedChannel(
      do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
  if (oldTimedChannel && newTimedChannel) {
    newTimedChannel->SetTimingEnabled(mTimingEnabled);

    if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
      newTimedChannel->SetRedirectCount(mRedirectCount);
      int8_t newCount = mInternalRedirectCount + 1;
      newTimedChannel->SetInternalRedirectCount(
          std::max(newCount, mInternalRedirectCount));
    } else {
      int8_t newCount = mRedirectCount + 1;
      newTimedChannel->SetRedirectCount(std::max(newCount, mRedirectCount));
      newTimedChannel->SetInternalRedirectCount(mInternalRedirectCount);
    }

    // If the RedirectStart is null, we will use the AsyncOpen value of the
    // previous channel (this is the first redirect in the redirects chain).
    if (mRedirectStartTimeStamp.IsNull()) {
      // Only do this for real redirects.  Internal redirects should be hidden.
      if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
        TimeStamp asyncOpen;
        oldTimedChannel->GetAsyncOpen(&asyncOpen);
        newTimedChannel->SetRedirectStart(asyncOpen);
      }
    } else {
      newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp);
    }

    // For internal redirects just propagate the last redirect end time
    // forward.  Otherwise the new redirect end time is the last response
    // end time.
    TimeStamp newRedirectEnd;
    if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
      oldTimedChannel->GetRedirectEnd(&newRedirectEnd);
    } else {
      oldTimedChannel->GetResponseEnd(&newRedirectEnd);
    }
    newTimedChannel->SetRedirectEnd(newRedirectEnd);

    nsAutoString initiatorType;
    oldTimedChannel->GetInitiatorType(initiatorType);
    newTimedChannel->SetInitiatorType(initiatorType);

    // Check whether or not this was a cross-domain redirect.
    newTimedChannel->SetAllRedirectsSameOrigin(
        mAllRedirectsSameOrigin && SameOriginWithOriginalUri(newURI));

    // Execute the timing allow check to determine whether
    // to report the redirect timing info
    nsCOMPtr<nsILoadInfo> loadInfo;
    GetLoadInfo(getter_AddRefs(loadInfo));
    // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
    // AllRedirectsPassTimingAllowCheck on them.
    if (loadInfo && loadInfo->GetExternalContentPolicyType() !=
                        nsIContentPolicy::TYPE_DOCUMENT) {
      nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
      newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
          mAllRedirectsPassTimingAllowCheck &&
          oldTimedChannel->TimingAllowCheck(principal));
    }

    // Propagate service worker measurements across redirects.  The
    // PeformanceResourceTiming.workerStart API expects to see the
    // worker start time after a redirect.
    newTimedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
    newTimedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
    newTimedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
    newTimedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
    newTimedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
    newTimedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
  }

  // Pass the preferred alt-data type on to the new channel.
  nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
  if (cacheInfoChan) {
    cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
  }

  if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                       nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
    // Copy non-origin related headers to the new channel.
    nsCOMPtr<nsIHttpHeaderVisitor> visitor =
        new AddHeadersToChannelVisitor(httpChannel);
    rv = mRequestHead.VisitHeaders(visitor);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  // This channel has been redirected. Don't report timing info.
  mTimingEnabled = false;
  return NS_OK;
}

// Redirect Tracking
bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
  nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false);
  return (NS_SUCCEEDED(rv));
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIClassifiedChannel

NS_IMETHODIMP
HttpBaseChannel::GetMatchedList(nsACString& aList) {
  aList = mMatchedList;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetMatchedProvider(nsACString& aProvider) {
  aProvider = mMatchedProvider;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash) {
  aFullHash = mMatchedFullHash;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
                                const nsACString& aProvider,
                                const nsACString& aFullHash) {
  NS_ENSURE_ARG(!aList.IsEmpty());

  mMatchedList = aList;
  mMatchedProvider = aProvider;
  mMatchedFullHash = aFullHash;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsITimedChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::SetTimingEnabled(bool enabled) {
  mTimingEnabled = enabled;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTimingEnabled(bool* _retval) {
  *_retval = mTimingEnabled;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
  *_retval = mChannelCreationTimestamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
  *_retval = mAsyncOpenTime;
  return NS_OK;
}

/**
 * @return the number of redirects. There is no check for cross-domain
 * redirects. This check must be done by the consumers.
 */
NS_IMETHODIMP
HttpBaseChannel::GetRedirectCount(uint8_t* aRedirectCount) {
  *aRedirectCount = mRedirectCount;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) {
  mRedirectCount = aRedirectCount;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
  *aRedirectCount = mInternalRedirectCount;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
  mInternalRedirectCount = aRedirectCount;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) {
  *_retval = mRedirectStartTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart) {
  mRedirectStartTimeStamp = aRedirectStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval) {
  *_retval = mRedirectEndTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd) {
  mRedirectEndTimeStamp = aRedirectEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
  *aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
  mAllRedirectsSameOrigin = aAllRedirectsSameOrigin;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
  *aPassesCheck = mAllRedirectsPassTimingAllowCheck;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
  mAllRedirectsPassTimingAllowCheck = aPassesCheck;
  return NS_OK;
}

// http://www.w3.org/TR/resource-timing/#timing-allow-check
NS_IMETHODIMP
HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
  nsCOMPtr<nsIPrincipal> resourcePrincipal;
  nsresult rv =
      ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
  if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
    *_retval = false;
    return NS_OK;
  }

  bool sameOrigin = false;
  rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
  if (NS_SUCCEEDED(rv) && sameOrigin) {
    *_retval = true;
    return NS_OK;
  }

  nsAutoCString headerValue;
  rv =
      GetResponseHeader(NS_LITERAL_CSTRING("Timing-Allow-Origin"), headerValue);
  if (NS_FAILED(rv)) {
    *_retval = false;
    return NS_OK;
  }

  nsAutoCString origin;
  nsContentUtils::GetASCIIOrigin(aOrigin, origin);

  Tokenizer p(headerValue);
  Tokenizer::Token t;

  p.Record();
  nsAutoCString headerItem;
  while (p.Next(t)) {
    if (t.Type() == Tokenizer::TOKEN_EOF ||
        t.Equals(Tokenizer::Token::Char(','))) {
      p.Claim(headerItem);
      headerItem.StripWhitespace();
      // If the list item contains a case-sensitive match for the value of the
      // origin, or a wildcard, return pass
      if (headerItem == origin || headerItem == "*") {
        *_retval = true;
        return NS_OK;
      }
      // We start recording again for the following items in the list
      p.Record();
    }
  }

  *_retval = false;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mLaunchServiceWorkerStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
  mLaunchServiceWorkerStart = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mLaunchServiceWorkerEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
  mLaunchServiceWorkerEnd = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mDispatchFetchEventStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
  mDispatchFetchEventStart = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mDispatchFetchEventEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
  mDispatchFetchEventEnd = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mHandleFetchEventStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
  mHandleFetchEventStart = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
  MOZ_ASSERT(_retval);
  *_retval = mHandleFetchEventEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
  mHandleFetchEventEnd = aTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
  *_retval = mTransactionTimings.domainLookupStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
  *_retval = mTransactionTimings.domainLookupEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
  *_retval = mTransactionTimings.connectStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
  *_retval = mTransactionTimings.tcpConnectEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
  *_retval = mTransactionTimings.secureConnectionStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
  *_retval = mTransactionTimings.connectEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
  *_retval = mTransactionTimings.requestStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
  *_retval = mTransactionTimings.responseStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
  *_retval = mTransactionTimings.responseEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
  *_retval = mCacheReadStart;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
  *_retval = mCacheReadEnd;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetInitiatorType(nsAString& aInitiatorType) {
  aInitiatorType = mInitiatorType;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetInitiatorType(const nsAString& aInitiatorType) {
  mInitiatorType = aInitiatorType;
  return NS_OK;
}

#define IMPL_TIMING_ATTR(name)                                           \
  NS_IMETHODIMP                                                          \
  HttpBaseChannel::Get##name##Time(PRTime* _retval) {                    \
    TimeStamp stamp;                                                     \
    Get##name(&stamp);                                                   \
    if (stamp.IsNull()) {                                                \
      *_retval = 0;                                                      \
      return NS_OK;                                                      \
    }                                                                    \
    *_retval =                                                           \
        mChannelCreationTime +                                           \
        (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
    return NS_OK;                                                        \
  }

IMPL_TIMING_ATTR(ChannelCreation)
IMPL_TIMING_ATTR(AsyncOpen)
IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
IMPL_TIMING_ATTR(DispatchFetchEventStart)
IMPL_TIMING_ATTR(DispatchFetchEventEnd)
IMPL_TIMING_ATTR(HandleFetchEventStart)
IMPL_TIMING_ATTR(HandleFetchEventEnd)
IMPL_TIMING_ATTR(DomainLookupStart)
IMPL_TIMING_ATTR(DomainLookupEnd)
IMPL_TIMING_ATTR(ConnectStart)
IMPL_TIMING_ATTR(TcpConnectEnd)
IMPL_TIMING_ATTR(SecureConnectionStart)
IMPL_TIMING_ATTR(ConnectEnd)
IMPL_TIMING_ATTR(RequestStart)
IMPL_TIMING_ATTR(ResponseStart)
IMPL_TIMING_ATTR(ResponseEnd)
IMPL_TIMING_ATTR(CacheReadStart)
IMPL_TIMING_ATTR(CacheReadEnd)
IMPL_TIMING_ATTR(RedirectStart)
IMPL_TIMING_ATTR(RedirectEnd)

#undef IMPL_TIMING_ATTR

mozilla::dom::PerformanceStorage* HttpBaseChannel::GetPerformanceStorage() {
  // If performance timing is disabled, there is no need for the Performance
  // object anymore.
  if (!mTimingEnabled) {
    return nullptr;
  }

  // There is no point in continuing, since the performance object in the parent
  // isn't the same as the one in the child which will be reporting resource
  // performance.
  if (XRE_IsE10sParentProcess()) {
    return nullptr;
  }

  if (!mLoadInfo) {
    return nullptr;
  }

  // If a custom performance storage is set, let's use it.
  mozilla::dom::PerformanceStorage* performanceStorage =
      mLoadInfo->GetPerformanceStorage();
  if (performanceStorage) {
    return performanceStorage;
  }

  // We don't need to report the resource timing entry for a TYPE_DOCUMENT load.
  if (mLoadInfo->GetExternalContentPolicyType() ==
      nsIContentPolicy::TYPE_DOCUMENT) {
    return nullptr;
  }

  nsCOMPtr<nsIDOMDocument> domDocument;
  mLoadInfo->GetLoadingDocument(getter_AddRefs(domDocument));
  if (!domDocument) {
    return nullptr;
  }

  nsCOMPtr<nsIDocument> loadingDocument = do_QueryInterface(domDocument);
  if (!loadingDocument) {
    return nullptr;
  }

  if (!mLoadInfo->TriggeringPrincipal()->Equals(
          loadingDocument->NodePrincipal())) {
    return nullptr;
  }

  if (mLoadInfo->GetExternalContentPolicyType() ==
          nsIContentPolicy::TYPE_SUBDOCUMENT &&
      !mLoadInfo->GetIsFromProcessingFrameAttributes()) {
    // We only report loads caused by processing the attributes of the
    // browsing context container.
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowInner> innerWindow = loadingDocument->GetInnerWindow();
  if (!innerWindow) {
    return nullptr;
  }

  mozilla::dom::Performance* performance = innerWindow->GetPerformance();
  if (!performance) {
    return nullptr;
  }

  return performance->AsPerformanceStorage();
}

NS_IMETHODIMP
HttpBaseChannel::SetReportResourceTiming(bool enabled) {
  mReportTiming = enabled;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
  *_retval = mReportTiming;
  return NS_OK;
}

nsIURI* HttpBaseChannel::GetReferringPage() {
  nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
  if (!pDomWindow) {
    return nullptr;
  }
  return pDomWindow->GetDocumentURI();
}

nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() {
  nsCOMPtr<nsILoadContext> loadContext;
  NS_QueryNotificationCallbacks(this, loadContext);
  if (!loadContext) {
    return nullptr;
  }
  nsCOMPtr<mozIDOMWindowProxy> domWindow;
  loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
  if (!domWindow) {
    return nullptr;
  }
  auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
  if (!pDomWindow) {
    return nullptr;
  }
  nsCOMPtr<nsPIDOMWindowInner> innerWindow =
      pDomWindow->GetCurrentInnerWindow();
  if (!innerWindow) {
    return nullptr;
  }

  return innerWindow;
}

//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIThrottledInputChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_FAILURE;
  }

  mThrottleQueue = aQueue;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
  *aQueue = mThrottleQueue;
  return NS_OK;
}

//------------------------------------------------------------------------------

bool HttpBaseChannel::EnsureRequestContextID() {
  if (mRequestContextID) {
    // Already have a request context ID, no need to do the rest of this work
    LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
         mRequestContextID));
    return true;
  }

  // Find the loadgroup at the end of the chain in order
  // to make sure all channels derived from the load group
  // use the same connection scope.
  nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
  if (!childLoadGroup) {
    return false;
  }

  nsCOMPtr<nsILoadGroup> rootLoadGroup;
  childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
  if (!rootLoadGroup) {
    return false;
  }

  // Set the load group connection scope on this channel and its transaction
  rootLoadGroup->GetRequestContextID(&mRequestContextID);

  LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
       mRequestContextID));

  return true;
}

bool HttpBaseChannel::EnsureRequestContext() {
  if (mRequestContext) {
    // Already have a request context, no need to do the rest of this work
    return true;
  }

  if (!EnsureRequestContextID()) {
    return false;
  }

  nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
  if (!rcsvc) {
    return false;
  }

  rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
  if (!mRequestContext) {
    return false;
  }

  return true;
}

void HttpBaseChannel::EnsureTopLevelOuterContentWindowId() {
  if (mTopLevelOuterContentWindowId) {
    return;
  }

  nsCOMPtr<nsILoadContext> loadContext;
  GetCallback(loadContext);
  if (!loadContext) {
    return;
  }

  nsCOMPtr<mozIDOMWindowProxy> topWindow;
  loadContext->GetTopWindow(getter_AddRefs(topWindow));
  if (!topWindow) {
    return;
  }

  mTopLevelOuterContentWindowId =
      nsPIDOMWindowOuter::From(topWindow)->WindowID();
}

void HttpBaseChannel::SetCorsPreflightParameters(
    const nsTArray<nsCString>& aUnsafeHeaders) {
  MOZ_RELEASE_ASSERT(!mRequestObserversCalled);

  mRequireCORSPreflight = true;
  mUnsafeHeaders = aUnsafeHeaders;
}

void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
  mAltDataForChild = aIsForChild;
}

NS_IMETHODIMP
HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
  if (!aValue) {
    return NS_ERROR_FAILURE;
  }

  *aValue = mBlockAuthPrompt;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
  ENSURE_CALLED_BEFORE_CONNECT();

  mBlockAuthPrompt = aValue;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) {
  if (!mConnectionInfo) {
    return NS_ERROR_FAILURE;
  }
  aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::GetLastRedirectFlags(uint32_t* aValue) {
  NS_ENSURE_ARG(aValue);
  *aValue = mLastRedirectFlags;
  return NS_OK;
}

NS_IMETHODIMP
HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue) {
  mLastRedirectFlags = aValue;
  return NS_OK;
}

nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
  if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
    // Some platform features, like Service Workers, depend on internal
    // redirects.  We should allow some number of internal redirects above
    // and beyond the normal redirect limit so these features continue
    // to work.
    static const int8_t kMinInternalRedirects = 5;

    if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
      LOG(("internal redirection limit reached!\n"));
      return NS_ERROR_REDIRECT_LOOP;
    }
    return NS_OK;
  }

  MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
                               nsIChannelEventSink::REDIRECT_PERMANENT |
                               nsIChannelEventSink::REDIRECT_STS_UPGRADE));

  if (mRedirectCount >= mRedirectionLimit) {
    LOG(("redirection limit reached!\n"));
    return NS_ERROR_REDIRECT_LOOP;
  }

  return NS_OK;
}

// NOTE: This function duplicates code from nsBaseChannel. This will go away
// once HTTP uses nsBaseChannel (part of bug 312760)
/* static */ void HttpBaseChannel::CallTypeSniffers(void* aClosure,
                                                    const uint8_t* aData,
                                                    uint32_t aCount) {
  nsIChannel* chan = static_cast<nsIChannel*>(aClosure);

  nsAutoCString newType;
  NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
  if (!newType.IsEmpty()) {
    chan->SetContentType(newType);
  }
}

template <class T>
static void ParseServerTimingHeader(const nsAutoPtr<T>& aHeader,
                                    nsIMutableArray* aOutput) {
  if (!aHeader) {
    return;
  }

  nsAutoCString serverTimingHeader;
  Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
  if (serverTimingHeader.IsEmpty()) {
    return;
  }

  ServerTimingParser parser(serverTimingHeader);
  parser.Parse();

  nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
  for (const auto& data : array) {
    aOutput->AppendElement(data);
  }
}

NS_IMETHODIMP
HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) {
  NS_ENSURE_ARG_POINTER(aServerTiming);

  bool isHTTPS = false;
  if (NS_SUCCEEDED(mURI->SchemeIs("https", &isHTTPS)) && isHTTPS) {
    nsTArray<nsCOMPtr<nsIServerTiming>> data;
    nsresult rv = NS_OK;
    nsCOMPtr<nsIMutableArray> array =
        do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    ParseServerTimingHeader(mResponseHead, array);
    ParseServerTimingHeader(mResponseTrailers, array);

    array.forget(aServerTiming);
  }

  return NS_OK;
}

}  // namespace net
}  // namespace mozilla