netwerk/protocol/http/nsHttpChannel.cpp
author Jeff Walden <jwalden@mit.edu>
Tue, 19 Nov 2019 04:55:39 +0000
changeset 502538 b5c5ba07d3dbd0d07b66fa42a103f4df2c27d3a2
parent 502479 765b9da8b818804ee68c8e18f92d2bdaa1794d8e
permissions -rw-r--r--
Bug 1596544 - intl_ValidateAndCanonicalizeUnicodeExtensionType should ignore the second |option| argument until it's needed to report an error. r=anba Differential Revision: https://phabricator.services.mozilla.com/D53145

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

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

#include <inttypes.h>

#include "DocumentChannelParent.h"
#include "mozilla/MozPromiseInlines.h"  // For MozPromise::FromDomPromise
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/dom/nsCSPContext.h"

#include "nsHttp.h"
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.h"
#include "nsString.h"
#include "nsIApplicationCacheService.h"
#include "nsIApplicationCacheContainer.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "nsICacheEntry.h"
#include "nsICaptivePortalService.h"
#include "nsICookieService.h"
#include "nsICryptoHash.h"
#include "nsINetworkInterceptController.h"
#include "nsINSSErrorsService.h"
#include "nsISecurityReporter.h"
#include "nsIStringBundle.h"
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsILoadGroupChild.h"
#include "nsIProtocolProxyService2.h"
#include "nsIURIClassifier.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIURL.h"
#include "nsIURIMutator.h"
#include "nsIStreamTransportService.h"
#include "prnetdb.h"
#include "nsEscape.h"
#include "nsStreamUtils.h"
#include "nsIOService.h"
#include "nsDNSPrefetch.h"
#include "nsChannelClassifier.h"
#include "nsIRedirectResultListener.h"
#include "mozIThirdPartyUtil.h"
#include "mozilla/TimeStamp.h"
#include "nsError.h"
#include "nsPrintfCString.h"
#include "nsAlgorithm.h"
#include "nsQueryObject.h"
#include "nsThreadUtils.h"
#include "GeckoProfiler.h"
#include "nsIConsoleService.h"
#include "mozilla/AntiTrackingCommon.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StaticPrefs_security.h"
#include "nsISSLSocketControl.h"
#include "sslt.h"
#include "nsContentUtils.h"
#include "nsContentSecurityManager.h"
#include "nsIClassOfService.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsITransportSecurityInfo.h"
#include "nsIWebProgressListener.h"
#include "LoadContextInfo.h"
#include "netCore.h"
#include "nsHttpTransaction.h"
#include "nsICacheEntryDescriptor.h"
#include "nsICancelable.h"
#include "nsIHttpChannelAuthProvider.h"
#include "nsIHttpChannelInternal.h"
#include "nsIPrompt.h"
#include "nsInputStreamPump.h"
#include "nsIURIFixup.h"
#include "nsURLHelper.h"
#include "nsISocketTransport.h"
#include "nsIStreamConverterService.h"
#include "nsISiteSecurityService.h"
#include "nsString.h"
#include "nsCRT.h"
#include "CacheObserver.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/Telemetry.h"
#include "AlternateServices.h"
#include "InterceptedChannel.h"
#include "nsIHttpPushListener.h"
#include "nsIX509Cert.h"
#include "ScopedNSSTypes.h"
#include "nsIDeprecationWarner.h"
#include "mozilla/dom/Document.h"
#include "nsICompressConvStats.h"
#include "nsCORSListenerProxy.h"
#include "nsISocketProvider.h"
#include "mozilla/extensions/StreamFilterParent.h"
#include "mozilla/net/Predictor.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/NullPrincipal.h"
#include "CacheControlParser.h"
#include "nsMixedContentBlocker.h"
#include "CacheStorageService.h"
#include "HttpChannelParent.h"
#include "ParentChannelListener.h"
#include "InterceptedHttpChannel.h"
#include "nsIBufferedStreams.h"
#include "nsIFileStreams.h"
#include "nsIMIMEInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "../../cache2/CacheFileUtils.h"
#include "../../cache2/CacheHashUtils.h"
#include "nsINetworkLinkService.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/net/AsyncUrlChannelClassifier.h"
#include "mozilla/net/CookieSettings.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsIWebNavigation.h"
#include "HttpTrafficAnalyzer.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "js/Conversions.h"

#ifdef MOZ_TASK_TRACER
#  include "GeckoTaskTracer.h"
#endif

#ifdef MOZ_GECKO_PROFILER
#  include "ProfilerMarkerPayload.h"
#endif

namespace mozilla {

using namespace dom;

namespace net {

namespace {

static bool sRCWNEnabled = false;
static uint32_t sRCWNQueueSizeNormal = 50;
static uint32_t sRCWNQueueSizePriority = 10;
static uint32_t sRCWNSmallResourceSizeKB = 256;
static uint32_t sRCWNMinWaitMs = 0;
static uint32_t sRCWNMaxWaitMs = 500;

// True if the local cache should be bypassed when processing a request.
#define BYPASS_LOCAL_CACHE(loadFlags, isPreferCacheLoadOverBypass) \
  (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE |                    \
                nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE) &&     \
   !((loadFlags & nsIRequest::LOAD_FROM_CACHE) &&                  \
     isPreferCacheLoadOverBypass))

#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
  ((result) == NS_ERROR_FILE_NOT_FOUND ||     \
   (result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)

#define WRONG_RACING_RESPONSE_SOURCE(req)                                     \
  (mRaceCacheWithNetwork &&                                                   \
   (((mFirstResponseSource == RESPONSE_FROM_CACHE) && (req != mCachePump)) || \
    ((mFirstResponseSource == RESPONSE_FROM_NETWORK) &&                       \
     (req != mTransactionPump))))

static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);

void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss) {
  Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
}

// Computes and returns a SHA1 hash of the input buffer. The input buffer
// must be a null-terminated string.
nsresult Hash(const char* buf, nsACString& hash) {
  nsresult rv;

  nsCOMPtr<nsICryptoHash> hasher =
      do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = hasher->Init(nsICryptoHash::SHA1);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf), strlen(buf));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = hasher->Finish(true, hash);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

bool IsInSubpathOfAppCacheManifest(nsIApplicationCache* cache,
                                   nsACString const& uriSpec) {
  MOZ_ASSERT(cache);

  nsresult rv;

  nsCOMPtr<nsIURI> uri;
  rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
  if (NS_FAILED(rv)) {
    return false;
  }

  nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
  if (NS_FAILED(rv)) {
    return false;
  }

  nsAutoCString directory;
  rv = url->GetDirectory(directory);
  if (NS_FAILED(rv)) {
    return false;
  }

  nsCOMPtr<nsIURI> manifestURI;
  rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
  if (NS_FAILED(rv)) {
    return false;
  }

  nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
  if (NS_FAILED(rv)) {
    return false;
  }

  nsAutoCString manifestDirectory;
  rv = manifestURL->GetDirectory(manifestDirectory);
  if (NS_FAILED(rv)) {
    return false;
  }

  return StringBeginsWith(directory, manifestDirectory);
}

}  // unnamed namespace

// We only treat 3xx responses as redirects if they have a Location header and
// the status code is in a whitelist.
bool nsHttpChannel::WillRedirect(nsHttpResponseHead* response) {
  return IsRedirectStatus(response->Status()) &&
         response->HasHeader(nsHttp::Location);
}

nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
                                    nsHttpRequestHead* requestHead);

class AutoRedirectVetoNotifier {
 public:
  explicit AutoRedirectVetoNotifier(nsHttpChannel* channel)
      : mChannel(channel) {
    if (mChannel->mHasAutoRedirectVetoNotifier) {
      MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
      mChannel = nullptr;
      return;
    }

    mChannel->mHasAutoRedirectVetoNotifier = true;
  }
  ~AutoRedirectVetoNotifier() { ReportRedirectResult(false); }
  void RedirectSucceeded() { ReportRedirectResult(true); }

 private:
  nsHttpChannel* mChannel;
  void ReportRedirectResult(bool succeeded);
};

void AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) {
  if (!mChannel) return;

  mChannel->mRedirectChannel = nullptr;

  if (succeeded) {
    mChannel->RemoveAsNonTailRequest();
  }

  nsCOMPtr<nsIRedirectResultListener> vetoHook;
  NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
                                getter_AddRefs(vetoHook));

  nsHttpChannel* channel = mChannel;
  mChannel = nullptr;

  if (vetoHook) vetoHook->OnRedirectResult(succeeded);

  // Drop after the notification
  channel->mHasAutoRedirectVetoNotifier = false;
}

//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------

nsHttpChannel::nsHttpChannel()
    : HttpAsyncAborter<nsHttpChannel>(this),
      mCacheDisposition(kCacheUnresolved),
      mLogicalOffset(0),
      mPostID(0),
      mRequestTime(0),
      mOfflineCacheLastModifiedTime(0),
      mSuspendTotalTime(0),
      mRedirectType(0),
      mCacheOpenWithPriority(false),
      mCacheQueueSizeWhenOpen(0),
      mCachedContentIsValid(false),
      mCachedContentIsPartial(false),
      mCacheOnlyMetadata(false),
      mTransactionReplaced(false),
      mAuthRetryPending(false),
      mProxyAuthPending(false),
      mCustomAuthHeader(false),
      mResuming(false),
      mInitedCacheEntry(false),
      mFallbackChannel(false),
      mCustomConditionalRequest(false),
      mFallingBack(false),
      mWaitingForRedirectCallback(false),
      mRequestTimeInitialized(false),
      mCacheEntryIsReadOnly(false),
      mCacheEntryIsWriteOnly(false),
      mCacheEntriesToWaitFor(0),
      mConcurrentCacheAccess(0),
      mIsPartialRequest(0),
      mHasAutoRedirectVetoNotifier(0),
      mPinCacheContent(0),
      mIsCorsPreflightDone(0),
      mStronglyFramed(false),
      mUsedNetwork(0),
      mAuthConnectionRestartable(0),
      mChannelClassifierCancellationPending(0),
      mAsyncResumePending(0),
      mHasBeenIsolatedChecked(0),
      mIsIsolated(0),
      mTopWindowOriginComputed(0),
      mPushedStream(nullptr),
      mLocalBlocklist(false),
      mOnTailUnblock(nullptr),
      mWarningReporter(nullptr),
      mIsReadingFromCache(false),
      mFirstResponseSource(RESPONSE_PENDING),
      mRaceCacheWithNetwork(false),
      mRaceDelay(0),
      mIgnoreCacheEntry(false),
      mRCWNLock("nsHttpChannel.mRCWNLock"),
      mDidReval(false) {
  LOG(("Creating nsHttpChannel [this=%p]\n", this));
  mChannelCreationTime = PR_Now();
  mChannelCreationTimestamp = TimeStamp::Now();
}

nsHttpChannel::~nsHttpChannel() {
  LOG(("Destroying nsHttpChannel [this=%p]\n", this));

  if (mAuthProvider) {
    DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  ReleaseMainThreadOnlyReferences();
}

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

  nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
  arrayToRelease.AppendElement(mApplicationCacheForWrite.forget());
  arrayToRelease.AppendElement(mAuthProvider.forget());
  arrayToRelease.AppendElement(mRedirectURI.forget());
  arrayToRelease.AppendElement(mRedirectChannel.forget());
  arrayToRelease.AppendElement(mPreflightChannel.forget());
  arrayToRelease.AppendElement(mDNSPrefetch.forget());

  NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
}

nsresult nsHttpChannel::Init(nsIURI* uri, uint32_t caps, nsProxyInfo* proxyInfo,
                             uint32_t proxyResolveFlags, nsIURI* proxyURI,
                             uint64_t channelId,
                             nsContentPolicyType aContentPolicyType) {
  nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags,
                                      proxyURI, channelId, aContentPolicyType);
  if (NS_FAILED(rv)) return rv;

  LOG1(("nsHttpChannel::Init [this=%p]\n", this));

  return rv;
}

nsresult nsHttpChannel::AddSecurityMessage(const nsAString& aMessageTag,
                                           const nsAString& aMessageCategory) {
  if (mWarningReporter) {
    return mWarningReporter->ReportSecurityMessage(aMessageTag,
                                                   aMessageCategory);
  }
  return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
}

NS_IMETHODIMP
nsHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
                                     const nsACString& aCategory) {
  if (mWarningReporter) {
    return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory);
  }
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
nsHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
                                   bool aWarning, const nsAString& aURL,
                                   const nsAString& aContentType) {
  if (mWarningReporter) {
    return mWarningReporter->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
                                                 aContentType);
  }
  return NS_ERROR_UNEXPECTED;
}

//-----------------------------------------------------------------------------
// nsHttpChannel <private>
//-----------------------------------------------------------------------------

nsresult nsHttpChannel::PrepareToConnect() {
  LOG(("nsHttpChannel::PrepareToConnect [this=%p]\n", this));

  AddCookiesToRequest();

  // notify "http-on-modify-request" observers
  CallOnModifyRequestObservers();

  if (mCanceled) {
    return mStatus;
  }

  if (mSuspendCount) {
    // We abandon the connection here if there was one.
    LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
    MOZ_ASSERT(!mCallOnResume);
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleOnBeforeConnect();
      return NS_OK;
    };
    return NS_OK;
  }

  return OnBeforeConnect();
}

void nsHttpChannel::HandleContinueCancellingByURLClassifier(
    nsresult aErrorCode) {
  MOZ_ASSERT(
      UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");

  if (mSuspendCount) {
    LOG(
        ("Waiting until resume HandleContinueCancellingByURLClassifier "
         "[this=%p]\n",
         this));
    mCallOnResume = [aErrorCode](nsHttpChannel* self) {
      self->HandleContinueCancellingByURLClassifier(aErrorCode);
      return NS_OK;
    };
    return;
  }

  LOG(("nsHttpChannel::HandleContinueCancellingByURLClassifier [this=%p]\n",
       this));
  ContinueCancellingByURLClassifier(aErrorCode);
}

void nsHttpChannel::HandleOnBeforeConnect() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");
  nsresult rv;

  if (mSuspendCount) {
    LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleOnBeforeConnect();
      return NS_OK;
    };
    return;
  }

  LOG(("nsHttpChannel::HandleOnBeforeConnect [this=%p]\n", this));
  rv = OnBeforeConnect();
  if (NS_FAILED(rv)) {
    CloseCacheEntry(false);
    Unused << AsyncAbort(rv);
  }
}

nsresult nsHttpChannel::OnBeforeConnect() {
  nsresult rv;

  // Check if request was cancelled during suspend AFTER on-modify-request
  if (mCanceled) {
    return mStatus;
  }

  // Check to see if we should redirect this channel elsewhere by
  // nsIHttpChannel.redirectTo API request
  if (mAPIRedirectToURI) {
    return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
  }

  // Note that we are only setting the "Upgrade-Insecure-Requests" request
  // header for *all* navigational requests instead of all requests as
  // defined in the spec, see:
  // https://www.w3.org/TR/upgrade-insecure-requests/#preference
  nsContentPolicyType type = mLoadInfo
                                 ? mLoadInfo->GetExternalContentPolicyType()
                                 : nsIContentPolicy::TYPE_OTHER;

  if (type == nsIContentPolicy::TYPE_DOCUMENT ||
      type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
    rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
                          NS_LITERAL_CSTRING("1"), false);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIPrincipal> resultPrincipal;
  if (!mURI->SchemeIs("https") && mLoadInfo) {
    nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
        this, getter_AddRefs(resultPrincipal));
  }
  OriginAttributes originAttributes;
  if (!NS_GetOriginAttributes(this, originAttributes)) {
    return NS_ERROR_FAILURE;
  }

  // At this point it is no longer possible to call
  // HttpBaseChannel::UpgradeToSecure.
  mUpgradableToSecure = false;
  bool shouldUpgrade = mUpgradeToSecure;
  if (mURI->SchemeIs("http")) {
    if (!shouldUpgrade) {
      // Make sure http channel is released on main thread.
      // See bug 1539148 for details.
      nsMainThreadPtrHandle<nsHttpChannel> self(
          new nsMainThreadPtrHolder<nsHttpChannel>(
              "nsHttpChannel::OnBeforeConnect::self", this));
      auto resultCallback = [self(self)](bool aResult, nsresult aStatus) {
        MOZ_ASSERT(NS_IsMainThread());

        nsresult rv = self->ContinueOnBeforeConnect(aResult, aStatus);
        if (NS_FAILED(rv)) {
          self->CloseCacheEntry(false);
          Unused << self->AsyncAbort(rv);
        }
      };

      bool willCallback = false;
      rv = NS_ShouldSecureUpgrade(mURI, mLoadInfo, resultPrincipal,
                                  mPrivateBrowsing, mAllowSTS, originAttributes,
                                  shouldUpgrade, std::move(resultCallback),
                                  willCallback);
      LOG(
          ("nsHttpChannel::OnBeforeConnect "
           "[this=%p willCallback=%d rv=%" PRIx32 "]\n",
           this, willCallback, static_cast<uint32_t>(rv)));

      if (NS_FAILED(rv) || MOZ_UNLIKELY(willCallback)) {
        return rv;
      }
    }
  }

  return ContinueOnBeforeConnect(shouldUpgrade, NS_OK);
}

nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
                                                nsresult aStatus) {
  LOG(
      ("nsHttpChannel::ContinueOnBeforeConnect "
       "[this=%p aShouldUpgrade=%d rv=%" PRIx32 "]\n",
       this, aShouldUpgrade, static_cast<uint32_t>(aStatus)));

  if (NS_FAILED(aStatus)) {
    return aStatus;
  }

  if (aShouldUpgrade) {
    return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
  }

  // ensure that we are using a valid hostname
  if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin())))
    return NS_ERROR_UNKNOWN_HOST;

  if (mUpgradeProtocolCallback) {
    // Websockets can run over HTTP/2, but other upgrades can't.
    if (mUpgradeProtocol.EqualsLiteral("websocket") &&
        gHttpHandler->IsH2WebsocketsEnabled()) {
      // Need to tell the conn manager that we're ok with http/2 even with
      // the allow keepalive bit not set. That bit needs to stay off,
      // though, in case we end up having to fallback to http/1.1 (where
      // we absolutely do want to disable keepalive).
      mCaps |= NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE;
    } else {
      mCaps |= NS_HTTP_DISALLOW_SPDY;
    }
  }

  if (mIsTRRServiceChannel) {
    mCaps |= NS_HTTP_LARGE_KEEPALIVE | NS_HTTP_DISABLE_TRR;
  }

  if (mLoadFlags & LOAD_DISABLE_TRR) {
    mCaps |= NS_HTTP_DISABLE_TRR;
  }

  // Finalize ConnectionInfo flags before SpeculativeConnect
  mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
  mConnectionInfo->SetPrivate(mPrivateBrowsing);
  mConnectionInfo->SetIsolated(IsIsolated());
  mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
  mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
                                     mBeConservative);
  mConnectionInfo->SetTlsFlags(mTlsFlags);
  mConnectionInfo->SetIsTrrServiceChannel(mIsTRRServiceChannel);
  mConnectionInfo->SetTrrDisabled(mCaps & NS_HTTP_DISABLE_TRR);
  mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
  mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);

  // notify "http-on-before-connect" observers
  gHttpHandler->OnBeforeConnect(this);

  // Check if request was cancelled during http-on-before-connect.
  if (mCanceled) {
    return mStatus;
  }

  if (mSuspendCount) {
    // We abandon the connection here if there was one.
    LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
    MOZ_ASSERT(!mCallOnResume);
    mCallOnResume = [](nsHttpChannel* self) {
      self->OnBeforeConnectContinue();
      return NS_OK;
    };
    return NS_OK;
  }

  return Connect();
}

void nsHttpChannel::OnBeforeConnectContinue() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");
  nsresult rv;

  if (mSuspendCount) {
    LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->OnBeforeConnectContinue();
      return NS_OK;
    };
    return;
  }

  LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
  rv = Connect();
  if (NS_FAILED(rv)) {
    CloseCacheEntry(false);
    Unused << AsyncAbort(rv);
  }
}

nsresult nsHttpChannel::Connect() {
  LOG(("nsHttpChannel::Connect [this=%p]\n", this));

  // Don't allow resuming when cache must be used
  if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
    LOG(("Resuming from cache is not supported yet"));
    return NS_ERROR_DOCUMENT_NOT_CACHED;
  }

  if (ShouldIntercept()) {
    return RedirectToInterceptedChannel();
  }

  bool isTrackingResource = IsThirdPartyTrackingResource();
  LOG(("nsHttpChannel %p tracking resource=%d, cos=%u", this,
       isTrackingResource, mClassOfService));

  if (isTrackingResource) {
    AddClassFlags(nsIClassOfService::Tail);
  }

  if (WaitingForTailUnblock()) {
    MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
    mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
    return NS_OK;
  }

  return ConnectOnTailUnblock();
}

nsresult nsHttpChannel::ConnectOnTailUnblock() {
  nsresult rv;

  LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));

  // Consider opening a TCP connection right away.
  SpeculativeConnect();

  // open a cache entry for this channel...
  rv = OpenCacheEntry(mURI->SchemeIs("https"));

  // do not continue if asyncOpenCacheEntry is in progress
  if (AwaitingCacheCallbacks()) {
    LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
         this));
    MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");

    if (mNetworkTriggered && mWaitingForProxy) {
      // Someone has called TriggerNetwork(), meaning we are racing the
      // network with the cache.
      mWaitingForProxy = false;
      return ContinueConnect();
    }

    return NS_OK;
  }

  if (NS_FAILED(rv)) {
    LOG(("OpenCacheEntry failed [rv=%" PRIx32 "]\n",
         static_cast<uint32_t>(rv)));
    // if this channel is only allowed to pull from the cache, then
    // we must fail if we were unable to open a cache entry.
    if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
      // If we have a fallback URI (and we're not already
      // falling back), process the fallback asynchronously.
      if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
        return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
      }
      return NS_ERROR_DOCUMENT_NOT_CACHED;
    }
    // otherwise, let's just proceed without using the cache.
  }

  if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
                                 (mDidReval || mCachedContentIsPartial)) ||
                                mIgnoreCacheEntry)) {
    // We won't send the conditional request because the unconditional
    // request was already sent (see bug 1377223).
    AccumulateCategorical(
        Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
  }

  // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
  // returns, then we may not have started reading from the cache.
  // If the content is valid, we should attempt to do so, as technically the
  // cache has won the race.
  if (mRaceCacheWithNetwork && mCachedContentIsValid) {
    Unused << ReadFromCache(true);
  }

  return TriggerNetwork();
}

nsresult nsHttpChannel::ContinueConnect() {
  // If we need to start a CORS preflight, do it now!
  // Note that it is important to do this before the early returns below.
  if (!mIsCorsPreflightDone && mRequireCORSPreflight) {
    MOZ_ASSERT(!mPreflightChannel);
    nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
        this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
    return rv;
  }

  MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
                     "CORS preflight must have been finished by the time we "
                     "do the rest of ContinueConnect");

  // we may or may not have a cache entry at this point
  if (mCacheEntry) {
    // read straight from the cache if possible...
    if (mCachedContentIsValid) {
      nsRunnableMethod<nsHttpChannel>* event = nullptr;
      nsresult rv;
      if (!mCachedContentIsPartial) {
        rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
        if (NS_FAILED(rv)) {
          LOG(("  AsyncCall failed (%08x)", static_cast<uint32_t>(rv)));
        }
      }
      rv = ReadFromCache(true);
      if (NS_FAILED(rv) && event) {
        event->Revoke();
      }

      AccumulateCacheHitTelemetry(kCacheHit);
      mCacheDisposition = kCacheHit;

      return rv;
    }
    if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
      // the cache contains the requested resource, but it must be
      // validated before we can reuse it.  since we are not allowed
      // to hit the net, there's nothing more to do.  the document
      // is effectively not in the cache.
      LOG(("  !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
      return NS_ERROR_DOCUMENT_NOT_CACHED;
    }
  } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
    // If we have a fallback URI (and we're not already
    // falling back), process the fallback asynchronously.
    if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
      return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
    }
    LOG(("  !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
    return NS_ERROR_DOCUMENT_NOT_CACHED;
  }

  if (mLoadFlags & LOAD_NO_NETWORK_IO) {
    LOG(("  mLoadFlags & LOAD_NO_NETWORK_IO"));
    return NS_ERROR_DOCUMENT_NOT_CACHED;
  }

  // hit the net...
  return DoConnect();
}

nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
  LOG(("nsHttpChannel::DoConnect [this=%p, aTransWithStickyConn=%p]\n", this,
       aTransWithStickyConn));

  nsresult rv = SetupTransaction();
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (aTransWithStickyConn) {
    rv = gHttpHandler->InitiateTransactionWithStickyConn(
        mTransaction, mPriority, aTransWithStickyConn);
  } else {
    rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
  }

  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = mTransactionPump->AsyncRead(this, nullptr);
  if (NS_FAILED(rv)) {
    return rv;
  }

  uint32_t suspendCount = mSuspendCount;
  if (mAsyncResumePending) {
    LOG(
        ("  Suspend()'ing transaction pump once because of async resume pending"
         ", sc=%u, pump=%p, this=%p",
         suspendCount, mTransactionPump.get(), this));
    ++suspendCount;
  }
  while (suspendCount--) {
    mTransactionPump->Suspend();
  }

  return NS_OK;
}

void nsHttpChannel::SpeculativeConnect() {
  // Before we take the latency hit of dealing with the cache, try and
  // get the TCP (and SSL) handshakes going so they can overlap.

  // don't speculate if we are on uses of the offline application cache,
  // if we are offline, when doing http upgrade (i.e.
  // websockets bootstrap), or if we can't do keep-alive (because then we
  // couldn't reuse the speculative connection anyhow).
  if (mApplicationCache || gIOService->IsOffline() ||
      mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
    return;

  // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
  // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
  // so skip preconnects for them.
  if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
                    LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
    return;

  if (mAllowStaleCacheContent) {
    return;
  }

  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
                                         getter_AddRefs(callbacks));
  if (!callbacks) return;

  Unused << gHttpHandler->SpeculativeConnect(
      mConnectionInfo, callbacks,
      mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_DISABLE_TRR |
               NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6));
}

void nsHttpChannel::DoNotifyListenerCleanup() {
  // We don't need this info anymore
  CleanRedirectCacheChainIfNecessary();
}

void nsHttpChannel::ReleaseListeners() {
  HttpBaseChannel::ReleaseListeners();
  mChannelClassifier = nullptr;
  mWarningReporter = nullptr;
}

void nsHttpChannel::DoAsyncAbort(nsresult aStatus) {
  Unused << AsyncAbort(aStatus);
}

void nsHttpChannel::HandleAsyncRedirect() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");

  if (mSuspendCount) {
    LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleAsyncRedirect();
      return NS_OK;
    };
    return;
  }

  nsresult rv = NS_OK;

  LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));

  // since this event is handled asynchronously, it is possible that this
  // channel could have been canceled, in which case there would be no point
  // in processing the redirect.
  if (NS_SUCCEEDED(mStatus)) {
    PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
    rv = AsyncProcessRedirection(mResponseHead->Status());
    if (NS_FAILED(rv)) {
      PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
      // TODO: if !DoNotRender3xxBody(), render redirect body instead.
      // But first we need to cache 3xx bodies (bug 748510)
      rv = ContinueHandleAsyncRedirect(rv);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }
  } else {
    rv = ContinueHandleAsyncRedirect(mStatus);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
}

nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
  if (NS_FAILED(rv)) {
    // If AsyncProcessRedirection fails, then we have to send out the
    // OnStart/OnStop notifications.
    LOG(("ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32 "]\n",
         static_cast<uint32_t>(rv)));

    bool redirectsEnabled = !mLoadInfo || !mLoadInfo->GetDontFollowRedirects();

    if (redirectsEnabled) {
      // TODO: stop failing original channel if redirect vetoed?
      mStatus = rv;

      DoNotifyListener();

      // Blow away cache entry if we couldn't process the redirect
      // for some reason (the cache entry might be corrupt).
      if (mCacheEntry) {
        mCacheEntry->AsyncDoom(nullptr);
      }
    } else {
      DoNotifyListener();
    }
  }

  CloseCacheEntry(true);

  mIsPending = false;

  if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);

  return NS_OK;
}

void nsHttpChannel::HandleAsyncNotModified() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");

  if (mSuspendCount) {
    LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleAsyncNotModified();
      return NS_OK;
    };
    return;
  }

  LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));

  DoNotifyListener();

  CloseCacheEntry(false);

  mIsPending = false;

  if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}

void nsHttpChannel::HandleAsyncFallback() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");

  if (mSuspendCount) {
    LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleAsyncFallback();
      return NS_OK;
    };
    return;
  }

  nsresult rv = NS_OK;

  LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));

  // since this event is handled asynchronously, it is possible that this
  // channel could have been canceled, in which case there would be no point
  // in processing the fallback.
  if (!mCanceled) {
    PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
    bool waitingForRedirectCallback;
    rv = ProcessFallback(&waitingForRedirectCallback);
    if (waitingForRedirectCallback) return;
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
  }

  rv = ContinueHandleAsyncFallback(rv);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

nsresult nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) {
  if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
    // If ProcessFallback fails, then we have to send out the
    // OnStart/OnStop notifications.
    LOG(("ProcessFallback failed [rv=%" PRIx32 ", %d]\n",
         static_cast<uint32_t>(rv), mFallingBack));
    mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
    DoNotifyListener();
  }

  mIsPending = false;

  if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);

  return rv;
}

nsresult nsHttpChannel::SetupTransaction() {
  LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n", this,
       mClassOfService, mPriority));

  NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);

  nsresult rv;

  mozilla::MutexAutoLock lock(mRCWNLock);

  // If we're racing cache with network, conditional or byte range header
  // could be added in OnCacheEntryCheck. We cannot send conditional request
  // without having the entry, so we need to remove the headers here and
  // ignore the cache entry in OnCacheEntryAvailable.
  if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
    if (mDidReval) {
      LOG(("  Removing conditional request headers"));
      UntieValidationRequest();
      mDidReval = false;
      mIgnoreCacheEntry = true;
    }

    if (mCachedContentIsPartial) {
      LOG(("  Removing byte range request headers"));
      UntieByteRangeRequest();
      mCachedContentIsPartial = false;
      mIgnoreCacheEntry = true;
    }

    if (mIgnoreCacheEntry) {
      mAvailableCachedAltDataType.Truncate();
      mDeliveringAltData = false;
      mAltDataLength = -1;
      mCacheInputStream.CloseAndRelease();
    }
  }

  mUsedNetwork = 1;

  if (!mAllowSpdy) {
    mCaps |= NS_HTTP_DISALLOW_SPDY;
  }
  if (mBeConservative) {
    mCaps |= NS_HTTP_BE_CONSERVATIVE;
  }

  // Use the URI path if not proxying (transparent proxying such as proxy
  // CONNECT does not count here). Also figure out what HTTP version to use.
  nsAutoCString buf, path;
  nsCString* requestURI;

  // This is the normal e2e H1 path syntax "/index.html"
  rv = mURI->GetPathQueryRef(path);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // path may contain UTF-8 characters, so ensure that they're escaped.
  if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
                   buf)) {
    requestURI = &buf;
  } else {
    requestURI = &path;
  }

  // trim off the #ref portion if any...
  int32_t ref1 = requestURI->FindChar('#');
  if (ref1 != kNotFound) {
    requestURI->SetLength(ref1);
  }

  if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
    mRequestHead.SetVersion(gHttpHandler->HttpVersion());
  } else {
    mRequestHead.SetPath(*requestURI);

    // RequestURI should be the absolute uri H1 proxy syntax
    // "http://foo/index.html" so we will overwrite the relative version in
    // requestURI
    rv = mURI->GetUserPass(buf);
    if (NS_FAILED(rv)) return rv;
    if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
                           strncmp(mSpec.get(), "https:", 6) == 0)) {
      nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
      if (NS_WARN_IF(!urifixup)) {
        return NS_ERROR_FAILURE;
      }

      nsCOMPtr<nsIURI> tempURI;
      nsresult rv = urifixup->CreateExposableURI(mURI, getter_AddRefs(tempURI));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      rv = tempURI->GetAsciiSpec(path);
      if (NS_FAILED(rv)) return rv;
      requestURI = &path;
    } else {
      requestURI = &mSpec;
    }

    // trim off the #ref portion if any...
    int32_t ref2 = requestURI->FindChar('#');
    if (ref2 != kNotFound) {
      requestURI->SetLength(ref2);
    }

    mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
  }

  mRequestHead.SetRequestURI(*requestURI);

  // set the request time for cache expiration calculations
  mRequestTime = NowInSeconds();
  mRequestTimeInitialized = true;

  // if doing a reload, force end-to-end
  if (mLoadFlags & LOAD_BYPASS_CACHE) {
    // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
    // no proxy is configured since we might be talking with a transparent
    // proxy, i.e. one that operates at the network level.  See bug #14772.
    rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
    // no-cache'
    if (mRequestHead.Version() >= HttpVersion::v1_1) {
      rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
    }
  } else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
    // We need to send 'Cache-Control: max-age=0' to force each cache along
    // the path to the origin server to revalidate its own entry, if any,
    // with the next cache or server.  See bug #84847.
    //
    // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
    if (mRequestHead.Version() >= HttpVersion::v1_1)
      rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
    else
      rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  if (mResuming) {
    char byteRange[32];
    SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
    rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    if (!mEntityID.IsEmpty()) {
      // Also, we want an error if this resource changed in the meantime
      // Format of the entity id is: escaped_etag/size/lastmod
      nsCString::const_iterator start, end, slash;
      mEntityID.BeginReading(start);
      mEntityID.EndReading(end);
      mEntityID.BeginReading(slash);

      if (FindCharInReadable('/', slash, end)) {
        nsAutoCString ifMatch;
        rv = mRequestHead.SetHeader(
            nsHttp::If_Match,
            NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
        MOZ_ASSERT(NS_SUCCEEDED(rv));

        ++slash;  // Incrementing, so that searching for '/' won't find
                  // the same slash again
      }

      if (FindCharInReadable('/', slash, end)) {
        rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
                                    Substring(++slash, end));
        MOZ_ASSERT(NS_SUCCEEDED(rv));
      }
    }
  }

  // create wrapper for this channel's notification callbacks
  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
                                         getter_AddRefs(callbacks));

  // create the transaction object
  mTransaction = new nsHttpTransaction();
  LOG1(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
        mTransaction.get()));
  mTransaction->SetTransactionObserver(mTransactionObserver);
  mTransactionObserver = nullptr;

  // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
  if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;

  if (mTimingEnabled) mCaps |= NS_HTTP_TIMING_ENABLED;

  if (mUpgradeProtocolCallback) {
    rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
                                    true);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    mCaps |= NS_HTTP_STICKY_CONNECTION;
    mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
  }

  if (mPushedStream) {
    mTransaction->SetPushedStream(mPushedStream);
    mPushedStream = nullptr;
  }

  nsCOMPtr<nsIHttpPushListener> pushListener;
  NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                NS_GET_IID(nsIHttpPushListener),
                                getter_AddRefs(pushListener));
  if (pushListener) {
    mCaps |= NS_HTTP_ONPUSH_LISTENER;
  }

  EnsureTopLevelOuterContentWindowId();

  HttpTrafficCategory category = CreateTrafficCategory();

  nsCOMPtr<nsIAsyncInputStream> responseStream;
  rv = mTransaction->Init(
      mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
      mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
      mTopLevelOuterContentWindowId, category, getter_AddRefs(responseStream));
  if (NS_FAILED(rv)) {
    mTransaction = nullptr;
    return rv;
  }

  mTransaction->SetClassOfService(mClassOfService);
  if (EnsureRequestContext()) {
    mTransaction->SetRequestContext(mRequestContext);
  }

  rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
                                 responseStream);
  return rv;
}

HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
  MOZ_ASSERT(!mFirstPartyClassificationFlags ||
             !mThirdPartyClassificationFlags);

  if (!StaticPrefs::network_traffic_analyzer_enabled()) {
    return HttpTrafficCategory::eInvalid;
  }

  HttpTrafficAnalyzer::ClassOfService cos;
  {
    if ((mClassOfService & nsIClassOfService::Leader) &&
        mLoadInfo->GetExternalContentPolicyType() ==
            nsIContentPolicy::TYPE_SCRIPT) {
      cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
    } else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
      cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
    } else {
      cos = HttpTrafficAnalyzer::ClassOfService::eOther;
    }
  }

  bool isThirdParty =
      nsContentUtils::IsThirdPartyWindowOrChannel(nullptr, this, mURI);
  HttpTrafficAnalyzer::TrackingClassification tc;
  {
    uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
                                  : mFirstPartyClassificationFlags;

    using CF = nsIClassifiedChannel::ClassificationFlags;
    using TC = HttpTrafficAnalyzer::TrackingClassification;

    if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
      tc = TC::eContent;
    } else if (flags & CF::CLASSIFIED_FINGERPRINTING_CONTENT) {
      tc = TC::eFingerprinting;
    } else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
      tc = TC::eBasic;
    } else {
      tc = TC::eNone;
    }
  }

  bool isSystemPrincipal = mLoadInfo->LoadingPrincipal() &&
                           mLoadInfo->LoadingPrincipal()->IsSystemPrincipal();
  return HttpTrafficAnalyzer::CreateTrafficCategory(
      NS_UsePrivateBrowsing(this), isSystemPrincipal, isThirdParty, cos, tc);
}

enum class Report { Error, Warning };

// Helper Function to report messages to the console when the loaded
// script had a wrong MIME type.
void ReportMimeTypeMismatch(nsHttpChannel* aChannel, const char* aMessageName,
                            nsIURI* aURI, const nsACString& aContentType,
                            Report report) {
  NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
  NS_ConvertUTF8toUTF16 contentType(aContentType);

  aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
                                report == Report::Warning, spec, contentType);
}

// Check and potentially enforce X-Content-Type-Options: nosniff
nsresult ProcessXCTO(nsHttpChannel* aChannel, nsIURI* aURI,
                     nsHttpResponseHead* aResponseHead,
                     nsILoadInfo* aLoadInfo) {
  if (!aURI || !aResponseHead || !aLoadInfo) {
    // if there is no uri, no response head or no loadInfo, then there is
    // nothing to do
    return NS_OK;
  }

  // 1) Query the XCTO header and check if 'nosniff' is the first value.
  nsAutoCString contentTypeOptionsHeader;
  Unused << aResponseHead->GetHeader(nsHttp::X_Content_Type_Options,
                                     contentTypeOptionsHeader);
  if (contentTypeOptionsHeader.IsEmpty()) {
    // if there is no XCTO header, then there is nothing to do.
    return NS_OK;
  }
  // XCTO header might contain multiple values which are comma separated, so:
  // a) let's skip all subsequent values
  //     e.g. "   NoSniFF   , foo " will be "   NoSniFF   "
  int32_t idx = contentTypeOptionsHeader.Find(",");
  if (idx > 0) {
    contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
  }
  // b) let's trim all surrounding whitespace
  //    e.g. "   NoSniFF   " -> "NoSniFF"
  nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
                             contentTypeOptionsHeader);
  // c) let's compare the header (ignoring case)
  //    e.g. "NoSniFF" -> "nosniff"
  //    if it's not 'nosniff' then there is nothing to do here
  if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
    // since we are getting here, the XCTO header was sent;
    // a non matching value most likely means a mistake happenend;
    // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
    AutoTArray<nsString, 1> params;
    CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
    RefPtr<Document> doc;
    aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
    nsContentUtils::ReportToConsole(
        nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XCTO"), doc,
        nsContentUtils::eSECURITY_PROPERTIES, "XCTOHeaderValueMissing", params);
    return NS_OK;
  }

  // 2) Query the content type from the channel
  nsAutoCString contentType;
  aResponseHead->ContentType(contentType);

  // 3) Compare the expected MIME type with the actual type
  if (aLoadInfo->GetExternalContentPolicyType() ==
      nsIContentPolicy::TYPE_STYLESHEET) {
    if (contentType.EqualsLiteral(TEXT_CSS)) {
      return NS_OK;
    }
    ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
                           Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  if (aLoadInfo->GetExternalContentPolicyType() ==
      nsIContentPolicy::TYPE_SCRIPT) {
    if (nsContentUtils::IsJavascriptMIMEType(
            NS_ConvertUTF8toUTF16(contentType))) {
      return NS_OK;
    }
    ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
                           Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  auto policyType = aLoadInfo->GetExternalContentPolicyType();
  if ((policyType == nsIContentPolicy::TYPE_DOCUMENT ||
       policyType == nsIContentPolicy::TYPE_SUBDOCUMENT) &&
      gHttpHandler->IsDocumentNosniffEnabled()) {
    // If the header XCTO nosniff is set for any browsing context, then
    // we set the skipContentSniffing flag on the Loadinfo. Within
    // GetMIMETypeFromContent we then bail early and do not do any sniffing.
    aLoadInfo->SetSkipContentSniffing(true);
    return NS_OK;
  }

  return NS_OK;
}

// Ensure that a load of type script has correct MIME type
nsresult EnsureMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI,
                            nsHttpResponseHead* aResponseHead,
                            nsILoadInfo* aLoadInfo) {
  if (!aURI || !aResponseHead || !aLoadInfo) {
    // if there is no uri, no response head or no loadInfo, then there is
    // nothing to do
    return NS_OK;
  }

  if (aLoadInfo->GetExternalContentPolicyType() !=
      nsIContentPolicy::TYPE_SCRIPT) {
    // if this is not a script load, then there is nothing to do
    return NS_OK;
  }

  nsAutoCString contentType;
  aResponseHead->ContentType(contentType);
  NS_ConvertUTF8toUTF16 typeString(contentType);

  if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
    // script load has type script
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
    return NS_OK;
  }

  switch (aLoadInfo->InternalContentPolicyType()) {
    case nsIContentPolicy::TYPE_SCRIPT:
    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
    case nsIContentPolicy::TYPE_INTERNAL_MODULE:
    case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
      break;
    case nsIContentPolicy::TYPE_INTERNAL_WORKER:
    case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
      break;
    case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
      break;
    case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("unexpected script type");
      break;
  }

  nsCOMPtr<nsIURI> requestURI;
  aLoadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(requestURI));

  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
  bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
  nsresult rv = ssm->CheckSameOriginURI(requestURI, aURI, false, isPrivateWin);
  if (NS_SUCCEEDED(rv)) {
    // same origin
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
  } else {
    bool cors = false;
    nsAutoCString corsOrigin;
    rv = aResponseHead->GetHeader(
        nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin);
    if (NS_SUCCEEDED(rv)) {
      if (corsOrigin.Equals("*")) {
        cors = true;
      } else {
        nsCOMPtr<nsIURI> corsOriginURI;
        rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
        if (NS_SUCCEEDED(rv)) {
          bool isPrivateWin =
              aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
          rv = ssm->CheckSameOriginURI(requestURI, corsOriginURI, false,
                                       isPrivateWin);
          if (NS_SUCCEEDED(rv)) {
            cors = true;
          }
        }
      }
    }
    if (cors) {
      // cors origin
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
    } else {
      // cross origin
      AccumulateCategorical(
          Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
    }
  }

  bool block = false;
  if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
    // script load has type image
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
    block = true;
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
    // script load has type audio
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
    block = true;
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
    // script load has type video
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
    block = true;
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
    // script load has type text/csv
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
    block = true;
  }

  if (block) {
    // Do not block the load if the feature is not enabled.
    if (!StaticPrefs::security_block_script_with_wrong_mime()) {
      return NS_OK;
    }

    ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
                           contentType, Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
    // script load has type text/plain
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
    // script load has type text/xml
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
  } else if (StringBeginsWith(contentType,
                              NS_LITERAL_CSTRING("application/octet-stream"))) {
    // script load has type application/octet-stream
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
  } else if (StringBeginsWith(contentType,
                              NS_LITERAL_CSTRING("application/xml"))) {
    // script load has type application/xml
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
  } else if (StringBeginsWith(contentType,
                              NS_LITERAL_CSTRING("application/json"))) {
    // script load has type application/json
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/json"))) {
    // script load has type text/json
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
  } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
    // script load has type text/html
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
  } else if (contentType.IsEmpty()) {
    // script load has no type
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
  } else {
    // script load has unknown type
    AccumulateCategorical(
        Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
  }

  // We restrict importScripts() in worker code to JavaScript MIME types.
  nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
  if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS) {
    // Do not block the load if the feature is not enabled.
    if (!StaticPrefs::security_block_importScripts_with_wrong_mime()) {
      return NS_OK;
    }

    ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
                           aURI, contentType, Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
      internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
    // Do not block the load if the feature is not enabled.
    if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
      return NS_OK;
    }

    ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
                           contentType, Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  // ES6 modules require a strict MIME type check.
  if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
      internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
    ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
                           contentType, Report::Error);
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  return NS_OK;
}

// Warn when a load of type script uses a wrong MIME type and
// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
void WarnWrongMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI,
                           nsHttpResponseHead* aResponseHead,
                           nsILoadInfo* aLoadInfo) {
  if (!aURI || !aResponseHead || !aLoadInfo) {
    // If there is no uri, no response head or no loadInfo, then there is
    // nothing to do.
    return;
  }

  if (aLoadInfo->GetExternalContentPolicyType() !=
      nsIContentPolicy::TYPE_SCRIPT) {
    // If this is not a script load, then there is nothing to do.
    return;
  }

  nsAutoCString contentType;
  aResponseHead->ContentType(contentType);
  NS_ConvertUTF8toUTF16 typeString(contentType);
  if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
    ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
                           contentType, Report::Warning);
  }
}

void nsHttpChannel::SetCachedContentType() {
  if (!mResponseHead) {
    return;
  }

  nsAutoCString contentTypeStr;
  mResponseHead->ContentType(contentTypeStr);

  uint8_t contentType = nsICacheEntry::CONTENT_TYPE_OTHER;
  if (nsContentUtils::IsJavascriptMIMEType(
          NS_ConvertUTF8toUTF16(contentTypeStr))) {
    contentType = nsICacheEntry::CONTENT_TYPE_JAVASCRIPT;
  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("text/css")) ||
             mLoadInfo->GetExternalContentPolicyType() ==
                 nsIContentPolicy::TYPE_STYLESHEET) {
    contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
  } else if (StringBeginsWith(contentTypeStr,
                              NS_LITERAL_CSTRING("application/wasm"))) {
    contentType = nsICacheEntry::CONTENT_TYPE_WASM;
  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("image/"))) {
    contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("video/"))) {
    contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("audio/"))) {
    contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
  }

  mCacheEntry->SetContentType(contentType);
}

void nsHttpChannel::StoreSiteAccessToCacheEntry() {
  nsresult rv;

  nsCOMPtr<nsIURI> topWindowURI;
  rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
  if (NS_FAILED(rv)) {
    return;
  }

  nsCOMPtr<nsIEffectiveTLDService> eTLDService =
      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) {
    return;
  }

  nsAutoCString baseDomain;
  rv = eTLDService->GetBaseDomain(topWindowURI, 0, baseDomain);
  if (NS_FAILED(rv)) {
    return;
  }

  RefPtr<CacheHash> hash = new CacheHash();
  hash->Update(baseDomain.get(), baseDomain.Length());

  Unused << mCacheEntry->AddBaseDomainAccess(hash->GetHash());
}

nsresult nsHttpChannel::CallOnStartRequest() {
  LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));

  MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
                     "CORS preflight must have been finished by the time we "
                     "call OnStartRequest");

  if (mOnStartRequestCalled) {
    // This can only happen when a range request loading rest of the data
    // after interrupted concurrent cache read asynchronously failed, e.g.
    // the response range bytes are not as expected or this channel has
    // been externally canceled.
    //
    // It's legal to bypass CallOnStartRequest for that case since we've
    // already called OnStartRequest on our listener and also added all
    // content converters before.
    MOZ_ASSERT(mConcurrentCacheAccess);
    LOG(("CallOnStartRequest already invoked before"));
    return mStatus;
  }

  ReportContentTypeTelemetryForCrossOriginStylesheets();

  mTracingEnabled = false;

  // Ensure mListener->OnStartRequest will be invoked before exiting
  // this function.
  auto onStartGuard = MakeScopeExit([&] {
    LOG(
        ("  calling mListener->OnStartRequest by ScopeExit [this=%p, "
         "listener=%p]\n",
         this, mListener.get()));
    MOZ_ASSERT(!mOnStartRequestCalled);

    if (mListener) {
      nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
      mOnStartRequestCalled = true;
      deleteProtector->OnStartRequest(this);
    }
    mOnStartRequestCalled = true;
  });

  nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = ProcessXCTO(this, mURI, mResponseHead, mLoadInfo);
  NS_ENSURE_SUCCESS(rv, rv);

  WarnWrongMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);

  // Allow consumers to override our content type
  if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
    // NOTE: We can have both a txn pump and a cache pump when the cache
    // content is partial. In that case, we need to read from the cache,
    // because that's the one that has the initial contents. If that fails
    // then give the transaction pump a shot.

    nsIChannel* thisChannel = static_cast<nsIChannel*>(this);

    bool typeSniffersCalled = false;
    if (mCachePump) {
      typeSniffersCalled =
          NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
    }

    if (!typeSniffersCalled && mTransactionPump) {
      mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
    }
  }

  bool unknownDecoderStarted = false;
  if (mResponseHead && !mResponseHead->HasContentType()) {
    MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
    if (!mContentTypeHint.IsEmpty())
      mResponseHead->SetContentType(mContentTypeHint);
    else if (mResponseHead->Version() == HttpVersion::v0_9 &&
             mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
      mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
    else {
      // Uh-oh.  We had better find out what type we are!
      nsCOMPtr<nsIStreamConverterService> serv;
      rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
      // If we failed, we just fall through to the "normal" case
      if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<nsIStreamListener> converter;
        rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
                                    nullptr, getter_AddRefs(converter));
        if (NS_SUCCEEDED(rv)) {
          mListener = converter;
          unknownDecoderStarted = true;
        }
      }
    }
  }

  if (mResponseHead && !mResponseHead->HasContentCharset())
    mResponseHead->SetContentCharset(mContentCharsetHint);

  if (mCacheEntry && mCacheEntryIsWriteOnly) {
    SetCachedContentType();
  }

  if (mCacheEntry) {
    StoreSiteAccessToCacheEntry();
  }

  LOG(("  calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
       mListener.get()));

  // About to call OnStartRequest, dismiss the guard object.
  onStartGuard.release();

  if (mListener) {
    MOZ_ASSERT(!mOnStartRequestCalled,
               "We should not call OsStartRequest twice");
    nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
    mOnStartRequestCalled = true;
    rv = deleteProtector->OnStartRequest(this);
    if (NS_FAILED(rv)) return rv;
  } else {
    NS_WARNING("OnStartRequest skipped because of null listener");
    mOnStartRequestCalled = true;
  }

  // Install stream converter if required.
  // If we use unknownDecoder, stream converters will be installed later (in
  // nsUnknownDecoder) after OnStartRequest is called for the real listener.
  if (!unknownDecoderStarted) {
    nsCOMPtr<nsIStreamListener> listener;
    rv =
        DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
    if (NS_FAILED(rv)) {
      return rv;
    }
    if (listener) {
      mListener = listener;
      mCompressListener = listener;
    }
  }

  // if this channel is for a download, close off access to the cache.
  if (mCacheEntry && mChannelIsForDownload) {
    mCacheEntry->AsyncDoom(nullptr);

    // We must keep the cache entry in case of partial request.
    // Concurrent access is the same, we need the entry in
    // OnStopRequest.
    // We also need the cache entry when racing cache with network to find
    // out what is the source of the data.
    if (!mCachedContentIsPartial && !mConcurrentCacheAccess &&
        !(mRaceCacheWithNetwork &&
          mFirstResponseSource == RESPONSE_FROM_CACHE)) {
      CloseCacheEntry(false);
    }
  }

  if (!mCanceled) {
    // create offline cache entry if offline caching was requested
    if (ShouldUpdateOfflineCacheEntry()) {
      LOG(("writing to the offline cache"));
      rv = InitOfflineCacheEntry();
      if (NS_FAILED(rv)) return rv;

      // InitOfflineCacheEntry may have closed mOfflineCacheEntry
      if (mOfflineCacheEntry) {
        rv = InstallOfflineCacheListener();
        if (NS_FAILED(rv)) return rv;
      }
    } else if (mApplicationCacheForWrite) {
      LOG(("offline cache is up to date, not updating"));
      CloseOfflineCacheEntry();
    }
  }

  return NS_OK;
}

nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
  // Failure to set up a proxy tunnel via CONNECT means one of the following:
  // 1) Proxy wants authorization, or forbids.
  // 2) DNS at proxy couldn't resolve target URL.
  // 3) Proxy connection to target failed or timed out.
  // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
  //
  // Our current architecture would parse the proxy's response content with
  // the permission of the target URL.  Given #4, we must avoid rendering the
  // body of the reply, and instead give the user a (hopefully helpful)
  // boilerplate error page, based on just the HTTP status of the reply.

  MOZ_ASSERT(mConnectionInfo->UsingConnect(),
             "proxy connect failed but not using CONNECT?");
  nsresult rv;
  switch (httpStatus) {
    case 300:
    case 301:
    case 302:
    case 303:
    case 307:
    case 308:
      // Bad redirect: not top-level, or it's a POST, bad/missing Location,
      // or ProcessRedirect() failed for some other reason.  Legal
      // redirects that fail because site not available, etc., are handled
      // elsewhere, in the regular codepath.
      rv = NS_ERROR_CONNECTION_REFUSED;
      break;
    case 403:  // HTTP/1.1: "Forbidden"
    case 501:  // HTTP/1.1: "Not Implemented"
      // user sees boilerplate Mozilla "Proxy Refused Connection" page.
      rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
      break;
    case 407:  // ProcessAuthentication() failed (e.g. no header)
      rv = NS_ERROR_PROXY_AUTHENTICATION_FAILED;
      break;
    case 429:
      rv = NS_ERROR_TOO_MANY_REQUESTS;
      break;
      // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
    case 404:  // HTTP/1.1: "Not Found"
               // RFC 2616: "some deployed proxies are known to return 400 or
               // 500 when DNS lookups time out."  (Squid uses 500 if it runs
               // out of sockets: so we have a conflict here).
    case 400:  // HTTP/1.1 "Bad Request"
    case 500:  // HTTP/1.1: "Internal Server Error"
      /* User sees: "Address Not Found: Firefox can't find the server at
       * www.foo.com."
       */
      rv = NS_ERROR_UNKNOWN_HOST;
      break;
    case 502:  // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
      rv = NS_ERROR_PROXY_BAD_GATEWAY;
      break;
    case 503:  // HTTP/1.1: "Service Unavailable"
      // Squid returns 503 if target request fails for anything but DNS.
      /* User sees: "Failed to Connect:
       *  Firefox can't establish a connection to the server at
       *  www.foo.com.  Though the site seems valid, the browser
       *  was unable to establish a connection."
       */
      rv = NS_ERROR_CONNECTION_REFUSED;
      break;
    // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
    // do here: picking target timeout, as DNS covered by 400/404/500
    case 504:  // HTTP/1.1: "Gateway Timeout"
      // user sees: "Network Timeout: The server at www.foo.com
      //              is taking too long to respond."
      rv = NS_ERROR_PROXY_GATEWAY_TIMEOUT;
      break;
    // Confused proxy server or malicious response
    default:
      rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
      break;
  }
  LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
       httpStatus));

  // Make sure the connection is thrown away as it can be in a bad state
  // and the proxy may just hang on the next request.
  MOZ_ASSERT(mTransaction);
  mTransaction->DontReuseConnection();

  Cancel(rv);
  {
    nsresult rv = CallOnStartRequest();
    if (NS_FAILED(rv)) {
      LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
           httpStatus, static_cast<uint32_t>(rv)));
    }
  }
  return rv;
}

static void GetSTSConsoleErrorTag(uint32_t failureResult,
                                  nsAString& consoleErrorTag) {
  switch (failureResult) {
    case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
      consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
      break;
    case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
      consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
      break;
    case nsISiteSecurityService::ERROR_NO_MAX_AGE:
      consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
      break;
    case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
      consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
      break;
    case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
      consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
      break;
    case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
      consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
      break;
    case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
      consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
      break;
    case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
      consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
      break;
    default:
      consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
      break;
  }
}

static void GetPKPConsoleErrorTag(uint32_t failureResult,
                                  nsAString& consoleErrorTag) {
  switch (failureResult) {
    case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
      consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
      break;
    case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
      consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
      break;
    case nsISiteSecurityService::ERROR_NO_MAX_AGE:
      consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
      break;
    case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
      consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
      break;
    case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
      consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
      break;
    case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
      consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
      break;
    case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
      consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
      break;
    case nsISiteSecurityService::ERROR_INVALID_PIN:
      consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
      break;
    case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
      consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
      break;
    case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
      consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
      break;
    case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
      consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
      break;
    case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
      consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
      break;
    case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
      consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
      break;
    default:
      consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
      break;
  }
}

/**
 * Process a single security header. Only two types are supported: HSTS and
 * HPKP.
 */
nsresult nsHttpChannel::ProcessSingleSecurityHeader(
    uint32_t aType, nsITransportSecurityInfo* aSecInfo, uint32_t aFlags) {
  nsHttpAtom atom;
  switch (aType) {
    case nsISiteSecurityService::HEADER_HSTS:
      atom = nsHttp::ResolveAtom("Strict-Transport-Security");
      break;
    case nsISiteSecurityService::HEADER_HPKP:
      atom = nsHttp::ResolveAtom("Public-Key-Pins");
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid security header type");
      return NS_ERROR_FAILURE;
  }

  nsAutoCString securityHeader;
  nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
  if (NS_SUCCEEDED(rv)) {
    nsISiteSecurityService* sss = gHttpHandler->GetSSService();
    NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
    // Process header will now discard the headers itself if the channel
    // wasn't secure (whereas before it had to be checked manually)
    OriginAttributes originAttributes;
    NS_GetOriginAttributes(this, originAttributes);
    uint32_t failureResult;
    uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
    rv = sss->ProcessHeader(aType, mURI, securityHeader, aSecInfo, aFlags,
                            headerSource, originAttributes, nullptr, nullptr,
                            &failureResult);
    if (NS_FAILED(rv)) {
      nsAutoString consoleErrorCategory;
      nsAutoString consoleErrorTag;
      switch (aType) {
        case nsISiteSecurityService::HEADER_HSTS:
          GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
          consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
          break;
        case nsISiteSecurityService::HEADER_HPKP:
          GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
          consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
          break;
        default:
          return NS_ERROR_FAILURE;
      }
      Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
      LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
           atom.get()));
    }
  } else {
    if (rv != NS_ERROR_NOT_AVAILABLE) {
      // All other errors are fatal
      NS_ENSURE_SUCCESS(rv, rv);
    }
    LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
  }
  return NS_OK;
}

/**
 * Decide whether or not to remember Strict-Transport-Security, and whether
 * or not to enforce channel integrity.
 *
 * @return NS_ERROR_FAILURE if there's security information missing even though
 *             it's an HTTPS connection.
 */
nsresult nsHttpChannel::ProcessSecurityHeaders() {
  // If this channel is not loading securely, STS or PKP doesn't do anything.
  // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
  // channel load process.
  if (!mURI->SchemeIs("https")) {
    return NS_OK;
  }

  nsAutoCString asciiHost;
  nsresult rv = mURI->GetAsciiHost(asciiHost);
  NS_ENSURE_SUCCESS(rv, NS_OK);

  // If the channel is not a hostname, but rather an IP, do not process STS
  // or PKP headers
  PRNetAddr hostAddr;
  if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
    return NS_OK;

  // mSecurityInfo may not always be present, and if it's not then it is okay
  // to just disregard any security headers since we know nothing about the
  // security of the connection.
  NS_ENSURE_TRUE(mSecurityInfo, NS_OK);

  uint32_t flags =
      NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;

  // Get the TransportSecurityInfo
  nsCOMPtr<nsITransportSecurityInfo> transSecInfo =
      do_QueryInterface(mSecurityInfo);
  NS_ENSURE_TRUE(transSecInfo, NS_ERROR_FAILURE);

  rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
                                   transSecInfo, flags);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
                                   transSecInfo, flags);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

/**
 * Decide whether or not to send a security report and, if so, give the
 * SecurityReporter the information required to send such a report.
 */
void nsHttpChannel::ProcessSecurityReport(nsresult status) {
  uint32_t errorClass;
  nsCOMPtr<nsINSSErrorsService> errSvc =
      do_GetService("@mozilla.org/nss_errors_service;1");
  // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
  // not in the set of errors covered by the NSS errors service.
  nsresult rv = errSvc->GetErrorClass(status, &errorClass);
  if (!NS_SUCCEEDED(rv)) {
    return;
  }

  // if the content was not loaded succesfully and we have security info,
  // send a TLS error report - we must do this early as other parts of
  // OnStopRequest can return early
  bool reportingEnabled =
      Preferences::GetBool("security.ssl.errorReporting.enabled");
  bool reportingAutomatic =
      Preferences::GetBool("security.ssl.errorReporting.automatic");
  if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
    return;
  }

  nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(mSecurityInfo);
  nsCOMPtr<nsISecurityReporter> errorReporter =
      do_GetService("@mozilla.org/securityreporter;1");

  if (!secInfo || !mURI) {
    return;
  }

  nsAutoCString hostStr;
  int32_t port;
  rv = mURI->GetHost(hostStr);
  if (!NS_SUCCEEDED(rv)) {
    return;
  }

  rv = mURI->GetPort(&port);

  if (NS_SUCCEEDED(rv)) {
    errorReporter->ReportTLSError(secInfo, hostStr, port);
  }
}

bool nsHttpChannel::IsHTTPS() { return mURI->SchemeIs("https"); }

void nsHttpChannel::ProcessSSLInformation() {
  // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
  // can be whitelisted for TLS False Start in future sessions. We could
  // do the same for DH but its rarity doesn't justify the lookup.

  if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
      mPrivateBrowsing)
    return;

  nsCOMPtr<nsITransportSecurityInfo> securityInfo =
      do_QueryInterface(mSecurityInfo);
  if (!securityInfo) return;

  uint32_t state;
  if (securityInfo && NS_SUCCEEDED(securityInfo->GetSecurityState(&state)) &&
      (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
    // Send weak crypto warnings to the web console
    if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
      nsString consoleErrorTag = NS_LITERAL_STRING("WeakCipherSuiteWarning");
      nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
      Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
    }
  }

  // Send (SHA-1) signature algorithm errors to the web console
  nsCOMPtr<nsIX509Cert> cert;
  securityInfo->GetServerCert(getter_AddRefs(cert));
  if (cert) {
    UniqueCERTCertificate nssCert(cert->GetCert());
    if (nssCert) {
      SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
      LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag,
           this));
      // Check to see if the signature is sha-1 based.
      // Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
      // from http://tools.ietf.org/html/rfc2437#section-8 since I
      // can't see reference to it outside this spec
      if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
          tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
          tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
        nsString consoleErrorTag = NS_LITERAL_STRING("SHA1Sig");
        nsString consoleErrorMessage = NS_LITERAL_STRING("SHA-1 Signature");
        Unused << AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
      }
    }
  }

  uint16_t tlsVersion;
  nsresult rv = securityInfo->GetProtocolVersion(&tlsVersion);
  if (NS_SUCCEEDED(rv) &&
      tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_2 &&
      tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_3) {
    nsString consoleErrorTag = NS_LITERAL_STRING("DeprecatedTLSVersion");
    nsString consoleErrorCategory = NS_LITERAL_STRING("TLS");
    Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
  }
}

void nsHttpChannel::ProcessAltService() {
  // e.g. Alt-Svc: h2=":443"; ma=60
  // e.g. Alt-Svc: h2="otherhost:443"
  // Alt-Svc       = 1#( alternative *( OWS ";" OWS parameter ) )
  // alternative   = protocol-id "=" alt-authority
  // protocol-id   = token ; percent-encoded ALPN protocol identifier
  // alt-authority = quoted-string ;  containing [ uri-host ] ":" port

  if (!mAllowAltSvc) {  // per channel opt out
    return;
  }

  if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
    return;
  }

  nsAutoCString scheme;
  mURI->GetScheme(scheme);
  bool isHttp = scheme.EqualsLiteral("http");
  if (!isHttp && !scheme.EqualsLiteral("https")) {
    return;
  }

  nsAutoCString altSvc;
  Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
  if (altSvc.IsEmpty()) {
    return;
  }

  if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
    LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
    return;
  }

  nsAutoCString originHost;
  int32_t originPort = 80;
  mURI->GetPort(&originPort);
  if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
    return;
  }

  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  nsCOMPtr<nsProxyInfo> proxyInfo;
  NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
                                         getter_AddRefs(callbacks));
  if (mProxyInfo) {
    proxyInfo = do_QueryInterface(mProxyInfo);
  }

  OriginAttributes originAttributes;
  NS_GetOriginAttributes(this, originAttributes);

  AltSvcMapping::ProcessHeader(
      altSvc, scheme, originHost, originPort, mUsername, GetTopWindowOrigin(),
      mPrivateBrowsing, IsIsolated(), callbacks, proxyInfo,
      mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
}

nsresult nsHttpChannel::ProcessResponse() {
  uint32_t httpStatus = mResponseHead->Status();

  LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
       httpStatus));

  // Gather data on whether the transaction and page (if this is
  // the initial page load) is being loaded with SSL.
  Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
                        mConnectionInfo->EndToEndSSL());
  if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
    Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
                          mConnectionInfo->EndToEndSSL());
  }

  if (Telemetry::CanRecordPrereleaseData()) {
    // how often do we see something like Alt-Svc: "443:quic,p=1"
    nsAutoCString alt_service;
    Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
    bool saw_quic =
        (!alt_service.IsEmpty() && PL_strstr(alt_service.get(), "quic"))
            ? true
            : false;
    Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);

    // Gather data on how many URLS get redirected
    switch (httpStatus) {
      case 200:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
        break;
      case 301:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
        break;
      case 302:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
        break;
      case 304:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
        break;
      case 307:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
        break;
      case 308:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
        break;
      case 400:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
        break;
      case 401:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
        break;
      case 403:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
        break;
      case 404:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
        break;
      case 500:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
        break;
      default:
        Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
        break;
    }
  }

  // Let the predictor know whether this was a cacheable response or not so
  // that it knows whether or not to possibly prefetch this resource in the
  // future.
  // We use GetReferringPage because mReferrerInfo may not be set at all(this is
  // especially useful in xpcshell tests, where we don't have an actual pageload
  // to get a referrer from).
  nsCOMPtr<nsIURI> referrer = GetReferringPage();
  if (!referrer && mReferrerInfo) {
    referrer = mReferrerInfo->GetOriginalReferrer();
  }

  if (referrer) {
    nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
    mozilla::net::Predictor::UpdateCacheability(
        referrer, mURI, httpStatus, mRequestHead, mResponseHead, lci,
        IsThirdPartyTrackingResource());
  }

  // Only allow 407 (authentication required) to continue
  if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
    return ProcessFailedProxyConnect(httpStatus);
  }

  MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
             "We should not be hitting the network if we have valid cached "
             "content unless we are racing the network and cache");

  ProcessSSLInformation();

  // notify "http-on-examine-response" observers
  gHttpHandler->OnExamineResponse(this);

  return ContinueProcessResponse1();
}

void nsHttpChannel::AsyncContinueProcessResponse() {
  nsresult rv;
  rv = ContinueProcessResponse1();
  if (NS_FAILED(rv)) {
    // A synchronous failure here would normally be passed as the return
    // value from OnStartRequest, which would in turn cancel the request.
    // If we're continuing asynchronously, we need to cancel the request
    // ourselves.
    Unused << Cancel(rv);
  }
}

nsresult nsHttpChannel::ContinueProcessResponse1() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");
  nsresult rv;

  if (mSuspendCount) {
    LOG(("Waiting until resume to finish processing response [this=%p]\n",
         this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->AsyncContinueProcessResponse();
      return NS_OK;
    };
    return NS_OK;
  }

  // Check if request was cancelled during http-on-examine-response.
  if (mCanceled) {
    return CallOnStartRequest();
  }

  uint32_t httpStatus = mResponseHead->Status();

  // STS, Cookies and Alt-Service should not be handled on proxy failure.
  // If proxy CONNECT response needs to complete, wait to process connection
  // for Strict-Transport-Security.
  if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
      (httpStatus != 407)) {
    nsAutoCString cookie;
    if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
      SetCookie(cookie);
    }

    // Given a successful connection, process any STS or PKP data that's
    // relevant.
    DebugOnly<nsresult> rv = ProcessSecurityHeaders();
    MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");

    if ((httpStatus < 500) && (httpStatus != 421)) {
      ProcessAltService();
    }
  }

  if (mConcurrentCacheAccess && mCachedContentIsPartial && httpStatus != 206) {
    LOG(
        ("  only expecting 206 when doing partial request during "
         "interrupted cache concurrent read"));
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  // handle unused username and password in url (see bug 232567)
  if (httpStatus != 401 && httpStatus != 407) {
    if (!mAuthRetryPending) {
      rv = mAuthProvider->CheckForSuperfluousAuth();
      if (NS_FAILED(rv)) {
        LOG(("  CheckForSuperfluousAuth failed (%08x)",
             static_cast<uint32_t>(rv)));
      }
    }
    if (mCanceled) return CallOnStartRequest();

    // reset the authentication's current continuation state because ourvr
    // last authentication attempt has been completed successfully
    rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
    if (NS_FAILED(rv)) {
      LOG(("  Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
    }
    mAuthProvider = nullptr;
    LOG(("  continuation state has been reset"));
  }

  rv = ProcessCrossOriginEmbedderPolicyHeader();
  if (NS_FAILED(rv)) {
    mStatus = NS_ERROR_BLOCKED_BY_POLICY;
    HandleAsyncAbort();
    return NS_OK;
  }

  rv = ProcessCrossOriginResourcePolicyHeader();
  if (NS_FAILED(rv)) {
    mStatus = NS_ERROR_DOM_CORP_FAILED;
    HandleAsyncAbort();
    return NS_OK;
  }

  rv = NS_OK;
  if (!mCanceled) {
    rv = ComputeCrossOriginOpenerPolicyMismatch();
    if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
      // this navigates the doc's browsing context to a network error.
      mStatus = NS_ERROR_BLOCKED_BY_POLICY;
      HandleAsyncAbort();
      return NS_OK;
    }

    AssertNotDocumentChannel();
  }

  // No process switch needed, continue as normal.
  return ContinueProcessResponse2(rv);
}

void nsHttpChannel::AssertNotDocumentChannel() {
  if (!mLoadInfo || !IsDocument()) {
    return;
  }

#ifndef DEBUG
  if (!StaticPrefs::fission_autostart()) {
    // This assertion is firing in the wild (Bug 1593545) and its not clear
    // why. Disable the assertion in non-fission non-debug configurations to
    // avoid crashing user's browsers until we're done dogfooding fission.
    return;
  }
#endif

  nsCOMPtr<nsIParentChannel> parentChannel;
  NS_QueryNotificationCallbacks(this, parentChannel);
  RefPtr<DocumentChannelParent> documentChannelParent =
      do_QueryObject(parentChannel);
  if (documentChannelParent) {
    // The load is using document channel.
    return;
  }

  RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
  if (!httpParent) {
    // The load was initiated in the parent and doesn't need document
    // channel.
    return;
  }

  nsContentPolicyType contentPolicy;
  MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetExternalContentPolicyType(&contentPolicy));
  RefPtr<BrowsingContext> bc;
  if (contentPolicy == CSPService::TYPE_DOCUMENT) {
    MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
  } else {
    MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetFrameBrowsingContext(getter_AddRefs(bc)));
  }
  if (!bc) {
    return;
  }

  if (mLoadInfo->LoadingPrincipal() &&
      mLoadInfo->LoadingPrincipal()->IsSystemPrincipal()) {
    // Loads with the system principal can skip document channel
    return;
  }

  // The load was supposed to use document channel but didn't.
  MOZ_DIAGNOSTIC_ASSERT(
      !StaticPrefs::browser_tabs_documentchannel(),
      "DocumentChannel is enabled but this load was done without it");
}

nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
  if (NS_FAILED(rv) && !mCanceled) {
    // The process switch failed, cancel this channel.
    Cancel(rv);
    return CallOnStartRequest();
  }

  if (mAPIRedirectToURI && !mCanceled) {
    MOZ_ASSERT(!mOnStartRequestCalled);
    nsCOMPtr<nsIURI> redirectTo;
    mAPIRedirectToURI.swap(redirectTo);

    PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
    rv = StartRedirectChannelToURI(redirectTo,
                                   nsIChannelEventSink::REDIRECT_TEMPORARY);
    if (NS_SUCCEEDED(rv)) {
      return NS_OK;
    }
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
  }

  // Hack: ContinueProcessResponse3 uses NS_OK to detect successful
  // redirects, so we distinguish this codepath (a non-redirect that's
  // processing normally) by passing in a bogus error code.
  return ContinueProcessResponse3(NS_BINDING_FAILED);
}

nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
  LOG(("nsHttpChannel::ContinueProcessResponse3 [this=%p, rv=%" PRIx32 "]",
       this, static_cast<uint32_t>(rv)));

  if (NS_SUCCEEDED(rv)) {
    // redirectTo() has passed through, we don't want to go on with
    // this channel.  It will now be canceled by the redirect handling
    // code that called this function.
    return NS_OK;
  }

  rv = NS_OK;

  uint32_t httpStatus = mResponseHead->Status();

  // handle different server response categories.  Note that we handle
  // caching or not caching of error pages in
  // nsHttpResponseHead::MustValidate; if you change this switch, update that
  // one
  switch (httpStatus) {
    case 200:
    case 203:
      // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
      // So if a server does that and sends 200 instead of 206 that we
      // expect, notify our caller.
      // However, if we wanted to start from the beginning, let it go through
      if (mResuming && mStartPos != 0) {
        LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
        Cancel(NS_ERROR_NOT_RESUMABLE);
        rv = CallOnStartRequest();
        break;
      }
      // these can normally be cached
      rv = ProcessNormal();
      MaybeInvalidateCacheEntryForSubsequentGet();
      break;
    case 206:
      if (mCachedContentIsPartial) {  // an internal byte range request...
        auto func = [](auto* self, nsresult aRv) {
          return self->ContinueProcessResponseAfterPartialContent(aRv);
        };
        rv = ProcessPartialContent(func);
        // Directly call ContinueProcessResponseAfterPartialContent if channel
        // is not suspended or ProcessPartialContent throws.
        if (!mSuspendCount || NS_FAILED(rv)) {
          return ContinueProcessResponseAfterPartialContent(rv);
        }
        return NS_OK;
      } else {
        mCacheInputStream.CloseAndRelease();
        rv = ProcessNormal();
      }
      break;
    case 300:
    case 301:
    case 302:
    case 307:
    case 308:
    case 303:
#if 0
    case 305: // disabled as a security measure (see bug 187996).
#endif
      // don't store the response body for redirects
      MaybeInvalidateCacheEntryForSubsequentGet();
      PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
      rv = AsyncProcessRedirection(httpStatus);
      if (NS_FAILED(rv)) {
        PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
        LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
             static_cast<uint32_t>(rv)));
        // don't cache failed redirect responses.
        if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
        if (DoNotRender3xxBody(rv)) {
          mStatus = rv;
          DoNotifyListener();
        } else {
          rv = ContinueProcessResponse4(rv);
        }
      }
      break;
    case 304:
      if (!ShouldBypassProcessNotModified()) {
        auto func = [](auto* self, nsresult aRv) {
          return self->ContinueProcessResponseAfterNotModified(aRv);
        };
        rv = ProcessNotModified(func);
        // Directly call ContinueProcessResponseAfterNotModified if channel
        // is not suspended or ProcessNotModified throws.
        if (!mSuspendCount || NS_FAILED(rv)) {
          return ContinueProcessResponseAfterNotModified(rv);
        }
        return NS_OK;
      }

      // Don't cache uninformative 304
      if (mCustomConditionalRequest) {
        CloseCacheEntry(false);
      }

      if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
        rv = ProcessNormal();
      }
      break;
    case 401:
    case 407:
      if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
        // When a custom auth header fails, we don't want to try
        // any cached credentials, nor we want to ask the user.
        // It's up to the consumer to re-try w/o setting a custom
        // auth header if cached credentials should be attempted.
        rv = NS_ERROR_FAILURE;
      } else {
        rv = mAuthProvider->ProcessAuthentication(
            httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
                            mTransaction->ProxyConnectFailed());
      }
      if (rv == NS_ERROR_IN_PROGRESS) {
        // authentication prompt has been invoked and result
        // is expected asynchronously
        mAuthRetryPending = true;
        if (httpStatus == 407 ||
            (mTransaction && mTransaction->ProxyConnectFailed()))
          mProxyAuthPending = true;

        // suspend the transaction pump to stop receiving the
        // unauthenticated content data. We will throw that data
        // away when user provides credentials or resume the pump
        // when user refuses to authenticate.
        LOG(
            ("Suspending the transaction, asynchronously prompting for "
             "credentials"));
        mTransactionPump->Suspend();
        rv = NS_OK;
      } else if (NS_FAILED(rv)) {
        LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
             static_cast<uint32_t>(rv)));
        if (mTransaction && mTransaction->ProxyConnectFailed()) {
          return ProcessFailedProxyConnect(httpStatus);
        }
        if (!mAuthRetryPending) {
          rv = mAuthProvider->CheckForSuperfluousAuth();
          if (NS_FAILED(rv)) {
            LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
                 static_cast<uint32_t>(rv)));
          }
        }
        rv = ProcessNormal();
      } else {
        mAuthRetryPending = true;  // see DoAuthRetry
      }
      break;

    case 425:
    case 429:
      // Do not cache 425 and 429.
      CloseCacheEntry(false);
      MOZ_FALLTHROUGH;  // process normally
    default:
      rv = ProcessNormal();
      MaybeInvalidateCacheEntryForSubsequentGet();
      break;
  }

  UpdateCacheDisposition(false, false);
  return rv;
}

nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
    nsresult aRv) {
  LOG(
      ("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
       "[this=%p, rv=%" PRIx32 "]",
       this, static_cast<uint32_t>(aRv)));

  UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
  return aRv;
}

nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
  LOG(
      ("nsHttpChannel::ContinueProcessResponseAfterNotModified "
       "[this=%p, rv=%" PRIx32 "]",
       this, static_cast<uint32_t>(aRv)));

  if (NS_SUCCEEDED(aRv)) {
    mTransactionReplaced = true;
    UpdateCacheDisposition(true, false);
    return NS_OK;
  }

  LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
       static_cast<uint32_t>(aRv)));

  // We cannot read from the cache entry, it might be in an
  // incosistent state.  Doom it and redirect the channel
  // to the same URI to reload from the network.
  mCacheInputStream.CloseAndRelease();
  if (mCacheEntry) {
    mCacheEntry->AsyncDoom(nullptr);
    mCacheEntry = nullptr;
  }

  nsresult rv =
      StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
  if (NS_SUCCEEDED(rv)) {
    return NS_OK;
  }

  // Don't cache uninformative 304
  if (mCustomConditionalRequest) {
    CloseCacheEntry(false);
  }

  if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
    rv = ProcessNormal();
  }

  UpdateCacheDisposition(false, false);
  return rv;
}

void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
                                           bool aPartialContentUsed) {
  if (mRaceDelay && !mRaceCacheWithNetwork &&
      (mCachedContentIsPartial || mDidReval)) {
    if (aSuccessfulReval || aPartialContentUsed) {
      AccumulateCategorical(
          Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
    } else {
      AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
                                CachedContentNotUsed);
    }
  }

  if (Telemetry::CanRecordPrereleaseData()) {
    CacheDisposition cacheDisposition;
    if (!mDidReval) {
      cacheDisposition = kCacheMissed;
    } else if (aSuccessfulReval) {
      cacheDisposition = kCacheHitViaReval;
    } else {
      cacheDisposition = kCacheMissedViaReval;
    }
    AccumulateCacheHitTelemetry(cacheDisposition);
    mCacheDisposition = cacheDisposition;

    Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
                          static_cast<uint32_t>(mResponseHead->Version()));

    if (mResponseHead->Version() == HttpVersion::v0_9) {
      // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
      // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
      uint32_t v09Info = 0;
      if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
        v09Info += 1;
      }
      if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
        v09Info += 2;
      }
      Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
    }
  }
}

nsresult nsHttpChannel::ContinueProcessResponse4(nsresult rv) {
  bool doNotRender = DoNotRender3xxBody(rv);

  if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
    bool isHTTP =
        mRedirectURI->SchemeIs("http") || mRedirectURI->SchemeIs("https");
    if (!isHTTP) {
      // This was a blocked attempt to redirect and subvert the system by
      // redirecting to another protocol (perhaps javascript:)
      // In that case we want to throw an error instead of displaying the
      // non-redirected response body.
      LOG(("ContinueProcessResponse4 detected rejected Non-HTTP Redirection"));
      doNotRender = true;
      rv = NS_ERROR_CORRUPTED_CONTENT;
    }
  }

  if (doNotRender) {
    Cancel(rv);
    DoNotifyListener();
    return rv;
  }

  if (NS_SUCCEEDED(rv)) {
    UpdateInhibitPersistentCachingFlag();

    rv = InitCacheEntry();
    if (NS_FAILED(rv)) {
      LOG(
          ("ContinueProcessResponse4 "
           "failed to init cache entry [rv=%x]\n",
           static_cast<uint32_t>(rv)));
    }
    CloseCacheEntry(false);

    if (mApplicationCacheForWrite) {
      // Store response in the offline cache
      Unused << InitOfflineCacheEntry();
      CloseOfflineCacheEntry();
    }
    return NS_OK;
  }

  LOG(("ContinueProcessResponse4 got failure result [rv=%" PRIx32 "]\n",
       static_cast<uint32_t>(rv)));
  if (mTransaction && mTransaction->ProxyConnectFailed()) {
    return ProcessFailedProxyConnect(mRedirectType);
  }
  return ProcessNormal();
}

nsresult nsHttpChannel::ProcessNormal() {
  nsresult rv;

  LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));

  bool succeeded;
  rv = GetRequestSucceeded(&succeeded);
  if (NS_SUCCEEDED(rv) && !succeeded) {
    PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
    bool waitingForRedirectCallback;
    Unused << ProcessFallback(&waitingForRedirectCallback);
    if (waitingForRedirectCallback) {
      // The transaction has been suspended by ProcessFallback.
      return NS_OK;
    }
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
  }

  return ContinueProcessNormal(NS_OK);
}

nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
  LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));

  if (NS_FAILED(rv)) {
    // Fill the failure status here, we have failed to fall back, thus we
    // have to report our status as failed.
    mStatus = rv;
    DoNotifyListener();
    return rv;
  }

  if (mFallingBack) {
    // Do not continue with normal processing, fallback is in
    // progress now.
    return NS_OK;
  }

  // if we're here, then any byte-range requests failed to result in a partial
  // response.  we must clear this flag to prevent BufferPartialContent from
  // being called inside our OnDataAvailable (see bug 136678).
  mCachedContentIsPartial = false;

  ClearBogusContentEncodingIfNeeded();

  UpdateInhibitPersistentCachingFlag();

  // this must be called before firing OnStartRequest, since http clients,
  // such as imagelib, expect our cache entry to already have the correct
  // expiration time (bug 87710).
  if (mCacheEntry) {
    rv = InitCacheEntry();
    if (NS_FAILED(rv)) CloseCacheEntry(true);
  }

  // Check that the server sent us what we were asking for
  if (mResuming) {
    // Create an entity id from the response
    nsAutoCString id;
    rv = GetEntityID(id);
    if (NS_FAILED(rv)) {
      // If creating an entity id is not possible -> error
      Cancel(NS_ERROR_NOT_RESUMABLE);
    } else if (mResponseHead->Status() != 206 &&
               mResponseHead->Status() != 200) {
      // Probably 404 Not Found, 412 Precondition Failed or
      // 416 Invalid Range -> error
      LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
           this));
      Cancel(NS_ERROR_ENTITY_CHANGED);
    }
    // If we were passed an entity id, verify it's equal to the server's
    else if (!mEntityID.IsEmpty()) {
      if (!mEntityID.Equals(id)) {
        LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
             mEntityID.get(), id.get(), this));
        Cancel(NS_ERROR_ENTITY_CHANGED);
      }
    }
  }

  rv = CallOnStartRequest();
  if (NS_FAILED(rv)) return rv;

  // install cache listener if we still have a cache entry open
  if (mCacheEntry && !mCacheEntryIsReadOnly) {
    rv = InstallCacheListener();
    if (NS_FAILED(rv)) return rv;
  }

  return NS_OK;
}

nsresult nsHttpChannel::PromptTempRedirect() {
  if (!gHttpHandler->PromptTempRedirect()) {
    return NS_OK;
  }
  nsresult rv;
  nsCOMPtr<nsIStringBundleService> bundleService =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIStringBundle> stringBundle;
  rv =
      bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
  if (NS_FAILED(rv)) return rv;

  nsAutoString messageString;
  rv = stringBundle->GetStringFromName("RepostFormData", messageString);
  if (NS_SUCCEEDED(rv)) {
    bool repost = false;

    nsCOMPtr<nsIPrompt> prompt;
    GetCallback(prompt);
    if (!prompt) return NS_ERROR_NO_INTERFACE;

    prompt->Confirm(nullptr, messageString.get(), &repost);
    if (!repost) return NS_ERROR_FAILURE;
  }

  return rv;
}

nsresult nsHttpChannel::ProxyFailover() {
  LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));

  nsresult rv;

  nsCOMPtr<nsIProtocolProxyService> pps =
      do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIProxyInfo> pi;
  rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
                                getter_AddRefs(pi));
  if (NS_FAILED(rv)) return rv;

  // XXXbz so where does this codepath remove us from the loadgroup,
  // exactly?
  return AsyncDoReplaceWithProxy(pi);
}

void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");

  if (mSuspendCount) {
    LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
         this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleAsyncRedirectChannelToHttps();
      return NS_OK;
    };
    return;
  }

  nsresult rv = StartRedirectChannelToHttps();
  if (NS_FAILED(rv)) {
    rv = ContinueAsyncRedirectChannelToURI(rv);
    if (NS_FAILED(rv)) {
      LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
           static_cast<uint32_t>(rv), this));
    }
  }
}

nsresult nsHttpChannel::StartRedirectChannelToHttps() {
  LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));

  nsCOMPtr<nsIURI> upgradedURI;
  nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
  NS_ENSURE_SUCCESS(rv, rv);

  return StartRedirectChannelToURI(
      upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
                       nsIChannelEventSink::REDIRECT_STS_UPGRADE);
}

void nsHttpChannel::HandleAsyncAPIRedirect() {
  MOZ_ASSERT(!mCallOnResume, "How did that happen?");
  MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");

  if (mSuspendCount) {
    LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
    mCallOnResume = [](nsHttpChannel* self) {
      self->HandleAsyncAPIRedirect();
      return NS_OK;
    };
    return;
  }

  nsresult rv = StartRedirectChannelToURI(
      mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
  if (NS_FAILED(rv)) {
    rv = ContinueAsyncRedirectChannelToURI(rv);
    if (NS_FAILED(rv)) {
      LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
           static_cast<uint32_t>(rv), this));
    }
  }
}

nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI* upgradedURI,
                                                  uint32_t flags) {
  nsresult rv = NS_OK;
  LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));

  nsCOMPtr<nsIChannel> newChannel;
  nsCOMPtr<nsILoadInfo> redirectLoadInfo =
      CloneLoadInfoForRedirect(upgradedURI, flags);

  nsCOMPtr<nsIIOService> ioService;
  rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
                             redirectLoadInfo,
                             nullptr,  // PerformanceStorage
                             nullptr,  // aLoadGroup
                             nullptr,  // aCallbacks
                             nsIRequest::LOAD_NORMAL, ioService);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
  NS_ENSURE_SUCCESS(rv, rv);

  // Inform consumers about this fake redirect
  mRedirectChannel = newChannel;

  PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
  rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);

  if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();

  if (NS_FAILED(rv)) {
    AutoRedirectVetoNotifier notifier(this);

    /* Remove the async call to ContinueAsyncRedirectChannelToURI().
     * It is called directly by our callers upon return (to clean up
     * the failed redirect). */
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
  }

  return rv;
}

nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
  LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));

  // Since we handle mAPIRedirectToURI also after on-examine-response handler
  // rather drop it here to avoid any redirect loops, even just hypothetical.
  mAPIRedirectToURI = nullptr;

  if (NS_SUCCEEDED(rv)) {
    rv = OpenRedirectChannel(rv);
  }

  if (NS_FAILED(rv)) {
    // Cancel the channel here, the update to https had been vetoed
    // but from the security reasons we have to discard the whole channel
    // load.
    Cancel(rv);
  }

  if (mLoadGroup) {
    mLoadGroup->RemoveRequest(this, nullptr, mStatus);
  }

  if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
    // We have to manually notify the listener because there is not any pump
    // that would call our OnStart/StopRequest after resume from waiting for
    // the redirect callback.
    DoNotifyListener();
  }

  return rv;
}

nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
  AutoRedirectVetoNotifier notifier(this);

  // Make sure to do this after we received redirect veto answer,
  // i.e. after all sinks had been notified
  mRedirectChannel->SetOriginalURI(mOriginalURI);

  // open new channel
  rv = mRedirectChannel->AsyncOpen(mListener);

  NS_ENSURE_SUCCESS(rv, rv);

  mStatus = NS_BINDING_REDIRECTED;

  notifier.RedirectSucceeded();

  ReleaseListeners();

  return NS_OK;
}

nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) {
  LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
  nsresult rv;

  nsCOMPtr<nsIChannel> newChannel;
  rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI,
                                       mLoadInfo, getter_AddRefs(newChannel));
  if (NS_FAILED(rv)) return rv;

  uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;

  rv = SetupReplacementChannel(mURI, newChannel, true, flags);
  if (NS_FAILED(rv)) return rv;

  // Inform consumers about this fake redirect
  mRedirectChannel = newChannel;

  PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
  rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);

  if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();

  if (NS_FAILED(rv)) {
    AutoRedirectVetoNotifier notifier(this);
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
  }

  return rv;
}

nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
  AutoRedirectVetoNotifier notifier(this);

  if (NS_FAILED(rv)) return rv;

  MOZ_ASSERT(mRedirectChannel, "No redirect channel?");

  // Make sure to do this after we received redirect veto answer,
  // i.e. after all sinks had been notified
  mRedirectChannel->SetOriginalURI(mOriginalURI);

  // open new channel
  rv = mRedirectChannel->AsyncOpen(mListener);
  NS_ENSURE_SUCCESS(rv, rv);

  mStatus = NS_BINDING_REDIRECTED;

  notifier.RedirectSucceeded();

  ReleaseListeners();

  return rv;
}

nsresult nsHttpChannel::ResolveProxy() {
  LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));

  nsresult rv;

  nsCOMPtr<nsIProtocolProxyService> pps =
      do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;

  // using the nsIProtocolProxyService2 allows a minor performance
  // optimization, but if an add-on has only provided the original interface
  // then it is ok to use that version.
  nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
  if (pps2) {
    rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
                             getter_AddRefs(mProxyRequest));
  } else {
    rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
                           this, nullptr, getter_AddRefs(mProxyRequest));
  }

  return rv;
}

bool nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) {
  nsresult rv;
  nsAutoCString buf, metaKey;
  Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
  if (!buf.IsEmpty()) {
    NS_NAMED_LITERAL_CSTRING(prefix, "request-");

    // enumerate the elements of the Vary header...
    char* val = buf.BeginWriting();  // going to munge buf
    char* token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
    while (token) {
      LOG(
          ("nsHttpChannel::ResponseWouldVary [channel=%p] "
           "processing %s\n",
           this, token));
      //
      // if "*", then assume response would vary.  technically speaking,
      // "Vary: header, *" is not permitted, but we allow it anyways.
      //
      // We hash values of cookie-headers for the following reasons:
      //
      //   1- cookies can be very large in size
      //
      //   2- cookies may contain sensitive information.  (for parity with
      //      out policy of not storing Set-cookie headers in the cache
      //      meta data, we likewise do not want to store cookie headers
      //      here.)
      //
      if (*token == '*')
        return true;  // if we encounter this, just get out of here

      // build cache meta data key...
      metaKey = prefix + nsDependentCString(token);

      // check the last value of the given request header to see if it has
      // since changed.  if so, then indeed the cached response is invalid.
      nsCString lastVal;
      entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
      LOG(
          ("nsHttpChannel::ResponseWouldVary [channel=%p] "
           "stored value = \"%s\"\n",
           this, lastVal.get()));

      // Look for value of "Cookie" in the request headers
      nsHttpAtom atom = nsHttp::ResolveAtom(token);
      nsAutoCString newVal;
      bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
      if (!lastVal.IsEmpty()) {
        // value for this header in cache, but no value in request
        if (!hasHeader) {
          return true;  // yes - response would vary
        }

        // If this is a cookie-header, stored metadata is not
        // the value itself but the hash. So we also hash the
        // outgoing value here in order to compare the hashes
        nsAutoCString hash;
        if (atom == nsHttp::Cookie) {
          rv = Hash(newVal.get(), hash);
          // If hash failed, be conservative (the cached hash
          // exists at this point) and claim response would vary
          if (NS_FAILED(rv)) return true;
          newVal = hash;

          LOG(
              ("nsHttpChannel::ResponseWouldVary [this=%p] "
               "set-cookie value hashed to %s\n",
               this, newVal.get()));
        }

        if (!newVal.Equals(lastVal)) {
          return true;  // yes, response would vary
        }

      } else if (hasHeader) {  // old value is empty, but newVal is set
        return true;
      }

      // next token...
      token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
    }
  }
  return false;
}

// We need to have an implementation of this function just so that we can keep
// all references to mCallOnResume of type nsHttpChannel:  it's not OK in C++
// to set a member function ptr to  a base class function.
void nsHttpChannel::HandleAsyncAbort() {
  HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
}

//-----------------------------------------------------------------------------
// nsHttpChannel <byte-range>
//-----------------------------------------------------------------------------

bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
                                bool ignoreMissingPartialLen) const {
  bool hasContentEncoding =
      mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);

  nsAutoCString etag;
  Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
  bool hasWeakEtag =
      !etag.IsEmpty() && StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));

  return (partialLen < contentLength) &&
         (partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
         !hasWeakEtag && mCachedResponseHead->IsResumable() &&
         !mCustomConditionalRequest && !mCachedResponseHead->NoStore();
}

nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
    int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
  // Be pesimistic
  mIsPartialRequest = false;

  if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen))
    return NS_ERROR_NOT_RESUMABLE;

  // looks like a partial entry we can reuse; add If-Range
  // and Range headers.
  nsresult rv = SetupByteRangeRequest(partialLen);
  if (NS_FAILED(rv)) {
    // Make the request unconditional again.
    UntieByteRangeRequest();
  }

  return rv;
}

nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
  // cached content has been found to be partial, add necessary request
  // headers to complete cache entry.

  // use strongest validator available...
  nsAutoCString val;
  Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
  if (val.IsEmpty())
    Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
  if (val.IsEmpty()) {
    // if we hit this code it means mCachedResponseHead->IsResumable() is
    // either broken or not being called.
    MOZ_ASSERT_UNREACHABLE("no cache validator");
    mIsPartialRequest = false;
    return NS_ERROR_FAILURE;
  }

  char buf[64];
  SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);

  DebugOnly<nsresult> rv;
  rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  mIsPartialRequest = true;

  return NS_OK;
}

void nsHttpChannel::UntieByteRangeRequest() {
  DebugOnly<nsresult> rv;
  rv = mRequestHead.ClearHeader(nsHttp::Range);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  rv = mRequestHead.ClearHeader(nsHttp::If_Range);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

nsresult nsHttpChannel::ProcessPartialContent(
    const std::function<nsresult(nsHttpChannel*, nsresult)>&
        aContinueProcessResponseFunc) {
  // ok, we've just received a 206
  //
  // we need to stream whatever data is in the cache out first, and then
  // pick up whatever data is on the wire, writing it into the cache.

  LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));

  NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
  NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);

  // Make sure to clear bogus content-encodings before looking at the header
  ClearBogusContentEncodingIfNeeded();

  // Check if the content-encoding we now got is different from the one we
  // got before
  nsAutoCString contentEncoding, cachedContentEncoding;
  // It is possible that there is not such headers
  Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
  Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
                                           cachedContentEncoding);
  if (PL_strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) != 0) {
    Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
    return CallOnStartRequest();
  }

  nsresult rv;

  int64_t cachedContentLength = mCachedResponseHead->ContentLength();
  int64_t entitySize = mResponseHead->TotalEntitySize();

  nsAutoCString contentRange;
  Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
  LOG(
      ("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
       "original content-length %" PRId64 ", entity-size %" PRId64
       ", content-range %s\n",
       this, mTransaction.get(), cachedContentLength, entitySize,
       contentRange.get()));

  if ((entitySize >= 0) && (cachedContentLength >= 0) &&
      (entitySize != cachedContentLength)) {
    LOG(
        ("nsHttpChannel::ProcessPartialContent [this=%p] "
         "206 has different total entity size than the content length "
         "of the original partially cached entity.\n",
         this));

    mCacheEntry->AsyncDoom(nullptr);
    Cancel(NS_ERROR_CORRUPTED_CONTENT);
    return CallOnStartRequest();
  }

  if (mConcurrentCacheAccess) {
    // We started to read cached data sooner than its write has been done.
    // But the concurrent write has not finished completely, so we had to
    // do a range request.  Now let the content coming from the network
    // be presented to consumers and also stored to the cache entry.

    rv = InstallCacheListener(mLogicalOffset);
    if (NS_FAILED(rv)) return rv;

    if (mOfflineCacheEntry) {
      rv = InstallOfflineCacheListener(mLogicalOffset);
      if (NS_FAILED(rv)) return rv;
    }
  } else {
    // suspend the current transaction
    rv = mTransactionPump->Suspend();
    if (NS_FAILED(rv)) return rv;
  }

  // merge any new headers with the cached response headers
  rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
  if (NS_FAILED(rv)) return rv;

  // update the cached response head
  nsAutoCString head;
  mCachedResponseHead->Flatten(head, true);
  rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
  if (NS_FAILED(rv)) return rv;

  // make the cached response be the current response
  mResponseHead = std::move(mCachedResponseHead);

  UpdateInhibitPersistentCachingFlag();

  rv = UpdateExpirationTime();
  if (NS_FAILED(rv)) return rv;

  // notify observers interested in looking at a response that has been
  // merged with any cached headers (http-on-examine-merged-response).
  gHttpHandler->OnExamineMergedResponse(this);

  if (mConcurrentCacheAccess) {
    mCachedContentIsPartial = false;
    // Leave the mConcurrentCacheAccess flag set, we want to use it
    // to prevent duplicate OnStartRequest call on the target listener
    // in case this channel is canceled before it gets its OnStartRequest
    // from the http transaction.
    return rv;
  }

  // Now we continue reading the network response.
  // the cached content is valid, although incomplete.
  mCachedContentIsValid = true;
  return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
    nsresult rv = self->ReadFromCache(false);
    return aContinueProcessResponseFunc(self, rv);
  });
}

nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool* streamDone) {
  nsresult rv;

  LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));

  // by default, assume we would have streamed all data or failed...
  *streamDone = true;

  // setup cache listener to append to cache entry
  int64_t size;
  rv = mCacheEntry->GetDataSize(&size);
  if (NS_FAILED(rv)) return rv;

  rv = InstallCacheListener(size);
  if (NS_FAILED(rv)) return rv;

  // Entry is valid, do it now, after the output stream has been opened,
  // otherwise when done earlier, pending readers would consider the cache
  // entry still as partial (CacheEntry::GetDataSize would return the partial
  // data size) and consumers would do the conditional request again.
  rv = mCacheEntry->SetValid();
  if (NS_FAILED(rv)) return rv;

  // need to track the logical offset of the data being sent to our listener
  mLogicalOffset = size;

  // we're now completing the cached content, so we can clear this flag.
  // this puts us in the state of a regular download.
  mCachedContentIsPartial = false;
  // The cache input stream pump is finished, we do not need it any more.
  // (see bug 1313923)
  mCachePump = nullptr;

  // resume the transaction if it exists, otherwise the pipe contained the
  // remaining part of the document and we've now streamed all of the data.
  if (mTransactionPump) {
    rv = mTransactionPump->Resume();
    if (NS_SUCCEEDED(rv)) *streamDone = false;
  } else
    MOZ_ASSERT_UNREACHABLE("no transaction");
  return rv;
}

//-----------------------------------------------------------------------------
// nsHttpChannel <cache>
//-----------------------------------------------------------------------------

bool nsHttpChannel::ShouldBypassProcessNotModified() {
  if (mCustomConditionalRequest) {
    LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
    return true;
  }

  if (!mDidReval) {
    LOG(
        ("Server returned a 304 response even though we did not send a "
         "conditional request"));
    return true;
  }

  return false;
}

nsresult nsHttpChannel::ProcessNotModified(
    const std::function<nsresult(nsHttpChannel*, nsresult)>&
        aContinueProcessResponseFunc) {
  nsresult rv;

  LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));

  // Assert ShouldBypassProcessNotModified() has been checked before call to
  // ProcessNotModified().
  MOZ_ASSERT(!ShouldBypassProcessNotModified());

  MOZ_ASSERT(mCachedResponseHead);
  MOZ_ASSERT(mCacheEntry);
  NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);

  // If the 304 response contains a Last-Modified different than the
  // one in our cache that is pretty suspicious and is, in at least the
  // case of bug 716840, a sign of the server having previously corrupted
  // our cache with a bad response. Take the minor step here of just dooming
  // that cache entry so there is a fighting chance of getting things on the
  // right track.

  nsAutoCString lastModifiedCached;
  nsAutoCString lastModified304;

  rv =
      mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
  if (NS_SUCCEEDED(rv)) {
    rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
  }

  if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
    LOG(
        ("Cache Entry and 304 Last-Modified Headers Do Not Match "
         "[%s] and [%s]\n",
         lastModifiedCached.get(), lastModified304.get()));

    mCacheEntry->AsyncDoom(nullptr);
    Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
  }

  // merge any new headers with the cached response headers
  rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
  if (NS_FAILED(rv)) return rv;

  // update the cached response head
  nsAutoCString head;
  mCachedResponseHead->Flatten(head, true);
  rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
  if (NS_FAILED(rv)) return rv;

  // make the cached response be the current response
  mResponseHead = std::move(mCachedResponseHead);

  UpdateInhibitPersistentCachingFlag();

  rv = UpdateExpirationTime();
  if (NS_FAILED(rv)) return rv;

  rv = AddCacheEntryHeaders(mCacheEntry);
  if (NS_FAILED(rv)) return rv;

  // notify observers interested in looking at a reponse that has been
  // merged with any cached headers
  gHttpHandler->OnExamineMergedResponse(this);

  mCachedContentIsValid = true;

  // Tell other consumers the entry is OK to use
  rv = mCacheEntry->SetValid();
  if (NS_FAILED(rv)) return rv;

  return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
    nsresult rv = self->ReadFromCache(false);
    return aContinueProcessResponseFunc(self, rv);
  });
}

nsresult nsHttpChannel::ProcessFallback(bool* waitingForRedirectCallback) {
  LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
  nsresult rv;

  *waitingForRedirectCallback = false;
  mFallingBack = false;

  // At this point a load has failed (either due to network problems
  // or an error returned on the server).  Perform an application
  // cache fallback if we have a URI to fall back to.
  if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
    LOG(("  choosing not to fallback [%p,%s,%d]", mApplicationCache.get(),
         mFallbackKey.get(), mFallbackChannel));
    return NS_OK;
  }

  // Make sure the fallback entry hasn't been marked as a foreign
  // entry.
  uint32_t fallbackEntryType;
  rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
  NS_ENSURE_SUCCESS(rv, rv);

  if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
    // This cache points to a fallback that refers to a different
    // manifest.  Refuse to fall back.
    return NS_OK;
  }

  if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
    // Refuse to fallback if the fallback key is not contained in the same
    // path as the cache manifest.
    return NS_OK;
  }

  MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
             "Fallback entry not marked correctly!");

  // Kill any offline cache entry, and disable offline caching for the
  // fallback.
  if (mOfflineCacheEntry) {
    mOfflineCacheEntry->AsyncDoom(nullptr);
    mOfflineCacheEntry = nullptr;
  }

  mApplicationCacheForWrite = nullptr;
  mOfflineCacheEntry = nullptr;

  // Close the current cache entry.
  CloseCacheEntry(true);

  // Create a new channel to load the fallback entry.
  RefPtr<nsIChannel> newChannel;
  rv = gHttpHandler->NewChannel(mURI, mLoadInfo, getter_AddRefs(newChannel));
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
  rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
  NS_ENSURE_SUCCESS(rv, rv);

  // Make sure the new channel loads from the fallback key.
  nsCOMPtr<nsIHttpChannelInternal> httpInternal =
      do_QueryInterface(newChannel, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
  NS_ENSURE_SUCCESS(rv, rv);

  // ... and fallbacks should only load from the cache.
  uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
  rv = newChannel->SetLoadFlags(newLoadFlags);

  // Inform consumers about this fake redirect
  mRedirectChannel = newChannel;

  PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
  rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);

  if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();

  if (NS_FAILED(rv)) {
    AutoRedirectVetoNotifier notifier(this);
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
    return rv;
  }

  // Indicate we are now waiting for the asynchronous redirect callback
  // if all went OK.
  *waitingForRedirectCallback = true;
  return NS_OK;
}

nsresult nsHttpChannel::ContinueProcessFallback(nsresult rv) {
  AutoRedirectVetoNotifier notifier(this);

  if (NS_FAILED(rv)) return rv;

  MOZ_ASSERT(mRedirectChannel, "No redirect channel?");

  // Make sure to do this after we received redirect veto answer,
  // i.e. after all sinks had been notified
  mRedirectChannel->SetOriginalURI(mOriginalURI);

  rv = mRedirectChannel->AsyncOpen(mListener);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
    MaybeWarnAboutAppCache();
  }

  // close down this channel
  Cancel(NS_BINDING_REDIRECTED);

  notifier.RedirectSucceeded();

  ReleaseListeners();

  mFallingBack = true;

  return NS_OK;
}

// Determines if a request is a byte range request for a subrange,
// i.e. is a byte range request, but not a 0- byte range request.
static bool IsSubRangeRequest(nsHttpRequestHead& aRequestHead) {
  nsAutoCString byteRange;
  if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
    return false;
  }
  return !byteRange.EqualsLiteral("bytes=0-");
}

nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
  // Drop this flag here
  mConcurrentCacheAccess = 0;

  mLoadedFromApplicationCache = false;

  LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));

  // make sure we're not abusing this function
  MOZ_ASSERT(!mCacheEntry, "cache entry already open");

  if (mRequestHead.IsPost()) {
    // If the post id is already set then this is an attempt to replay
    // a post transaction via the cache.  Otherwise, we need a unique
    // post id for this transaction.
    if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
  } else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
    // don't use the cache for other types of requests
    return NS_OK;
  }

  // Pick up an application cache from the notification
  // callbacks if available and if we are not an intercepted channel.
  if (!mApplicationCache && mInheritApplicationCache) {
    nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
    GetCallback(appCacheContainer);

    if (appCacheContainer) {
      appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
    }
  }

  return OpenCacheEntryInternal(isHttps, mApplicationCache, true);
}

bool nsHttpChannel::IsIsolated() {
  if (mHasBeenIsolatedChecked) {
    return mIsIsolated;
  }
  mIsIsolated = StaticPrefs::browser_cache_cache_isolation() ||
                (IsThirdPartyTrackingResource() &&
                 !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
                     this, mURI, nullptr));
  mHasBeenIsolatedChecked = true;
  return mIsIsolated;
}

const nsCString& nsHttpChannel::GetTopWindowOrigin() {
  if (mTopWindowOriginComputed) {
    return mTopWindowOrigin;
  }

  nsCOMPtr<nsIURI> topWindowURI;
  nsresult rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
  bool isDocument = false;
  if (NS_FAILED(rv) && NS_SUCCEEDED(GetIsMainDocumentChannel(&isDocument)) &&
      isDocument) {
    // For top-level documents, use the document channel's origin to compute
    // the unique storage space identifier instead of the top Window URI.
    rv = NS_GetFinalChannelURI(this, getter_AddRefs(topWindowURI));
    NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
  }

  rv = nsContentUtils::GetASCIIOrigin(topWindowURI ? topWindowURI : mURI,
                                      mTopWindowOrigin);
  NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);

  mTopWindowOriginComputed = true;

  return mTopWindowOrigin;
}

nsresult nsHttpChannel::OpenCacheEntryInternal(
    bool isHttps, nsIApplicationCache* applicationCache,
    bool allowApplicationCache) {
  MOZ_ASSERT_IF(!allowApplicationCache, !applicationCache);

  nsresult rv;

  if (mResuming) {
    // We don't support caching for requests initiated
    // via nsIResumableChannel.
    return NS_OK;
  }

  // Don't cache byte range requests which are subranges, only cache 0-
  // byte range requests.
  if (IsSubRangeRequest(mRequestHead)) {
    return NS_OK;
  }

  // Handle correctly mCacheEntriesToWaitFor
  AutoCacheWaitFlags waitFlags(this);

  nsAutoCString cacheKey;
  nsAutoCString extension;

  nsCOMPtr<nsICacheStorageService> cacheStorageService(
      services::GetCacheStorageService());
  if (!cacheStorageService) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCOMPtr<nsICacheStorage> cacheStorage;
  nsCOMPtr<nsIURI> openURI;
  if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
    // This is a fallback channel, open fallback URI instead
    rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    openURI = mURI;
  }

  RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
  if (!info) {
    return NS_ERROR_FAILURE;
  }

  uint32_t cacheEntryOpenFlags;
  bool offline = gIOService->IsOffline();

  bool maybeRCWN = false;

  nsAutoCString cacheControlRequestHeader;
  Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
                                   cacheControlRequestHeader);
  CacheControlParser cacheControlRequest(cacheControlRequestHeader);
  if (cacheControlRequest.NoStore()) {
    goto bypassCacheEntryOpen;
  }

  if (offline || (mLoadFlags & INHIBIT_CACHING)) {
    if (BYPASS_LOCAL_CACHE(mLoadFlags, mPreferCacheLoadOverBypass) &&
        !offline) {
      goto bypassCacheEntryOpen;
    }
    cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
    mCacheEntryIsReadOnly = true;
  } else if (BYPASS_LOCAL_CACHE(mLoadFlags, mPreferCacheLoadOverBypass) &&
             !applicationCache) {
    cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
  } else {
    cacheEntryOpenFlags =
        nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
  }

  // Remember the request is a custom conditional request so that we can
  // process any 304 response correctly.
  mCustomConditionalRequest =
      mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
      mRequestHead.HasHeader(nsHttp::If_None_Match) ||
      mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
      mRequestHead.HasHeader(nsHttp::If_Match) ||
      mRequestHead.HasHeader(nsHttp::If_Range);

  if (!mPostID && applicationCache) {
    rv = cacheStorageService->AppCacheStorage(info, applicationCache,
                                              getter_AddRefs(cacheStorage));
  } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
    rv = cacheStorageService->MemoryCacheStorage(
        info,  // ? choose app cache as well...
        getter_AddRefs(cacheStorage));
  } else if (mPinCacheContent) {
    rv = cacheStorageService->PinningCacheStorage(info,
                                                  getter_AddRefs(cacheStorage));
  } else {
    bool lookupAppCache =
        (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)) &&
        !mPostID && MOZ_LIKELY(allowApplicationCache);
    // Try to race only if we use disk cache storage and we don't lookup
    // app cache first
    maybeRCWN = (!lookupAppCache) && mRequestHead.IsSafeMethod();
    rv = cacheStorageService->DiskCacheStorage(info, lookupAppCache,
                                               getter_AddRefs(cacheStorage));
  }
  NS_ENSURE_SUCCESS(rv, rv);

  if ((mClassOfService & nsIClassOfService::Leader) ||
      (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
    cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;

  // Only for backward compatibility with the old cache back end.
  // When removed, remove the flags and related code snippets.
  if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
    cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;

  if (mPostID) {
    extension.Append(nsPrintfCString("%d", mPostID));
  }
  if (mIsTRRServiceChannel) {
    extension.Append("TRR");
  }
  if (mRequestHead.IsHead()) {
    extension.Append("HEAD");
  }

  if (IsIsolated()) {
    auto& topWindowOrigin = GetTopWindowOrigin();
    if (topWindowOrigin.IsEmpty()) {
      return NS_ERROR_FAILURE;
    }

    extension.Append("-unique:");
    extension.Append(topWindowOrigin);
  }

  mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
  mCacheQueueSizeWhenOpen =
      CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);

  if (sRCWNEnabled && maybeRCWN && !mApplicationCacheForWrite) {
    bool hasAltData = false;
    uint32_t sizeInKb = 0;
    rv = cacheStorage->GetCacheIndexEntryAttrs(openURI, extension, &hasAltData,
                                               &sizeInKb);

    // We will attempt to race the network vs the cache if we've found
    // this entry in the cache index, and it has appropriate attributes
    // (doesn't have alt-data, and has a small size)
    if (NS_SUCCEEDED(rv) && !hasAltData &&
        sizeInKb < sRCWNSmallResourceSizeKB) {
      MaybeRaceCacheWithNetwork();
    }
  }

  if (!mCacheOpenDelay) {
    MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
    if (mNetworkTriggered) {
      mRaceCacheWithNetwork = sRCWNEnabled;
    }
    rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags,
                                    this);
  } else {
    // We pass `this` explicitly as a parameter due to the raw pointer
    // to refcounted object in lambda analysis.
    mCacheOpenFunc = [openURI, extension, cacheEntryOpenFlags,
                      cacheStorage](nsHttpChannel* self) -> void {
      MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
      cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, self);
    };

    // calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
    NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), this,
                            mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
  }
  NS_ENSURE_SUCCESS(rv, rv);

  waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);

bypassCacheEntryOpen:
  if (!mApplicationCacheForWrite || !allowApplicationCache) return NS_OK;

  // If there is an app cache to write to, open the entry right now in parallel.

  // make sure we're not abusing this function
  MOZ_ASSERT(!mOfflineCacheEntry, "cache entry already open");

  if (offline) {
    // only put things in the offline cache while online
    return NS_OK;
  }

  if (mLoadFlags & INHIBIT_CACHING) {
    // respect demand not to cache
    return NS_OK;
  }

  if (!mRequestHead.IsGet()) {
    // only cache complete documents offline
    return NS_OK;
  }

  rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
                                            getter_AddRefs(cacheStorage));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = cacheStorage->AsyncOpenURI(mURI, EmptyCString(),
                                  nsICacheStorage::OPEN_TRUNCATE, this);
  NS_ENSURE_SUCCESS(rv, rv);

  waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);

  return NS_OK;
}

nsresult nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
                                     int64_t* aContentLength) {
  return nsHttp::CheckPartial(
      aEntry, aSize, aContentLength,
      mCachedResponseHead ? mCachedResponseHead : mResponseHead);
}

void nsHttpChannel::UntieValidationRequest() {
  DebugOnly<nsresult> rv;
  // Make the request unconditional again.
  rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  rv = mRequestHead.ClearHeader(nsHttp::ETag);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

NS_IMETHODIMP
nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry,
                                 nsIApplicationCache* appCache,
                                 uint32_t* aResult) {
  nsresult rv = NS_OK;

  LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
       entry));

  mozilla::MutexAutoLock lock(mRCWNLock);

  if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
    LOG(
        ("Not using cached response because we've already got one from the "
         "network\n"));
    *aResult = ENTRY_NOT_WANTED;

    // Net-win indicates that mOnStartRequestTimestamp is from net.
    int64_t savedTime =
        (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
    Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
                          savedTime);
    return NS_OK;
  } else if (mRaceCacheWithNetwork &&
             mFirstResponseSource == RESPONSE_PENDING) {
    mOnCacheEntryCheckTimestamp = TimeStamp::Now();
  }

  nsAutoCString cacheControlRequestHeader;
  Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
                                   cacheControlRequestHeader);
  CacheControlParser cacheControlRequest(cacheControlRequestHeader);

  if (cacheControlRequest.NoStore()) {
    LOG(
        ("Not using cached response based on no-store request cache "
         "directive\n"));
    *aResult = ENTRY_NOT_WANTED;
    return NS_OK;
  }

  // Be pessimistic: assume the cache entry has no useful data.
  *aResult = ENTRY_WANTED;
  mCachedContentIsValid = false;

  nsCString buf;

  // Get the method that was used to generate the cached response
  rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
  NS_ENSURE_SUCCESS(rv, rv);

  bool methodWasHead = buf.EqualsLiteral("HEAD");
  bool methodWasGet = buf.EqualsLiteral("GET");

  if (methodWasHead) {
    // The cached response does not contain an entity.  We can only reuse
    // the response if the current request is also HEAD.
    if (!mRequestHead.IsHead()) {
      *aResult = ENTRY_NOT_WANTED;
      return NS_OK;
    }
  }
  buf.Adopt(nullptr);

  // We'll need this value in later computations...
  uint32_t lastModifiedTime;
  rv = entry->GetLastModified(&lastModifiedTime);
  NS_ENSURE_SUCCESS(rv, rv);

  // Determine if this is the first time that this cache entry
  // has been accessed during this session.
  bool fromPreviousSession =
      (gHttpHandler->SessionStartTime() > lastModifiedTime);

  // Get the cached HTTP response headers
  mCachedResponseHead = new nsHttpResponseHead();

  rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, mCachedResponseHead);
  NS_ENSURE_SUCCESS(rv, rv);

  bool isCachedRedirect = WillRedirect(mCachedResponseHead);

  // Do not return 304 responses from the cache, and also do not return
  // any other non-redirect 3xx responses from the cache (see bug 759043).
  NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
                 NS_ERROR_ABORT);

  if (mCachedResponseHead->NoStore() && mCacheEntryIsReadOnly) {
    // This prevents loading no-store responses when navigating back
    // while the browser is set to work offline.
    LOG(("  entry loading as read-only but is no-store, set INHIBIT_CACHING"));
    mLoadFlags |= nsIRequest::INHIBIT_CACHING;
  }

  // Don't bother to validate items that are read-only,
  // unless they are read-only because of INHIBIT_CACHING or because
  // we're updating the offline cache.
  // Don't bother to validate if this is a fallback entry.
  if (!mApplicationCacheForWrite &&
      (appCache || (mCacheEntryIsReadOnly &&
                    !(mLoadFlags & nsIRequest::INHIBIT_CACHING)))) {
    if (!appCache) {
      int64_t size, contentLength;
      rv = CheckPartial(entry, &size, &contentLength);
      NS_ENSURE_SUCCESS(rv, rv);

      if (contentLength != int64_t(-1) && contentLength != size) {
        *aResult = ENTRY_NOT_WANTED;
        return NS_OK;
      }
    }

    rv = OpenCacheInputStream(entry, true, !!appCache);
    if (NS_SUCCEEDED(rv)) {
      mCachedContentIsValid = true;
      entry->MaybeMarkValid();
    }
    return rv;
  }

  bool wantCompleteEntry = false;

  if (!methodWasHead && !isCachedRedirect) {
    // If the cached content-length is set and it does not match the data
    // size of the cached content, then the cached response is partial...
    // either we need to issue a byte range request or we need to refetch
    // the entire document.
    //
    // We exclude redirects from this check because we (usually) strip the
    // entity when we store the cache entry, and even if we didn't, we
    // always ignore a cached redirect's entity anyway. See bug 759043.
    int64_t size, contentLength;
    rv = CheckPartial(entry, &size, &contentLength);
    NS_ENSURE_SUCCESS(rv, rv);

    if (size == int64_t(-1)) {
      LOG(("  write is in progress"));
      if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
        LOG(
            ("  not interested in the entry, "
             "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));

        *aResult = ENTRY_NOT_WANTED;
        return NS_OK;
      }

      // Ignore !(size > 0) from the resumability condition
      if (!IsResumable(size, contentLength, true)) {
        if (IsNavigation()) {
          LOG(
              ("  bypassing wait for the entry, "
               "this is a navigational load"));
          *aResult = ENTRY_NOT_WANTED;
          return NS_OK;
        }

        LOG(
            ("  wait for entry completion, "
             "response is not resumable"));

        wantCompleteEntry = true;
      } else {
        mConcurrentCacheAccess = 1;
      }
    } else if (contentLength != int64_t(-1) && contentLength != size) {
      LOG(
          ("Cached data size does not match the Content-Length header "
           "[content-length=%" PRId64 " size=%" PRId64 "]\n",
           contentLength, size));

      rv = MaybeSetupByteRangeRequest(size, contentLength);
      mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
      if (mCachedContentIsPartial) {
        rv = OpenCacheInputStream(entry, false, !!appCache);
        if (NS_FAILED(rv)) {
          UntieByteRangeRequest();
          return rv;
        }

        *aResult = ENTRY_NEEDS_REVALIDATION;
        return NS_OK;
      }

      if (size == 0 && mCacheOnlyMetadata) {
        // Don't break cache entry load when the entry's data size
        // is 0 and mCacheOnlyMetadata flag is set. In that case we
        // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
        // also set.
        MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
      } else {
        return rv;
      }
    }
  }

  bool isHttps = mURI->SchemeIs("https");

  bool doValidation = false;
  bool doBackgroundValidation = false;
  bool canAddImsHeader = true;

  bool isForcedValid = false;
  entry->GetIsForcedValid(&isForcedValid);

  bool weaklyFramed, isImmutable;
  nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead, isHttps,
                                          &weaklyFramed, &isImmutable);

  // Cached entry is not the entity we request (see bug #633743)
  if (ResponseWouldVary(entry)) {
    LOG(("Validating based on Vary headers returning TRUE\n"));
    canAddImsHeader = false;
    doValidation = true;
  } else {
    doValidation = nsHttp::ValidationRequired(
        isForcedValid, mCachedResponseHead, mLoadFlags, mAllowStaleCacheContent,
        isImmutable, mCustomConditionalRequest, mRequestHead, entry,
        cacheControlRequest, fromPreviousSession, &doBackgroundValidation);
  }

  nsAutoCString requestedETag;
  if (!doValidation &&
      NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
      (methodWasGet || methodWasHead)) {
    nsAutoCString cachedETag;
    Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
    if (!cachedETag.IsEmpty() &&
        (StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
         !requestedETag.Equals(cachedETag))) {
      // User has defined If-Match header, if the cached entry is not
      // matching the provided header value or the cached ETag is weak,
      // force validation.
      doValidation = true;
    }
  }

  // Previous error should not be propagated.
  rv = NS_OK;

  if (!doValidation) {
    //
    // Check the authorization headers used to generate the cache entry.
    // We must validate the cache entry if:
    //
    // 1) the cache entry was generated prior to this session w/
    //    credentials (see bug 103402).
    // 2) the cache entry was generated w/o credentials, but would now
    //    require credentials (see bug 96705).
    //
    // NOTE: this does not apply to proxy authentication.
    //
    entry->GetMetaDataElement("auth", getter_Copies(buf));
    doValidation =
        (fromPreviousSession && !buf.IsEmpty()) ||
        (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
  }

  // Bug #561276: We maintain a chain of cache-keys which returns cached
  // 3xx-responses (redirects) in order to detect cycles. If a cycle is
  // found, ignore the cached response and hit the net. Otherwise, use
  // the cached response and add the cache-key to the chain. Note that
  // a limited number of redirects (cached or not) is allowed and is
  // enforced independently of this mechanism
  if (!doValidation && isCachedRedirect) {
    nsAutoCString cacheKey;
    rv = GenerateCacheKey(mPostID, cacheKey);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    if (!mRedirectedCachekeys)
      mRedirectedCachekeys = new nsTArray<nsCString>();
    else if (mRedirectedCachekeys->Contains(cacheKey))
      doValidation = true;

    LOG(("Redirection-chain %s key %s\n",
         doValidation ? "contains" : "does not contain", cacheKey.get()));

    // Append cacheKey if not in the chain already
    if (!doValidation) mRedirectedCachekeys->AppendElement(cacheKey);
  }

  mCachedContentIsValid = !doValidation;

  if (doValidation) {
    //
    // now, we are definitely going to issue a HTTP request to the server.
    // make it conditional if possible.
    //
    // do not attempt to validate no-store content, since servers will not
    // expect it to be cached.  (we only keep it in our cache for the
    // purposes of back/forward, etc.)
    //
    // the request method MUST be either GET or HEAD (see bug 175641) and
    // the cached response code must be < 400
    //
    // the cached content must not be weakly framed or marked immutable
    //
    // do not override conditional headers when consumer has defined its own
    if (!mCachedResponseHead->NoStore() &&
        (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
        !mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
        (mCachedResponseHead->Status() < 400)) {
      if (mConcurrentCacheAccess) {
        // In case of concurrent read and also validation request we
        // must wait for the current writer to close the output stream
        // first.  Otherwise, when the writer's job would have been interrupted
        // before all the data were downloaded, we'd have to do a range request
        // which would be a second request in line during this channel's
        // life-time.  nsHttpChannel is not designed to do that, so rather
        // turn off concurrent read and wait for entry's completion.
        // Then only re-validation or range-re-validation request will go out.
        mConcurrentCacheAccess = 0;
        // This will cause that OnCacheEntryCheck is called again with the same
        // entry after the writer is done.
        wantCompleteEntry = true;
      } else {
        nsAutoCString val;
        // Add If-Modified-Since header if a Last-Modified was given
        // and we are allowed to do this (see bugs 510359 and 269303)
        if (canAddImsHeader) {
          Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
          if (!val.IsEmpty()) {
            rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
            MOZ_ASSERT(NS_SUCCEEDED(rv));
          }
        }
        // Add If-None-Match header if an ETag was given in the response
        Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
        if (!val.IsEmpty()) {
          rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
          MOZ_ASSERT(NS_SUCCEEDED(rv));
        }
        mDidReval = true;
      }
    }
  }

  if (mCachedContentIsValid || mDidReval) {
    rv = OpenCacheInputStream(entry, mCachedContentIsValid, !!appCache);
    if (NS_FAILED(rv)) {
      // If we can't get the entity then we have to act as though we
      // don't have the cache entry.
      if (mDidReval) {
        UntieValidationRequest();
        mDidReval = false;
      }
      mCachedContentIsValid = false;
    }
  }

  if (mDidReval)
    *aResult = ENTRY_NEEDS_REVALIDATION;
  else if (wantCompleteEntry)
    *aResult = RECHECK_AFTER_WRITE_FINISHED;
  else {
    *aResult = ENTRY_WANTED;

    if (doBackgroundValidation) {
      PerformBackgroundCacheRevalidation();
    }
  }

  if (mCachedContentIsValid) {
    entry->MaybeMarkValid();
  }

  LOG(
      ("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
       "result=%d]\n",
       this, doValidation, *aResult));
  return rv;
}

NS_IMETHODIMP
nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
                                     nsIApplicationCache* aAppCache,
                                     nsresult status) {
  MOZ_ASSERT(NS_IsMainThread());

  nsresult rv;

  LOG(
      ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
       "new=%d appcache=%p status=%" PRIx32
       " mAppCache=%p mAppCacheForWrite=%p]\n",
       this, entry, aNew, aAppCache, static_cast<uint32_t>(status),
       mApplicationCache.get(), mApplicationCacheForWrite.get()));

  // if the channel's already fired onStopRequest, then we should ignore
  // this event.
  if (!mIsPending) {
    mCacheInputStream.CloseAndRelease();
    return NS_OK;
  }

  rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
  if (NS_FAILED(rv)) {
    CloseCacheEntry(false);
    if (mRaceCacheWithNetwork && mNetworkTriggered &&
        mFirstResponseSource != RESPONSE_FROM_CACHE) {
      // Ignore the error if we're racing cache with network and the cache
      // didn't win, The network part will handle cancelation or any other
      // error. Otherwise we could end up calling the listener twice, see
      // bug 1397593.
      LOG(
          ("  not calling AsyncAbort() because we're racing cache with "
           "network"));
    } else {
      Unused << AsyncAbort(rv);
    }
  }

  return NS_OK;
}

nsresult nsHttpChannel::OnCacheEntryAvailableInternal(
    nsICacheEntry* entry, bool aNew, nsIApplicationCache* aAppCache,
    nsresult status) {
  nsresult rv;

  if (mCanceled) {
    LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
         static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
    return mStatus;
  }

  if (mIgnoreCacheEntry) {
    if (!entry || aNew) {
      // We use this flag later to decide whether to report
      // LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
      // an usable entry, so drop the flag.
      mIgnoreCacheEntry = false;
    }
    entry = nullptr;
    status = NS_ERROR_NOT_AVAILABLE;
  }

  if (aAppCache) {
    if (mApplicationCache == aAppCache && !mCacheEntry) {
      rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
    } else if (mApplicationCacheForWrite == aAppCache && aNew &&
               !mOfflineCacheEntry) {
      rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
    } else {
      rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
    }
  } else {
    rv = OnNormalCacheEntryAvailable(entry, aNew, status);
  }

  if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
    // If we have a fallback URI (and we're not already
    // falling back), process the fallback asynchronously.
    if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
      return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
    }

    return NS_ERROR_DOCUMENT_NOT_CACHED;
  }

  if (NS_FAILED(rv)) {
    return rv;
  }

  // We may be waiting for more callbacks...
  if (AwaitingCacheCallbacks()) {
    return NS_OK;
  }

  if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
                                 (mDidReval || mCachedContentIsPartial)) ||
                                mIgnoreCacheEntry)) {
    // We won't send the conditional request because the unconditional
    // request was already sent (see bug 1377223).
    AccumulateCategorical(
        Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
  }

  if (mRaceCacheWithNetwork && mCachedContentIsValid) {
    Unused << ReadFromCache(true);
  }

  return TriggerNetwork();
}

nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
                                                    bool aNew,
                                                    nsresult aEntryStatus) {
  mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;

  if (NS_FAILED(aEntryStatus) || aNew) {
    // Make sure this flag is dropped.  It may happen the entry is doomed
    // between OnCacheEntryCheck and OnCacheEntryAvailable.
    mCachedContentIsValid = false;

    // From the same reason remove any conditional headers added
    // in OnCacheEntryCheck.
    if (mDidReval) {
      LOG(("  Removing conditional request headers"));
      UntieValidationRequest();
      mDidReval = false;
    }

    if (mCachedContentIsPartial) {
      LOG(("  Removing byte range request headers"));
      UntieByteRangeRequest();
      mCachedContentIsPartial = false;
    }

    if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
      // if this channel is only allowed to pull from the cache, then
      // we must fail if we were unable to open a cache entry for read.
      return NS_ERROR_DOCUMENT_NOT_CACHED;
    }
  }

  if (NS_SUCCEEDED(aEntryStatus)) {
    mCacheEntry = aEntry;
    mCacheEntryIsWriteOnly = aNew;

    if (!aNew && !mAsyncOpenTime.IsNull()) {
      // We use microseconds for IO operations. For consistency let's use
      // microseconds here too.
      uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
      bool isSlow = false;
      if ((mCacheOpenWithPriority &&
           mCacheQueueSizeWhenOpen >= sRCWNQueueSizePriority) ||
          (!mCacheOpenWithPriority &&
           mCacheQueueSizeWhenOpen >= sRCWNQueueSizeNormal)) {
        isSlow = true;
      }
      CacheFileUtils::CachePerfStats::AddValue(
          CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
    }

    if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
      Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false);
    }
  }

  return NS_OK;
}

nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(
    nsICacheEntry* aEntry, bool aNew, nsIApplicationCache* aAppCache,
    nsresult aEntryStatus) {
  MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
  MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);

  mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;

  nsresult rv;

  if (NS_SUCCEEDED(aEntryStatus)) {
    if (!mApplicationCache) {
      mApplicationCache = aAppCache;
    }

    // We successfully opened an offline cache session and the entry,
    // so indicate we will load from the offline cache.
    mLoadedFromApplicationCache = true;
    mCacheEntryIsReadOnly = true;
    mCacheEntry = aEntry;
    mCacheEntryIsWriteOnly = false;

    if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
      MaybeWarnAboutAppCache();
    }

    return NS_OK;
  }

  if (!mApplicationCacheForWrite && !mFallbackChannel) {
    if (!mApplicationCache) {
      mApplicationCache = aAppCache;
    }

    // Check for namespace match.
    nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
    rv = mApplicationCache->GetMatchingNamespace(
        mSpec, getter_AddRefs(namespaceEntry));
    NS_ENSURE_SUCCESS(rv, rv);

    uint32_t namespaceType = 0;
    if (!namespaceEntry ||
        NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
        (namespaceType & (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
                          nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) ==
            0) {
      // When loading from an application cache, only items
      // on the whitelist or matching a
      // fallback namespace should hit the network...
      mLoadFlags |= LOAD_ONLY_FROM_CACHE;

      // ... and if there were an application cache entry,
      // we would have found it earlier.
      return NS_ERROR_CACHE_KEY_NOT_FOUND;
    }

    if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
      nsAutoCString namespaceSpec;
      rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
      NS_ENSURE_SUCCESS(rv, rv);

      // This prevents fallback attacks injected by an insecure subdirectory
      // for the whole origin (or a parent directory).
      if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
        return NS_OK;
      }

      rv = namespaceEntry->GetData(mFallbackKey);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) {
      LOG(
          ("nsHttpChannel::OnOfflineCacheEntryAvailable this=%p, URL matches "
           "NETWORK,"
           " looking for a regular cache entry",
           this));

      bool isHttps = mURI->SchemeIs("https");
      rv = OpenCacheEntryInternal(isHttps, nullptr,
                                  false /* don't allow appcache lookups */);
      if (NS_FAILED(rv)) {
        // Don't let this fail when cache entry can't be synchronously open.
        // We want to go forward even without a regular cache entry.
        return NS_OK;
      }
    }
  }

  return NS_OK;
}

nsresult nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(
    nsICacheEntry* aEntry, nsIApplicationCache* aAppCache,
    nsresult aEntryStatus) {
  MOZ_ASSERT(mApplicationCacheForWrite &&
             aAppCache == mApplicationCacheForWrite);

  mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;

  if (NS_SUCCEEDED(aEntryStatus)) {
    mOfflineCacheEntry = aEntry;
    if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
      mOfflineCacheLastModifiedTime = 0;
    }
  }

  return aEntryStatus;
}

// Generates the proper cache-key for this instance of nsHttpChannel
nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
                                         nsACString& cacheKey) {
  AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(), postID,
                   cacheKey);
  return NS_OK;
}

// Assembles a cache-key from the given pieces of information and |mLoadFlags|
void nsHttpChannel::AssembleCacheKey(const char* spec, uint32_t postID,
                                     nsACString& cacheKey) {
  cacheKey.Truncate();

  if (mLoadFlags & LOAD_ANONYMOUS) {
    cacheKey.AssignLiteral("anon&");
  }

  if (postID) {
    char buf[32];
    SprintfLiteral(buf, "id=%x&", postID);
    cacheKey.Append(buf);
  }

  if (!cacheKey.IsEmpty()) {
    cacheKey.AppendLiteral("uri=");
  }

  // Strip any trailing #ref from the URL before using it as the key
  const char* p = strchr(spec, '#');
  if (p)
    cacheKey.Append(spec, p - spec);
  else
    cacheKey.Append(spec);
}

nsresult DoUpdateExpirationTime(nsHttpChannel* aSelf,
                                nsICacheEntry* aCacheEntry,
                                nsHttpResponseHead* aResponseHead,
                                uint32_t& aExpirationTime) {
  MOZ_ASSERT(aExpirationTime == 0);
  NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);

  nsresult rv;

  if (!aResponseHead->MustValidate()) {
    // For stale-while-revalidate we use expiration time as the absolute base
    // for calculation of the stale window absolute end time.  Hence, when the
    // entry may be served w/o revalidation, we need a non-zero value for the
    // expiration time.  Let's set it to |now|, which basicly means "expired",
    // same as when set to 0.
    uint32_t now = NowInSeconds();
    aExpirationTime = now;

    uint32_t freshnessLifetime = 0;

    rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
    if (NS_FAILED(rv)) return rv;

    if (freshnessLifetime > 0) {
      uint32_t currentAge = 0;

      rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
                                            &currentAge);
      if (NS_FAILED(rv)) return rv;

      LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
           currentAge));

      if (freshnessLifetime > currentAge) {
        uint32_t timeRemaining = freshnessLifetime - currentAge;
        // be careful... now + timeRemaining may overflow
        if (now + timeRemaining < now) {
          aExpirationTime = uint32_t(-1);
        } else {
          aExpirationTime = now + timeRemaining;
        }
      }
    }
  }

  rv = aCacheEntry->SetExpirationTime(aExpirationTime);
  NS_ENSURE_SUCCESS(rv, rv);

  return rv;
}

// UpdateExpirationTime is called when a new response comes in from the server.
// It updates the stored response-time and sets the expiration time on the
// cache entry.
//
// From section 13.2.4 of RFC2616, we compute expiration time as follows:
//
//    timeRemaining = freshnessLifetime - currentAge
//    expirationTime = now + timeRemaining
//
nsresult nsHttpChannel::UpdateExpirationTime() {
  uint32_t expirationTime = 0;
  nsresult rv =
      DoUpdateExpirationTime(this, mCacheEntry, mResponseHead, expirationTime);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mOfflineCacheEntry) {
    rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

bool nsHttpChannel::ShouldUpdateOfflineCacheEntry() {
  if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
    return false;
  }

  // if we're updating the cache entry, update the offline cache entry too
  if (mCacheEntry && mCacheEntryIsWriteOnly) {
    return true;
  }

  // if there's nothing in the offline cache, add it
  if (mOfflineCacheEntry) {
    return true;
  }

  // if the document is newer than the offline entry, update it
  uint32_t docLastModifiedTime;
  nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
  if (NS_FAILED(rv)) {
    return true;
  }

  if (mOfflineCacheLastModifiedTime == 0) {
    return false;
  }

  if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
    return true;
  }

  return false;
}

nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
                                             bool startBuffering,
                                             bool checkingAppCacheEntry) {
  nsresult rv;

  if (mURI->SchemeIs("https")) {
    rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
    if (NS_FAILED(rv)) {
      LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
           cacheEntry));
      NS_WARNING("failed to parse security-info");
      cacheEntry->AsyncDoom(nullptr);
      return rv;
    }

    // XXX: We should not be skilling this check in the offline cache
    // case, but we have to do so now to work around bug 794507.
    bool mustHaveSecurityInfo =
        !mLoadedFromApplicationCache && !checkingAppCacheEntry;
    MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
    if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
      LOG(
          ("mCacheEntry->GetSecurityInfo returned success but did not "
           "return the security info [channel=%p, entry=%p]",
           this, cacheEntry));
      cacheEntry->AsyncDoom(nullptr);
      return NS_ERROR_UNEXPECTED;  // XXX error code
    }
  }

  // Keep the conditions below in sync with the conditions in ReadFromCache.

  rv = NS_OK;

  if (WillRedirect(mCachedResponseHead)) {
    // Do not even try to read the entity for a redirect because we do not
    // return an entity to the application when we process redirects.
    LOG(("Will skip read of cached redirect entity\n"));
    return NS_OK;
  }

  if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
      !mCachedContentIsPartial) {
    // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
    // cached entity.
    if (!mApplicationCacheForWrite) {
      LOG(
          ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
           "load flag\n"));
      return NS_OK;
    }

    // If offline caching has been requested and the offline cache needs
    // updating, we must complete the call even if the main cache entry
    // is up to date. We don't know yet for sure whether the offline
    // cache needs updating because at this point we haven't opened it
    // for writing yet, so we have to start reading the cached entity now
    // just in case.
    LOG(
        ("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
         "load flag\n"));
  }

  // Open an input stream for the entity, so that the call to OpenInputStream
  // happens off the main thread.
  nsCOMPtr<nsIInputStream> stream;

  // If an alternate representation was requested, try to open the alt
  // input stream.
  // If the entry has a "is-from-child" metadata, then only open the altdata
  // stream if the consumer is also from child.
  bool altDataFromChild = false;
  {
    nsCString value;
    rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
                                        getter_Copies(value));
    altDataFromChild = !value.IsEmpty();
  }

  nsAutoCString altDataType;
  Unused << cacheEntry->GetAltDataType(altDataType);

  nsAutoCString contentType;
  mCachedResponseHead->ContentType(contentType);

  bool foundAltData = false;
  bool deliverAltData = true;
  if (!altDataType.IsEmpty() && !mPreferredCachedAltDataTypes.IsEmpty() &&
      altDataFromChild == mAltDataForChild) {
    for (auto& pref : mPreferredCachedAltDataTypes) {
      if (pref.type() == altDataType &&
          (pref.contentType().IsEmpty() || pref.contentType() == contentType)) {
        foundAltData = true;
        deliverAltData = pref.deliverAltData();
        break;
      }
    }
  }

  nsCOMPtr<nsIInputStream> altData;
  int64_t altDataSize;
  if (foundAltData) {
    rv = cacheEntry->OpenAlternativeInputStream(altDataType,
                                                getter_AddRefs(altData));
    if (NS_SUCCEEDED(rv)) {
      LOG(("Opened alt-data input stream type=%s", altDataType.get()));
      // We have succeeded.
      mAvailableCachedAltDataType = altDataType;
      mDeliveringAltData = deliverAltData;

      // Set the correct data size on the channel.
      Unused << cacheEntry->GetAltDataSize(&altDataSize);
      mAltDataLength = altDataSize;

      if (deliverAltData) {
        stream = altData;
      }
    }
  }

  if (!stream) {
    rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
  }

  if (NS_FAILED(rv)) {
    LOG(
        ("Failed to open cache input stream [channel=%p, "
         "mCacheEntry=%p]",
         this, cacheEntry));
    return rv;
  }

  if (startBuffering) {
    bool nonBlocking;
    rv = stream->IsNonBlocking(&nonBlocking);
    if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
  }

  if (!startBuffering) {
    // Bypass wrapping the input stream for the new cache back-end since
    // nsIStreamTransportService expects a blocking stream.  Preloading of
    // the data must be done on the level of the cache backend, internally.
    //
    // We do not connect the stream to the stream transport service if we
    // have to validate the entry with the server. If we did, we would get
    // into a race condition between the stream transport service reading
    // the existing contents and the opening of the cache entry's output
    // stream to write the new contents in the case where we get a non-304
    // response.
    LOG(
        ("Opened cache input stream without buffering [channel=%p, "
         "mCacheEntry=%p, stream=%p]",
         this, cacheEntry, stream.get()));
    mCacheInputStream.takeOver(stream);
    return rv;
  }

  // Have the stream transport service start reading the entity on one of its
  // background threads.

  nsCOMPtr<nsITransport> transport;
  nsCOMPtr<nsIInputStream> wrapper;

  nsCOMPtr<nsIStreamTransportService> sts(
      services::GetStreamTransportService());
  rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
  if (NS_SUCCEEDED(rv)) {
    rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
  }
  if (NS_SUCCEEDED(rv)) {
    rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
  }
  if (NS_SUCCEEDED(rv)) {
    LOG(
        ("Opened cache input stream [channel=%p, wrapper=%p, "
         "transport=%p, stream=%p]",
         this, wrapper.get(), transport.get(), stream.get()));
  } else {
    LOG(
        ("Failed to open cache input stream [channel=%p, "
         "wrapper=%p, transport=%p, stream=%p]",
         this, wrapper.get(), transport.get(), stream.get()));

    stream->Close();
    return rv;
  }

  mCacheInputStream.takeOver(wrapper);

  return NS_OK;
}

// Actually process the cached response that we started to handle in CheckCache
// and/or StartBufferingCachedEntity.
nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
  NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
  NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
  NS_ENSURE_TRUE(!mCachePump, NS_OK);  // already opened

  LOG(
      ("nsHttpChannel::ReadFromCache [this=%p] "
       "Using cached copy of: %s\n",
       this, mSpec.get()));

  // When racing the cache with the network with a timer, and we get data from
  // the cache, we should prevent the timer from triggering a network request.
  if (mNetworkTriggerTimer) {
    mNetworkTriggerTimer->Cancel();
    mNetworkTriggerTimer = nullptr;
  }

  if (mRaceCacheWithNetwork) {
    MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
    if (mFirstResponseSource == RESPONSE_PENDING) {
      LOG(("First response from cache\n"));
      mFirstResponseSource = RESPONSE_FROM_CACHE;

      // Cancel the transaction because we will serve the request from the cache
      CancelNetworkRequest(NS_BINDING_ABORTED);
      if (mTransactionPump && mSuspendCount) {
        uint32_t suspendCount = mSuspendCount;
        while (suspendCount--) {
          mTransactionPump->Resume();
        }
      }
      mTransaction = nullptr;
      mTransactionPump = nullptr;
    } else {
      MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
      LOG(
          ("Skipping read from cache because first response was from "
           "network\n"));

      if (!mOnCacheEntryCheckTimestamp.IsNull()) {
        TimeStamp currentTime = TimeStamp::Now();
        int64_t savedTime =
            (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
        Telemetry::Accumulate(
            Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);

        int64_t diffTime =
            (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
        Telemetry::Accumulate(
            Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
            diffTime);
      }
      return NS_OK;
    }
  }

  if (mCachedResponseHead) mResponseHead = std::move(mCachedResponseHead);

  UpdateInhibitPersistentCachingFlag();

  // if we don't already have security info, try to get it from the cache
  // entry. there are two cases to consider here: 1) we are just reading
  // from the cache, or 2) this may be due to a 304 not modified response,
  // in which case we could have security info from a socket transport.
  if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;

  if (!alreadyMarkedValid && !mCachedContentIsPartial) {
    // We validated the entry, and we have write access to the cache, so
    // mark the cache entry as valid in order to allow others access to
    // this cache entry.
    //
    // TODO: This should be done asynchronously so we don't take the cache
    // service lock on the main thread.
    mCacheEntry->MaybeMarkValid();
  }

  nsresult rv;

  // Keep the conditions below in sync with the conditions in
  // StartBufferingCachedEntity.

  if (WillRedirect(mResponseHead)) {
    // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
    // to avoid event dispatching latency.
    MOZ_ASSERT(!mCacheInputStream);
    LOG(("Skipping skip read of cached redirect entity\n"));
    return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
  }

  if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
    if (!mApplicationCacheForWrite) {
      LOG(
          ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
           "load flag\n"));
      MOZ_ASSERT(!mCacheInputStream);
      // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
      // here, to avoid event dispatching latency.
      return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
    }

    if (!ShouldUpdateOfflineCacheEntry()) {
      LOG(
          ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
           "load flag (mApplicationCacheForWrite not null case)\n"));
      mCacheInputStream.CloseAndRelease();
      // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
      // here, to avoid event dispatching latency.
      return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
    }
  }

  MOZ_ASSERT(mCacheInputStream);
  if (!mCacheInputStream) {
    NS_ERROR(
        "mCacheInputStream is null but we're expecting to "
        "be able to read from it.");
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();

  rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
                                 true);
  if (NS_FAILED(rv)) {
    inputStream->Close();
    return rv;
  }

  rv = mCachePump->AsyncRead(this, nullptr);
  if (NS_FAILED(rv)) return rv;

  if (mTimingEnabled) mCacheReadStart = TimeStamp::Now();

  uint32_t suspendCount = mSuspendCount;
  if (mAsyncResumePending) {
    LOG(
        ("  Suspend()'ing cache pump once because of async resume pending"
         ", sc=%u, pump=%p, this=%p",
         suspendCount, mCachePump.get(), this));
    ++suspendCount;
  }
  while (suspendCount--) {
    mCachePump->Suspend();
  }

  return NS_OK;
}

void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
  mCacheInputStream.CloseAndRelease();

  if (!mCacheEntry) return;

  LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
       " mCacheEntryIsWriteOnly=%x",
       this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
       mCacheEntryIsWriteOnly));

  // If we have begun to create or replace a cache entry, and that cache
  // entry is not complete and not resumable, then it needs to be doomed.
  // Otherwise, CheckCache will make the mistake of thinking that the
  // partial cache entry is complete.

  bool doom = false;
  if (mInitedCacheEntry) {
    MOZ_ASSERT(mResponseHead, "oops");
    if (NS_FAILED(mStatus) && doomOnFailure && mCacheEntryIsWriteOnly &&
        !mResponseHead->IsResumable())
      doom = true;
  } else if (mCacheEntryIsWriteOnly)
    doom = true;

  if (doom) {
    LOG(("  dooming cache entry!!"));
    mCacheEntry->AsyncDoom(nullptr);
  } else {
    // Store updated security info, makes cached EV status race less likely
    // (see bug 1040086)
    if (mSecurityInfo) mCacheEntry->SetSecurityInfo(mSecurityInfo);
  }

  mCachedResponseHead = nullptr;

  mCachePump = nullptr;
  // This releases the entry for other consumers to use.
  // We call Dismiss() in case someone still keeps a reference
  // to this entry handle.
  mCacheEntry->Dismiss();
  mCacheEntry = nullptr;
  mCacheEntryIsWriteOnly = false;
  mInitedCacheEntry = false;
}

void nsHttpChannel::CloseOfflineCacheEntry() {
  if (!mOfflineCacheEntry) return;

  LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));

  if (NS_FAILED(mStatus)) {
    mOfflineCacheEntry->AsyncDoom(nullptr);
  } else {
    bool succeeded;
    if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
      mOfflineCacheEntry->AsyncDoom(nullptr);
  }

  mOfflineCacheEntry = nullptr;
}

// Initialize the cache entry for writing.
//  - finalize storage policy
//  - store security info
//  - update expiration time
//  - store headers and other meta data
nsresult nsHttpChannel::InitCacheEntry() {
  nsresult rv;

  NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
  // if only reading, nothing to be done here.
  if (mCacheEntryIsReadOnly) return NS_OK;

  // Don't cache the response again if already cached...
  if (mCachedContentIsValid) return NS_OK;

  LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
       mCacheEntry.get()));

  bool recreate = !mCacheEntryIsWriteOnly;
  bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;

  if (!recreate && dontPersist) {
    // If the current entry is persistent but we inhibit peristence
    // then force recreation of the entry as memory/only.
    rv = mCacheEntry->GetPersistent(&recreate);
    if (NS_FAILED(rv)) return rv;
  }

  if (recreate) {
    LOG(
        ("  we have a ready entry, but reading it again from the server -> "
         "recreating cache entry\n"));
    // clean the altData cache and reset this to avoid wrong content length
    mAvailableCachedAltDataType.Truncate();
    mDeliveringAltData = false;

    nsCOMPtr<nsICacheEntry> currentEntry;
    currentEntry.swap(mCacheEntry);
    rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
    if (NS_FAILED(rv)) {
      LOG(("  recreation failed, the response will not be cached"));
      return NS_OK;
    }

    mCacheEntryIsWriteOnly = true;
  }

  // Set the expiration time for this cache entry
  rv = UpdateExpirationTime();
  if (NS_FAILED(rv)) return rv;

  // mark this weakly framed until a response body is seen
  mCacheEntry->SetMetaDataElement("strongly-framed", "0");

  rv = AddCacheEntryHeaders(mCacheEntry);
  if (NS_FAILED(rv)) return rv;

  mInitedCacheEntry = true;

  // Don't perform the check when writing (doesn't make sense)
  mConcurrentCacheAccess = 0;

  return NS_OK;
}

void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
  // The no-store directive within the 'Cache-Control:' header indicates
  // that we must not store the response in a persistent cache.
  if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;

  // Only cache SSL content on disk if the pref is set
  if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
      mURI->SchemeIs("https")) {
    mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
  }
}

nsresult nsHttpChannel::InitOfflineCacheEntry() {
  // This function can be called even when we fail to connect (bug 551990)

  if (!mOfflineCacheEntry) {
    return NS_OK;
  }

  if (!mResponseHead || mResponseHead->NoStore()) {
    if (mResponseHead && mResponseHead->NoStore()) {
      mOfflineCacheEntry->AsyncDoom(nullptr);
    }

    CloseOfflineCacheEntry();

    if (mResponseHead && mResponseHead->NoStore()) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    return NS_OK;
  }

  // This entry's expiration time should match the main entry's expiration
  // time.  UpdateExpirationTime() will keep it in sync once the offline
  // cache entry has been created.
  if (mCacheEntry) {
    uint32_t expirationTime;
    nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
    NS_ENSURE_SUCCESS(rv, rv);

    mOfflineCacheEntry->SetExpirationTime(expirationTime);
  }

  return AddCacheEntryHeaders(mOfflineCacheEntry);
}

nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
                                nsHttpRequestHead* requestHead,
                                nsHttpResponseHead* responseHead,
                                nsISupports* securityInfo) {
  nsresult rv;

  LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
  // Store secure data in memory only
  if (securityInfo) entry->SetSecurityInfo(securityInfo);

  // Store the HTTP request method with the cache entry so we can distinguish
  // for example GET and HEAD responses.
  nsAutoCString method;
  requestHead->Method(method);
  rv = entry->SetMetaDataElement("request-method", method.get());
  if (NS_FAILED(rv)) return rv;

  // Store the HTTP authorization scheme used if any...
  rv = StoreAuthorizationMetaData(entry, requestHead);
  if (NS_FAILED(rv)) return rv;

  // Iterate over the headers listed in the Vary response header, and
  // store the value of the corresponding request header so we can verify
  // that it has not varied when we try to re-use the cached response at
  // a later time.  Take care to store "Cookie" headers only as hashes
  // due to security considerations and the fact that they can be pretty
  // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
  //
  // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
  // in the cache.  we could try to avoid needlessly storing the "accept"
  // header in this case, but it doesn't seem worth the extra code to perform
  // the check.
  {
    nsAutoCString buf, metaKey;
    Unused << responseHead->GetHeader(nsHttp::Vary, buf);
    if (!buf.IsEmpty()) {
      NS_NAMED_LITERAL_CSTRING(prefix, "request-");

      char* bufData = buf.BeginWriting();  // going to munge buf
      char* token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
      while (token) {
        LOG(
            ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
             "processing %s",
             self, token));
        if (*token != '*') {
          nsHttpAtom atom = nsHttp::ResolveAtom(token);
          nsAutoCString val;
          nsAutoCString hash;
          if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
            // If cookie-header, store a hash of the value
            if (atom == nsHttp::Cookie) {
              LOG(
                  ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
                   "cookie-value %s",
                   self, val.get()));
              rv = Hash(val.get(), hash);
              // If hash failed, store a string not very likely
              // to be the result of subsequent hashes
              if (NS_FAILED(rv)) {
                val = NS_LITERAL_CSTRING("<hash failed>");
              } else {
                val = hash;
              }

              LOG(("   hashed to %s\n", val.get()));
            }

            // build cache meta data key and set meta data element...
            metaKey = prefix + nsDependentCString(token);
            entry->SetMetaDataElement(metaKey.get(), val.get());
          } else {
            LOG(
                ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
                 "clearing metadata for %s",
                 self, token));
            metaKey = prefix + nsDependentCString(token);
            entry->SetMetaDataElement(metaKey.get(), nullptr);
          }
        }
        token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
      }
    }
  }

  // Store the received HTTP head with the cache entry as an element of
  // the meta data.
  nsAutoCString head;
  responseHead->Flatten(head, true);
  rv = entry->SetMetaDataElement("response-head", head.get());
  if (NS_FAILED(rv)) return rv;
  head.Truncate();
  responseHead->FlattenNetworkOriginalHeaders(head);
  rv = entry->SetMetaDataElement("original-response-headers", head.get());
  if (NS_FAILED(rv)) return rv;

  // Indicate we have successfully finished setting metadata on the cache entry.
  rv = entry->MetaDataReady();

  return rv;
}

nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry* entry) {
  return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead,
                                mSecurityInfo);
}

inline void GetAuthType(const char* challenge, nsCString& authType) {
  const char* p;

  // get the challenge type
  if ((p = strchr(challenge, ' ')) != nullptr)
    authType.Assign(challenge, p - challenge);
  else
    authType.Assign(challenge);
}

nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
                                    nsHttpRequestHead* requestHead) {
  // Not applicable to proxy authorization...
  nsAutoCString val;
  if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
    return NS_OK;
  }

  // eg. [Basic realm="wally world"]
  nsAutoCString buf;
  GetAuthType(val.get(), buf);
  return entry->SetMetaDataElement("auth", buf.get());
}

// Finalize the cache entry
//  - may need to rewrite response headers if any headers changed
//  - may need to recalculate the expiration time if any headers changed
//  - called only for freshly written cache entries
nsresult nsHttpChannel::FinalizeCacheEntry() {
  LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));

  // Don't update this meta-data on 304
  if (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
    LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
         this));
    mCacheEntry->SetMetaDataElement("strongly-framed", "1");
  }

  if (mResponseHead && mResponseHeadersModified) {
    // Set the expiration time for this cache entry
    nsresult rv = UpdateExpirationTime();
    if (NS_FAILED(rv)) return rv;
  }
  return NS_OK;
}

// Open an output stream to the cache entry and insert a listener tee into
// the chain of response listeners.
nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
  nsresult rv;

  LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));

  MOZ_ASSERT(mCacheEntry);
  MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial ||
             mRaceCacheWithNetwork);
  MOZ_ASSERT(mListener);

  nsAutoCString contentEncoding, contentType;
  Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
  mResponseHead->ContentType(contentType);
  // If the content is compressible and the server has not compressed it,
  // mark the cache entry for compression.
  if (contentEncoding.IsEmpty() &&
      (contentType.EqualsLiteral(TEXT_HTML) ||
       contentType.EqualsLiteral(TEXT_PLAIN) ||
       contentType.EqualsLiteral(TEXT_CSS) ||
       contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
       contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
       contentType.EqualsLiteral(TEXT_XML) ||
       contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
       contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
       contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
       contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
    rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
    if (NS_FAILED(rv)) {
      LOG(("unable to mark cache entry for compression"));
    }
  }

  LOG(("Trading cache input stream for output stream [channel=%p]", this));

  // We must close the input stream first because cache entries do not
  // correctly handle having an output stream and input streams open at
  // the same time.
  mCacheInputStream.CloseAndRelease();

  int64_t predictedSize = mResponseHead->TotalEntitySize();
  if (predictedSize != -1) {
    predictedSize -= offset;
  }

  nsCOMPtr<nsIOutputStream> out;
  rv =
      mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
  if (rv == NS_ERROR_NOT_AVAILABLE) {
    LOG(("  entry doomed, not writing it [channel=%p]", this));
    // Entry is already doomed.
    // This may happen when expiration time is set to past and the entry
    // has been removed by the background eviction logic.
    return NS_OK;
  }
  if (rv == NS_ERROR_FILE_TOO_BIG) {
    LOG(("  entry would exceed max allowed size, not writing it [channel=%p]",
         this));
    mCacheEntry->AsyncDoom(nullptr);
    return NS_OK;
  }
  if (NS_FAILED(rv)) return rv;

  if (mCacheOnlyMetadata) {
    LOG(("Not storing content, cacheOnlyMetadata set"));
    // We must open and then close the output stream of the cache entry.
    // This way we indicate the content has been written (despite with zero
    // length) and the entry is now in the ready state with "having data".

    out->Close();
    return NS_OK;
  }

  // XXX disk cache does not support overlapped i/o yet
#if 0
    // Mark entry valid inorder to allow simultaneous reading...
    rv = mCacheEntry->MarkValid();
    if (NS_FAILED(rv)) return rv;
#endif

  nsCOMPtr<nsIStreamListenerTee> tee =
      do_CreateInstance(kStreamListenerTeeCID, &rv);
  if (NS_FAILED(rv)) return rv;

  LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
       static_cast<uint32_t>(rv)));
  rv = tee->Init(mListener, out, nullptr);
  if (NS_FAILED(rv)) return rv;

  mListener = tee;
  return NS_OK;
}

nsresult nsHttpChannel::InstallOfflineCacheListener(int64_t offset) {
  nsresult rv;

  LOG(("Preparing to write data into the offline cache [uri=%s]\n",
       mSpec.get()));

  MOZ_ASSERT(mOfflineCacheEntry);
  MOZ_ASSERT(mListener);

  nsCOMPtr<nsIOutputStream> out;
  rv = mOfflineCacheEntry->OpenOutputStream(offset, -1, getter_AddRefs(out));
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIStreamListenerTee> tee =
      do_CreateInstance(kStreamListenerTeeCID, &rv);
  if (NS_FAILED(rv)) return rv;

  rv = tee->Init(mListener, out, nullptr);
  if (NS_FAILED(rv)) return rv;

  mListener = tee;

  return NS_OK;
}

void nsHttpChannel::ClearBogusContentEncodingIfNeeded() {
  // For .gz files, apache sends both a Content-Type: application/x-gzip
  // as well as Content-Encoding: gzip, which is completely wrong.  In
  // this case, we choose to ignore the rogue Content-Encoding header. We
  // must do this early on so as to prevent it from being seen up stream.
  // The same problem exists for Content-Encoding: compress in default
  // Apache installs.
  nsAutoCString contentType;
  mResponseHead->ContentType(contentType);
  if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") &&
      (contentType.EqualsLiteral(APPLICATION_GZIP) ||
       contentType.EqualsLiteral(APPLICATION_GZIP2) ||
       contentType.EqualsLiteral(APPLICATION_GZIP3))) {
    // clear the Content-Encoding header
    mResponseHead->ClearHeader(nsHttp::Content_Encoding);
  } else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding,
                                           "compress") &&
             (contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
              contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
    // clear the Content-Encoding header
    mResponseHead->ClearHeader(nsHttp::Content_Encoding);
  }
}

//-----------------------------------------------------------------------------
// nsHttpChannel <redirect>
//-----------------------------------------------------------------------------

nsresult nsHttpChannel::SetupReplacementChannel(nsIURI* newURI,
                                                nsIChannel* newChannel,
                                                bool preserveMethod,
                                                uint32_t redirectFlags) {
  LOG(
      ("nsHttpChannel::SetupReplacementChannel "
       "[this=%p newChannel=%p preserveMethod=%d]",
       this, newChannel, preserveMethod));

  nsresult rv = HttpBaseChannel::SetupReplacementChannel(
      newURI, newChannel, preserveMethod, redirectFlags);
  if (NS_FAILED(rv)) return rv;

  rv = CheckRedirectLimit(redirectFlags);
  NS_ENSURE_SUCCESS(rv, rv);

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

  // convey the mApplyConversion flag (bug 91862)
  nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
  if (encodedChannel) encodedChannel->SetApplyConversion(mApplyConversion);

  // transfer the resume information
  if (mResuming) {
    nsCOMPtr<nsIResumableChannel> resumableChannel(
        do_QueryInterface(newChannel));
    if (!resumableChannel) {
      NS_WARNING(
          "Got asked to resume, but redirected to non-resumable channel!");
      return NS_ERROR_NOT_RESUMABLE;
    }
    resumableChannel->ResumeAt(mStartPos, mEntityID);
  }

  nsCOMPtr<nsIHttpChannelInternal> internalChannel =
      do_QueryInterface(newChannel, &rv);
  if (NS_SUCCEEDED(rv)) {
    TimeStamp timestamp;
    rv = GetNavigationStartTimeStamp(&timestamp);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    if (timestamp) {
      Unused << internalChannel->SetNavigationStartTimeStamp(timestamp);
    }
  }

  return NS_OK;
}

nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
  LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
       redirectType));

  nsAutoCString location;

  // if a location header was not given, then we can't perform the redirect,
  // so just carry on as though this were a normal response.
  if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location)))
    return NS_ERROR_FAILURE;

  // If we were told to not follow redirects automatically, then again
  // carry on as though this were a normal response.
  if (mLoadInfo && mLoadInfo->GetDontFollowRedirects()) {
    return NS_ERROR_FAILURE;
  }

  // make sure non-ASCII characters in the location header are escaped.
  nsAutoCString locationBuf;
  if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
                   locationBuf))
    location = locationBuf;

  mRedirectType = redirectType;

  LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
       uint32_t(mRedirectionLimit)));

  nsresult rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));

  if (NS_FAILED(rv)) {
    LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  if (mApplicationCache) {
    // if we are redirected to a different origin check if there is a fallback
    // cache entry to fall back to. we don't care about file strict
    // checking, at least mURI is not a file URI.
    if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
      PushRedirectAsyncFunc(
          &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
      bool waitingForRedirectCallback;
      Unused << ProcessFallback(&waitingForRedirectCallback);
      if (waitingForRedirectCallback) return NS_OK;
      PopRedirectAsyncFunc(
          &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
    }
  }

  return ContinueProcessRedirectionAfterFallback(NS_OK);
}

nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
  if (NS_SUCCEEDED(rv) && mFallingBack) {
    // do not continue with redirect processing, fallback is in
    // progress now.
    return NS_OK;
  }

  // Kill the current cache entry if we are redirecting
  // back to ourself.
  bool redirectingBackToSameURI = false;
  if (mCacheEntry && mCacheEntryIsWriteOnly &&
      NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
      redirectingBackToSameURI)
    mCacheEntry->AsyncDoom(nullptr);

  // move the reference of the old location to the new one if the new
  // one has none.
  PropagateReferenceIfNeeded(mURI, mRedirectURI);

  bool rewriteToGET =
      ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());

  // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
  if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
    rv = PromptTempRedirect();
    if (NS_FAILED(rv)) return rv;
  }

#ifdef MOZ_GECKO_PROFILER
  if (profiler_can_accept_markers()) {
    int32_t priority = PRIORITY_NORMAL;
    GetPriority(&priority);

    TimingStruct timings;
    if (mTransaction) {
      timings = mTransaction->Timings();
    }

    profiler_add_network_marker(
        mURI, priority, mChannelId, NetworkLoadType::LOAD_REDIRECT,
        mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
        mCacheDisposition, &timings, mRedirectURI, std::move(mSource));
  }
#endif

  nsCOMPtr<nsIIOService> ioService;
  rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
  if (NS_FAILED(rv)) return rv;

  uint32_t redirectFlags;
  if (nsHttp::IsPermanentRedirect(mRedirectType))
    redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
  else
    redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;

  nsCOMPtr<nsIChannel> newChannel;
  nsCOMPtr<nsILoadInfo> redirectLoadInfo =
      CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
  rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
                             redirectLoadInfo,
                             nullptr,  // PerformanceStorage
                             nullptr,  // aLoadGroup
                             nullptr,  // aCallbacks
                             nsIRequest::LOAD_NORMAL, ioService);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
                               redirectFlags);
  if (NS_FAILED(rv)) return rv;

  // verify that this is a legal redirect
  mRedirectChannel = newChannel;

  PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
  rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);

  if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();

  if (NS_FAILED(rv)) {
    AutoRedirectVetoNotifier notifier(this);
    PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
  }

  return rv;
}

nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
  AutoRedirectVetoNotifier notifier(this);

  LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
       static_cast<uint32_t>(rv), this));
  if (NS_FAILED(rv)) return rv;

  MOZ_ASSERT(mRedirectChannel, "No redirect channel?");

  // Make sure to do this after we received redirect veto answer,
  // i.e. after all sinks had been notified
  mRedirectChannel->SetOriginalURI(mOriginalURI);

  // XXX we used to talk directly with the script security manager, but that
  // should really be handled by the event sink implementation.

  // begin loading the new channel
  rv = mRedirectChannel->AsyncOpen(mListener);
  LOG(("  new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
  NS_ENSURE_SUCCESS(rv, rv);

  // close down this channel
  Cancel(NS_BINDING_REDIRECTED);

  notifier.RedirectSucceeded();

  ReleaseListeners();

  return NS_OK;
}

//-----------------------------------------------------------------------------
// nsHttpChannel <auth>
//-----------------------------------------------------------------------------

NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
  LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));

  // setting mAuthRetryPending flag and resuming the transaction
  // triggers process of throwing away the unauthenticated data already
  // coming from the network
  mAuthRetryPending = true;
  mProxyAuthPending = false;
  LOG(("Resuming the transaction, we got credentials from user"));
  if (mTransactionPump) {
    mTransactionPump->Resume();
  }

  return NS_OK;
}

NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
  LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));

  if (mTransactionPump) {
    // If the channel is trying to authenticate to a proxy and
    // that was canceled we cannot show the http response body
    // from the 40x as that might mislead the user into thinking
    // it was a end host response instead of a proxy reponse.
    // This must check explicitly whether a proxy auth was being done
    // because we do want to show the content if this is an error from
    // the origin server.
    if (mProxyAuthPending) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);

    // ensure call of OnStartRequest of the current listener here,
    // it would not be called otherwise at all
    nsresult rv = CallOnStartRequest();

    // drop mAuthRetryPending flag and resume the transaction
    // this resumes load of the unauthenticated content data (which
    // may have been canceled if we don't want to show it)
    mAuthRetryPending = false;
    LOG(("Resuming the transaction, user cancelled the auth dialog"));
    mTransactionPump->Resume();

    if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
  }

  mProxyAuthPending = false;
  return NS_OK;
}

NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
  LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));

  // Require we are between OnStartRequest and OnStopRequest, because
  // what we do here takes effect in OnStopRequest (not reusing the
  // connection for next authentication round).
  if (!mIsPending) {
    LOG(("  channel not pending"));
    NS_ERROR(
        "CloseStickyConnection not called before OnStopRequest, won't have any "
        "effect");
    return NS_ERROR_UNEXPECTED;
  }

  MOZ_ASSERT(mTransaction);
  if (!mTransaction) {
    return NS_ERROR_UNEXPECTED;
  }

  if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
        mTransaction->HasStickyConnection())) {
    LOG(("  not sticky"));
    return NS_OK;
  }

  mTransaction->DontReuseConnection();
  return NS_OK;
}

NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
  LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
       aRestartable));
  mAuthConnectionRestartable = aRestartable;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// nsHttpChannel::nsISupports
//-----------------------------------------------------------------------------

NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)

NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
  NS_INTERFACE_MAP_ENTRY(nsIRequest)
  NS_INTERFACE_MAP_ENTRY(nsIChannel)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
  NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
  NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
  NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
  NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
  NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
  NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
  NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
  NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
  NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
  NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
  NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
  NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
  NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
  NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
  NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
  NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
  NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
  NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
  NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
  NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
  NS_INTERFACE_MAP_ENTRY(nsIProcessSwitchRequestor)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)

//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequest
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsHttpChannel::Cancel(nsresult status) {
  MOZ_ASSERT(NS_IsMainThread());
  // We should never have a pump open while a CORS preflight is in progress.
  MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
#ifdef DEBUG
  if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
    MOZ_CRASH_UNSAFE_PRINTF("Blocking classifier error %" PRIx32
                            " need to be handled by CancelByURLClassifier()",
                            static_cast<uint32_t>(status));
  }
#endif

  LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
       static_cast<uint32_t>(status)));
  if (mCanceled) {
    LOG(("  ignoring; already canceled\n"));
    return NS_OK;
  }

  if (mWaitingForRedirectCallback) {
    LOG(("channel canceled during wait for redirect callback"));
  }

  return CancelInternal(status);
}

NS_IMETHODIMP
nsHttpChannel::CancelByURLClassifier(nsresult aErrorCode) {
  MOZ_ASSERT(
      UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
  MOZ_ASSERT(NS_IsMainThread());
  // We should never have a pump open while a CORS preflight is in progress.
  MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);

  LOG(("nsHttpChannel::CancelByURLClassifier [this=%p]\n", this));

  if (mCanceled) {
    LOG(("  ignoring; already canceled\n"));
    return NS_OK;
  }

  // We are being canceled by the channel classifier because of tracking
  // protection, but we haven't yet had a chance to dispatch the
  // "http-on-modify-request" notifications yet (this would normally be
  // done in PrepareToConnect()).  So do that now, before proceeding to
  // cancel.
  //
  // Note that running these observers can itself result in the channel
  // being canceled.  In that case, we accept that cancelation code as
  // the cause of the cancelation, as if the classification of the channel
  // would have occurred past this point!

  // notify "http-on-modify-request" observers
  CallOnModifyRequestObservers();

  // Check if request was cancelled during on-modify-request
  if (mCanceled) {
    return mStatus;
  }

  if (mSuspendCount) {
    LOG(("Waiting until resume in Cancel [this=%p]\n", this));
    MOZ_ASSERT(!mCallOnResume);
    mChannelClassifierCancellationPending = 1;
    mCallOnResume = [aErrorCode](nsHttpChannel* self) {
      self->HandleContinueCancellingByURLClassifier(aErrorCode);
      return NS_OK;
    };
    return NS_OK;
  }

  // Check to see if we should redirect this channel elsewhere by
  // nsIHttpChannel.redirectTo API request
  if (mAPIRedirectToURI) {
    mChannelClassifierCancellationPending = 1;
    return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
  }

  return CancelInternal(aErrorCode);
}

void nsHttpChannel::ContinueCancellingByURLClassifier(nsresult aErrorCode) {
  MOZ_ASSERT(
      UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
  MOZ_ASSERT(NS_IsMainThread());
  // We should never have a pump open while a CORS preflight is in progress.
  MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);

  LOG(("nsHttpChannel::ContinueCancellingByURLClassifier [this=%p]\n", this));
  if (mCanceled) {
    LOG(("  ignoring; already canceled\n"));
    return;
  }

  // Check to see if we should redirect this channel elsewhere by
  // nsIHttpChannel.redirectTo API request
  if (mAPIRedirectToURI) {
    Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
    return;
  }

  Unused << CancelInternal(aErrorCode);
}

nsresult nsHttpChannel::CancelInternal(nsresult status) {
  bool channelClassifierCancellationPending =
      !!mChannelClassifierCancellationPending;
  if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
    mChannelClassifierCancellationPending = 0;
  }

  mCanceled = true;
  mStatus = status;
  if (mProxyRequest) mProxyRequest->Cancel(status);
  CancelNetworkRequest(status);
  mCacheInputStream.CloseAndRelease();
  if (mCachePump) mCachePump->Cancel(status);
  if (mAuthProvider) mAuthProvider->Cancel(status);
  if (mPreflightChannel) mPreflightChannel->Cancel(status);
  if (mRequestContext && mOnTailUnblock) {
    mOnTailUnblock = nullptr;
    mRequestContext->CancelTailedRequest(this);
    CloseCacheEntry(false);
    Unused << AsyncAbort(status);
  } else if (channelClassifierCancellationPending) {
    // If we're coming from an asynchronous path when canceling a channel due
    // to safe-browsing protection, we need to AsyncAbort the channel now.
    Unused << AsyncAbort(status);
  }
  return NS_OK;
}

void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
  if (mTransaction) {
    nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
    if (NS_FAILED(rv)) {
      LOG(("failed to cancel the transaction\n"));
    }
  }
  if (mTransactionPump) mTransactionPump->Cancel(aStatus);
}

NS_IMETHODIMP
nsHttpChannel::Suspend() {
  nsresult rv = SuspendInternal();

  nsresult rvParentChannel = NS_OK;
  if (mParentChannel) {
    rvParentChannel = mParentChannel->SuspendMessageDiversion();
  }

  return NS_FAILED(rv) ? rv : rvParentChannel;
}

NS_IMETHODIMP
nsHttpChannel::Resume() {
  nsresult rv = ResumeInternal();

  nsresult rvParentChannel = NS_OK;
  if (mParentChannel) {
    rvParentChannel = mParentChannel->ResumeMessageDiversion();
  }</