netwerk/protocol/http/nsHttpChannel.cpp
author Brian Hackett <bhackett1024@gmail.com>
Sat, 20 Oct 2018 07:59:49 -0600
changeset 442271 d603faf452ce
parent 441874 42319047f3d9
permissions -rw-r--r--
Bug 1496881 - Fix test for whether we are paused at a console message.

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 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 "mozilla/dom/nsCSPContext.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"

#include "nsHttp.h"
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.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/dom/ContentVerifier.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 "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 "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 "nsIDocument.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 "HttpChannelParentListener.h"
#include "InterceptedHttpChannel.h"
#include "nsIBufferedStreams.h"
#include "nsIFileStreams.h"
#include "nsIMIMEInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "../../cache2/CacheFileUtils.h"
#include "nsINetworkLinkService.h"
#include "nsIRedirectProcessChooser.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerUtils.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) \
        (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
                      nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))

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

enum CacheDisposition {
    kCacheHit = 1,
    kCacheHitViaReval = 2,
    kCacheMissedViaReval = 3,
    kCacheMissed = 4
};

using mozilla::Telemetry::LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED;

static const struct {
  LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED mTelemetryLabel;
  const char* mHostName;
} gFastBlockAnalyticsProviders[] = {
  // clang-format off
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::googleanalytics, "google-analytics.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::scorecardresearch, "scorecardresearch.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::hotjar, "hotjar.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::newrelic, "newrelic.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::nrdata, "nr-data.net" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::crwdcntrl, "crwdcntrl.net" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::eyeota, "eyeota.net" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::yahooanalytics, "analytics.yahoo.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::statcounter, "statcounter.com" },
  { LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::v12group, "v12group.com" }
  // clang-format on
};

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)
    , 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)
    , mHasQueryString(0)
    , mConcurrentCacheAccess(0)
    , mIsPartialRequest(0)
    , mHasAutoRedirectVetoNotifier(0)
    , mPinCacheContent(0)
    , mIsCorsPreflightDone(0)
    , mStronglyFramed(false)
    , mUsedNetwork(0)
    , mAuthConnectionRestartable(0)
    , mTrackingProtectionCancellationPending(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());

    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)
{
    nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo,
                                        proxyResolveFlags, proxyURI, channelId);
    if (NS_FAILED(rv))
        return rv;

    LOG(("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;
}

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

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

    AddCookiesToRequest();

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

    SetLoadGroupUserAgentOverride();

    // Check if request was cancelled during on-modify-request or on-useragent.
    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::HandleOnBeforeConnect;
        return NS_OK;
    }

    return OnBeforeConnect();
}

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

    if (mSuspendCount) {
        LOG(("Waiting until resume HandleContinueCancelledByTrackingProtection [this=%p]\n", this));
        mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
        return;
    }

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

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::HandleOnBeforeConnect;
        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 or
    // on-useragent.
    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);
    }

    bool isHttps = false;
    rv = mURI->SchemeIs("https", &isHttps);
    NS_ENSURE_SUCCESS(rv,rv);
    nsCOMPtr<nsIPrincipal> resultPrincipal;
    if (!isHttps && mLoadInfo) {
        nsContentUtils::GetSecurityManager()->
          GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal));
    }
    OriginAttributes originAttributes;
    if (!NS_GetOriginAttributes(this, originAttributes)) {
        return NS_ERROR_FAILURE;
    }
    bool isHttp = false;
    rv = mURI->SchemeIs("http", &isHttp);
    NS_ENSURE_SUCCESS(rv,rv);

    // At this point it is no longer possible to call HttpBaseChannel::UpgradeToSecure.
    mUpgradableToSecure = false;
    if (isHttp) {
        bool shouldUpgrade = mUpgradeToSecure;
        if (!shouldUpgrade) {
            rv = NS_ShouldSecureUpgrade(mURI,
                                        mLoadInfo,
                                        resultPrincipal,
                                        mPrivateBrowsing,
                                        mAllowSTS,
                                        originAttributes,
                                        shouldUpgrade);
            NS_ENSURE_SUCCESS(rv, rv);
        }
        if (shouldUpgrade) {
            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) {
        mCaps |=  NS_HTTP_DISALLOW_SPDY;
    }

    if (mTRR) {
        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->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
    mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) || mBeConservative);
    mConnectionInfo->SetTlsFlags(mTlsFlags);
    mConnectionInfo->SetTrrUsed(mTRR);
    mConnectionInfo->SetTrrDisabled(mCaps & NS_HTTP_DISABLE_TRR);

    // 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::OnBeforeConnectContinue;
        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::OnBeforeConnectContinue;
        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 = mIsThirdPartyTrackingResource; // is atomic
    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();
}

static bool
IsContentPolicyTypeWhitelistedForFastBlock(nsILoadInfo* aLoadInfo)
{
  nsContentPolicyType type = aLoadInfo ?
                             aLoadInfo->GetExternalContentPolicyType() :
                             nsIContentPolicy::TYPE_OTHER;
  switch (type) {
  // images
  case nsIContentPolicy::TYPE_IMAGE:
  case nsIContentPolicy::TYPE_IMAGESET:
  case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
  case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
  case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
  // fonts
  case nsIContentPolicy::TYPE_FONT:
  // stylesheets
  case nsIContentPolicy::TYPE_STYLESHEET:
  case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
  case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
    return true;
  default:
    return false;
  }
}

bool
nsHttpChannel::CheckFastBlocked()
{
    LOG(("nsHttpChannel::CheckFastBlocked [this=%p, url=%s]",
         this, mSpec.get()));
    MOZ_ASSERT(mIsThirdPartyTrackingResource);

    static bool sFastBlockInited = false;
    static uint32_t sFastBlockTimeout = 0;
    static uint32_t sFastBlockLimit = 0;

    if (!sFastBlockInited) {
        sFastBlockInited = true;
        Preferences::AddUintVarCache(&sFastBlockTimeout, "browser.fastblock.timeout");
        Preferences::AddUintVarCache(&sFastBlockLimit, "browser.fastblock.limit");
    }

    if (!StaticPrefs::browser_contentblocking_enabled() ||
        !StaticPrefs::browser_fastblock_enabled()) {
        LOG(("FastBlock disabled by pref [this=%p]\n", this));

        return false;
    }

    TimeStamp timestamp;
    if (NS_FAILED(GetNavigationStartTimeStamp(&timestamp)) || !timestamp) {
        LOG(("FastBlock passed (no timestamp) [this=%p]\n", this));

        return false;
    }

    bool engageFastBlock = false;

    TimeDuration duration = TimeStamp::NowLoRes() - timestamp;
    if (IsContentPolicyTypeWhitelistedForFastBlock(mLoadInfo)) {
        LOG(("FastBlock passed (whitelisted content type %u) (%lf) [this=%p]\n",
             mLoadInfo ? mLoadInfo->GetExternalContentPolicyType() : nsIContentPolicy::TYPE_OTHER,
             duration.ToMilliseconds(), this));
    } else if (mLoadInfo && mLoadInfo->GetDocumentHasUserInteracted()) {
        LOG(("FastBlock passed (user interaction) (%lf) [this=%p]\n",
             duration.ToMilliseconds(), this));
    } else if (mLoadInfo && mLoadInfo->GetDocumentHasLoaded()) {
        LOG(("FastBlock passed (document loaded) (%lf) [this=%p]\n",
             duration.ToMilliseconds(), this));
    } else {
            bool hasFastBlockStarted = duration.ToMilliseconds() >= sFastBlockTimeout;
        bool hasFastBlockStopped = false;
        if ((sFastBlockLimit != 0) && (sFastBlockLimit > sFastBlockTimeout)) {
            hasFastBlockStopped = duration.ToMilliseconds() > sFastBlockLimit;
        }
        LOG(("FastBlock started=%d stopped=%d (%lf) [this=%p]\n",
             static_cast<int>(hasFastBlockStarted),
             static_cast<int>(hasFastBlockStopped),
             duration.ToMilliseconds(),
             this));
        engageFastBlock = hasFastBlockStarted && !hasFastBlockStopped;
    }

    // Remember the data needed for fastblock telemetry in case fastblock is
    // enabled, we have decided to block the channel, and the channel isn't
    // marked as private.
    if (engageFastBlock && !NS_UsePrivateBrowsing(this)) {
        nsCOMPtr<nsIURI> uri;
        nsresult rv = GetURI(getter_AddRefs(uri));
        NS_ENSURE_SUCCESS(rv, false);

        nsAutoCString host;
        rv = uri->GetHost(host);
        NS_ENSURE_SUCCESS(rv, false);

        nsCOMPtr<nsIEffectiveTLDService> tldService =
            do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
        NS_ENSURE_TRUE(tldService, false);

        LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED label =
            LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::other;
        for (const auto& entry : gFastBlockAnalyticsProviders) {
          // For each entry in the list of our analytics providers, use the
          // effective TLD service to look up subdomains to make sure we find a
          // potential match if one is available.
          while (true) {
            if (host == entry.mHostName) {
              label = entry.mTelemetryLabel;
              break;
            }

            nsAutoCString newHost;
            rv = tldService->GetNextSubDomain(host, newHost);
            if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
              // we're done searching this entry.
              break;
            }
            NS_ENSURE_SUCCESS(rv, false);

            host = newHost;
          }

          if (label != LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED::other) {
            // We have found a label in the previous loop, bail out now!
            break;
          }
        }

        if (mLoadInfo) {
          MOZ_ALWAYS_SUCCEEDS(mLoadInfo->SetIsTrackerBlocked(true));
          MOZ_ALWAYS_SUCCEEDS(mLoadInfo->SetTrackerBlockedReason(label));
        }
    }

    return engageFastBlock;
}

nsresult
nsHttpChannel::ConnectOnTailUnblock()
{
    nsresult rv;

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

    bool isTrackingResource = mIsThirdPartyTrackingResource; // is atomic
    if (isTrackingResource && CheckFastBlocked()) {
        AntiTrackingCommon::NotifyRejection(this,
                                            nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT);
        Unused << AsyncAbort(NS_ERROR_TRACKING_ANNOTATION_URI);
        CloseCacheEntry(false);
        return NS_OK;
    }

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

    // open a cache entry for this channel...
    bool isHttps = false;
    rv = mURI->SchemeIs("https", &isHttps);
    NS_ENSURE_SUCCESS(rv,rv);
    rv = OpenCacheEntry(isHttps);

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

            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...
    nsresult rv = SetupTransaction();
    if (NS_FAILED(rv)) return rv;

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

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::HandleAsyncRedirect;
        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::HandleAsyncNotModified;
        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::HandleAsyncFallback;
        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) {
            if (!mAvailableCachedAltDataType.IsEmpty()) {
                mAvailableCachedAltDataType.Truncate();
                mAltDataLength = 0;
            }
            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<nsIURI> tempURI;
            rv = NS_MutateURI(mURI)
                   .SetUserPass(EmptyCString())
                   .Finalize(tempURI);
            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();
    LOG(("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();

    nsCOMPtr<nsIAsyncInputStream> responseStream;
    rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
                            mUploadStream, mReqContentLength,
                            mUploadStreamHasHeaders,
                            GetCurrentThreadEventTarget(), callbacks, this,
                            mTopLevelOuterContentWindowId,
                            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;
}

// Helper Function to report messages to the console when loading
// a resource was blocked due to a MIME type mismatch.
void
ReportTypeBlocking(nsIURI* aURI,
                   nsILoadInfo* aLoadInfo,
                   const char* aMessageName)
{
    NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
    const char16_t* params[] = { specUTF16.get() };
    nsCOMPtr<nsIDocument> doc;
    if (aLoadInfo) {
        aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
    }
    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
                                    NS_LITERAL_CSTRING("MIMEMISMATCH"),
                                    doc,
                                    nsContentUtils::eSECURITY_PROPERTIES,
                                    aMessageName,
                                    params, ArrayLength(params));
}

// Check and potentially enforce X-Content-Type-Options: nosniff
nsresult
ProcessXCTO(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"
    contentTypeOptionsHeader.StripWhitespace();
    // 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.
        NS_ConvertUTF8toUTF16 char16_header(contentTypeOptionsHeader);
        const char16_t* params[] = { char16_header.get() };
        nsCOMPtr<nsIDocument> doc;
        aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
        nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                        NS_LITERAL_CSTRING("XCTO"),
                                        doc,
                                        nsContentUtils::eSECURITY_PROPERTIES,
                                        "XCTOHeaderValueMissing",
                                        params, ArrayLength(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;
        }
        ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
        return NS_ERROR_CORRUPTED_CONTENT;
    }

    if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SCRIPT) {
        if (nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(contentType))) {
            return NS_OK;
        }
        ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
        return NS_ERROR_CORRUPTED_CONTENT;
    }
    return NS_OK;
}

// Ensure that a load of type script has correct MIME type
nsresult
EnsureMIMEOfScript(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_2::javaScript);
        return NS_OK;
    }

    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_2::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_2::CORS_origin);
        } else {
            //cross origin
            AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::cross_origin);
        }
    }

    bool block = false;
    if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
        // script load has type image
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::image);
        block = true;
    } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
        // script load has type audio
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::audio);
        block = true;
    } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
        // script load has type video
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::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_2::text_csv);
        block = true;
    }

    if (block) {
        // Instead of consulting Preferences::GetBool() all the time we
        // can cache the result to speed things up.
        static bool sCachedBlockScriptWithWrongMime = false;
        static bool sIsInited = false;
        if (!sIsInited) {
            sIsInited = true;
            Preferences::AddBoolVarCache(&sCachedBlockScriptWithWrongMime,
            "security.block_script_with_wrong_mime");
        }

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

        ReportTypeBlocking(aURI, aLoadInfo, "BlockScriptWithWrongMimeType");
        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_2::text_plain);
        return NS_OK;
    }

    if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
        // script load has type text/xml
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_xml);
        return NS_OK;
    }

    if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/octet-stream"))) {
        // script load has type application/octet-stream
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::app_octet_stream);
        return NS_OK;
    }

    if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/xml"))) {
        // script load has type application/xml
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::app_xml);
        return NS_OK;
    }

    if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
        // script load has type text/html
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_html);
        return NS_OK;
    }

    if (contentType.IsEmpty()) {
        // script load has no type
        AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::empty);
        return NS_OK;
    }

    // script load has unknown type
    AccumulateCategorical(Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::unknown);
    return NS_OK;
}


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

    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);
            deleteProtector->OnStartRequest(this, mListenerContext);
        }

        mOnStartRequestCalled = true;
    });

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

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

    // 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,
                                            mListenerContext,
                                            getter_AddRefs(converter));
                if (NS_SUCCEEDED(rv)) {
                    mListener = converter;
                    unknownDecoderStarted = true;
                }
            }
        }
    }

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

    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);
        rv = deleteProtector->OnStartRequest(this, mListenerContext);
        mOnStartRequestCalled = true;
        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;
      nsISupports *ctxt = mListenerContext;
      rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
      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();
        }
    }

    // Check for a Content-Signature header and inject mediator if the header is
    // requested and available.
    // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
    // present but not valid, fail this channel and return
    // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
    // fallback load in nsDocShell.
    // Note that OnStartRequest has already been called on the target stream
    // listener at this point. We have to add the listener here that late to
    // ensure that it's the last listener and can thus block the load in
    // OnStopRequest.
    if (!mCanceled) {
        rv = ProcessContentSignatureHeader(mResponseHead);
        if (NS_FAILED(rv)) {
            LOG(("Content-signature verification failed.\n"));
            return rv;
        }
    }

    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 407: // ProcessAuthentication() failed
    case 501: // HTTP/1.1: "Not Implemented"
        // user sees boilerplate Mozilla "Proxy Refused Connection" page.
        rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
        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)
    // Squid returns 503 if target request fails for anything but DNS.
    case 503: // HTTP/1.1: "Service Unavailable"
        /* 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_NET_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));
    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()
{
    nsresult rv;
    bool isHttps = false;
    rv = mURI->SchemeIs("https", &isHttps);
    NS_ENSURE_SUCCESS(rv, rv);

    // 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 (!isHttps)
        return NS_OK;

    nsAutoCString asciiHost;
    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;
}

nsresult
nsHttpChannel::ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead)
{
    nsresult rv = NS_OK;

    // we only do this if we require it in loadInfo
    if (!mLoadInfo || !mLoadInfo->GetVerifySignedContent()) {
        return NS_OK;
    }

    NS_ENSURE_TRUE(aResponseHead, NS_ERROR_ABORT);
    nsAutoCString contentSignatureHeader;
    nsHttpAtom atom = nsHttp::ResolveAtom("Content-Signature");
    rv = aResponseHead->GetHeader(atom, contentSignatureHeader);
    if (NS_FAILED(rv)) {
        LOG(("Content-Signature header is missing but expected."));
        DoInvalidateCacheEntry(mURI);
        return NS_ERROR_INVALID_SIGNATURE;
    }

    // if we require a signature but it is empty, fail
    if (contentSignatureHeader.IsEmpty()) {
      DoInvalidateCacheEntry(mURI);
      LOG(("An expected content-signature header is missing.\n"));
      return NS_ERROR_INVALID_SIGNATURE;
    }

    // we ensure a content type here to avoid running into problems with
    // content sniffing, which might sniff parts of the content before we can
    // verify the signature
    if (!aResponseHead->HasContentType()) {
        NS_WARNING("Empty content type can get us in trouble when verifying "
                   "content signatures");
        return NS_ERROR_INVALID_SIGNATURE;
    }
    // create a new listener that meadiates the content
    RefPtr<ContentVerifier> contentVerifyingMediator =
      new ContentVerifier(mListener, mListenerContext);
    rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
                                        mListenerContext);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
    mListener = contentVerifyingMediator;

    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()
{
    bool isHttps;
    if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
        return false;
    return true;
}

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

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->GetHost(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, mPrivateBrowsing, 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 (gHttpHandler->IsTelemetryEnabled()) {
        // 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 mReferrer may not be set at all, or may
    // not be a full URI (HttpBaseChannel::SetReferrer has the gorey details).
    // If that's null, though, we'll fall back to mReferrer just in case (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) {
        referrer = mReferrer;
    }

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

    // 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::AsyncContinueProcessResponse;
        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.get());
        }

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

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

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

    // Hack: ContinueProcessResponse2 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 ContinueProcessResponse2(NS_BINDING_FAILED);
}

nsresult
nsHttpChannel::ContinueProcessResponse2(nsresult rv)
{
    LOG(("nsHttpChannel::ContinueProcessResponse1 [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();

    bool successfulReval = false;
    bool partialContentUsed = false;

    // 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...
            rv = ProcessPartialContent();
            if (NS_SUCCEEDED(rv)) {
                partialContentUsed = true;
            }
        } 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::ContinueProcessResponse3);
        rv = AsyncProcessRedirection(httpStatus);
        if (NS_FAILED(rv)) {
            PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
            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 = ContinueProcessResponse3(rv);
            }
        }
        break;
    case 304:
        if (!ShouldBypassProcessNotModified()) {
            rv = ProcessNotModified();
            if (NS_SUCCEEDED(rv)) {
                successfulReval = true;
                break;
            }

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

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

            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();
        }
        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:
        // Do not cache 425.
        CloseCacheEntry(false);
        MOZ_FALLTHROUGH; // process normally
    default:
        rv = ProcessNormal();
        MaybeInvalidateCacheEntryForSubsequentGet();
        break;
    }

    if (mRaceDelay && !mRaceCacheWithNetwork &&
        (mCachedContentIsPartial || mDidReval)) {
        if (successfulReval || partialContentUsed) {
            AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
        } else {
            AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentNotUsed);
        }
    }


    if (gHttpHandler->IsTelemetryEnabled()) {
        CacheDisposition cacheDisposition;
        if (!mDidReval) {
            cacheDisposition = kCacheMissed;
        } else if (successfulReval) {
            cacheDisposition = kCacheHitViaReval;
        } else {
            cacheDisposition = kCacheMissedViaReval;
        }
        AccumulateCacheHitTelemetry(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);
        }
    }
    return rv;
}

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

    if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
        bool isHTTP = false;
        if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
            isHTTP = false;
        if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
            isHTTP = false;

        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(("ContinueProcessResponse3 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(("ContinueProcessResponse3 "
                 "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(("ContinueProcessResponse3 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::HandleAsyncRedirectChannelToHttps;
        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::HandleAsyncAPIRedirect;
        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
    if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
        MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
        rv = mRedirectChannel->AsyncOpen2(mListener);
    }
    else {
        rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
    }
    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->NewProxiedChannel2(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
    if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
        MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
        rv = mRedirectChannel->AsyncOpen2(mListener);
    }
    else {
        rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
    }
    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()
{
    // 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.

        // Now we continue reading the network response.
    } else {
        // the cached content is valid, although incomplete.
        mCachedContentIsValid = true;
        rv = ReadFromCache(false);
    }

    return 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()
{
    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;

    rv = ReadFromCache(false);
    if (NS_FAILED(rv)) return rv;

    mTransactionReplaced = true;
    return NS_OK;
}

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->NewChannel2(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);

    if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
        MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
        rv = mRedirectChannel->AsyncOpen2(mListener);
    }
    else {
        rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
    }
    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;
    mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);

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

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) && !offline) {
            goto bypassCacheEntryOpen;
        }
        cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
        mCacheEntryIsReadOnly = true;
    }
    else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !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 (mTRR) {
        extension.Append("TRR");
    }

    if (mIsThirdPartyTrackingResource &&
        !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(this, mURI, nullptr)) {
        nsCOMPtr<nsIURI> topWindowURI;
        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, rv);
        }

        nsAutoString topWindowOrigin;
        rv = nsContentUtils::GetUTFOrigin(topWindowURI ? topWindowURI : mURI,
                                          topWindowOrigin);
        NS_ENSURE_SUCCESS(rv, rv);

        extension.Append("-unique:");
        extension.Append(NS_ConvertUTF16toUTF8(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");
            if (self->mNetworkTriggered) {
                self->mRaceCacheWithNetwork = true;
            }
            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()) {
            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 = false;
    rv = mURI->SchemeIs("https", &isHttps);
    NS_ENSURE_SUCCESS(rv,rv);

    bool doValidation = 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);
    }


    // If a content signature is expected to be valid in this load,
    // set doValidation to force a signature check.
    if (!doValidation &&
        mLoadInfo && mLoadInfo->GetVerifySignedContent()) {
        doValidation = true;
    }

    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 (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 = false;
            rv = mURI->SchemeIs("https", &isHttps);
            NS_ENSURE_SUCCESS(rv, rv);

            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()) {
        uint32_t freshnessLifetime = 0;

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

        if (freshnessLifetime > 0) {
            uint32_t now = NowInSeconds(), 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;
            }
            else
                aExpirationTime = 0;
        }
    }

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

/*static*/ inline bool
nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
{
    // Must be called on the main thread because nsIURI does not implement
    // thread-safe QueryInterface.
    MOZ_ASSERT(NS_IsMainThread());

    if (method != nsHttpRequestHead::kMethod_Get &&
        method != nsHttpRequestHead::kMethod_Head)
        return false;

    nsAutoCString query;
    nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
    nsresult rv = url->GetQuery(query);
    return NS_SUCCEEDED(rv) && !query.IsEmpty();
}

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;

    bool isHttps = false;
    rv = mURI->SchemeIs("https", &isHttps);
    NS_ENSURE_SUCCESS(rv,rv);

    if (isHttps) {
        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;
    if (!altDataType.IsEmpty() &&
        !mPreferredCachedAltDataTypes.IsEmpty() &&
        altDataFromChild == mAltDataForChild) {
        for (auto& pref : mPreferredCachedAltDataTypes) {
            if (mozilla::Get<0>(pref) == altDataType &&
                (mozilla::Get<1>(pref).IsEmpty() || mozilla::Get<1>(pref) == contentType)) {
                foundAltData = true;
                break;
            }
        }
    }
    if (foundAltData) {
        rv = cacheEntry->OpenAlternativeInputStream(altDataType,
                                                    getter_AddRefs(stream));
        if (NS_SUCCEEDED(rv)) {
            LOG(("Opened alt-data input stream type=%s", altDataType.get()));
            // We have succeeded.
            mAvailableCachedAltDataType = altDataType;
            // Set the correct data size on the channel.
            int64_t altDataSize;
            if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
                mAltDataLength = altDataSize;
            }
        }
    }


    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, mListenerContext);
    if (NS_FAILED(rv)) return rv;

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

    uint32_t suspendCount = mSuspendCount;
    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();

        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
    bool isHttps;
    if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
        NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
        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));
        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;

    // 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_is_active()) {
        int32_t priority = PRIORITY_NORMAL;
        GetPriority(&priority);
        profiler_add_network_marker(mURI, priority, mChannelId, NetworkLoadType::LOAD_REDIRECT,
                                    mLastStatusReported, TimeStamp::Now(),
                                    mLogicalOffset, nullptr,
                                    mRedirectURI);
    }
#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
    if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
        MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
        rv = mRedirectChannel->AsyncOpen2(mListener);
    }
    else {
        rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
    }
    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->Caps() & NS_HTTP_STICKY_CONNECTION)) {
        LOG(("  not sticky"));
        return NS_OK;
    }

    RefPtr<nsAHttpConnection> conn = mTransaction->GetConnectionReference();
    if (!conn) {
        LOG(("  no connection"));
        return NS_OK;
    }

    // This turns the IsPersistent() indicator on the connection to false,
    // and makes us throw it away in OnStopRequest.
    conn->DontReuse();
    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_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);
    MOZ_ASSERT(status != NS_ERROR_TRACKING_URI,
               "NS_ERROR_TRACKING_URI needs to be handled by CancelForTrackingProtection()");

    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::CancelForTrackingProtection()
{
    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::CancelForTrackingProtection [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();

    SetLoadGroupUserAgentOverride();

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

    if (mSuspendCount) {
        LOG(("Waiting until resume in Cancel [this=%p]\n", this));
        MOZ_ASSERT(!mCallOnResume);
        mTrackingProtectionCancellationPending = 1;
        mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
        return NS_OK;
    }

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

    return CancelInternal(NS_ERROR_TRACKING_URI);
}

void
nsHttpChannel::ContinueCancelledByTrackingProtection()
{
    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::ContinueCancelledByTrackingProtection [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(NS_ERROR_TRACKING_URI);
}

nsresult
nsHttpChannel::CancelInternal(nsresult status)
{
    bool trackingProtectionCancellationPending =
      !!mTrackingProtectionCancellationPending;
    if (status == NS_ERROR_TRACKING_URI) {
      mTrackingProtectionCancellationPending = 0;
      if (mLoadInfo) {
        MOZ_ALWAYS_SUCCEEDS(mLoadInfo->SetIsTracker(true));
        MOZ_ALWAYS_SUCCEEDS(mLoadInfo->SetIsTrackerBlocked(true));
      }
    }

    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 (trackingProtectionCancellationPending) {
        // If we're coming from an asynchronous path when canceling a channel due
        // to tracking 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();
    }

    return NS_FAILED(rv) ? rv : rvParentChannel;
}

//-----------------------------------------------------------------------------
// nsHttpChannel::nsIChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
{
    NS_ENSURE_ARG_POINTER(securityInfo);
    *securityInfo = mSecurityInfo;
    NS_IF_ADDREF(*securityInfo);
    return NS_OK;
}

// If any of the functions that AsyncOpen calls returns immediately an error
// AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
// To be sure that they are not call ReleaseListeners() is called.
// If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
// any error.
NS_IMETHODIMP
nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
{
    MOZ_ASSERT(!mLoadInfo ||
               mLoadInfo->GetSecurityMode() == 0 ||
               mLoadInfo->GetInitialSecurityCheckDone() ||
               (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
                nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
               "security flags in loadInfo but asyncOpen2() not called");

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

#ifdef MOZ_TASK_TRACER
    if (tasktracer::IsStartLogging()) {
        uint64_t sourceEventId, parentTaskId;
        tasktracer::SourceEventType sourceEventType;
        GetCurTraceInfo(&sourceEventId, &parentTaskId, &sourceEventType);
        nsAutoCString urispec;
        mURI->GetSpec(urispec);
        tasktracer::AddLabel("nsHttpChannel::AsyncOpen %s", urispec.get());
    }
#endif

#ifdef MOZ_GECKO_PROFILER
    mLastStatusReported = TimeStamp::Now(); // in case we enable the profiler after AsyncOpen()
    if (profiler_is_active()) {
        profiler_add_network_marker(mURI, mPriority, mChannelId, NetworkLoadType::LOAD_START,
                                    mChannelCreationTimestamp, mLastStatusReported,
                                    0);
    }
#endif

    NS_CompareLoadInfoAndLoadContext(this);

#ifdef DEBUG
    AssertPrivateBrowsingId();
#endif

    NS_ENSURE_ARG_POINTER(listener);
    NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
    NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);

    if (MaybeWaitForUploadStreamLength(listener, context)) {
        return NS_OK;
    }

    nsresult rv;

    MOZ_ASSERT(NS_IsMainThread());

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

    static bool sRCWNInited = false;
    if (!sRCWNInited) {
        sRCWNInited = true;
        Preferences::AddBoolVarCache(&sRCWNEnabled, "network.http.rcwn.enabled");
        Preferences::AddUintVarCache(&sRCWNQueueSizeNormal, "network.http.rcwn.cache_queue_normal_threshold");
        Preferences::AddUintVarCache(&sRCWNQueueSizePriority, "network.http.rcwn.cache_queue_priority_threshold");
        Preferences::AddUintVarCache(&sRCWNSmallResourceSizeKB, "network.http.rcwn.small_resource_size_kb");
        Preferences::AddUintVarCache(&sRCWNMinWaitMs, "network.http.rcwn.min_wait_before_racing_ms");
        Preferences::AddUintVarCache(&sRCWNMaxWaitMs, "network.http.rcwn.max_wait_before_racing_ms");
    }

    rv = NS_CheckPortSafety(mURI);
    if (NS_FAILED(rv)) {
        ReleaseListeners();
        return rv;
    }

    if (!mLoadGroup && !mCallbacks) {
        // If no one called SetLoadGroup or SetNotificationCallbacks, the private
        // state has not been updated on PrivateBrowsingChannel (which we derive from)
        // Hence, we have to call UpdatePrivateBrowsing() here
        UpdatePrivateBrowsing();
    }

    if (WaitingForTailUnblock()) {
        // This channel is marked as Tail and is part of a request context
        // that has positive number of non-tailed requestst, hence this channel
        // has been put to a queue.
        // When tail is unblocked, OnTailUnblock on this channel will be called
        // to continue AsyncOpen.
        mListener = listener;
        mListenerContext = context;
        MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
        mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;

        LOG(("  put on hold until tail is unblocked"));
        return NS_OK;
    }

    // Remember the cookie header that was set, if any
    nsAutoCString cookieHeader;
    if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
        mUserSetCookieHeader = cookieHeader;
    }

    // Set user agent override, do so before OnOpeningRequest notification
    // since we want to allow consumers of that notification change or remove
    // the User-Agent request header.
    HttpBaseChannel::SetDocshellUserAgentOverride();

    // After we notify any observers (on-opening-request, loadGroup, etc) we
    // must return NS_OK and return any errors asynchronously via
    // OnStart/OnStopRequest.  Observers may add a reference to the channel
    // and expect to get OnStopRequest so they know when to drop the reference,
    // etc.

    // notify "http-on-opening-